本文主要介绍在琴房预约项目中所用到的资源访问加锁技术Redlock,以及如果实现将redlock转为非阻塞锁。
1. 背景简介
1.1 项目简介
本次实践项目是THU琴房预约小程序与web管理端,后端使用koa框架。主要功能有预约琴房(用户)、更改琴房可用时间(管理端)等。后端使用mysql存储琴房信息(包含琴房可用时间串)。
1.2 问题介绍
- 多个用户可能在同一时间对同一琴房的同一时间段进行预约
- 用户与管理员可能同时进行预约、更改琴房可用时间
根据上篇文章所描述的,因为存在不同操作之间的资源竞争,所以我们这里使用增加信号量的方式——Redlock。
2. 加锁实践
redlock是一个基于redis数据库的分布式锁。通过在redis数据库中设定键值来进行信号量的定义。
- 我们把“预约“和“更改琴房可用时间”定义为使用同一个键值。
- 在操作前首先获取此键值,如果获取不到,说明被占用。
关于这个流程,包括set键值与”占用"和"未占用“状态的更改是由Redlock实现的。
2.1 给资源加锁
// 获取权限
let redis = require("redis");
let client = redis.createClient("to change: your redis port","to change: your server ip");
let redlock = new Redlock([client]);
// 设置时间
let totalTime = 5000;
let key = "to change: as you like";
// 加锁
redlock.lock(key, totalTime).then(async function(lock){
// to do
// your operation block
// release lock
lock.unlock().catch(function(err){})
}
}).catch(()=>{}) // 如果不加catch会直接报错终止程序
主要流程为:
- 获取redis的操作权限
- 设定键值,最长等待时间(防止一直被占用)
- 执行操作
- 释放锁
按照上面流程,我们成功进行了加锁。
键值的设置非常自由,我们可以通过键值的设置,控制加锁覆盖的范围以及力度。
2.2 阻塞锁与非阻塞锁
在完成加锁之后,我们进行压力测试,发现了一个非常坑的情况:Redlock是非阻塞锁。
这就意味着,当一个用户进行预约的时候,别的用户如果有请求就会直接失败,这样是非常用户不友好的。所以我们需要一些操作来将redlock转为非阻塞锁。有两个方案:
- 方案一:实现一个请求队列以及回调函数,在资源被释放的时候,进行回调。
- 方案二:设置最长等待时间,在此时间段内进行轮询,如果超过此时间,放弃请求。
对于方案一,请求队列可能会很长,如果前面的资源不释放,就会一直等待;对于方案二,轮询需要消耗更多的资源。
我们这里说明方案二的实现(目前最常用的转阻塞锁的方案)
let redis = require("redis");
let client = redis.createClient(config.redisPort,config.serverIp);
let redlock = new Redlock([client]);
let totalTime = 5000;
let key = "to change: as you like";
let intervalTime = 50; // 轮询时间间隔
let sleep = function(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
let tag = 0;
for(let j = 0; j<200; j++){
redlock.lock(key, totalTime).then(async function(lock){
if(tag === 1){
lock.unlock().catch(function(err){})
}
else{
tag = 1
// to do
// your operation block
// release lock
lock.unlock().catch(function(err){})
}
}).catch(()=>{})
if(tag === 1){
break
}
await sleep(intervalTime) // 设置间隔时间
}
if(tag === 0){
errorMsg = "请求超时"
return ;
}
3. 效果检验
在预约与检票单项测试的时候,1000个人中只有一个人成功,符合预期。
在预约与更改琴房可用时间混合测试的时候,2000个人中只有一个人成功,符合预期。
以上,说明这样加锁,可以成功解决资源访问竞争情况。