读B站的文章时候发现他们也用redis lua脚本做过这个,所以就想把自己在之前某论坛上发布的文章搬过来。
我当时的场景是多个服务去redis从同一个list中抢数据,为了确保数据不被重复消费浪费资源,所以使用lua确保操作的原子性。
redis_client = redis.Redis("127.0.0.1", decode_responses=True)
queue_name = "test_queue"
data_key = "grab_data"
data = redis_client.lpop(queue_name)
if data is None:
return
// 给data上锁,避免被其他服务器拿到重复数据
redis_client.set(data_key + data, data)
如果不懂上面的操作为什么会不原子性的话可以看下面的图:
# 通过lua脚本将pop和set合并为一个原子操作
lua_script = """
local value = redis.call('lpop', ARGV[1])
if value then
redis.call('set', ARGV[2], value)
end
return value
"""
lua_result = None
def pop_redis_queue(redis_client, queue_name, data_key):
"""
从redis的队列中pop出一个值, 并且将pop到的值通过set的方式存起来
:param redis_client: redis操作对象
:param queue_name: 队列名称
:data_key: 用于暂时存储获取值的key
:return: 1. 当data_key对用的value不为空时, 直接返回它的value
2. 当#{queue_name}队列的长度大于0时, 返回队首元素
3. 当#{queue_name}队列的长度为0时, 返回None
"""
# 先看是否有未处理的数据
value = redis_client.get(data_key)
if value:
return str(value)
global lua_result
if not lua_result:
lua_result = redis_client.register_script(lua_script)
data = lua_result(args=[queue_name, data_key], client=redis_client)
return data