当前位置: 首页 > news >正文

Redis事务详解

1、概念

Redis 事务的本质是将多个命令打包,然后按顺序、一次性、隔离地执行。它通过 MULTI, EXEC, DISCARD, WATCH 四个核心命令实现。

  • MULTI:开启一个事务,之后的命令都会放入队列,而不是立即执行。
  • EXEC:执行事务队列中的所有命令。
  • DISCARD:取消事务,清空队列。
  • WATCH:在 MULTI 之前,监视一个或多个 key。如果在 EXEC 执行前,这些被监视的 key 被其他客户端修改,则整个事务会失败(EXEC 返回 nil)。这是实现乐观锁(Optimistic Locking) 的基础。

与传统事务的差异

  1. 无回滚:Redis 设计哲学是简单和高效,失败的命令通常是编程错误(命令用错、数据类型错误),应该在开发阶段被发现,而不是在生产环境通过回滚来挽救。回滚会增加复杂性和性能开销。
  2. 隔离性:事务中的所有命令都会按顺序串行执行,并且在 EXEC 执行前,事务中的命令不会被其他客户端的命令插队。这保证了事务执行过程中不会受到其他客户端的干扰。
  3. 持久化无关:事务的 “执行成功” 仅表示命令入队并执行,不保证已持久化到磁盘(需配合 AOF 配置 appendfsync always 提升可靠性,但会牺牲性能)。
  4. 不保证原子性(Atomicity):这是最大的误区!Redis 事务保证的是隔离性(Isolation) 和顺序执行,但不保证原子性。这意味着如果事务中某个命令执行失败(例如对字符串执行了 HINCRBY),其他命令依然会继续执行,且不会回滚(Rollback)

2、最佳实践详解与示例

2.1 一个失败的事务会发生什么?


127.0.0.1:6379> SET key1 "hello"
OK
127.0.0.1:6379> SET key2 100
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR key1      # 错误:对字符串 'hello' 执行 INCR,这会失败!
QUEUED
127.0.0.1:6379> INCR key2      # 正确:对数字 100 执行 INCR
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range # 第一条命令报错
2) (integer) 101                                      # 第二条命令成功执行!
127.0.0.1:6379> GET key2
"101"

2.2 使用WATCH实现乐观锁

工作流程:

  1. WATCH 需要监控的 key。
  2. 读取 key 的值并进行业务逻辑计算。
  3. 开启 MULTI。
  4. 将写命令(如 SET, INCRBY)放入队列。
  5. 执行 EXEC。
  6. 如果成功:说明在 WATCH 到 EXEC 期间,被监控的 key 没有被其他客户端改动,事务执行成功,WATCH 可以监控多个 key,只要其中任何一个被修改,事务都会失败。
  7. 如果返回 nil:说明 key 已被修改,事务执行失败。通常的处理方式是重试整个流程。

import redis
import timer = redis.Redis(host='localhost', port=6379, db=0)def deduct_balance(user_id, amount):""" 扣减用户余额,使用乐观锁保证并发安全 """balance_key = f"user:{user_id}:balance"# 重试次数,避免死循环max_retries = 5retry_count = 0while retry_count < max_retries:retry_count += 1try:# 1. 开启 Pipeline(包含 WATCH 命令,Python redis库中,pipeline 默认隐含 MULTI/EXEC)# `transaction=True` 表示我们要使用事务,它内部会处理 WATCH/MULTI/EXECpipe = r.pipeline(transaction=True)# 2. WATCH 要监控的 keypipe.watch(balance_key)# 3. 读取并检查余额current_balance = int(pipe.get(balance_key) or 0)if current_balance < amount:pipe.unwatch() # 解除监控,可选,EXEC/DISCARD后也会自动解除print("Insufficient balance!")return False# 4. 开启事务,组装命令pipe.multi()pipe.decrby(balance_key, amount)# 5. 执行事务# 如果 balance_key 在 WATCH 后被执行了,这里会抛出 WatchError 异常result = pipe.execute() # execute() 成功返回命令执行结果的列表,例如 [95]print(f"Deduction successful! New balance: {result[0]}")return Trueexcept redis.WatchError:# 6. 捕获 WatchError,表示事务失败,进行重试print(f"Transaction conflicted. Retrying... ({retry_count}/{max_retries})")time.sleep(0.1) # 稍等片刻再重试,避免激烈竞争continueexcept Exception as e:# 处理其他异常print(f"Unexpected error: {e}")breakprint("Failed after max retries.")return False# 初始化用户余额
r.set("user:100:balance", 100)
# 模拟并发扣减
deduct_balance(100, 50)

3、使用 Lua 脚本替代复杂事务

对于非常复杂的逻辑,使用 WATCH 和 MULTI 会使得重试循环非常笨重。此时,Lua 脚本是更好的选择。

Lua 脚本的优势:

  1. 真正的原子性:整个脚本在执行时是以单线程、原子性的方式运行的,中间不会被执行任何其他命令,无需使用 WATCH。
  2. 减少网络开销:逻辑在服务器端执行,避免了多次网络往返。
  3. 复杂性封装:将复杂逻辑封装在服务器端的一个脚本中。

import redisr = redis.Redis(host='localhost', port=6379, db=0)# 定义Lua脚本代码
LUA_DEDUCT_SCRIPT = """
local balance_key = KEYS[1]
local amount = tonumber(ARGV[1])
local current_balance = tonumber(redis.call('get', balance_key) or 0)if current_balance < amount thenreturn false -- 余额不足,返回false
endredis.call('decrby', balance_key, amount)
return true -- 扣减成功,返回true
"""
# 预先加载脚本,获取一个sha1哈希摘要,后续可以用这个摘要来执行,效率更高。
script_sha = r.script_load(LUA_DEDUCT_SCRIPT)def deduct_balance_lua(user_id, amount):balance_key = f"user:{user_id}:balance"# 使用 evalsha 执行脚本# 1 表示后面有1个KEY, amount 是ARGV参数success = r.evalsha(script_sha, 1, balance_key, amount)return bool(success)# 初始化
r.set("user:100:balance", 100)
# 执行扣减
result = deduct_balance_lua(100, 50)
print(f"Deduction result: {result}")
print(f"Remaining balance: {r.get('user:100:balance')}")

最佳实践:

  1. 对于复杂的、需要原子性执行的逻辑,优先使用 Lua 脚本。

  2. 使用 SCRIPT LOAD 预加载脚本,然后用 EVALSHA 通过脚本的 SHA1 摘要来执行,可以节省每次传输完整脚本的网络带宽。

http://www.sczhlp.com/news/71906/

相关文章:

  • 9月1-5日小记 - L
  • 点击网站首页域名又添加一个google关键词推广
  • 有哪些网站做返利模式推广方法视频
  • 青岛网站建设报价网站跟换域名
  • 宁波网站建设速成企业展厅建设
  • P80023 [CSP-J二十连测第六套 ] --T3--回文(palindrome)
  • 京东网站开发多少钱专业设计服务
  • 2021年给我一个网站天津外贸网站建设公司
  • 网站建设述职报告有口碑的唐山网站建设
  • 女生学网站建设好学吗做网站全过程
  • 档案网站建设经验宜昌商城网站建设
  • 网站的建设时间表做网页用什么编程语言
  • 芜湖市建设投资有限公司网站夏天做那些网站致富
  • 网站系统中备案申请表wordpress 4.9.1模板
  • 怎么把自己做的网站上传到网上app小游戏开发公司
  • 平台建站建设深圳网站建设计
  • 网站即将上线页面代码百度智能建站适合优化吗
  • html网站登录界面模板下载网站的建设包括
  • 网站开发与数据库有关系吗德州市住房和城乡建设局网站
  • 做盗版电影网站教程药品招采网站建设费用
  • 做网站属于什么工作短视频推广计划
  • 网站的描述 都应该写 什么 优化锋创科技园网站建设
  • 比较好用的免费素材网做网站优化需要做哪些事项
  • 有什么做的好的ppt排版网站成都最新规划官方消息
  • 微信公众号属于网站建设wordpress 男孩同志
  • 客户案例 | 燕千云ITSM赋能制造业数字化转型:6大标杆案例全景展示
  • Pixar渲染技术挑战赛与IEEE里程碑
  • Python房价数据预测:StackingCVRegressor集成学习、Lasso、ElasticNet、XGBoost、LightGBM模型与特征工程可视化
  • 0905
  • 祝贺gls拿下World Final 金牌