jedis和lettuce的对⽐
⽹上都说jedis实例是⾮线程安全的,常常通过JedisPool连接池去管理实例,在多线程情况下让每个线程有⾃⼰独⽴的jedis实例,但都没有具体说明为啥jedis实例时⾮线程安全的,下⾯详细看⼀下⾮线程安全主要从哪个⾓度来看。
1. jedis类图
2. 为什么jedis不是线程安全的?
由上述类图可知,Jedis类中有RedisInputStream和RedisOutputStream两个属性,⽽发送命令和获取返回值都是使⽤这两个成员变量,显然,这很容易引发多线程问题。测试代码如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23public class BadConcurrentJedisTest {
private static final ExecutorService pool = wFixedThreadPool(20); private static final Jedis jedis = new Jedis("192.168.58.99", 6379);
public static void main(String[] args) {
for(int i=0;i<20;i++){
}
}
static class RedisSet implements Runnable{
@Override
public void run() {
while(true){
jedis.set("hello", "world");
}
}
}
报错:1
2
3
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21Exception in thread "pool-1-thread-4"redis.ptions.JedisConnectionException: java.SocketException: Socket Closed at redis.clients.t(Connection.java:164)
at redis.clients.t(BinaryClient.java:80)
at redis.clients.jedis.Connection.sendCommand(Connection.java:100)
at redis.clients.jedis.BinaryClient.set(BinaryClient.java:97)
at redis.clients.jedis.Client.set(Client.java:32)
at redis.clients.jedis.Jedis.set(Jedis.java:68)
at threadsafe.BadConcurrentJedisTest$RedisSet.run(BadConcurrentJedisTest.java:26)
at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at urrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.SocketException: Socket Closed
at java.AbstractPlainSocketImpl.setOption(AbstractPlainSocketImpl.java:212)
at java.Socket.setKeepAlive(Socket.java:1310)
at redis.clients.t(Connection.java:149)
... 9more
redis.ptions.JedisConnectionException: java.SocketException: Socket Closed
at redis.clients.t(Connection.java:164)
at redis.clients.t(BinaryClient.java:80)
at redis.clients.jedis.Connection.sendCommand(Connection.java:100)
at redis.clients.jedis.BinaryClient.set(BinaryClient.java:97)
at redis.clients.jedis.Client.set(Client.java:32)
21 22 23 24 25 26 27 28 at redis.clients.jedis.Jedis.set(Jedis.java:68)connect to和connect with的区别
at threadsafe.BadConcurrentJedisTest$RedisSet.run(BadConcurrentJedisTest.java:26) at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at urrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
.......
主要错误:
1 2ava.SocketException: Socket closed
java.SocketException: Socket is not connected
2.1 共享socket引起的异常
为什么会出现这2个错误呢?我们可以很容易的通过堆栈信息定位到redis.clients.jedis.Connection的connect⽅法1
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42public void connect() {
if(!isConnected()) {
try{
socket = new Socket();
// ->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.setSoTimeout(soTimeout);
if(ssl) {
if(null== sslSocketFactory) {
sslSocketFactory = (Default();
}
socket = (SSLSocket) ateSocket(socket, host, port, true); if(null!= sslParameters) {
((SSLSocket) socket).setSSLParameters(sslParameters);
}
if((null!= hostnameVerifier) &&
(!hostnameVerifier.verify(host, ((SSLSocket) socket).getSession()))) {
String message = String.format(
"The connection to '%s' failed ssl/tls hostname verification.", host);
throw new JedisConnectionException(message);
}
}
outputStream = new OutputStream());
inputStream = new InputStream());
} catch(IOException ex) {
broken = true;
throw new JedisConnectionException(ex);
}
}
}
jedis在执⾏每⼀个命令之前都会先执⾏connect⽅法,socket是⼀个共享变量,在多线程的情况下可能存在:线程1执⾏到了
1 2outputStream = new OutputStream()); inputStream = new InputStream());
线程2执⾏到了:
1 2 3socket = new Socket();
线程2
因为线程2重新初始化了socket但是还没有执⾏connect,所以线程1执⾏OutputStream()或者InputStream()就会抛出java.SocketException: Socket is not connected。java.SocketException: Socket closed是因为socket异常导致共享变量socket关闭了引起的。
2.2 共享数据流引起的异常
上⾯是因为多个线程共享jedis引起的socket异常。除了socket连接引起的异常之外,还有共享数据流引起的异常。下⾯就看⼀下,因为共享jedis实例引起的共享数据流错误问题。
为了避免多线程连接的时候引起的错误,我们在初始化的时候就先执⾏⼀下connect操作:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29public class BadConcurrentJedisTest1 {
private static final ExecutorService pool = wCachedThreadPool(); private static final Jedis jedis = new Jedis("192.168.58.99", 6379);
static{
}
public static void main(String[] args) {
for(int i=0;i<20;i++){
}
}
static class RedisTest implements Runnable{
@Override
public void run() {
while(true){
jedis.set("hello", "world");
}
}
}
}
报错:(每次报的错可能不完全⼀样)1
2 3 4 5 6 7 8Exception in thread "pool-1-thread-7"Exception in thread "pool-1-thread-1"redis.ptions.JedisDataException: ERR Protocol error: invalid multibulk length
at redis.clients.jedis.Protocol.processError(Protocol.java:123)
at redis.clients.jedis.Protocol.process(Protocol.java:157)
at redis.clients.ad(Protocol.java:211)
at redis.clients.adProtocolWithCheckingBroken(Connection.java:297)
at redis.clients.StatusCodeReply(Connection.java:196)
at redis.clients.jedis.Jedis.set(Jedis.java:69)
at threadsafe.BadConcurrentJedisTest1$RedisTest.run(BadConcurrentJedisTest1.java:30)
9 10 11 12 13 14 15 16 17 18 19 20 at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at urrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "pool-1-thread-4"redis.ptions.JedisConnectionException: java.SocketException: Broken pipe (Write failed) at redis.clients.jedis.Connection.flush(Connection.java:291)
at redis.clients.StatusCodeReply(Connection.java:194)
at redis.clients.jedis.Jedis.set(Jedis.java:69)
at threadsafe.BadConcurrentJedisTest1$RedisTest.run(BadConcurrentJedisTest1.java:30)
at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at urrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.SocketException: Broken pipe (Write failed)
送命令相关,⼀个jedis实例使⽤⼀个线程与使⽤100个线程去发送命令没有本质上的区别,所以没必要
设置为线程安全的。但是如果需要⽤多线程⽅式访问redis服务器怎么做呢?那就使⽤多个jedis实例,每个线程对应⼀个jedis实例,⽽不是⼀个jedis实例多个线程共享。⼀个jedis关联⼀个Client,相当于⼀个客户端,Client继承了Connection,Connection维护了Socket连接,对于Socket这种昂贵的连接,⼀般都会做池化,jedis提供了JedisPool
1
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48import urrent.CountDownLatch;
import urrent.ExecutorService;
import urrent.Executors;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisPoolTest {
private static final ExecutorService pool = wCachedThreadPool(); private static final CountDownLatch latch = new CountDownLatch(20);
private static final JedisPool jPool = new JedisPool("192.168.58.99", 6379);
public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i=0;i<20;i++){
}
try{
latch.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() - start);
pool.shutdownNow();
}
static class RedisTest implements Runnable{
@Override
public void run() {
Jedis jedis = Resource();
int i = 1000;
try{
while(i-->0){
jedis.set("hello", "world");
}
}finally{
jedis.close();
}
}
}
}
49
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应
为StatefulRedisConnection是线程安全的,所以⼀个连接实例(StatefulRedisConnection)就可以满⾜多线程环境下的并发访问,当然这个也是可伸缩的设计,⼀个连接实例不够的情况也可以按需增加连接实例。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论