43 lines
1.4 KiB
JavaScript
43 lines
1.4 KiB
JavaScript
import { randomId } from './crypto.js';
|
|
import { key } from './redisClient.js';
|
|
|
|
const QUEUE_KEY = key('queue:zset');
|
|
const QUEUE_LOCK_KEY = key('queue:lock');
|
|
|
|
export async function enqueue(redis, userId) {
|
|
const score = Date.now();
|
|
await redis.zAdd(QUEUE_KEY, [{ score, value: String(userId) }]);
|
|
return redis.zCard(QUEUE_KEY);
|
|
}
|
|
|
|
export async function leaveQueue(redis, userId) {
|
|
return redis.zRem(QUEUE_KEY, String(userId));
|
|
}
|
|
|
|
export async function getQueueSize(redis) {
|
|
return redis.zCard(QUEUE_KEY);
|
|
}
|
|
|
|
export async function dequeuePair(redis) {
|
|
// Simple lock to reduce thundering herd when multi-instance
|
|
const lockVal = randomId('lock_');
|
|
const locked = await redis.set(QUEUE_LOCK_KEY, lockVal, { NX: true, PX: 200 });
|
|
if (!locked) return null;
|
|
try {
|
|
const ids = await redis.zRange(QUEUE_KEY, 0, -1);
|
|
if (!ids || ids.length < 2) return null;
|
|
|
|
const firstIndex = Math.floor(Math.random() * ids.length);
|
|
let secondIndex = Math.floor(Math.random() * (ids.length - 1));
|
|
if (secondIndex >= firstIndex) secondIndex += 1;
|
|
|
|
const picked = [ids[firstIndex], ids[secondIndex]];
|
|
await redis.zRem(QUEUE_KEY, picked[0], picked[1]);
|
|
return [Number(picked[0]), Number(picked[1])];
|
|
} finally {
|
|
// best-effort unlock
|
|
const cur = await redis.get(QUEUE_LOCK_KEY);
|
|
if (cur === lockVal) await redis.del(QUEUE_LOCK_KEY);
|
|
}
|
|
}
|