Redis(四)Jedis客户端
一、客户端通信协议
二、Java客户端Jedis
1.获取Jedis
Jedis属于Java的第三方开发包,在Java中获取第三方开发包通常有两种方式:
- 直接下载目标版本的Jedis-${version}.jar包加入到项目中。
- 使用集成构建工具,例如maven、gradle等将Jedis目标版本的配置加入到项目中。
2.Jedis的基本使用方法
本次使用的jar包为:jedis-2.9.0.jar
bigjun@myubuntu:/home/workspace/JedisTest$ tree . ├── bin │ └── ubuntuJedis.class ├── lib │ └── jedis-2.9.0.jar └── src └── ubuntuJedis.java 3 directories, 3 files
Jedis构造器:
Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout) host:Redis实例的所在机器的IP port:Redis实例的端口 connectionTimeout:客户端连接超时 soTimeout:客户端读写超时
先看一个简单的代码实现set和get功能:
package JedisTest; import redis.clients.jedis.Jedis; public class JedisTest { public static void main(String[] args) { @SuppressWarnings("resource") Jedis jedis = new Jedis("localhost"); jedis.set("eclipse", "eclipseJedis"); String getResult = jedis.get("eclipse"); System.out.println(getResult); } }
在实际项目中,比较推荐使用try catch finally的形式来进行代码的书写:一方面可以在Jedis出现异常的时候(本身是网络操作),将异常进行捕获或者抛出;另一个方面无论执行成功或者失败,将Jedis连接关闭掉,在开发中关闭不用的连接资源是一种好的习惯,代码类似如下:
package JedisTest; import redis.clients.jedis.Jedis; public class JedisTest { public static void main(String[] args) { Jedis jedis = null; try { jedis = new Jedis("127.0.0.1", 6379); String string = jedis.get("hello"); System.out.println(string); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } }
给出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);
3.序列化和反序列化:
有了上述这些API的支持,就可以将Java对象序列化为二进制,当应用需要获取Java对象时,使用get(final byte[]key)函数将字节数组取出,然后反序列化为Java对象即可。
和很多NoSQL数据库(例如Memcache、Ehcache)的客户端不同,Jedis本身没有提供序列化的工具,也就是说开发者需要自己引入序列化的工具。
常用的序列化工具为JDK、XML、JSON和Protostuff,这几种序列化工具在Jedis中的使用见另外的一篇博客。
4.Jedis连接池的使用方法
(1)直连方式
直连是指Jedis每次都会新建TCP连接,使用后再断开连接,对于频繁访问Redis的场景显然不是高效的使用方式。
(2)连接池方式
生产环境中一般使用连接池的方式对Jedis连接进行管理,Jedis对象预先放在连接池(JedisPool)中,每次要连接Redis,只需要在池子中借,用完了再归还给池子。
(3)Jedis直连方式和连接池方式对比
(4)Jedis连接池方式示例
JedisPool版本:commons-pool2-2.5.0.jar
- 使用默认连接池配置来实现Redis的连接
package bigjun.iplab.jedisPoolTest; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class JedisPoolTest { public static void main(String[] args) { // 连接池配置,这里使用默认配置 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); @SuppressWarnings("resource") // 初始化Jedis连接池 JedisPool jedisPool = new JedisPool(poolConfig, "192.168.131.130", 6379); Jedis jedis = null; try { // 从连接池获取jedis对象 jedis = jedisPool.getResource(); // 执行操作 String getResult = jedis.get("Lua"); System.out.println(getResult); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } }
- Jedis中的close()方法
@Override public void close() {
// 如果连接池正在使用 if (dataSource != null) {
// 如果连接断开,就返还连接给连接池 if (client.isBroken()) { this.dataSource.returnBrokenResource(this); } else { this.dataSource.returnResource(this); }
// 直连方式的时候,关闭连接 } else { client.close(); } }
- 连接池配置的配置方法
// 默认配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); // 设置最大连接数为默认值的 5 倍 poolConfig.setMaxTotal(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL * 5); // 设置最大空闲连接数为默认值的 3 倍 poolConfig.setMaxIdle(GenericObjectPoolConfig.DEFAULT_MAX_IDLE * 3); // 设置最小空闲连接数为默认值的 2 倍 poolConfig.setMinIdle(GenericObjectPoolConfig.DEFAULT_MIN_IDLE * 2); // 设置开启 jmx 功能 poolConfig.setJmxEnabled(true); // 设置连接池没有连接后客户端的最大等待时间 ( 单位为毫秒 ) poolConfig.setMaxWaitMillis(3000);
5.Jedis中Pipeline使用方法
示例背景:Redis提供了mget、mset方法,但是没有提供mdel方法,可以用Pipeline来模拟批量删除,虽然不想mset和mget是原子命令,但是绝大时候可以使用。
(1)使用sync()执行命令
public void mdel(List<String> keys) { Jedis jedis = new Jedis("127.0.0.1"); // 1) 生成 pipeline 对象 Pipeline pipeline = jedis.pipelined(); // 2)pipeline 执行命令,注意此时命令并未真正执行 for (String key : keys) { pipeline.del(key); } // 3) 执行命令 pipeline.sync(); }
(2)使用syncAndReturnAll()方法执行命令并获得返回值
Jedis jedis = new Jedis("127.0.0.1"); Pipeline pipeline = jedis.pipelined(); pipeline.set("hello", "world"); pipeline.incr("counter"); List<Object> resultList = pipeline.syncAndReturnAll(); for (Object object : resultList) { System.out.println(object); }
6.Jedis的Lua脚本
(1)Jedis的三个重要的函数:
Object eval(String script, int keyCount, String... params) Object evalsha(String sha1, int keyCount, String... params) String scriptLoad(String script) script:Lua脚本内容。
sha1:脚本的SHA1. keyCount:键的个数。 params:相关参数KEYS和ARGV。
(2)使用eval()方法
在Redis中执行Lua脚本:
192.168.131.130:6379> get Lua "Redis" 192.168.131.130:6379> eval 'return redis.call("get", KEYS[1])' 1 Lua "Redis"
在Jedis中执行相同的Lua脚本:
try { // 从连接池获取jedis对象 jedis = jedisPool.getResource(); // 执行操作 String key = "Lua"; String script = "return redis.call('get', KEYS[1])"; Object result = jedis.eval(script, 1, key); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); }
(3)使用scriptLoad()和evalsha()方法
try { // 从连接池获取jedis对象 jedis = jedisPool.getResource(); // 执行操作 String key = "Lua"; String script = "return redis.call('get', KEYS[1])"; String scriptSha1 = jedis.scriptLoad(script); Object result = jedis.evalsha(scriptSha1, 1, key); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } }
四、客户端管理
1.客户端API
(1)client list
192.168.131.130:6379> client list id=3 addr=192.168.131.130:35096 fd=7 name= age=14770 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client id=7 addr=192.168.131.130:35104 fd=8 name= age=11574 idle=11465 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys id:客户端连接的唯一标识 addr:客户端连接的ip和端口 fd:socket的文件描述符 name:客户端的名字
age:当前客户端已经连接的时间
idle:当前客户端最近一次的空闲时间
flags:标识当前客户端的类型(N:普通客户端;S:slave客户端等11种客户端类型) qbuf:输入缓冲区的总容量 qbuf-free:输入缓冲区的剩余容量 obl:输出缓冲区固定缓冲区的长度
oll:输出缓冲区动态缓冲区列表的长度
omen:输出缓冲区使用的字节
- 输入缓冲区
监控输入缓冲区异常的两种方法:
- 输出缓冲区
按照客户端的不同分为三种:普通客户端、发布订阅客户端、slave客户端
输出缓冲区的容量可以通过参数client-output-buffer-limit来进行设置
和输入缓冲区一样有两种监控方法:client list和info clients
(2)client setName和client getName
在Redis只有一个应用方使用的情况下,IP和端口作为标识会更加清晰;当多个应用方共同使用一个Redis,client setName可以作为标识客户端的一个依据。
192.168.131.130:6379> client setName haveName_client OK 192.168.131.130:6379> client list id=3 addr=192.168.131.130:35096 fd=7 name=haveName_client age=16157 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client id=7 addr=192.168.131.130:35104 fd=8 name= age=12961 idle=12852 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys 192.168.131.130:6379> client getName "haveName_client"
(3)client kill ip:port
192.168.131.130:6379> client list id=3 addr=192.168.131.130:35096 fd=7 name=haveName_client age=16213 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client id=7 addr=192.168.131.130:35104 fd=8 name= age=13017 idle=12908 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys 192.168.131.130:6379> client kill 192.168.131.130:35104 OK 192.168.131.130:6379> client list id=3 addr=192.168.131.130:35096 fd=7 name=haveName_client age=16239 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
(4)阻塞客户端连接:client pause timeout(毫秒)
客户端1: 192.168.131.130:6379> client pause 10000 OK 客户端2: 192.168.131.130:6379> ping PONG (9.00s)
(5)监控Redis正在执行的命令:monitor
客户端1: 192.168.131.130:6379> monitor OK 客户端2: (9.00s) 192.168.131.130:6379> get Lua "Redis" 192.168.131.130:6379> ping PONG 客户端1: 192.168.131.130:6379> monitor OK 1528182107.437256 [0 192.168.131.130:35108] "get" "Lua" 1528182112.634521 [0 192.168.131.130:35108] "ping" ...
2.客户端相关配置
timeout:检测客户端空闲连接的超时时间,一旦idle时间达到了timeout,客户端将会被关闭,如果设置为0就不进行检测。 maxclients:客户端最大连接数赘述,这个参数会受到操作系统设置的限制, tcp-keepalive:检测TCP连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为60,那么Redis会每隔60秒对它创建的TCP连接进行活性检测,防止大量死连接占用系统资源。 tcp-backlog:TCP三次握手后,会将接受的连接放入队列中,tcp-backlog就是队列的大小,它在Redis中的默认值是511
3.客户端统计片段
192.168.131.130:6379> info clients # Clients connected_clients:2 client_longest_output_list:0 client_biggest_input_buf:0 blocked_clients:0 connected_clients:当前Redis节点的客户端连接数 client_longest_output_list:当前所有输出缓冲区中队列对象个数的最大值 client_biggest_input_buf:当前所有输入缓冲区中占用的最大容量 blocked_clients:正在执行阻塞命令的客户端的个数 192.168.131.130:6379> info stats # Stats total_connections_received:11 total_commands_processed:73 instantaneous_ops_per_sec:0 total_net_input_bytes:3618 total_net_output_bytes:25444 instantaneous_input_kbps:0.00 instantaneous_output_kbps:0.00 rejected_connections:0 ...
五、客户端常见异常
1.无法从连接池获取到连接
2.客户端读写超时
3.客户端连接超时
4.客户端缓冲区异常
5.Lua脚本正在执行
6.Redis正在加载持久化文件
7.Redis使用的内存超过maxmemory配置
8.客户端连接数过大