Jedis 的编程实践

来自Wikioe
跳到导航 跳到搜索


关于

Jedis 是一个高性能的 Java 客户端,是 Redis 官方推荐的 Java 开发工具。要在 Java 开发中访问 Redis 缓存服务器,必须对 Jedis 熟悉才能编写出“漂亮”的代码。

Jedis 的项目地址:https://github.com/alphazero/jredis


使用Jedis,可以在 Maven 的 pom 文件中,增加以下依赖:

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>${redis.version}</version>
</dependency>


Jedis 基本的使用十分简单:

  • 在每次使用时,构建 Jedis 对象即可。
  • 一个 Jedis 对象代表一条和 Redis 服务进行连接的 Socket 通道。
  • 使用完 Jedis 对象之后,需要调用 Jedis.close() 方法把连接关闭,否则会占用系统资源。


创建 Jedis 对象时,可以指定 Redis 服务的 host,port 和 password。大致的伪代码如下:

Jedis jedis = new Jedis("localhost", 6379);    // 指定Redis服务的主机和端口
jedis.auth("xxxx");    // 如果Redis服务连接需要密码,就设置密码

...   // 访问Redis服务

jedis.close();    // 使用完,就关闭连接

基础实践

Jedis 的 String操作函数、List操作函数、Hash操作函数、Set操作函数、Zset操作函数,和 Redis 客户端的相关操作命令,基本上可以一比一的相互对应。

Jedis 操作 String(字符串)

示例:

package com.crazymakercircle.redis.jedis;
//...省略import
public class StringDemo {
	/**
	* Jedis字符串数据类型的相关命令
	*/
	@Test
	public void operateString() {
		Jedis jedis = new Jedis("localhost", 6379);
		//如果返回 pong 代表链接成功
		Logger.info("jedis.ping():" + jedis.ping());
		//设置key0的值为 123456
		jedis.set("key0", "123456");
		
		//返回数据类型:string
		Logger.info("jedis.type(key0): " + jedis.type("key0"));
		//取得值
		Logger.info("jedis.get(key0): " + jedis.get("key0"));
		// key是否存在
		Logger.info("jedis.exists(key0):" + jedis.exists("key0"));
		//返回key的长度
		Logger.info("jedis.strlen(key0): " + jedis.strlen("key0"));
		//返回截取字符串, 范围“0,-1”表示截取全部
		Logger.info("jedis.getrange(key0): " + jedis.getrange("key0", 0, -1));
		//返回截取字符串, 范围“1,4”表示区间[1,4]
		Logger.info("jedis.getrange(key0): " + jedis.getrange("key0", 1, 4));
		//追加字符串
		Logger.info("jedis.append(key0): " + jedis.append("key0", "appendStr"));
		Logger.info("jedis.get(key0): " + jedis.get("key0"));
		
		//重命名
		jedis.rename("key0", "key0_new");
		//判断key 是否存在
		Logger.info("jedis.exists(key0): " + jedis.exists("key0"));
		
		//批量插入
		jedis.mset("key1", "val1", "key2", "val2", "key3", "100");		
		//批量取出
		Logger.info("jedis.mget(key1,key2,key3): " + jedis.mget("key1", "key2", "key3"));
		
		//删除
		Logger.info("jedis.del(key1): " + jedis.del("key1"));
		Logger.info("jedis.exists(key1): " + jedis.exists("key1"));
		
		//取出旧值并设置新值
		Logger.info("jedis.getSet(key2): " + jedis.getSet("key2", "value3"));
		//自增1
		Logger.info("jedis.incr(key3): " + jedis.incr("key3"));
		//自增15
		Logger.info("jedis.incrBy(key3): " + jedis.incrBy("key3", 15));
		//自减1
		Logger.info("jedis.decr(key3): " + jedis.decr("key3"));
		//自减15
		Logger.info("jedis.decrBy(key3): " + jedis.decrBy("key3", 15));
		//浮点数加
		Logger.info("jedis.incrByFloat(key3): " + jedis.incrByFloat("key3", 1.1));
		
		//返回0 只有在key不存在的时候才设置
		Logger.info("jedis.setnx(key3): " + jedis.setnx("key3", "existVal"));
		Logger.info("jedis.get(key3): " + jedis.get("key3"));// 3.1
		
		//只有key都不存在的时候才设置,这里返回 null
		Logger.info("jedis.msetnx(key2,key3): " + jedis.msetnx("key2", "exists1", "key3", "exists2"));
		Logger.info("jedis.mget(key2,key3): " + jedis.mget("key2", "key3"));
		
		//设置key,2 秒后失效
		jedis.setex("key4", 2, "2 seconds is no Val");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 2 seconds is no Val
		Logger.info("jedis.get(key4): " + jedis.get("key4"));
		
		jedis.set("key6", "123456789");
		//第三位之后,用新值覆盖旧值(下标从0开始)
		jedis.setrange("key6", 3, "abcdefg");
		//返回:123abcdefg
		Logger.info("jedis.get(key6): " + jedis.get("key6"));
		
		//返回所有匹配的key
		Logger.info("jedis.get(key*): " + jedis.keys("key*")); 
		
		// 关闭连接
		jedis.close();
	}
}

Jedis 操作 List(列表)

示例:

package com.crazymakercircle.redis.jedis;
//...
public class ListDemo {
	/**
	* Redis列表是简单的字符串列表,按照插入顺序排序。
	*/
	@Test
	public void operateList() {
		Jedis jedis = new Jedis("localhost");
		Logger.info("jedis.ping(): " +jedis.ping());
		
		jedis.del("list1");
		//从list列表尾部添加3个元素
		jedis.rpush("list1", "zhangsan", "lisi", "wangwu");
		
		//取得类型:list
		Logger.info("jedis.type(): " +jedis.type("list1"));
		
		//遍历区间[0,-1],取得全部的元素
		Logger.info("jedis.lrange(0,-1): " +jedis.lrange("list1", 0, -1));
		//遍历区间[1,2],取得区间的元素
		Logger.info("jedis.lrange(1,2): " +jedis.lrange("list1", 1, 2));
		
		//获取list列表的长度
		Logger.info("jedis.llen(list1): " +jedis.llen("list1"));
		//获取下标为 1 的元素
		Logger.info("jedis.lindex(list1,1): " +jedis.lindex("list1", 1));
		//左侧弹出元素
		Logger.info("jedis.lpop(): " +jedis.lpop("list1"));
		//右侧弹出元素
		Logger.info("jedis.rpop(): " +jedis.rpop("list1"));
		//设置下标为0的元素val
		jedis.lset("list1", 0, "lisi2");
		//最后,遍历区间[0,-1],取得全部的元素
		Logger.info("jedis.lrange(0,-1): " +jedis.lrange("list1", 0, -1));
		
		// 关闭连接
		jedis.close();
	}
}
运行结果:
[main|ListDemo.operateList] |>jedis.ping(): PONG
[main|ListDemo.operateList] |>jedis.type(): list
[main|ListDemo.operateList] |>jedis.lrange(0,-1): [zhangsan, lisi, wangwu]
[main|ListDemo.operateList] |>jedis.lrange(1,2): [lisi, wangwu]
[main|ListDemo.operateList] |>jedis.llen(list1): 3
[main|ListDemo.operateList] |>jedis.lindex(list1,1): lisi
[main|ListDemo.operateList] |>jedis.lpop(): zhangsan
[main|ListDemo.operateList] |>jedis.rpop(): wangwu
[main|ListDemo.operateList] |>jedis.lrange(0,-1): [lisi2]

Jedis 操作 Hash(哈希表)

示例:

package com.crazymakercircle.redis.jedis;
//…
public class HashDemo {
	/**
	* Redis hash 哈希表是一个string类型的field字段和value值的映射表,
	* hash特别适合用于存储对象。
	* Redis 中每个hash可以存储 2^32 - 1 键-值对(40多亿)
	*/
	@Test
	public void operateHash() {
		Jedis jedis = new Jedis("localhost");
		jedis.del("config");
		
		//设置hash的 field-value对:ip=127.0.0.1
		jedis.hset("config", "ip", "127.0.0.1");
		//取得hash的 field关联的value值
		Logger.info("jedis.hget(): " + jedis.hget("config", "ip"));
		
		//取得类型:hash
		Logger.info("jedis.type(): " + jedis.type("config"));
		
		//批量添加 field-value 对,参数为java map
		Map<String, String> configFields = new HashMap<String, String>();
		configFields.put("port", "8080");
		configFields.put("maxalive", "3600");
		configFields.put("weight", "1.0");
		//执行批量添加
		jedis.hmset("config", configFields);
		
		//批量获取:取得全部 field-value 对,返回 java map映射表
		Logger.info("jedis.hgetAll(): " + jedis.hgetAll("config"));
		//批量获取:取得部分 field对应的value,返回 java map
		Logger.info("jedis.hmget(): " + jedis.hmget("config", "ip", "port"));
		
		//浮点数加: 类似于String的incrByFloat
		jedis.hincrByFloat("config", "weight", 1.2);
		Logger.info("jedis.hget(weight): " + jedis.hget("config", "weight"));
		//获取所有的 field 字段
		Logger.info("jedis.hkeys(config): " + jedis.hkeys("config"));
		//获取所有的 value 字段
		Logger.info("jedis.hvals(config): " + jedis.hvals("config"));
		
		//获取长度
		Logger.info("jedis.hlen(): " + jedis.hlen("config"));
		//判断field是否存在
		Logger.info("jedis.hexists(weight): " + jedis.hexists("config", "weight"));
		//删除一个field
		jedis.hdel("config", "weight");
		Logger.info("jedis.hexists(weight): " + jedis.hexists("config", "weight"));
		
		// 关闭连接
		jedis.close();
	}
}
运行结果:
[main|HashDemo.operateHash] |>jedis.hget(): 127.0.0.1
[main|HashDemo.operateHash] |>jedis.type(): hash
[main|HashDemo.operateHash] |>jedis.hgetAll(): {port=8080, weight=1.0, maxalive=3600, ip=127.0.0.1}
[main|HashDemo.operateHash] |>jedis.hmget(): [127.0.0.1, 8080]
[main|HashDemo.operateHash] |>jedis.hget(): 2.2
[main|HashDemo.operateHash] |>jedis.hkeys(): [weight, maxalive, port, ip]
[main|HashDemo.operateHash] |>jedis.hvals(): [127.0.0.1, 8080, 2.2, 3600]
[main|HashDemo.operateHash] |>jedis.hlen(): 4
[main|HashDemo.operateHash] |>jedis.hexists(weight): true
[main|HashDemo.operateHash] |>jedis.hexists(weight): false

Jedis 操作 Set(集合)

示例:

package com.crazymakercircle.redis.jedis;
//....省略import
public class SetDemo {
	/**
	* Redis 的 Set集合是 String 类型的无序集合。
	* 集合成员是唯一的,集合中不能出现重复的元素。
	* Set集合是通过哈希表实现的,添加,删除,查找的复杂度都是 O(1)。
	*/
	@Test
	public void operateSet() {
		Jedis jedis = new Jedis("localhost");
		jedis.del("set1");
		Logger.info("jedis.ping(): " + jedis.ping());
		Logger.info("jedis.type(): " + jedis.type("set1"));
		
		//sadd函数: 向集合添加元素
		jedis.sadd("set1", "user01", "user02", "user03");
		//smembers函数: 遍历所有元素
		Logger.info("jedis.smembers(): " + jedis.smembers("set1"));
		//scard函数: 获取集合元素个数
		Logger.info("jedis.scard(): " + jedis.scard("set1"));
		//sismember判断是否是集合元素
		Logger.info("jedis.sismember(user04): " + jedis.sismember("set1", "user04"));
		//srem函数:移除元素
		Logger.info("jedis.srem(): " + jedis.srem("set1", "user02", "user01"));
		//smembers函数: 遍历所有元素
		Logger.info("jedis.smembers(): " + jedis.smembers("set1"));
		
		// 关闭连接
		jedis.close();
	}
}
运行结果:
[main|SetDemo.operateSet] |>jedis.ping(): PONG
[main|SetDemo.operateSet] |>jedis.type(): none
[main|SetDemo.operateSet] |>jedis.smembers(): [user02, user03, user01]
[main|SetDemo.operateSet] |>jedis.scard(): 3
[main|SetDemo.operateSet] |>jedis.sismember(user04): false
[main|SetDemo.operateSet] |>jedis.srem(): 2
[main|SetDemo.operateSet] |>jedis.smembers(): [user03]

Jedis 操作 Zset(有序集合)

示例:

有一个 salary 薪资的 Zset 有序集合,Zset 的 Key 键为用户id,Zset 的 score 分数值保存的是用户的薪资:
package com.crazymakercircle.redis.jedis;
//...
public class ZSetDemo {
	/**
	* Zset有序集合和Set集合都是string类型元素的集合,且不允许重复的元素。
	* 不同的是Zset的每个元素都会关联一个double类型的分数,用于从小到大进行排序。
	* 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
	* 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个元素)。
	*/
	@Test
	public void operateZset() {
		Jedis jedis = new Jedis("localhost");
		Logger.info("jedis.ping(): " + jedis.ping());
		
		jedis.del("salary");
		
		Map<String, Double> members = new HashMap<String, Double>();
		members.put("u01", 1000.0);
		members.put("u02", 2000.0);
		members.put("u03", 3000.0);
		members.put("u04", 13000.0);
		members.put("u05", 23000.0);
		//批量添加元素,类型为java map映射表
		jedis.zadd("salary", members);
		
		//type类型:Zset
		Logger.info("jedis.type(): " + jedis.type("salary"));
		
		//获取集合元素的个数
		Logger.info("jedis.zcard(): " + jedis.zcard("salary"));
		//按照下标[起,止]遍历元素
		Logger.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
		//按照下标[起,止]倒序遍历元素
		Logger.info("jedis.zrevrange(): " + jedis.zrevrange("salary", 0, -1));
		
		//按照分数(薪资)[起,止]遍历元素
		Logger.info("jedis.zrangeByScore(): " + jedis.zrangeByScore("salary", 1000, 10000));
		//按照薪资[起,止]遍历元素,带分数返回
		Set<Tuple> res0 = jedis.zrangeByScoreWithScores("salary", 1000, 10000);
		for (Tuple temp : res0) {
			Logger.info("Tuple.get(): " + temp.getElement() + " -> " + temp.getScore());
		}
		//按照分数[起,止]倒序遍历元素
		Logger.info("jedis.zrevrangeByScore(): " + jedis.zrevrangeByScore("salary", 1000, 4000));
		
		//获取元素[起,止]分数区间的元素数量
		Logger.info("jedis.zcount(): " + jedis.zcount("salary", 1000, 4000));
		
		//获取元素score值:薪资
		Logger.info("jedis.zscore(): " + jedis.zscore("salary", "u01"));
		//获取元素的下标
		Logger.info("jedis.zrank(u01): " + jedis.zrank("salary", "u01"));
		//倒序获取元素的下标
		Logger.info("jedis.zrevrank(u01): " + jedis.zrevrank("salary", "u01"));
		
		//删除元素
		Logger.info("jedis.zrem(): " + jedis.zrem("salary", "u01", "u02"));
		//删除元素,通过下标范围
		Logger.info("jedis.zremrangeByRank(): " +
		jedis.zremrangeByRank("salary", 0, 1));
		//删除元素,通过分数范围
		Logger.info("jedis.zremrangeByScore(): " + jedis.zremrangeByScore("salary", 20000, 30000));
		//按照下标[起,止]遍历元素
		Logger.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
		
		Map<String, Double> members2 = new HashMap<String, Double>();
		members2.put("u11", 1136.0);
		members2.put("u12", 2212.0);
		members2.put("u13", 3324.0);
		//批量添加元素
		jedis.zadd("salary", members2);
		//增加指定分数
		Logger.info("jedis.zincrby(10000): " + jedis.zincrby("salary", 10000, "u13"));
		//按照下标[起,止]遍历元素
		Logger.info("jedis.zrange(): " + jedis.zrange("salary", 0, -1));
		
		// 关闭连接
		jedis.close();
	}
}
运行结果:
[main|ZSetDemo.operateZset] |>jedis.ping(): PONG
[main|ZSetDemo.operateZset] |>jedis.type(): zset
[main|ZSetDemo.operateZset] |>jedis.zcard(): 5
[main|ZSetDemo.operateZset] |>jedis.zrange(): [u01, u02, u03, u04, u05]
[main|ZSetDemo.operateZset] |>jedis.zrangeByScore(): [u01, u02, u03]
[main|ZSetDemo.operateZset] |>Tuple.get(): u01 -> 1000.0
[main|ZSetDemo.operateZset] |>Tuple.get(): u02 -> 2000.0
[main|ZSetDemo.operateZset] |>Tuple.get(): u03 -> 3000.0
[main|ZSetDemo.operateZset] |>jedis.zrevrange(): [u05, u04, u03, u02, u01]
[main|ZSetDemo.operateZset] |>jedis.zrevrangeByScore(): []
[main|ZSetDemo.operateZset] |>jedis.zscore(): 1000.0
[main|ZSetDemo.operateZset] |>jedis.zcount(): 3
[main|ZSetDemo.operateZset] |>jedis.zrank(u01): 0
[main|ZSetDemo.operateZset] |>jedis.zrevrank(u01): 4
[main|ZSetDemo.operateZset] |>jedis.zrem(): 2
[main|ZSetDemo.operateZset] |>jedis.zremrangeByRank(): 2
[main|ZSetDemo.operateZset] |>jedis.zremrangeByScore(): 1
[main|ZSetDemo.operateZset] |>jedis.zrange(): []
[main|ZSetDemo.operateZset] |>jedis.get(): 13324.0
[main|ZSetDemo.operateZset] |>jedis.zrange(): [u11, u12, u13]

JedisPool连接池

使用 Jedis API 可以方便地在 Java 程序中操作 Redis,就像通过 JDBC API 操作数据库一样。但是,仅仅实现这一点还是不够的。

为什么呢?
大家知道,数据库连接的底层是一条 Socket 通道,创建和销毁很耗时。
在数据库连接过程中,为了防止数据库连接的频繁创建、销毁带来的性能损耗,常常会用到连接池(Connection Pool),例如淘宝的 Druid 连接池、Tomcat 的 DBCP 连接池。

Jedis 连接和数据库连接一样,也需要使用连接池(Connection Pool)来管理。

Jedis 开源库提供了一个负责管理 Jedis 连接对象的池,名为JedisPool 类,位于 redis.clients.jedis 包中。


JedisPool:配置

在使用 JedisPool 类创建 Jedis 连接池之前,首先要了解一个很重要的配置类——JedisPoolConfig 配置类,它也位于 redis.clients.jedis 包中。这个连接池的配置类负责配置 JedisPool 的参数。


JedisPoolConfig 配置类涉及到很多与连接管理和使用有关的参数:

  1. maxTotal:资源池中最大的连接数,默认值为 8
  2. maxIdle:资源池允许最大空闲的连接数,默认值为 8
  3. minIdle:资源池确保最少空闲的连接数,默认值为 0
    • 如果 JedisPool 开启了空闲连接的有效性检测,如果空闲连接无效,就销毁。销毁连接后,连接数量就少了,如果小于 minIdle 数量,就新建连接,维护数量不少于 minIdle 的数量。
    • minIdle 确保了线程池中有最小的空闲 Jedis 实例的数量。
  4. blockWhenExhausted:当资源池用尽后,调用者是否要等待,默认值为 true。
    • 当为 true 时,maxWaitMillis 才会生效。
  5. maxWaitMillis:当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)。
    • 默认值为 -1,表示永不超时,不建议使用默认值。
  6. testOnBorrow:向资源池借用连接时,是否做有效性检测(ping命令):如果是无效连接,会被移除。
    • 默认值为 false,表示不做检测。
    • 如果为 true,则得到的 Jedis 实例均是可用的。
    • 在业务量小的应用场景,建议设置为 true,确保连接可用;在业务量很大的应用场景,建议设置为 false(默认值),少一次 ping 命令的开销,有助于提升性能。
  7. testOnReturn:向资源池归还连接时,是否做有效性检测(ping命令):如果是无效连接,会被移除。
    • 默认值为 false,表示不做检测。
    • 同样,在业务量很大的应用场景,建议设置为 false(默认值),少一次 ping 命令的开销。
  8. testWhileIdle:如果为 true,表示用一个专门的线程对空闲的连接进行有效性的检测扫描,如果有效性检测失败,即表示无效连接,会从资源池中移除。
    • 默认值为true,表示进行空闲连接的检测。
    • 这个选项存在一个附加条件,需要配置项 timeBetweenEvictionRunsMillis 的值大于 0;否则,testWhileIdle 不会生效。
  9. timeBetweenEvictionRunsMillis:表示两次空闲连接扫描的活动之间,要睡眠的毫秒数,默认为30000毫秒,也就是30秒钟。
  10. minEvictableIdleTimeMillis:表示一个 Jedis 连接至少停留在空闲状态的最短时间,然后才能被空闲连接扫描线程进行有效性检测,默认值为 60000 毫秒,即 60 秒。
    也就是说在默认情况下,一条 Jedis 连接只有在空闲 60 秒后,才会参与空闲线程的有效性检测。
    • 这个选项存在一个附加条件,需要在 timeBetweenEvictionRunsMillis 大于 0 时才会生效。也就是说,如果不启动空闲检测线程,这个参数也没有什么意义。
  11. numTestsPerEvictionRun:表示空闲检测线程每次最多扫描的 Jedis 连接数,默认值为 -1,表示扫描全部的空闲连接。
    • 空闲扫描的选项在 JedisPoolConfig 的构造器中都有默认值,具体如下:
      package redis.clients.jedis;
      import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
      public class JedisPoolConfig extends GenericObjectPoolConfig {
      	public JedisPoolConfig() {
      		this.setTestWhileIdle(true);
      		this.setMinEvictableIdleTimeMillis(60000L);
      		this.setTimeBetweenEvictionRunsMillis(30000L);
      		this.setNumTestsPerEvictionRun(-1);
      	}
      }
      
  12. jmxEnabled:是否开启 jmx 监控,默认值为 true,建议开启。


关于“maxTotal”

有个实际的问题:如何推算一个连接池的最大连接数 maxTotal 呢?

实际上,这是一个很难精准回答的问题,主要是依赖的因素比较多。大致的推算方法是:“业务QPS / 单连接的QPS = 最大连接数”。

如何推算单个 Jedis 连接的 QPS 呢?
   假设一个Jedis命令操作的时间约为 5ms(包含:borrow + return + Jedis执行命令 + 网络延迟),那么,单个 Jedis 连接的 QPS 大约是“1000 / 5 = 200”。

所以,如果业务期望的 QPS 是 100000,则需要的最大连接数为“100000 / 200 = 500”。

事实上,上面的估算仅仅是个理论值。在实际的生产场景中,还要预留一些资源,通常来讲所配置的 maxTotal 要比理论值大一些

  • 如果连接数确实太多,可以考虑 Redis 集群,那么单个 Redis 节点的最大连接数的公式为:“maxTotal = 预估的连接数 / nodes节点数”。
在并发量不大时,maxTotal 设置过高会导致不必要的连接资源的浪费。可以根据实际总 QPS 和 nodes 节点数,合理评估每个节点所使用的最大连接数。


关于“maxIdle”

再看一个问题:如何推算连接池的最大空闲连接数 maxIdle 值呢?

实际上,maxTotal 只是给出了一个连接数量的上限,maxIdle 实际上才是业务可用的最大连接数,从这个层面来说,maxIdle不能设置过小,否则会有创建、销毁连接的开销。

使得连接池达到最佳性能的设置是“maxTotal = maxIdle”,应尽可能地避免由于频繁地创建和销毁 Jedis 连接所带来的连接池性能的下降。


JedisPool:创建、预热

关于预热:

虽然 JedisPool 定义了最大空闲资源数、最小空闲资源数,但是在创建的时候,不会真的创建好 Jedis 连接并放到 JedisPool 池子里。这样会导致一个问题:
   刚创建好的连接池,池子没有 Jedis 连接资源在使用,在初次访问请求到来的时候,才开始创建新的连接,不过,这样会导致一定的时间开销。

为了提升初次访问的性能,可以考虑在 JedisPool 创建后,为 JedisPool 提前进行预热,一般以最小空闲数量作为预热数量。


JedisPool 的创建和预热:

  1. 以 JedisPoolConfig 实例、Redis IP、Redis端口和其他可选选项(如超时时间、Auth密码)为参数,构造一个 JedisPool 连接池实例;
  2. 以最小空闲数量(即:minIdle 配置项)对 JedisPool 进行预热;
  3. 定义一个从连接池中获取 Jedis 连接的新方法——“getJedis()”,供其他模块调用。


代码实现:

package com.crazymakercircle.redis.jedisPool;
//...
public class JredisPoolBuilder {
	public static final int MAX_IDLE = 50;
	public static final int MAX_TOTAL = 50;
	private static JedisPool pool = null;
	
	static {
		// 创建连接池
		buildPool();
		// 预热连接池
		hotPool();
	}
	
	// 创建连接池
	private static JedisPoolbuildPool() {
		if (pool == null) {
			long start = System.currentTimeMillis();
			
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(MAX_TOTAL);
			config.setMaxIdle(MAX_IDLE);
			config.setMaxWaitMillis(1000 * 10);
			// 在borrow一个jedis实例时,是否提前进行有效检测操作;
			// 如果为true,则得到的jedis实例均是可用的;
			config.setTestOnBorrow(true);
			
			//new JedisPool(config, ADDR, PORT, TIMEOUT);
			pool = new JedisPool(config, "127.0.0.1", 6379, 10000);
			
			long end = System.currentTimeMillis();
			Logger.info("buildPool毫秒数:", end - start);
		}
		return pool;
	}
	
	// 连接池的预热
	public static void hotPool() {
		long start = System.currentTimeMillis();
		
		List<Jedis> minIdleJedisList = new ArrayList<Jedis>(MAX_IDLE);
		Jedis jedis = null;
		for (int i = 0; i< MAX_IDLE; i++) {
			try {
				jedis = pool.getResource();
				minIdleJedisList.add(jedis);
				jedis.ping();
			} catch (Exception e) {
				Logger.error(e.getMessage());
			} finally {
			}
		}
		for (int i = 0; i< MAX_IDLE; i++) {
			try {
				jedis = minIdleJedisList.get(i);
				jedis.close();
			} catch (Exception e) {
				Logger.error(e.getMessage());
			} finally {
			}
		}
		
		long end = System.currentTimeMillis();
		Logger.info(" hotPool毫秒数:", end - start);
	}
	
	//获取连接
	public static JedisgetJedis() {
		return pool.getResource();
	}
}


JedisPool:使用

JedisPool 的使用:

  1. 可以使用前面定义好的“getJedis()”方法,间接地通过“pool.getResource()”从连接池获取连接;也可以直接通过“pool.getResource()”方法获取 Jedis 连接。
  2. Jedis 连接使用完后,一定要调用“close()”方法关闭连接。
    • 这个关闭操作不是真正地关闭连接,而是归还给连接池。这一点和使用数据库连接池是一样的。
    • 一般来说,关闭操作放在 finally 代码段中,确保 Jedis 的关闭最终都会被执行到。


示例:

package com.crazymakercircle.redis.jedisPool;
//...
public class JredisPoolTester {
	public static final int NUM = 200;
	public static final String ZSET_KEY = "zset1";
	
	// 测试删除
	@Test
	public void testDel() {
		Jedisredis =null;
		try {
			redis = JredisPoolBuilder.getJedis();
			
			long start = System.currentTimeMillis();
			redis.del(ZSET_KEY);
			long end = System.currentTimeMillis();
			Logger.info("删除 zset1 毫秒数:", end - start);
		} finally {
			// 使用后一定关闭,还给连接池
			if (redis != null) {
			redis.close();
			}
		}
	}
	
	//...
}


由于 Jedis 类实现了java.io.Closeable接口,故而在 JDK 1.7 或者以上版本中可以使用 try-with-resources 语句,在其隐藏的 finally 部分自动调用 close 方法。

示例:

package com.crazymakercircle.redis.jedisPool;
//...
public class JredisPoolTester {
	public static final int NUM = 200;
	public static final String ZSET_KEY = "zset1";
	
	// 测试创建Zset
	@Test
	public void testSet() {
		testDel();   // 首先删除之前创建的Zset
		
		try (Jedis redis = JredisPoolBuilder.getJedis()) {
			int loop = 0;
			long start = System.currentTimeMillis();
			while (loop < NUM) {
				redis.zadd(ZSET_KEY, loop, "field-" + loop);
				loop++;
			}
			long end = System.currentTimeMillis();
			Logger.info("设置Zset :", loop, "次, 毫秒数:", end - start);
		}
	}
	
	//...
}