如题, 我看网上都说 redis 是单线程的,内部使用 epoll
我这边使用 jedis 来操作 redis, jedis 从 jedisPool 中取出来
有两个线程 1,2
线程 1 不断的 get ( keyA )
线程 2 不断的 lpop (keyB)
keyA 和 keyB 是 2 个不同的键
现在我发现在并发操作的时候,lpop 有一定几率 会把 keyA 里面数据给拿出来,get 操作 有一定几率会拿到 keyB 的数据
这是什么鬼。。。。。
1
Buffer2Disk OP 使用的可以是这种格式的
keyA queue:test keyB data:overload:user |
2
Buffer2Disk OP 抽风了, 使用的 key 是上面这种格式的
|
3
ke1e 2019-08-01 15:59:26 +08:00 via Android
这是 jedis 的问题吧
|
4
arrow8899 2019-08-01 16:04:53 +08:00
发一下代码吧,估计是代码写的有问题。
|
5
rrfeng 2019-08-01 16:05:51 +08:00 via Android
用了同一个 connection 吧,估计是用错了。
|
6
msg7086 2019-08-01 16:11:32 +08:00
看上去复用了一个网络连接,所以线程 1 发出请求的回答被线程 2 收走了。
|
7
pelloz 2019-08-01 16:12:53 +08:00
请用 Lettuce
|
8
Buffer2Disk OP @rrfeng
@arrow8899 @ke1e private static void initialPool() { ApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:config/spring-application-redis.xml"); jedisPool = (JedisPool) applicationContext.getBean("jedisPool"); logger.info("<---jedis pool init--->"); } /** * 初始化,加锁防止被多次初始化 pool */ private synchronized static void poolInit() { if (jedisPool == null) { initialPool(); } } /** * 获取 Jedis 实例 * * @return Jedis */ public static Jedis getJedis() { poolInit(); Jedis jedis = null; try { if (jedisPool != null) { jedis = jedisPool.getResource(); } } catch (Exception e) { logger.error("get jedis error",e); } finally { returnResource(jedis); logger.debug("<---jedis return resource--->"); } return jedis; } /** * 释放 jedis 资源 * * @param jedis */ public static void returnResource(final Jedis jedis) { if (jedis != null) { jedis.close(); } } public static String get(String key) { try { return getJedis().get(key); } catch (Exception ex) { logger.error("redis get error",ex); } return null; } public static String lpop(String key) { try { return getJedis().lpop(key); } catch (Exception ex) { logger.error("lpop error , key = {}", key, ex); } return null; } |
9
cheng6563 2019-08-01 16:14:29 +08:00 via iPhone
jedis 是线程不安全的
请使用 redisson |
10
Buffer2Disk OP @msg7086 还有复用同一个网络连接这么一说
|
11
Buffer2Disk OP @cheng6563
我看网上说直接使用 jedis 是线程不安全的,但是使用 JedisPool 是线程安全的, 我使用的就是 jedisPool 来获取 jedis 实例 Jedis 不是线程安全的,故不应该在多线程环境中共用一个 Jedis 实例。但是,也应该避免直接创建多个 Jedis 实例,因为这种做法会导致创建过多的 socket 连接,性能不高。 要保证线程安全且获得较好的性能,可以使用 JedisPool。JedisPool 是一个连接池,既可以保证线程安全,又可以保证了较高的效率。 可以声明一个全局的 JedisPool 变量来保存 JedisPool 对象的引用,然后在其他地方使用。要知道,JedisPool 是一个线程安全的连接池。 |
12
jorneyr 2019-08-01 16:31:51 +08:00
} finally {
returnResource(jedis); logger.debug("<---jedis return resource--->"); } return jedis; 这是什么操作,返回后马上把 redis 关闭 |
13
Jrue0011 2019-08-01 16:42:14 +08:00
看问题是拿到了同一个 jedis
原因应该是出在你获取 jedis 的代码,获取了 jedis 你还没用,你就在 finally 处调用了 jedis.close 将连接返回了连接池,导致连接池存在可用的 jedis,不会创建新对象,下一次拿到的还是相同的 jedis |
14
LeeSeoung 2019-08-01 16:42:29 +08:00
关注,有大佬找到原因的话告知下。。
|
15
glues 2019-08-01 16:51:12 +08:00
这一看就是 jedis 的问题,或者是你用的不对
|
16
Buffer2Disk OP 知道
@jorneyr 尴尬,应该是 public static Jedis getJedis() { poolInit(); Jedis jedis = null; try { if (jedisPool != null) { jedis = jedisPool.getResource(); } } catch (Exception e) { logger.error("get jedis error", e); returnResource(jedis); logger.debug("<---jedis return resource--->"); } return jedis; } |
17
Aresxue 2019-08-01 16:55:53 +08:00
歪个楼,初始化 JedisPool 用单例的安全检查或者静态内部类,别加锁。。。Jedis 不是熟,但是 Lettuce 现在 shi 更流行也更好用,所以换 Lettuce 是最好的方式。
|
18
Buffer2Disk OP @Jrue0011 大佬,为什么拿到相同的 jedis,使用的 key 是不同的,为什么会拿到别的 key 的结果呢?
|
19
Jrue0011 2019-08-01 17:07:29 +08:00
@Buffer2Disk 额。。你追踪下 Jedis 源码就知道了。一个 jedis -> client -> connection 维护一个 outputStream 和一个 inputStream,命令用 outputStream 发送,响应从 inputStream 读取,发送命令和读取响应的方法没加锁,一个线程读到另一个线程本该接收的响应很正常。。
|
20
Buffer2Disk OP @Jrue0011 改了以后确实可以了,有一个问题就是,jedis close() 后难道不是立马就释放回连接池的嘛?
我试了下循环一百次 get 操作,很容易就触发到 maxTotal 的阈值了,然后程序就一直 hang 在那里,像是在等待连接池释放新的连接出来一样,但是等了很久也没有出来,这个是有什么参数可以控制的么 |
21
Buffer2Disk OP @Jrue0011 我看到有个 maxWaitMillis
当资源池连接用尽后,调用者的最大等待时间(单位为毫秒),默认值 -1:表示永不超时 有没有什么参数可以控制 close 后,多久返回连接池的。,。。。 /** * 获取 Jedis 实例 * * @return Jedis */ public static Jedis getJedis() { poolInit(); Jedis jedis = null; try { if (jedisPool != null) { jedis = jedisPool.getResource(); } } catch (Exception e) { logger.error("get jedis error", e); returnResource(jedis); logger.debug("<---jedis return resource--->"); } return jedis; } public static String get(String key) { Jedis jedis = null; try { jedis = getJedis(); return jedis.get(key); } catch (Exception ex) { logger.error("redis get error", ex); } finally { returnResource(jedis); } return null; } /** * 释放 jedis 资源 * * @param jedis */ public static void returnResource(final Jedis jedis) { if (jedis != null) { jedis.close(); } } |
22
leafre 2019-08-01 18:06:12 +08:00
有安全问题,用 jedispool
|
23
Buffer2Disk OP @leafre 注意审题啊,我用的就是 jedisPool,只不过现在查出来是我的用法不太对
|
24
Jrue0011 2019-08-01 20:12:29 +08:00
@Buffer2Disk 没有设置调用 close()之后多久返回连接池的这种设定。。。只要调用 jedis.close(),内部做一些处理就会返回对象池了,你调用 100 次之后的阻塞应该是别的问题。
|
25
Buffer2Disk OP @Jrue0011 但是我测试以后,这个阻塞的上限确实是一直跟着 maxTotal 变化的,给人的感觉就像是用完以后没有释放回连接池里面一样。。。。但是你看我上面的代码,调用 get(key) 后确实 finally 里面 close()了
|
26
Buffer2Disk OP @Jrue0011 我设置了 maxWaitMillis 为 2 秒,当到达 maxTotal 2 秒后,抛出了这个错误
edis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool 说明确实拿不到新的实例了,那么为啥没释放回去呢。。。。 |
27
Buffer2Disk OP |
28
kppwp 2019-08-02 08:28:26 +08:00 via iPhone
redis 我记得都是原子操作
|
29
Buffer2Disk OP |
30
razertory 2019-08-02 10:39:48 +08:00
你可以认为 redis 中的指令没有并发安全问题。但是外部并发地去调用 redis 服务就需要自己去控制了
|
31
Joyboo 2019-08-02 11:43:22 +08:00
每个线程使用不同的 redis 连接
|