You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

关于Redis事务(WATCH&MULTI)模拟原子INCR命令的并发正确性问题咨询

关于Redis事务(WATCH&MULTI)模拟原子INCR命令的并发正确性问题咨询

最近我尝试用Redis的WATCH和MULTI命令来模拟INCR的原子递增操作,本以为能实现同样的效果,但在并发场景下发现了问题——最终的数值总是不符合预期,想跟大家拆解下这个情况。

问题核心

我原本的思路是这样的:通过WATCH监控目标key,获取当前值后加1,再用MULTI开启事务执行SET操作,以此模拟原子递增。但实际测试后发现,GET和MULTI之间并不是原子执行的,并发请求进来时,多个客户端可能同时拿到同一个旧值,导致后续的事务执行出现覆盖,最终结果失效。

对应的核心命令流程如下:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

对比测试代码与结果

我用Python asyncio写了两组并发测试代码,分别用原生INCR和WATCH&MULTI方案,依赖python3.8和asyncio-redis==0.16.0。

1. 原生原子INCR方案

这个方案下,10个并发worker执行递增,最终结果正确为10:

import asyncio
import asyncio_redis

async def worker(_id):
    global cache_pool
    r = await cache_pool.incr('some_key')
    print(f"worker_id: {_id} - {r}")

async def connect():
    global cache_pool
    cache_pool = await asyncio_redis.Pool.create(host='localhost', port=6379, poolsize=10)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(connect())
    for i in range(10):
        loop.create_task(worker(i))
    loop.run_forever()

2. WATCH&MULTI模拟方案

这个方案运行后,会出现部分worker的事务执行覆盖了刚递增的值,导致最终结果不符合预期:

import asyncio
import asyncio_redis

async def worker(_id):
    global cache_pool
    while True:
        try:
            await cache_pool.watch(['some_key'])
            value = await cache_pool.get('some_key')
            value = int(value or '0')
            value += 1
            tr = await cache_pool.multi()
            await tr.set('some_key', str(value))
            await tr.exec()
        except asyncio_redis.exceptions.TransactionError:
            pass
        else:
            print(f"worker: {_id} got optimistic lock - {value}")
            break

async def connect():
    global cache_pool
    cache_pool = await asyncio_redis.Pool.create(host='localhost', port=6379, poolsize=10)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(connect())
    for i in range(10):
        loop.create_task(worker(i))
    loop.run_forever()

出现问题的原因在于:当多个worker同时对some_key执行WATCH后,会同时获取到同一个初始值;第一个worker完成事务执行修改key后,其他worker的事务本应因为WATCH的key被修改而触发TransactionError并重试,但实际测试中还是会有部分worker串行进入事务块,覆盖掉刚更新的数值,导致最终结果错误。

备注:内容来源于stack exchange,提问作者Ali Ebrahimi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 13:23:14