分布式锁是否能实现锁住一个 key 范围

5 天前
 ShawyerPeng

最近项目中需要对锁的粒度进行优化,使用分布式锁的场景是:一个员工每天会进行作业,每次作业都会上报一条作业数据,其中有个 biz_time 字段表示作业实际发生的时间(系统会合并统计它多个作业任务的开始时间和结束时间,即最后数据表中会记录这个人当天的第一个任务的开始时间为 start_time ,最后一个任务的结束时间为 end_time ),但是上报作业数据是会发生乱序的,每次更新时间范围的左右区间不能发生并发覆盖更新。 当然系统需求会更加复杂,例如他的不同作业类型切换,需要终止当天的作业时长统计,并开启一段新作业的统计等等,先不用考虑。

现在是用乐观锁通过 version 控制的,由于并发量比较大,希望改造成分布式锁来实现单行数据的并发数据竞争。 预期能实现成[user_id + [start_time, end_time]]粒度,也就是某个人的作业时间在某个时间范围被锁住(类似数据库的间隙锁,锁定一个范围)。 但是没有思路怎么实现,在此请教一下各位大佬!

2541 次点击
所在节点    程序员
36 条回复
ShawyerPeng
5 天前
@zdking08135 问题 1 ,我在楼上回复了~是业务方因为某些原因,举个例子把 1 点到 2 点之间的所有操作攒到一起最后都为完成状态时,才一起上报过来(还不是批量上报,而是拆分成明细行进行上报,消费方无法在一个请求中在内存中处理 N 个时间窗口的更新逻辑,当然了,MQ 可以攒一批消息进行批量消费)
问题 2:业务的异常 case ,代码逻辑会做异常处理,本问题可以忽略不考虑
问题 3 ,不同的作业类型不一样,可能 1 秒一个,也可能十几分钟才做完一个任务。
ShawyerPeng
5 天前
@z1829909 有道理,不过消息队列 by partition 串行化,可以不用这么重的有序消费功能实现吗?
geebos
4 天前
你这个场景每人每天只会有一条 start_time, end_time 记录吧
YangQingLin
4 天前
@ShawyerPeng by partition 又不需要在消息队列里面做,消息队列可以暂存数据,攒到一定量之后一起取出来,排序、分类、合并,这不是挺快的嘛,复杂度顶天了 O(N*log(N))
spritecn
4 天前
延迟队列..延个 5 分钟够用了
LiaoMatt
4 天前
我的理解, 一天的时间是固定的, 粒度使用分钟, 那么需要 24 * 60 = 1440 分钟, 转换成 byte 除以 8 就是 180B, 每次更新把时间区间覆盖的 bit 置为 1, 等到所有数据更新完成后,在从左读最小值, 从右读最大值, 从而得到工作时长; 这样的好处是每次操作都是幂等, 不需要关心版本竞争问题
sujin190
4 天前
看了半天似乎你这个不就是某个人开始结束时间更新不应该因为时序和并发问题覆盖回跳吧,不用那么纠结按人加锁就是了吧,我看你补充说可能操作的时候不上报结束统一上报,但也就是几百个而已
加锁要分析的是冲突率、单操作耗时和等待时长,然后以此计算线程连接各种资源是否能满足需要的并发,不是加锁就不能高并发吧
就你这也就计算更新个时间耗时这么低,按人加锁也没多大影响吧
sujin190
4 天前
顺便说如果你用的是大家用挺多的 redis 分布式锁,因为 redis 协议原因,冲突率较高情况下性能会有较大下降,单个加锁延时在冲突率高情况下提高不少,也是需要考虑的
一般来说各种介绍分布式锁使用的文章说到的场景实际冲突率都低于百分之一吧,可以统计或者预估下冲突率在啥价格
如果冲突率实在高又并发不均衡就是有较大峰值,那还是好好的用 kafka 来搞吧
litchinn
4 天前
不知道我看懂需求没,你是有一个消息流,每次要更新一个人每天的 start_time 和 end_time ,并保证在消息无序的情况下不会发送时间回跳对吗
用 ringbuffer 转到单线程操作,如果服务是多实例,那再考虑分布式锁
unused
4 天前
@ShawyerPeng #11 这个 a, b 哪来的,和 c 什么关联
shigella
4 天前
看了半天。
因为不希望用户间存在相互阻塞的情况,所以 Kafka 按分区串行是不行的了。
因为需要处理跨天的情况,所以单纯的锁 user 粒度是不够的,所以才提出需要 user+时间范围。
最后我感觉还是#26 那种按天将最小时间粒度 bit 将提交的区间存起来,然后取结果的时候取头尾的办法最合适。
cnhongwei
4 天前
把锁分两个锁,实际的区间数据,可以放到 redis 中单个 value 中,或放到一个 set 中,因为没有一个原子的操作来一次完成更新与比较,所以另外放一个锁,先获取这个锁,对新的时间范围和现有的时间范围(初始为空)做比较,如果有重复,获取锁失败,释放第一个锁;如果比较没有重复,将新的时候范围和现有的时间合并,获取锁成功,释放第一个锁,并做下一步处理。
z1829909
4 天前
@ShawyerPeng 这里的重是指引入了消息队列,中间件变多,还是消息队列配套的消费代码重
cloudzhou
4 天前
把思路转变一下,引入一个中心调度,分发 [user_id + [start_time, end_time]] job
收到的都可以处理,把锁竞争放在分发这一步
处理完成发送中心调度标记完成/失败,继续分发

对于业务开发,只要收到消息就可以处理,然后发送成功失败回调,不考虑锁
WithoutSugarMiao
4 天前
直接用逻辑来判断可以吗?比如 设置 redis 的 key 为 userid + 10:20-10:30 ,含义是 锁住该 user 今天的十点二十到十点三十这段?
liuhan907
4 天前
你的分布式锁本身打算用什么实现?如果用 redis 这类自带脚本的东西那简单做一个按开始秒数作为 key 的有序集合,每次上锁用 lua 检查集合内是否有交集区间,有的话就失败让客户端等待,没有就写进去然后上锁成功。超时失败重试之类的都可以参考标准 redis 锁。感觉一小时就能搞完

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1148302

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX