(一)获取Jedis
Jedis是基于java语言的redis_cli
maven依赖:
<!-- Redis的redis客户端
https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
</dependency>
(二)Jedis基本使用
1、Jedis直连:
Jedis直连相当于一个TCP连接,数据传输完成后关闭连接
//1.生成一个Jedis对象,这个对象负责和指定的Redis节点进行通信
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.jedis执行set操作
jedis.set("hello","world");
//3.jedis执行get操作,value=“world”
String value =jedis.get("hello");
2、Jedis构造函数参数的意义:
Jedis(String host, int port, int connectionTimeout, int soTimeout)
host:Redis节点所在的机器的IP
port:Redis节点的端口
connectionTimeout:客户端连接超时
soTimeout:客户端读写超时
3、简单使用:
//Jedis数据类型:
//1.string
jedis.set("hello","world");
//输出结果: world
System.out.println(jedis.get("hello"));
//对counter自增,输出结果:1
System.out.println(jedis.incr("counter"));
//2.jash
jedis.hset("myhash","f1","v1");
jedis.hset("myhash","f2","v2");
//输出结果:{f2=v2, f1=v1}
System.out.println(jedis.hgetAll("myhash"));
//3.list
jedis.rpush("mylist","1");
jedis.rpush("mylist","2");
jedis.rpush("mylist","3");
//输出结果:[1, 2, 3]
System.out.println(jedis.lrange("mylist",0,-1));
//4.set
jedis.sadd("myset","a");
jedis.sadd("myset","b");
jedis.sadd("myset","a");
//输出结果:[a, b]
System.out.println(jedis.smembers("myset"));
//5.zset
jedis.zadd("myzset",22,"a");
jedis.zadd("myzset",33,"b");
jedis.zadd("myzset",35,"c");
//输出结果:[[a,22.0], [b,33.0], [c,35.0]]
System.out.println(jedis.zrangeWithScores("myzset",0,-1));
(三)Jedis连接池基本使用
1、Jedis Pool的简单介绍
使用Jedis线程池可以不需要创建新的Jedis对象连接Redis,可以大大减少对于创建和回收Redis连接的开销
2、对使用线程池和使用Jedis直连两方案的对比
3、Jedis Pool的简单使用
通常来讲JedisPool是单例的
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
Jedis jedis = null;
try {
//从连接池获取jedis对象
jedis = jedisPool.getResource();
//执行操作
jedis.set("java", "good");
System.out.println(jedis.get("java"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
//这里使用的close不代表关闭连接,指的是归还资源
jedisPool.close();
}
}
4、Jedis配置优化思路
规划出连接池最大连接数,最大空闲数,最小空闲数,如果在借用池中连接没有资源时,是等待还是超时,如何控制连接池中的空闲对象。
1)如何确定maxTotal?
首先举个例子:
1.命令平均执行时间0.1ms = 0.001s。
2.业务需要50000QPS
3.maxTotal理论值 = 0.001*50000=50个。但是实际设计时可能会偏大一些。
由以上可以得出,需要考虑:业务希望的Redis并发量、客户执行命令的时间、Redis资源:例如nodes(应用个数)+maxTotal是不能超过redis的最大连接数的(config get maxclients)
2)如何确定合适的maxIdle和minIdle?
建议maxIdle=maxTotal,为什么这么说呢?假如现在maxTotal是100,maxIdle是50,那么允许的最大空闲数是50,那么在一个高峰期,如果连接池中的50个已经通过连接池的getResource获取到了,这个时候第51个连接是要通过newJedis以及TCP三次握手建立一个新的连接,实际上这本身是有一定开销的。这样可以减少新的开销。建议预热minIdle,第一次getResource时是newJedis并建立TCP三次握手的,对于并发量较大的情况是无法容忍第一次开销的,那么可以在应用初始化的时候提前使用getResource做一些操作。
5、常见问题:
获取连接超时,主要原因在maxWaitMills这个参数:
#获取连接超时,主要原因在maxWaitMills这个参数:
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)
#连接池资源耗尽,可能原因:maxIdle!=maxTotal
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:449)
解决这一类问题的思路:
1.慢查询阻塞:连接池连接都被hang住。比如多个连接都在执行keys *,或者这redis本身的单线程被阻塞,当这两种情况发生时,都会出现上面两个问题,这就需要对每个操作设置超时时间,对maxWaitMills进行合理配置去观察是否合理,最重要的就是去解决这些慢查询。
2.资源池参数不合理:比如QPS高,连接池小。
3.连接泄露(没有close()):这类问题比较难定位,比如使用client list、netstat等,最重要的是写合理的代码,比如不使用try catch finally,如果中间发生了异常,close就无法执行了。
4.DNS异常也会导致这种问题。
举个例子(只要将注释内的内容取消就可以解决bug了):
public static void main(String[] args) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10);
poolConfig.setMaxWaitMillis(1000);
JedisPool jedisPool = new JedisPool(poolConfig, "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();
/*} finally {
if (jedis != null) {
jedisPool.close();
}*/
}
}
jedisPool.getResource().ping();
}