You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现队列系统中按部门线程安全的取号功能?

解决方案

一、数据库层面(推荐,更可靠)

应用层锁在分布式部署场景下会失效,数据库层面的行级锁能从根源避免重复取号问题,同时保证不同部门的并行性。

1. 优化查询逻辑,使用行级锁

修改getNextTurnForUser的底层查询,在获取下一个号时直接锁定对应的行,且仅锁定当前用户所属部门的未调用号。PostgreSQL支持SELECT ... FOR UPDATE SKIP LOCKED语法,能跳过已被锁定的行,让同部门的其他请求直接取下一个可用号,无需等待:

SELECT * FROM daily_turns
WHERE turn_status_id = ? -- 未被调用的状态值
AND department_id = (SELECT department_id FROM users WHERE user_id = ?) -- 关联用户所属部门
ORDER BY turn_id ASC
LIMIT 1
FOR UPDATE SKIP LOCKED;
  • FOR UPDATE:锁定选中的行,防止其他事务修改
  • SKIP LOCKED:让后续请求跳过已被锁定的行,直接获取下一个可用号,提升并发效率

后续的更新操作(save)只会处理拿到行锁的记录,同部门请求自动串行处理,不同部门因为查询的是不同部门的行,完全不会互相阻塞。

2. 原子化更新操作

把“取号+更新”合并成一个原子SQL,彻底消除中间状态的并发问题。PostgreSQL的UPDATE ... RETURNING语法可以直接完成这个操作:

UPDATE daily_turns
SET turn_status_id = ?, event_date = ?, user_id = ?
WHERE turn_id = (
    SELECT turn_id FROM daily_turns
    WHERE turn_status_id = ?
    AND department_id = (SELECT department_id FROM users WHERE user_id = ?)
    ORDER BY turn_id ASC
    LIMIT 1
    FOR UPDATE SKIP LOCKED
)
RETURNING *;

整个操作由数据库保证原子性,不需要应用层处理并发逻辑,同一部门的请求自动串行,不同部门完全并行。

二、Java应用层方案(仅适用于单节点部署)

如果是单节点应用,可以通过部门ID映射锁对象,实现按部门的同步控制。

1. 定义部门锁容器

用ConcurrentHashMap存储每个部门对应的锁对象,确保每个部门只有一个锁:

private static final ConcurrentHashMap<Integer, Object> DEPARTMENT_LOCKS = new ConcurrentHashMap<>();

// 获取部门锁的工具方法
private Object getDepartmentLock(int departmentId) {
    return DEPARTMENT_LOCKS.computeIfAbsent(departmentId, k -> new Object());
}

2. 修改取号方法,按部门加锁

先通过userId获取对应部门ID,再用该部门的锁对象做同步:

private DailyTurns callTurnLocal(int userId) {
    // 先获取用户所属部门ID,假设你有对应的查询方法
    int departmentId = getDepartmentIdByUserId(userId);
    Object lock = getDepartmentLock(departmentId);
    
    synchronized (lock) { // 同部门共用一个锁,不同部门用不同锁,实现并行
        try {
            DailyTurns turn = null;
            DailyTurns updateTurn = null;
            
            turn = getNextTurnForUser(userId);

            if (turn != null)
            {
                updateTurn = turn;
                updateTurn.setTurnStatusId(TURN_STATUS_CALLED);
                updateTurn.setEventDate(new Date());
                updateTurn.setUserId(userId);
                updateTurn = save(updateTurn);
            }
            
            return updateTurn;
        }
        catch (Exception e)
        {
            logger.error( "Exception: " + e.getMessage(), e );
            return null;
        }
    }
}

注意:该方案仅适合单节点应用,多节点部署时不同节点的锁对象独立,会出现跨节点的同部门并发问题。另外要定期清理废弃部门的锁对象,避免内存泄漏。

总结

  • 分布式部署:优先用数据库层面的SELECT ... FOR UPDATE SKIP LOCKED或UPDATE ... RETURNING方案,可靠且能保证不同部门的并行性。
  • 单节点部署:可以用应用层部门锁方案,实现简单,但要注意锁对象的内存管理。

内容的提问来源于stack exchange,提问作者camaleongdl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 03:46:12