1. 前言
开发时,碰到互斥问题,须要保障在分布式环境下,防止重复性操作批改用户状态,如:用户订单状态,购票时,批改票的余额等
2. 分布式锁的条件
-
分布式锁须要满足下列条件
- 锁须要有短缺的可拜访的存储空间
- 锁必须被惟一标识
- 锁至多要有两种状态
-
同时,要保障
- 平安个性:互斥拜访,永远只有一个 client 能拿到锁
- 防止死锁:client 最初能够拿到锁,不会呈现死锁,即便本来上锁的 client 呈现问题无奈解锁
- 容错性:容错,只有大多数 redis 节点可能失常工作,客户端端都能获取和开释锁。
3.Redis 单节点上锁的实现
-
应用下列语句获取一个不存在的 key,如果能够已存在则创立失败,确保 key 值惟一,加上过期工夫,确保零碎谬误后及时解锁,防止死锁
SET key value NX PX 30000
然而这种办法在主库谬误时,会产生谬误,redis 主从同步是异步,主库谬误时,从库若还没有锁的信息,则会导致多个过程持有锁
3. Redlock 算法(官网文档)
在分布式版本的算法里咱们假如咱们有 N 个 Redis master 节点,这些节点都是齐全独立的,咱们不必任何复制或者其余隐含的分布式协调算法。咱们曾经形容了如何在单节点环境下平安地获取和开释锁。因而咱们天经地义地该当用这个办法在每个单节点里来获取和开释锁。在咱们的例子外面咱们把 N 设成 5,这个数字是一个绝对比拟正当的数值,因而咱们须要在不同的计算机或者虚拟机上运行 5 个 master 节点来保障他们大多数状况下都不会同时宕机。一个客户端须要做如下操作来获取锁:
- 获取以后工夫(单位是毫秒)。
- 轮流用雷同的 key 和随机值在 N 个节点上申请锁,在这一步里,客户端在每个 master 上申请锁时,会有一个和总的锁开释工夫相比小的多的超时工夫。比方如果锁主动开释工夫是 10 秒钟,那每个节点锁申请的超时工夫可能是 5 -50 毫秒的范畴,这个能够避免一个客户端在某个宕掉的 master 节点上阻塞过长时间,如果一个 master 节点不可用了,咱们应该尽快尝试下一个 master 节点。
- 客户端计算第二步中获取锁所花的工夫,只有当客户端在大多数 master 节点上胜利获取了锁(在这里是 3 个),而且总共耗费的工夫不超过锁开释工夫,这个锁就认为是获取胜利了。
- 如果锁获取胜利了,那当初锁主动开释工夫就是最后的锁开释工夫减去之前获取锁所耗费的工夫。
- 如果锁获取失败了,不论是因为获取胜利的锁不超过一半(N/2+1) 还是因为总耗费工夫超过了锁开释工夫,客户端都会到每个 master 节点上开释锁,即使是那些他认为没有获取胜利的锁。
4. 应用示例
-
首先装置 redis 库与 redlock 库 (官网库,在 git 上名叫 node-redlock),在 node 我的项目中,间接应用 npm 或者 yarn 下载安装即可,redis 库我应用的是 ioredis(间接装置 redis 库也可)
npm i --save ioredis npm i --save redlock
-
次要用到的办法(官网文档)
/** 申请锁 */ Redlock.prototype.lock(resource, ttl, ?callback) // resource 锁的名称 // ttl 锁的有效期 // callback 回调函数,当应用 promise 的写法的时候,能够填写这个参数 /** 开释锁 */ Redlock.prototype.unlock(lock, ?callback) /** 缩短锁的无效工夫 */ Lock.prototype.extend(lock, ttl, ?callback) // 都反对 callback、promise、yield 写法
-
代码示例
const ioredis = require('ioredis') const Redlock = require('redlock') const client = new ioredis(REDIS_SERVER) const redlock = new Redlock([client]) co(function* () {while (true) { let lock = null try {lock = yield redlock.lock('lock', 1000) // 这种写法取不到锁时会间接抛出谬误 } catch (error) {lock = null} yield sleep(30 * 1000) // 解决逻辑 lock.unlock()} })