Jedis是什么
jedis就是基于java语言的redis客户端,集成了redis的命令操作,提供了连接池管理。
redis-cli是redis官方提供的客户端,可以看作一个shell程序,它可以发送命令对redis进行操作。
对于jedis同理是使用java语言操作redis,双方都遵循redis提供的协议,按照协议开发对应的客户端。
Maven依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
<scope>compile</scope>
</dependency>
Jedis直连
jedis直连,本质是定义一个tcp连接,然后使用socket技术进行通信
//1.生成一个jedis对象,这个对象负责和指定Redis节点进行通信
Jedis jedis = new Jedis("119.23.226.29", 6379);
//带密码需要执行认证方法
//jedis.auth("123456");
//2.jedis执行set操作
jedis.set("hello", "world");
//3.jedis执行get操作,value="world"
String value = jedis.get("hello");
构造方法参数介绍
Jedis简单使用
字符串
// 1.string
//输出结果: OK
jedis.set("hello", "world");
//输出结果: world
jedis.get("hello");
//输出结果:1
jedis.incr("counter");
哈希
// 2.hash
jedis.hset("myhash", "f1", "v1");
jedis.hset("myhash", "f2", "v2");
//输出结果 : {f1=v1, f2=v2}
jedis.hgetAll("myhash");
列表
// 3.list
jedis.rpush("mylist", "1");
jedis.rpush("mylist", "2");
jedis.rpush(" mylist", "3");
//输出结果 : [1, 2, 3]
jedis.lrange("mylist", 0, -1);
集合
// 4.set
jedis.sadd(" myset", "a");
jedis.sadd(" myset", "b");
jedis.sadd(" myset", "a");
//输出结果 : [b, a]
jedis.smembers("myset");
有序集合
// 5.zset
jedis.zadd("myzset", 99, "tom");
jedis.zadd("myzset", 66, "peter");
jedis.zadd("myzset", 33, "james");
//输出结果 : [[["james"],33.0], [["peter"],66.0], [["tom"],99.0]]
jedis.zrangeWithScores("myzset", 0, -1);
Jedis连接池
jedis直连
每次操作创建一个jedis对象,执行完毕后关闭连接,对应的就是一次Tcp连接。
jedis连接池
预先生成一批jedis连接对象放入连接池中,当需要对redis进行操作时从连接池中借用jedis对象,操作完成后归还。这样jedis对象可以重复使用,避免了频繁创建socket连接,节省了连接开销。
方案对比
连接池简单使用
这里只是对连接池进行一个简单使用,实际开发通常会对JedisPool进行封装,进行一些参数配置和方法定义等,在使用Jedis API时,也会对常用API进行封装,方便程序调用
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class Demo {
public static void main(String[] args) {
//连接池配置对象,包含了很多默认配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
//初始化Jedis连接池,通常来讲JedisPool是单例的
JedisPool jedisPool = new JedisPool(poolConfig, "119.23.226.29", 6379);
Jedis jedis = null;
try {
//1.从连接池获取jedis对象
jedis = jedisPool.getResource();
//2.执行操作
jedis.set("hello", "jedis");
System.out.println(jedis.get("hello"));
} catch (Exception e) {
e.printStackTrace();
} finally{
//如果使用JedisPool,那么close操作不是关闭连接,代表归还连接池
if(jedis != null){
jedis.close();
}
}
}
}
Jedis配置优化
对于企业级开发来说,连接池的合理使用是非常重要的,如果设置不当会引起很多不必要的麻烦,容易造成线上的故障。
其实关于配置是一个比较难或者说没有确定答案的部分,这里只能给出一些思路和解决一些异常的方法。
连接池重要配置
为了方便使用,Jedis提供了JedisPoolConfig
,它本身继承了GenericObjectPoolConfig
设置了一些空闲监测设置
资源数控制
借还参数
适合的maxTotal
其实这个参数是比较难确定的,举个例子:
- 命令平均执行时间0.1ms = 0.001s
- 业务需要50000 QPS
- maxTotal理论值 = 0.001 * 50000 = 50个。实际值要偏大一些
对于适合的maxTotal而言,我们需要考虑
- 业务希望Redis并发量
- 客户端执行命令时间
- Redis资源:例如 nodes(例如应用个数) * maxTotal 是不能超过redis的最大连接数
- 资源开销:例如虽然希望控制空闲连接,但是不希望因为连接池的频繁释放创建连接造成不必靠开销
适合的maxIdle和minIdle
- 建议maxIdle = maxTotal,减少创建新连接的开销
- 建议预热minIdle,减少第一次启动后的新连接开销
常见问题
无法从资源池获取到资源,原因是获取空闲连接超时了。
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
…
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
无法从资源池获取到资源,原因是池子中资源已经耗尽了。
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
…
Caused by: java.util.NoSuchElementException: Pool exhausted
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)
解决思路
- 慢查询阻塞:池子连接都被hang住。
- 资源池参数不合理:例如QPS高、池子小。
- 连接泄露(没有close()):此类问题比较难定位,例如client list、netstat等,最重要的是写合理的代码。
- DNS异常等。
例如连接泄漏
public static void main(String[] args){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10);
jedisPoolConfig.setMaxWaitMillis(1000);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
for(int i = 0; i < 10; i++){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
jedis.ping();
//没有进行连接的归还
}catch(Exception e){
e.printStackTrace();
}
//再次获取资源就会出错
jedisPool.getResource().ping();
}
}