需求背景:
多个并发,下一个请求依赖上一个请求的处理结果,因此需求让多个并发处于队列中,依次执行。
使用技术栈:
egg.js + nodejs包redlock + redis
封装方法:
/**
* 自动并发锁下执行任务
* @param this
* @param fn 执行任务方法
* @param resources redis keys
* @param mySetting redlock相关配置
* @param duration 锁(redis key)过期时间,该方法会自动延长key过期时间,所以一般不用设置
* @returns 任务执行结果
*/
async runWithAutoLock(fn, resources, mySetting = {}, duration = 5000) {
// const duration = 5000; // redis key 过期时间,调lock.release()会删除key,所以这个时间长一点也没关系
let error;
let lock;
let timeout;
let extension;
resources = Array.isArray(resources) ? resources : [resources];
const settings = Object.assign(Object.assign({}, this.app.redlock.settings), mySetting);
if (typeof fn !== 'function') {
throw new Error('INVARIANT: fn is not a function.');
}
if (settings.automaticExtensionThreshold > duration - 100) {
throw new Error('A lock `duration` must be at least 100ms greater than the `automaticExtensionThreshold` setting.');
}
const queue = () => {
timeout = setTimeout(() => (extension = extend()), lock.expiration - Date.now() - settings.automaticExtensionThreshold);
};
const extend = async () => {
// timeout = undefined;
try {
lock = await lock.extend(duration);
queue();
}
catch (err) {
// 可能会触发死循环
// if (lock.expiration > Date.now()) {
// return (extension = extend());
// }
this.logger.error(err);
}
};
try {
lock = await this.app.redlock.acquire(resources, duration, settings);
queue();
try {
return await fn();
}
catch (err) {
error = err;
}
finally {
// Clean up the timer.
if (timeout !== undefined && timeout !== null) {
clearTimeout(timeout);
timeout = undefined;
}
// Wait for an in-flight extension to finish.
if (extension) {
await extension.catch(() => {
// An error here doesn't matter at all, because the routine has
// already completed, and a release will be attempted regardless. The
// only reason for waiting here is to prevent possible contention
// between the extension and release.
});
}
// 释放锁也可能会报错
let i = 0;
while (i++ < 5) {
// 最多重试5次
try {
await lock.release();
break;
}
catch (e) {
this.logger.error(`lock release failed, keys: ${JSON.stringify(resources)}, `, e);
}
await this.sleep(200);
}
}
}
catch (e) {
// 获取锁失败会抛错
// this.logger.warn('require lock failed:', e);
}
if (error !== undefined) {
throw error;
}
}