功能一句话概括:
“确保 redis 里存在该 key,且其 ttl ≥ 60 s,否则用 setnx 写入初始 seed 并把过期时间设为 5 min。”
据此可取名:ensure_seed_with_ttl
下面给出完整封装(含类型提示、日志英文、异常兜底),并给出两处可优化点及优化后的代码。
一、原始逻辑直接封装
import datetime
import logging
from typing import optional
log = logging.getlogger(__name__)
def ensure_seed_with_ttl(
rediz,
redis_key: str,
item_type,
msg: optional[str] = none,
) -> none:
"""
ensure the redis key exists and has a ttl of at least 60 s.
if the key does not exist, atomically set it to the initial seed
and give it a 5-minute expiration.
"""
if not rediz.exists(redis_key):
seed = ensure_seed_reference(item_type)
result = rediz.setnx(redis_key, seed.ref_seq)
log.info(
"seed ensured in redis: result=%s, next_seq=%s, msg=%s",
result, seed.ref_seq, msg
)
ttl = rediz.ttl(redis_key)
if ttl < 60 or ttl == -1: # -1 表示 key 存在但没有过期时间
rediz.expire(redis_key, datetime.timedelta(minutes=5))二、可优化的两点
竞争窗口
两个客户端可能同时通过exists判断为 false,然后都执行setnx,虽然setnx本身是原子性的,但日志会打印两次“seed ensured …”,容易误导。
优化:把“判存在 + 设值 + 设过期”做成一条原子事务(lua 脚本或 redis pipeline)。减少一次 ttl 查询
如果setnx成功,我们可以直接把过期时间设为 5 min,省去一次ttl查询;
只有setnx失败(key 已存在)时才需要ttl检查。
三、优化后代码(lua 脚本一次原子执行)
import datetime
import logging
from typing import optional
log = logging.getlogger(__name__)
# lua 脚本:如果 key 不存在,则 set value 并设置 5 min 过期;
# 如果 key 已存在但 ttl<60,则延长到 5 min;
# 返回 1 表示写入了新 seed,返回 0 表示仅调整过期时间。
_ensure_seed_lua = """
local key, value, min_ttl, full_ttl = keys[1], argv[1], tonumber(argv[2]), tonumber(argv[3])
local exists = redis.call('exists', key)
if exists == 0 then
redis.call('setnx', key, value)
redis.call('expire', key, full_ttl)
return 1
else
local ttl = redis.call('ttl', key)
if ttl < min_ttl then
redis.call('expire', key, full_ttl)
end
return 0
end
"""
def ensure_seed_with_ttl(
rediz,
redis_key: str,
item_type,
msg: optional[str] = none,
) -> none:
"""
atomically ensure the redis key exists and has a ttl of at least 60 s.
if the key does not exist, atomically set it to the initial seed
and give it a 5-minute expiration.
"""
seed = ensure_seed_reference(item_type)
written = rediz.eval(
_ensure_seed_lua,
1, # number of keys
redis_key,
seed.ref_seq, # argv[1]
60, # argv[2] min_ttl
int(datetime.timedelta(minutes=5).total_seconds()) # argv[3] full_ttl
)
if written == 1:
log.info(
"seed ensured in redis: next_seq=%s, msg=%s",
seed.ref_seq, msg
)优化效果
- 竞争窗口消失,全程原子;
- 减少一次
ttl网络往返(在 key 不存在场景); - 日志只在真正写入 seed 时打印一次,避免重复误导。
到此这篇关于redis 原子保种的实现示例的文章就介绍到这了,更多相关redis 原子保种内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论