C#怎么实现Redis分布式锁_C#如何防止高并发超卖【核心】

胖伟小哥_8122

胖伟小哥_8122

2026-03-29

337人浏览

原创

Redis分布式锁必须用原子命令SET key value EX seconds NX,C#中调用StringSet(..., When.NotExists);释放锁须用Lua脚本校验value后删除;需结合DB乐观锁防超卖;锁续期是刚需。

c#怎么实现redis分布式锁_c#如何防止高并发超卖【核心】

Redis分布式锁必须用 SET 命令加 NX + EX 参数

直接用 SET key value 再 EXPIRE 是错的——这两步不原子,中间崩了就锁残留。C# 里用 StackExchange.Redis 时,必须走 Database.StringSet(key, value, expiry, When.NotExists),它底层对应 SET key value EX seconds NX。

常见错误现象:Lock acquired but never released 或者多个服务同时写库存;本质是锁没设过期时间,或设了但没和值绑定,导致误删别人锁。

  • value 必须是唯一标识(比如 Guid.NewGuid().ToString()),不能用固定字符串,否则释放锁时无法校验所有权
  • expiry 别拍脑袋设 10 秒——得大于业务最长执行时间,但也不能太长(比如 5 分钟),否则故障时锁堆积
  • 别用 Database.LockTake:它底层不是原子 SET,且已标记为 obsolete

释放锁必须用 Lua 脚本校验 value 再删

先 GET 再 DEL 是经典竞态:A 拿到锁,超时前执行完,去 GET 发现还是自己的 value,正要 DEL,B 的锁刚好过期被自动清理,C 立刻抢到锁,这时 A 的 DEL 就删掉了 C 的锁。

正确做法是用一段 Lua 脚本,由 Redis 保证原子性:

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

C# 中调用:Database.ScriptEvaluate(luaScript, new RedisKey[] { lockKey }, new RedisValue[] { lockValue })

  • 脚本里用 KEYS[1] 和 ARGV[1],别硬编码 key 名或 value
  • 返回值是 long 类型,1 表示删除成功,0 表示锁已失效或不属于你
  • 别在 finally 里无条件 DEL——那是裸奔

超卖防护不能只靠锁,得结合数据库乐观并发控制

锁只解决「同一时刻只有一个线程改库存」,但挡不住:A 扣减前查到库存 100,B 同时也查到 100,A 扣 1 后写回 99,B 还按 100-1=99 写,结果变成 99 而不是 98。

C#
C#

"避开 C# 常见错误——空陷阱、异步陷阱、LINQ 陷阱及资源泄漏。"

下载

所以 Redis 锁之后,DB 层必须用 where 条件兜底:

UPDATE products SET stock = stock - 1 
WHERE id = @id AND stock >= 1

然后检查 RowsAffected 是否为 1。

  • 如果 RowsAffected == 0,说明库存不足或已被扣完,此时要主动释放 Redis 锁(避免锁空占)
  • 别把库存扣减逻辑全放 Redis 里做 DECR——没法和订单、日志等事务联动
  • 高并发下,SELECT ... FOR UPDATE 在 MySQL 里容易引发间隙锁争用,不如 where + affect rows 简单可靠

锁续期(watchdog)不是可选功能,而是防误删的刚需

业务执行时间波动大,比如下单链路有时快有时慢,如果锁过期时间设死,就可能在执行中锁自动消失,被别人抢走——这比没锁还危险。

StackExchange.Redis 自带 LockTake / LockRelease 不支持续期,得自己实现后台心跳:

  • 拿到锁后,立刻起一个 Task.Run 定时器,每隔 expiry / 3 时间(比如 expiry=30s,就每 10s 刷一次)执行 Lua 续期脚本
  • 续期脚本类似释放逻辑,先校验 value,再用 EXPIRE 延长 TTL:if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("expire", KEYS[1], ARGV[2]) else return 0 end
  • 任务必须在锁释放时显式取消,否则可能对已失效的锁反复续期,污染 Redis

这个点最容易被跳过——大家觉得“我业务很快,设个 10 秒够了”,但网络抖动、GC 暂停、磁盘 IO 高峰都可能让 10 秒变 15 秒。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c#

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1891

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

5164

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1829

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

31197

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3687

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3707

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1243

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

616

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2883

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习