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() } })