多端同时操作元素的同步锁方案设计
1. 背景
产品需要做一个多人同步实时操作元素的功能,但需要确保同一个元素每次只能由一个用户获得控制权,各端同步这个用户的元素状态。
2. 实现思路
考虑使用一个同步锁机制,元素通过一个 加锁状态(lock)以及 加锁时间戳(lockTime)实现。只有获得锁的用户才能拥有对元素状态的控制权,控制权的优先级由时间戳决定。
3. 代码分析
简单实现了一个代码demo。
3.1 元素架构设计
元素需要实现三个基础功能接口
- 设置本地操作监听函数
- 远端同步状态函数
- 接收来自远端状态的函数
class DemoElement {
// 2099-12-31 23:59:59
static readonly LOCK_RELEASE_TIMESTAMP = 4102415999000;
public lock: boolean
public lockTime: number
public container: HTMLDivElement
constructor() {
this.lock = false;
this.lockTime = DemoElement.LOCK_RELEASE_TIMESTAMP;
this.container = document.createElement('div');
this.bindEvent();
}
// 绑定本地操作监听
bindEvent() {}
// 向远端同步状态
sendRealtimeData() {}
// 接收来自远端的状态
syncData({ lock, lockTime }) {}
}
3.2 bindEvent 函数实现
这里以 pointerdown – pointermove – pointerup 处理流程为例,展示本地操作的加锁、释放锁的流程
bindEvent() {
this.container.addEventListener('pointerdown', (e) => {
e.preventDefault();
e.stopPropagation();
const time = new Date().valueOf();
// 加锁时间小于当前时间,说明已被加锁,本地不可占用
if (time > this.lockTime) {
return;
}
// 加锁
this.lock = true;
this.lockTime = time;
const onMove = (e) => {
// 没有获得锁,不能占用
if (!this.lock) {
removeListener();
return;
}
}
const onMoveEnd = (e) => {
// 没有获得锁,不能占用
if (!this.lock) {
removeListener();
return;
}
// 释放锁
this.lock = false;
this.lockTime = DemoElement.LOCK_RELEASE_TIMESTAMP;
removeListener();
}
const removeListener = () => {
this.container.removeEventListener('pointermove', onMove, false);
this.container.removeEventListener('pointerup', onMoveEnd, false);
}
this.container.addEventListener('pointermove', onMove, false);
this.container.addEventListener('pointerup', onMoveEnd, false);
});
}
3.3 sendRealtimeData 函数实现
实时同步元素状态时,除了元素的属性,还需要把锁的状态同步出去
sendRealtimeData() {
return {
lock: this.lock,
lockTime: this.lockTime
}
}
3.4 syncData 函数实现
接收来自远端的元素状态时,根据 lock 和 lockTime 判断远端的加锁状态以及本地能否获得锁权限
syncData({ lock, lockTime }) {
if (lock) {
// 传入lockTime小于等于本地lockTime,已经被远端加锁,则本地解除资源占用
if (lockTime <= this.lockTime) {
// 释放锁
this.lock = false;
this.lockTime = lockTime;
} else {
// 本地先加锁,不接受外部同步信息
}
} else {
// 远端解除占用
if (this.lock) {
// 本地先加锁,不接受外部同步信息
} else {
// 释放锁
this.lock = false;
this.lockTime = lockTime;
}
}
}