
文章目录1. Redis应用--分布式锁1.1 什么是分布式锁1.2 分布式锁的实现1.3 引入过期时间1.4 引入校验id1.5 引入lua1.6 引入watch dog(看门狗)1.7 引入Redlock算法1. Redis应用–分布式锁1.1 什么是分布式锁在一个分布式系统中也会涉及到多个节点访问统一公共资源的情况。此时就需要通过锁来做互斥控制避免出现类似于“线程安全”的问题。而 Java 的 synchronized 或者 C 的 std::mutex这样的锁都是只能在当前进程中生效在分布式锁的这种多个进程多个主机之间就很难产生制约分布式系统中多个进程之间的执行顺序也是不确定的。此时就需要分布式锁。1.2 分布式锁的实现分布式锁也是一个/一组单独是服务器程序给其他的服务器提供加锁服务。Redis就是一种典型的可以用来实现分布式锁的方案。以买票服务器为例1. 在进行买票操作的时候先加锁在Redis 上设置一个特殊的 key-value完成了买票操作再把这个 key-value 删除掉。2. 其他服务器也想买票的时候也去 Redis 上尝试设置 key-value如果发现 key-value 已经存在就认为是加锁失败。3. 这样就保证了在第一个服务器执行“查询-更新过程中第二个服务器不会执行查询也就解决了超卖问题。1.3 引入过期时间使用set nx可以达到加锁的效果使用del来完成解锁的效果。但是如果设置的 key-value 还没来得及解锁进程异常终止了这样就会导致 Redis 上设置的 key 无人删除也就导致其他服务器无法获取到锁了。这种情况就可以给set的key设置一个过期时间一旦时间到了key就会被自动删除掉。不能使用setnx expire这两个命令分开设置的方式务必使用set ex nx这样的方式来进行设置。因为 Redis 上的多个命令之间是无法保证原子性的此时就可能出现一个命令成功一个失败的情况。相比之下使用一条命令设置更加稳妥。1.4 引入校验id是否会出现服务器1 执行了加锁服务器2 执行了解锁操作呢正常来说一般肯定不是故意执行解锁的但是代码总会有bug不小心执行到了解锁操作因此就可能进一步的给系统带来更严重的问题。为了解决上述问题就需要引入一点校验机制。给服务器进行编号每个服务器都有一个自己的身份标识。进行加锁的时候设置 key-valuekey 对应着要针对哪个资源加锁value 就可以存储刚才服务器的编号标识出当前这个锁是哪个服务器加上的。后续再解锁的时候先查询一下锁对应的服务器编号然后判定一下这个编号是否就是当前执行解锁的服务器编号如果是才能真正执行del如果不是就解锁失败。1.5 引入lua在通过上述的校验的方式来进行解锁1. 查询判定。2. del此处的操作是两步操作不是原子的就可能会出现问题。前提服务器1执行了加锁操作。服务器1的线程A和线程B先后执行了GET操作进行校验由于是服务器1进行的加锁操作所以线程A和线程B都能校验通过。服务器1的线程A先执行了DEL操作实现了解锁。在服务器1的线程A执行DEL和线程B执行DEL之间服务器2的线程C执行来了set nx ex加锁操作此时肯定是能加锁成功的。然后服务器1的线程B执行了DEL操作。此时就出现了问题。虽然 Redis 中的事务能够解决上面的问题但是在实践中往往是使用 lua 脚本。lua 是一个编程语言作为 Redis 内嵌的脚本。MySQL8 支持 js 作为内嵌语言。lua 语言特别轻量实现一个lua解释器消耗的体积是非常小的。可以使用 lua 编写一些逻辑把这个 lua 脚本上传到 Redis 服务器上然后可以让客户端来控制 Redis 执行上述脚本。Redis 执行 lua 脚本的过程也是原子的相当于执行一条命令。1.6 引入watch dog(看门狗)我们为了保证一些异常情况导致这个锁一直不能被释放所以要在加锁的时候给 key 设置一个过期时间那么久可能出现下面两种情况如果过期时间设置的短就可能在业务逻辑还没有执行完就释放锁了。如果过期时间设置的过长就会导致锁释放不及时的问题。那么最好的方式就是动态续约往往需要在服务器这边安排一个专门的线程负责续约的事情我们把这个负责的线程叫做“看门狗”。初始情况下设置一个过期时间比如说是1s就提前在还剩 300ms可灵活调整的时候如果当前任务还没有执行完就把过期时间再续上1s等到时间又快到了任务还没执行完就再续上。1.7 引入Redlock算法使用 Redis 作为分布式锁Redis 是有可能挂的。那么我们就要保证高可用。在集群中主节点和从节点之间的数据同步是存在延迟的。可能主节点收到了 set 请求但是还没来得及同步给从节点的时候主节点就已经挂了即使从节点升级成了主节点但是刚才的 set 的 key-value 也已经不存在了。Redis作者针对这个问题给出了一个方案。那就是redback算法冗余。我们引⼊⼀组 Redis 节点. 其中每⼀组 Redis 节点都包含⼀个主节点和若⼲从节点. 并且组和组之间存储的数据都是⼀致的, 相互之间是 “备份” 关系(⽽并⾮是数据集合的⼀部分这点有别于 Redis cluster)。 加锁的时候按照⼀定的顺序写多个 master 节点。在写锁的时候需要设定操作的 “超时时间”⽐如 50ms 即如果 setnx 操作超过了 50ms 还没有成功就视为加锁失败。如果给某个节点加锁失败就⽴即再尝试下⼀个节点。 当加锁成功的节点数超过总节点数的⼀半才视为加锁成功。这样的话即使有某些节点挂了也不影响锁的正确性。同理释放锁的时候也需要把所有节点都进⾏解锁操作(即使是之前超时的节点, 也要尝试解锁尽量保证逻辑严密)。