寒假复习第3天-分布式锁、限流算法
一、Redis做分布式锁用什么命令?
SETNX
格式:setnx key value 将 key 的值设为 value,当且仅当 key 不存在,若给定的 key 已经存在,则 SETNX 不做任何动作,操作失败。
SETNX 是 【SET if Not Exists】的简写。(如果不存在,则SET)
加锁(10秒过期):set key value nx ex 10s
释放锁:delete key
二、Redis做分布式锁死锁有哪些情况,如何解决?
情况1:加锁,没有释放锁。需要加释放锁的操作。如:delete key
情况2:加锁后,程序还没有执行释放锁,程序挂了。需要用 key 的过期机制。
三、Redis如何做分布式锁?
假设有两个服务A、B都希望获得锁,执行过程大致如下:
步骤1:
服务A为了获得锁,向Redis发起了如下命令:SET productId:lock 0xx9p03001 NX EX 30000
其中:
productId:【key】由自己定义,可以是与本次业务有关的id;
0xx9p03001:【value】是一串随机值,必须保证全局唯一;
NX:当且仅当 key 在 Redis 中不存在时,返回成功,否则执行失败。
EX 30000:30秒后,key 将过期,自动删除。
执行命令后,返回成功,表明服务成功获得了锁。
步骤2:
服务B为了获得锁,向Redis发起了同样的命令:SET productId:lock 0xf2p03002 NX EX 30000
由于Redis内已经存在同名 key,并且尚未过期,因此命令执行失败,服务B未能获得锁,服务B进入循环请求状态。比如:每隔1秒钟(自行设置)向Redis发送请求,直到命令执行成功,服务B获得锁。(也可以直接提示系统匆忙,稍后再试)
步骤3:
服务A的业务代码执行时长超过30秒,导致 key 超时,因此Redis自动删除了 key 。此时,如果服务B再次发起命令将执行成功;假设本次请求中设置的 value 值为 0xf2p03002。这时,服务A的业务代码未执行完,所以需要服务A对 key 进行无限 续期 直到执行完:watch dog
步骤4:
服务A执行完毕,为了释放锁,服务A会主动向Redis发起删除 key 的请求。
注意:在删除 key 之前,一定要判断 服务A 持有的 value 与 Redis 内存储的 value 是否一致。
比如当前场景下:
Redis中的锁早就不是 服务A 持有的那把了,而是由 服务B 创建的,如果贸然使用 服务A 持有的 key 来删除锁,则会误将 服务B 的锁释放掉。
此外,删除锁时,涉及到一系列的判断逻辑,因此一般使用 lua脚本 ,具体如下:
if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
四、基于Zookeeper的分布式锁实现原理是什么?
1、顺序结点特性:
使用 ZooKeeper 的顺序结点特性,假如我们在 /lock/ 目录下创建 3 个节点,ZK 集群会按照发起创建的顺序来创建节点,节点分别为 /lock/0000000001、 /lock/0000000002、 /lock/0000000003,最后一位数依次递增,节点名由ZK来完成。
2、临时节点特性:
ZK还有一种名为 临时节点 的 节点,临时节点由某个客户创建,当 客户端 与 ZK集群 断开连接,则该节点自动被删除。EPHEMERAL_SEQUENTIAL 为临时顺序节点。
分布式锁基本逻辑:
根据ZK中节点是否存在,可以作为分布式锁的锁状态,以此来实现一个分布式锁。
1、客户端A 调用 create() 方法创建名为 “/业务Id/lock-” 的 临时顺序节点。
2、客服端A 调用 getChildren("业务Id") 方法来获取所有已经创建的子节点。
3、客户端A 获取所有子节点 path 后,判断 自己创建的 节点是 所有节点中序号最小的。
如果是,则客户端A 获得锁。
如果不是,则监视前一个比自己序号小的节点,进入等待,直到监视的子节点变更时,再次获取 子节点,进行大小判断。
五、ZooKeeper和Redis做分布式锁的区别?
Redis:
1、Redis只保证最终结果一致性,副本间的数据复制是异步进行的。(Set是写,Get是读,Redis集群一般是 读写分离 架构,存在主从同步延迟的情况)主从切换后,可能有部分数据并没有复制,造成【锁丢失】的情况,故,强一致性要求的业务不推荐使用Redis,推荐使用ZK。
2、Redis集群各方法的响应时间均为最低(内存I/O),随着并发量和业务数量的提升其响应时间会明显上升。(公网集群影响因素偏大),但是极限 qps (每秒查询率)可以达到最大且基本无异常。
ZooKeeper:
1、使用ZooKeeper集群,锁原理是使用ZooKeeper的临时顺序节点,临时顺序节点的生命周期在 客户端(Client) 与 集群 的 连接(Session)结束时结束。因此如果某个 客户端节点 存在网络问题,与ZooKeeper集群断开连接、连接超时同样会导致 锁 被错误的释放(导致被其他线程错误的持有),因此ZooKeeper 也无法保证完全一致。
2、ZooKeeper具有较好的稳定性;响应时间抖动较小,没有出现异常,但随着并发量和业务数量的提升其响应时间和qps会明显下降。
总结:
1、ZooKeeper每次进行锁操作前都要创建若干节点,完成后要释放节点,会浪费时间。【强致性强,慢】
2、Redis只是简单的数据操作,没有值个问题。【异步,速度快】
六、MySQL如何做分布式锁?
主键互斥、唯一键互斥
在MySQL中创建一张表,设置一个主键或者 UNIQUE KEY(唯一键)这个 key 就是要锁的 key。所以同一个key 在MySQL表中只能插入一次,这样对锁的竞争就交给了 数据库,处理同一个key数据库,处理同一个key数据库保证了只有一个节点能插入成功。
DB分布式锁的实现:通过主键Id或者 唯一约束 的 唯一性 进行加锁。就是加锁的形式是向一张表中插入一条数据,该数据的 Id 就是一把 分布式锁。
例如:当一次请求插入了一条Id为 1 的数据,其他想要进行插入数据的并发请求,必须等第一次请求完成并删除了这条Id为 1 的数据才能继续插入,实现了分布式锁的功能。
这样lock 和 unlock 的思路就很简单了,伪代码:
def lock:
exec sql: insert into locked-table(xxx) value (xxx)
if result == true :
return true
else
return false
def unlock:
exec sql: delete from locked Or der where order_id='order_id'
七、计数器算法是什么?
计数器算法,是指在指定的时间周期内累加访问次数,达到设定的阈值时,触发限流策略。下一个时间周期访问时,访问次数清零。
此算法无论是单机还是分布式环境下实现都非常简单,使用Redis的 Incr 原子自增性,再结合 key 的过期时间,即可轻松实现。
缺点:
我们设置一分钟的阈值是 100,在0:00 到 1:00 内请求数是 60,当 1:00时,请求数清零,从0开始计算,这时在 1:00 到 2:00 之间我们能处理的最大请求数为 100。超过100个请求,会被限流。
这个算法存在临界问题,比如:在0:50有 60 个请求,而在 1:00 到 2:00 之间,只在1:10 有 60 个请求。虽然在两个 1分钟内,没有超过 100 阈值。但在 0:50 到 1:10 这 20秒 内,却有120个 请求。这20秒 超过了我们设置 1分钟内 100个请求的阈值。
八、滑动时间窗口算法是什么?
为了解决计数器算法的临界值问题,发明了滑动窗口算法,在 TCP 网络通信协议中,就采用了滑动时间窗口算法来解决网络拥堵问题。
滑动时间窗口是将 计数器算法中的实际周期切分成多个小的时间窗口。分别在每个小的时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只要统计滑动窗口范围内的小时间窗口总的请求数即可。
在滑动时间窗口算法中,我们的小窗口划分的越多,滑动窗口的滑动就越平滑,限流统计就会越精确。
九、漏桶限流算法是什么?
漏桶算法的原理:维持一个漏斗,它有恒定的流出速度,不管水流流入的速度有多快,漏斗出水的速度始终保持恒定不变。类似于消息中间件,不管消息的生产者请求量有多大,消息处理能力取决于消费者。
漏桶的容量 = 漏桶的流出速度 * 可接受的等待时长,在这个容量范围内的请求可以排队等待系统的处理,超过这个容量的请求,才会被抛弃。
使用场景:
1、当请求速度大于漏桶的流出速度时,也就是请求量大于当前服务所能处理的最大极限时,触发限流策略。
2、请求速度小于或等于漏桶流出速度时,也就是服务器的处理能力大于或等于请求量时,正常执行。
缺点:
当系统在短时间内有突发的大流量时,漏桶算法处理不了。

浙公网安备 33010602011771号