Quarkus高并发Redis操作报错ERR EXEC without MULTI问题咨询
问题根因
- 核心原因是Quarkus 2自带的
quarkus-redis-client默认使用共享连接池,你在多线程消费场景下调用RedisClient.createClient()获取到的是同一个底层TCP连接。而Redis的事务(MULTI/EXEC/DISCARD)是绑定连接状态的,多个线程并发在同一个连接上操作事务会打乱连接状态:比如线程A刚发送MULTI开启事务,线程B紧接着发送了EXEC提交,这时候线程A后续再执行EXEC/DISCARD时,连接上已经不存在活跃事务,就会抛出ERR EXEC without MULTI/ERR DISCARD without MULTI错误。 - 次要问题:你当前的事务写法无法保证数据一致性,事务开启前的HGET查询和后续的HINCRBY事务操作之间存在时间差,高并发下查询结果已经失效,就算事务不报错也会出现数据不一致的情况。
解决方案
- 使用独占连接执行事务:不要用普通共享客户端执行事务操作,应该通过客户端提供的
withTransaction方法执行事务逻辑,框架会自动为你分配独占连接、管理事务的开启/提交/回滚,避免多线程共享连接导致的状态混乱。示例写法参考:
// 注入RedisClient,不要每次都createClient @Inject RedisClient redisClient; public void process(Foo foo) { try { // 开启事务,回调内的所有命令都会在独占连接上执行 List<Object> txResult = redisClient.withTransaction(tx -> { // 先加WATCH监听要操作的key,保证查询到执行事务期间数据不被修改 tx.watch("foos"); // 事务前查询逻辑 String val1 = tx.hget("foos", "foo_field1:" + foo.getId()).toString(); // 其他查询逻辑... // 入队写操作 tx.hincrby("foos", "foo_field1:" + foo.getId(), 1); // 其他hincrby操作... }); log.info("事务执行结果:{}", txResult); } catch (Exception e) { log.error("处理foo:{}失败", foo, e); } }
- 添加WATCH保证一致性:在查询前对要操作的key执行WATCH命令,如果WATCH的key在事务提交前被其他客户端修改,事务会自动放弃执行,保证数据一致性。
- 不要每次调用
process方法都创建新的RedisClient,直接通过注入复用客户端实例即可,避免不必要的连接创建开销。
内容的提问来源于stack exchange,提问作者oscar
相关产品推荐
相关产品推荐

