石小疯 发表于 2025-1-11 08:46:24

分布式锁 Redis vs etcd

为什么要实现分布式锁?



[*]相识分布式锁的底层工作原理,以及假如在工程中应用,我们应该如何选择.
为什么需要分布式锁,分布式锁的作用是什么,哪些场景会利用到分布式锁?

为什么需要分布式锁


[*]解决分布式业务需要互斥的问题(同时只能有一次客户端的一个线程可以执行的场景),保护共享数据
[*]详细场景: 支付业务(制止多端同时支付),消息队列的斲丧(制止多端同时斲丧产生重复斲丧的问题),服务器的偏执业务(有些业务(数据同步)只需要一个服务器执行,不需要全部都开启,这时候可以利用分布式锁,拿到锁的执行,其他就不执行)
分布式锁的作用


[*]分布式实现线程互斥,确保只有一个线程能够执行
分布式锁的利用场景


[*]需要分布式线程互斥逻辑的场景都可以利用分布式锁
分布式锁的实现方式有哪些



[*]redis 分布式锁
[*]etcd 分布式锁
[*]…
分布式锁的焦点原理是什么



[*]互斥性:确保只有一个程序的一个线程可以获取到锁
[*]死锁防备: 假如程序意外停止会释放锁而不是持续占用锁资源,导致死锁

[*]实例自动接纳(过期),客户端定时续期来实现

[*]锁的公平竞争: 制止锁被饿死
如何实现分布式锁

redis(自旋锁版本)



[*]原子性保证

[*]redis 单线程本身的读写都是原子的,没有并发问题
[*]对相同的 key 利用 setnx(set not exit 只会在不存在的时候设置乐成,假如存在表示锁被占用的环境则会返回 0(获取锁失败))

[*]死锁防备:

[*]利用 redis 过期机制实现
[*]加锁: 利用合成下令或者 lua 脚本保证加锁的原子性

[*]合成下令: setnx k v ex ex_time 一次性在获取锁的时候就设置完成过期时间
[*]将获取锁与设置锁关过期时间的两条 redis 下令写到一个 lua 脚本中保证锁设置的原子性

[*]可不可以将其拆分成为两步:先抢锁(setnx),在设置过期时间(setex)

[*]不可以:由于设置过期时间大概有失败的风险(好比网络问题导致失败,那么这个可以有死锁的风险


   

[*]续期:

[*]假如加锁乐成则立刻开启定时任务(ticker),定时更新过期时间,直到锁的释放(吸收到 delete 信号)
[*]制止信号延迟或者丢失造成锁的自动过期导致的原子性被粉碎(多个线程同时获取到锁):一个过期时间内将会发送三次续期的心跳,只要任何一个续期哀求到达 redis 都可以续期乐成


   

[*]锁的公平性:

[*]所有的锁都以自旋的方式获取锁,公平的竞争锁

[*]自旋时间隔断随自旋的次数增加而减少(每次自旋减少 1ms,最低 10ms 自旋一次),所以阻塞时间越久的线程获取到锁的概率越高,防止锁被饿死.

[*]问题,再大量并发的环境下,仍旧有被饿死的风险

[*]锁的误删制止: 确保每个线程只能删除自己的锁不能删除其他线程的锁

[*]假如锁的删除下令由于网络等原因导致延迟到达(其他线程获取了锁,这时删除下令到达,会误删去他的锁,粉碎锁的原子性)
[*]利用唯一 value + lua 脚本举行实现

[*]利用 lua 脚本在保证原子性的环境下删除自己的 key

[*]加锁时 value 在客户端生成唯一标识(可以利用 uuid)
[*]释放锁的时候:lua(检查 redis 中value 是否与客户端的 value 相同,假如相同就删除,假如不同证明自己已经不持有该锁,就不做任何操作)




etcd 的分布式锁(互斥锁(信号控制)版本)

etcd 没有像 redis 那样 setnx 的下令,但是他的 Revision(版本号)与 watch 机制可以实现锁


[*]原子性: 保证只有一个线程获取到锁

[*]预写: 所有获取锁的线程都会先将 k-v 写到 etcd 中,etcd 会给每个写操作都迭代一个递增的版本号(获取锁的根本原理就是所有 kv 中最小的为当前持有锁)(有问题)
[*]试加锁: 再检查自己的版本号是否是列表中最小的(假如是就获取到锁,直接返回)
[*]等待锁:等待自己前面的锁全部释放,直到自己是 kv 对中版本最小的一个

[*]利用 watch 监听自己前面的删除,直到自己前面已经没有任何 kv 对(有点问题在里面)

[*]再检查: 检查自己的kv 对是否在队列中,假如不在就不能加锁,由于不知道被谁删除了,或者过期了,就自己返回错误)(由于你的 k-v 不在 etcd 中的话,你的背面一个就会发现自己是最小的,假如你也运行程序,那么就会有两个线程同时获取到锁,粉碎锁的原子性,所以要先检查再获取锁)

[*]防止死锁:

[*]租约 : etcd 有自己的租约,每一个 k-v 设置的时候就会绑定租约(与 redis 的过期时间+心跳续期的原理类似,只是这个是隐式的,在会话中实现的)
[*]当锁释放的时候就会停止续租并删除 k-v对(在背面的线程就会监听到他的删除)

[*]锁的公平性:(版本号)

[*]etcd 锁是天然公平的,是通过每个 kv 的版本号控制的
[*]由于版本号是递增的,那么整个锁就形成了一个先进先出的队列模式,只有到达队头(版本号最小)的线程会获取到锁执行业务逻辑,其他线程都是阻塞等待的状态

[*]锁的误删:

[*]etcd 中每个锁都只持有自己的 key,所以只能对自己的 k-v 对举行操作,不会出现误删的环境.

分布式锁对比

redis vs etcd

性能: 吞吐量
redis: 性能本身就更好,redis 有 20w 并发
redis 的性能瓶颈在网络 io,Reds 利用的 tcp 的协议,io 的性能就是 20 万
etcd 要略低只有 10 多万作用
etcd 利用的 grpc 的架构,底层是 http2 的传输协议; grpc 的性能是 15 万的 qps,所以 etcd 的性能只有 15 万左右
强同等性
Redis 分片的意外瓦解大概会导致锁的原子性被粉碎
解决方法:redlock 模式
etcd 集群本身具有强同等性,不需要担心单个实例的瓦解粉碎锁原子性的问题
服务器的影响:
redis 利用的自旋的形式获取锁,会斲丧一定的服务器 cpu
etcd 是等待信号通知的形式(watch),不需要自旋与循环,对服务器性能影响小
实例储存的数据量
Redis 一个 lock 只需要维护一个 k-v 对,储存数据少,但是 Redis 大概会处理大量自旋哀求.
etcd 每一个线程的获取锁都是一个 k- v 对,储存的数据多,而且要维护所有线程的连接,开销大,不能同时担当太多获取锁的哀求,否则会资源耗尽(连接)(假如并发数目高,而且有线程长期持有锁大概会导致连接的大量堆积)
总结


[*]当我们不需要很强的同等性,但是需要比较高的性能的时候,大量并发获取锁的景象下我们可以选择 Redis 的分布式锁
[*]假如我们需要强同等,高性能,高并发的场景的话我们可以利用 Redis 的 redlock 模式
[*]假如我们需要强同等,但是性能不需要太高,而且并发数目并不高的环境,可以选择利用 etcd 的分布式锁
参考:
https://blog.csdn.net/qq_16399991/article/details/130732780
https://time.geekbang.org/column/article/350285
https://blog.csdn.net/boonya/article/details/117307663

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 分布式锁 Redis vs etcd