之前几节,通过命令,我们把数据存储到了redis上,但是最终我还是需要从程序上去对数据进行操作。可以连接redis进行操作的语言很多,Jedis 是Redis官方首选的 Java 客户端开发包。有了之前命令的基础,使用jedis就显得比较容易了。下面通过几个例子来说明下。
一、POM依赖
我们使用jedis 2.9.0的版本,并引入junit方便我们的测试。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ljs</groupId> <artifactId>jedis-demo</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project>
二、基本操作
从代码中,我们演示了string和list两个操作,看操作的方法名,和我们redis的命令基本是一一对应的。所以其他操作类似,就不再说明。
package com.leijs.jedis; import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.List; /** * JedisTest * * @author LeiJiSong * @date 2020/11/3 */ public class JedisTest { @Test public void testJedis() { // 1. 连接redis Jedis jedis = new Jedis("127.0.0.1", 6379); // 2. 操作redis jedis.set("name", "Bob"); String name = jedis.get("name"); System.out.println(name); jedis.lpush("list1", "a", "b", "c"); List<String> list1 = jedis.lrange("list1", 0, -1); list1.forEach(u -> { System.out.println(u); }); // 3. 关闭连接 jedis.close(); } }
三、jedis的连接池
上述基本案例,我们每次操作都需要和redis建立连接,jedis连接资源的创建与销毁是很消耗程序性能,所以jedis为我们提供了jedis的池化技术,jedisPool在创建时初始化一些连接资源存储到连接池中,使用jedis连接资源时不需要创建,而是从连接池中获取一个资源进行redis的操作,使用完毕后,不需要销毁该jedis连接资源,而是将该资源归还给连接池,供其他请求使用。程序上可以如下实现:
首先,我们可以把基本的配置放在redis.properties中,如:
redis.maxTotal=30 redis.maxIdle=10 redis.host=127.0.0.1 redis.port=6379
接下来是工具类实现:
package com.redis.util; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.util.ResourceBundle; /** * Jedis工具类 * * @author LeiJiSong * @date 2020/11/4 */ public class JedisUtils { private static JedisPool jp = null; private static String host = null; private static int maxTotal; private static int maxIdle; private static int port; static { // 获取配置 ResourceBundle rb = ResourceBundle.getBundle("redis"); host = rb.getString("redis.host"); port = Integer.parseInt(rb.getString("redis.port")); JedisPoolConfig jpc = new JedisPoolConfig(); maxTotal = Integer.parseInt(rb.getString("redis.maxTotal")); jpc.setMaxTotal(maxTotal); maxIdle = Integer.parseInt(rb.getString("redis.maxIdle")); jpc.setMaxIdle(maxIdle); jp = new JedisPool(jpc, host, port); } /** * 获得连接 * @return */ public static Jedis getJedis() { return jp.getResource(); } /** * 关闭连接 * @param jedis */ public static void close(Jedis jedis){ if(jedis!=null){ jedis.close(); } } }
核心参数详解:
参数 | 说明 |
maxActive |
控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取; 如果赋值为-1,则表示不限制; 如果pool已经分配了maxActive个jedis实例,则此时pool的状态就成exhausted了 |
maxIdle |
控制一个pool最多有多少个状态为idle的jedis实例 |
whenExhaustedAction |
表示当pool中的jedis实例都被allocated完时,pool要采取的操作; 默认有三种WHEN_EXHAUSTED_FAIL(表示无jedis实例时,直接抛出NoSuchElementException)、 WHEN_EXHAUSTED_BLOCK(则表示阻塞住,或者达到maxWait时抛出JedisConnectionException)、 WHEN_EXHAUSTED_GROW(则表示新建一个jedis实例,也就说设置的maxActive无用) |
maxWait |
表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间, 则直接抛出JedisConnectionException |
testOnBorrow |
在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的 |
testOnReturn |
在return给pool时,是否提前进行validate操作 |
testWhileIdle |
如果为true,表示有一个idle object evitor线程对idle object进行扫描, 如果validate失败,此object会被从pool中drop掉; 这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义; |
timeBetweenEvictionRunsMillis |
表示idle object evitor两次扫描之间要sleep的毫秒数 |
numTestsPerEvictionRun |
表示idle object evitor每次扫描的最多的对象数 |
minEvictableIdleTimeMillis |
表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐; 这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义 |
softMinEvictableIdleTimeMillis |
在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。 如果为-1,evicted不会根据idle time驱逐任何对象。 如果minEvictableIdleTimeMillis>0,则此项设置无意义, 且只有在timeBetweenEvictionRunsMillis大于0时才有意义 |
lifo | borrowObject返回对象时,是采用DEFAULT_LIFO(last in first out,即类似cache的最频繁使用队列), 如果为False,则表示FIFO队列 |
四、一个简单实际场景的应用
场景:比方说百度网盘,用过的都知道,在下载文件的时候,普通用户体验只有一分钟,然后就提示到时间,想用就要去充值了,VIP客户可以不限制时间,但是一个月或者半年的时间限制。
针对这种场景,我们进行稍微变形来模拟这样一种场景:普通用户对我们下载提速的接口:一天只能调用一次,VIP则可以调用30次;
接口的调用内容我们采取打印,对于超过次数的调用采用异常提示的方式,普通用户和VIP的调用采用两个线程来模拟。简单实现如下:
业务接口:
package com.redis.service; import com.redis.util.JedisUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisDataException; import java.util.Objects; /** * 提供业务服务模拟增值调用 * * @author LeiJiSong * @date 2020/11/4 */ public class BusinessService { private String userId; private int count; public BusinessService(String userId, int count) { this.userId = userId; this.count = count; } /** * 业务控制单元 */ public void service() { Jedis jedis = JedisUtils.getJedis(); String accountId = "accountId:" + userId; String value = jedis.get(accountId); try { if (Objects.isNull(value)) { // 不存在就设值 // 这里用达到增长上限值来模拟达到最高次数 jedis.setex(accountId, 25, Long.MAX_VALUE - count + ""); } else { // 存在就自增 Long currentValue = jedis.incr(accountId); business(userId, count - (Long.MAX_VALUE - currentValue)); } } catch (JedisDataException e) { System.out.println("使用已经达到上限了,可以升级会员处理"); return; } finally { jedis.close(); } } public void business(String id, Long value) { System.out.println("用户" + id + "业务执行第" + value + "次操作"); } }
这里设计调用次数的值,setex方法,我们采用了之前章节说到的redis string自增的最大值,当达到最大值的时候,就会抛出JedisDataException异常。
用户线程:
package com.redis.service; /** * 模拟线程 * * @author LeiJiSong * @date 2020/11/4 */ public class UserThread implements Runnable { BusinessService businessService; public UserThread(String id, int count) { this.businessService = new BusinessService(id ,count); } @Override public void run() { while(true) { businessService.service(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
主线程:
传入用户类型和可调用的次数,方便模拟,我们改了下key的失效时间和调用次数。
package com.redis.service; /** * 主程序 * * @author LeiJiSong * @date 2020/11/4 */ public class Main { public static void main(String[] args) { Thread t1 = new Thread(new UserThread("base", 10)); Thread t2 = new Thread(new UserThread("vip", 20)); t1.start(); t2.start(); } }
程序刚开始运行:
在中间时间:
达到新的可以操作的周期,又可以调用:
程序的整体实现比较简陋, 比方说写死了失效的时间,以后想扩展超级VIP不限制次数,嗯呢,这些都是可以实现的。
嗯,简单介绍到这里。下一篇,聊一聊redis的持久化。