Jedis
连接池
Redis
连接池和数据库连接池一样,也是预先创建和管理一组连接,这样当需要与Redis
服务器交互时,就可以直接复用连接。Redis
的客户端Jedis
、Lettuce
都实现了连接池的功能。我们本篇文章先以 Jedis
为例,从连接的获取、归还、关闭、创建几个方面详细介绍具体的实现功能。
多线程的使用
在数据库中如果多个线程复用一个连接会存在数据库事务问题,那么在Redis
中我们先来看下在多线程环境下使用一个连接会产生什么问题?
首先启动两个线程,共同操作同一个 Jedis
实例,每一个线程循环 500 次,分别读取 Key 为 a 和 b 的值
java
复制代码
Jedis jedis = new Jedis("127.0.0.1", 6379); new Thread(() -> { for (int i = 0; i < 500; i++) { String result = jedis.get("a"); System.out.println(result); } }).start(); new Thread(() -> { for (int i = 0; i < 500; i++) { String result = jedis.get("b"); System.out.println(result); } }).start();
执行程序多次,可以看到日志中出现了各种奇怪的异常信息,有的未知答复错误,还有的是连接关闭异常等
错误1:redis.clients.jedis.exceptions.JedisConnectionException: Unknown reply: 1
错误2:java.io.IOException: Socket Closed
那我们先来看下 Jedis
常用的(3.x)版本的源码
java
复制代码
public class Jedis extends BinaryJedis implements JedisCommands, MultiKeyCommands, AdvancedJedisCommands, ScriptingCommands, BasicCommands, ClusterCommands, SentinelCommands, ModuleCommands{} public class BinaryJedis implements BasicCommands, BinaryJedisCommands, MultiKeyBinaryCommands, AdvancedBinaryJedisCommands, BinaryScriptingCommands, Closeable { protected final Client client; } public class Client extends BinaryClient implements Commands {} public class BinaryClient extends Connection {} public class Connection implements Closeable { private Socket socket; private RedisOutputStream outputStream; private RedisInputStream inputStream; }
首先Jedis
继承了 BinaryJedis
,BinaryJedis
中保存了单个 Client
的实例,Client
最终继承了 Connection
,Connection
中保存了单个 Socket
的实例以及对应的两个读写流一个 RedisOutputStream
一个是 RedisInputStream
。
BinaryClient
封装了各种 Redis
命令,其最终会调用的是 sendCommand
方法,发现其发送命令时是直接操作 RedisOutputStream
写入字节。
java
复制代码
private static void sendCommand(final RedisOutputStream os, final byte[] command, final byte[]... args) { try { os.write(ASTERISK_BYTE); os.writeIntCrLf(args.length + 1); os.write(DOLLAR_BYTE); os.writeIntCrLf(command.length); os.write(command); os.writeCrLf(); for (final byte[] arg : args) { os.write(DOLLAR_BYTE); os.writeIntCrLf(arg.length); os.write(arg); os.writeCrLf(); } } catch (IOException e) { throw new JedisConnectionException(e); } }
所以在多线程环境下使用 Jedis
,其实就是在复用RedisOutputStream
。如果多个线程在执行操作,那么无法保证整条命令是原子写入 Socket
。比如,写操作互相干扰,多条命令相互穿插的话,必然不是合法的 Redis
命令也就导致等等各种问题。
这也说明了Jedis
是非线程安全。但是可以通过JedisPool
连接池去管理实例,在多线程情况下让每个线程有自己独立的Jedis
实例,可变为线程安全。
java
复制代码
//使用redis连接池,不会有线程安全问题 private static JedisPool jedisPool = new JedisPool("127.0.0.1", 6379); new Thread(() -> { try (Jedis jedis = jedisPool.getResource()) { for (int i = 0; i < 1000; i++) { String result = jedis.get("a"); System.out.println(result); } } }).start(); new Thread(() -> { try (Jedis jedis = jedisPool.getResource()) { for (int i = 0; i < 1000; i++) { String result = jedis.get("b"); System.out.println(result); } } }).start();
连接池的管理
JedisPool
的连接池是基于 Apache Commons Pool