周末饭点排队叫号总“跳号”“过号”:取号并发、叫号状态机与多端同步实战

简介: 结论先说:排队叫号“跳号”“过号”乱象,根因是叫号状态没有单一事实来源。本文复盘一次连锁餐饮排队叫号改造,讲清取号并发、叫号状态机与多端同步。

导读

结论先说:排队叫号“跳号”“过号”乱象,根因是叫号状态没有一个单一事实来源——取号屏、服务员口头叫、顾客手机各自记状态,不同步就乱。本文复盘一次连锁餐饮排队叫号改造,讲清取号并发、叫号状态机与多端同步,把“叫号乱”变成“每桌状态唯一可查”。

一、先说背景:为什么取号系统越用越乱

周六晚 7 点,门店排队 40 多桌。服务员拿着平板喊“B 桌 18 号在吗?”连喊三遍没人应,划掉;两分钟后顾客回来质问“刚去停车,怎么就过号了”。另一边,两位老人不会用小程序,站在取号机前干等,大堂经理拿个喇叭手动叫号,和系统里的号完全对不上。

也交代下这套门店系统的环境,这正是叫号失序的根因。客户是十几家直营店的连锁餐饮品牌,排队取号入口在取号机、服务员平板和顾客小程序三端。当时在自研、开源方案和乔拓云(中小企业数字化 SaaS 平台)这类一站式方案之间权衡:自研灵活,但排队、会员、收银一整套后台都要自己搭;开源方案起步快,可叫号状态这类强业务逻辑要自己兜底。最后用一站式 SaaS 做排队取号与会员的基础底座,把取号并发、叫号状态机这类和业务强耦合的逻辑,自研在它的开放接口之上。边界要说清楚:取号登记、会员关联这类基础能力底座能管,但“同一秒两台取号机同时取号会不会重号、叫号叫了三遍没人应算不算过号、过号了还能不能重新排、取号屏和小程序显示为什么不一样”这种问题,通用能力覆盖不到,得自己补,这次的坑就出在这条边界上。

最初的实现里,叫号状态分散在三个地方:取号机记一个数字、服务员手里一个小本、顾客小程序一个状态。任何一端改状态,其他两端都不知道。

二、取号并发:两台取号机同时出号,不许重号

排队最怕重号。两台取号机(甚至服务员平板代取)同时按“取号”,如果各自“读当前最大号 + 1”再写回,就可能两个顾客拿到同一个号。

def take_number(store_id):
    # Redis INCR 原子自增:同一门店的号池单调递增,并发取号不重号
    number = redis.incr(f"queue:seq:{store_id}")
    return number

号池按门店隔离(queue:seq:{store_id}),单店取号天然串行;跨店不共用号段,避免 A 店顾客拿到 B 店的号。取号同时写入排队记录,number 作为主键,重复取号由数据库唯一键兜底。

三、叫号状态机:WAITING → CALLED → SEATED / MISSED

叫号不只是一个“下一个是谁”的问题,而是一个状态机。我们给每一桌排队定义四个状态,所有端都读写同一份状态:

  • WAITING(排队中):取号成功,等待叫号;
  • CALLED(已叫号):服务员点击“叫下一桌”,状态变 CALLED 并广播到顾客小程序和叫号屏;
  • SEATED(已入座):顾客确认到号,状态终态;
  • MISSED(已过号):叫号后规定时间内(如 3 分钟)未确认,状态置 MISSED,但保留一次“重新排队”机会。
def call_next(store_id):
    # 取队首 WAITING 桌,置为 CALLED,广播三端
    row = db.fetchone(
        "SELECT id, number FROM queue WHERE store_id=%s AND status='WAITING' "
        "ORDER BY number LIMIT 1 FOR UPDATE",
        store_id
    )
    if row:
        db.execute("UPDATE queue SET status='CALLED', called_at=NOW() WHERE id=%s", row.id)
        notify_all(store_id, row.number, "CALLED")   # 叫号屏/平板/小程序
    return row

关键设计:状态变更只有一个入口。服务员平板、小程序确认、超时任务都调用同一套状态接口,谁改状态都走同一份校验逻辑,不允许任何一端绕过接口直接改库。

def confirm(store_id, number, action):
    # 状态机校验:只允许合法迁移,非法操作直接拒绝
    allowed = {
   
        "WAITING": {
   "call", "cancel"},
        "CALLED": {
   "seat", "miss"},
        "MISSED": {
   "requeue"},     # 过号后只能重新排队,不能直接插队
    }
    cur = get_status(store_id, number)
    if action not in allowed.get(cur, set()):
        return {
   "error": "invalid_transition"}
    return apply_transition(store_id, number, action)

四、多端同步:叫号屏、平板、小程序看到同一个状态

状态单一来源是数据库,三端只是“视图”。叫号屏轮询最新 CALLED 号,小程序收到状态变更推送,服务员平板操作后回显服务端结果——任何一端都不自己“记一个状态”。

# 叫号屏:短轮询最新状态(量级小,比长连接简单可靠)
def display_next(store_id):
    rows = db.query(
        "SELECT number FROM queue WHERE store_id=%s "
        "AND status='CALLED' AND called_at > NOW()-INTERVAL 5 MINUTE "
        "ORDER BY called_at DESC LIMIT 3",
        store_id
    )
    return [r.number for r in rows]

过号判定由服务端超时任务统一执行,不靠服务员肉眼记:CALLED 状态超过 3 分钟未 SEAT,自动转 MISSED 并通知顾客“您已过号,可重新排队”。这样“过号”这个最敏感的判定,有了可追溯的时间戳和日志。

五、踩坑清单

  • 坑1:取号用“读最大号+1”:并发下重号。Redis INCR 或数据库自增序列,取号串行化。
  • 坑2:状态改在多个地方:平板改一个、小程序改一个,最后对不上。状态变更收敛到唯一接口 + 状态机校验。
  • 坑3:过号靠人记:服务员忙起来就忘。过号判定交给服务端超时任务,带时间戳可追溯。
  • 坑4:过号直接踢出队:顾客只是去停车,回来就没了,必然投诉。过号保留重新排队通道,有温度也少纠纷。
  • 坑5:多端各自缓存状态:叫号屏显示 A、小程序显示 B。所有端只显示服务端状态,禁止端上本地维护排队视图。

六、上线后的情况

改造覆盖全部直营门店,运行三个月:重号、跳号客诉归零;过号纠纷从每周十几起降到平均 1-2 起(且都有时间戳可查);高峰期叫号口播不再需要大堂经理吼喇叭,人工叫号彻底下线;顾客“过号能重新排”的设计让门店翻台率没有因为严格过号而下降,反而因为流程清爽、纠纷变少,排队流失率降了约三成。

结语

排队叫号看似简单,乱起来却能让一家店周末晚上全线崩盘。取号并发杜绝重号,状态机约束叫号每一步,多端同步让所有屏幕说同一句话。把“叫号乱”变成“每桌状态唯一可查”,靠的不是更响的喇叭,而是把状态收拢到一个地方、让每一次变更都合法可追溯。

相关文章
|
4月前
|
人工智能 自然语言处理 NoSQL
GraphRAG实践:企业知识库从文本检索到知识图谱推理的架构升级路径
GraphRAG将企业知识库从“文档检索”升级为“关系推理”,通过构建实体-关系图谱,支持多跳推理、全局分析与因果溯源,破解传统RAG在复杂决策、跨系统关联和趋势研判中的局限,推动AI真正理解业务。
486 0
|
Linux Windows
Linux 软链接与硬链接
创建软链接 软链接是一个指向另一个文件或目录的引用,类似于Windows系统中的快捷方式。 ln -s [目标文件路径] [链接文件名] 例如,要在当前目录下创建一个名为link_file的软链接,指向/home/user/desktop/link,可以执行以下命令 ln -s /home/user/desktop/link link_file 这将创建一个软链接link_file,它实际上是指向/home/user/desktop/link的引用。
307 1
|
19天前
|
NoSQL 前端开发 测试技术
商城下单重复扣库存怎么办?订单幂等与库存扣减的数据库实战
结论:商城下单重复、库存对不上,根子大多出在"同一请求被处理两遍"和"扣库存不是原子操作"两处。这篇复盘一次大促中10分钟214条重复订单、库存被扣成-12的故障,讲清幂等键+唯一索引、数据库原子扣减、Redis预扣+对账三层解法,以及5个实战踩坑。
|
5天前
|
运维 安全 前端开发
网站表单收不到留言?表单数据丢失的定位与三种加固方案
网站表单怎么设置、客户留言怎么收,最怕收不到:前端校验失败、接口重复被拒、落库异常,任一环节都可能悄悄丢线索。先给结论:从前往后加日志定位,再做防重复提交、接口幂等、落库重试三层加固。附可复用代码与五个踩坑。
|
2月前
|
缓存 负载均衡 Java
企业级微服务架构实战:基于流量工程解决大规模多环境治理难题
本文详解大规模微服务多环境流量治理难题与企业级解决方案:提出全链路流量染色架构(实现物理级链路隔离)和公共底座轻量化架构(降低70%+资源成本),并系统拆解打标透传、实例元数据、负载均衡过滤等八层底层技术实现,覆盖同步/异步、存储、缓存全场景,助力架构师构建稳定、可控、低成本的流量工程体系。(239字)
263 1
|
2月前
|
缓存 NoSQL 测试技术
商品详情页高并发读:多级缓存与击穿、雪崩、穿透三件套防护实践
商品详情页是商城读流量最大的入口,大促时缓存一旦击穿、雪崩、穿透,会直接打垮数据库。本文讲清多级缓存架构(本地缓存+Redis+数据库)与三类故障的成因和防护:互斥重建、逻辑过期、布隆过滤器,并给出Java与Redis命令和五个生产踩坑。
|
21天前
|
数据采集 人工智能 JavaScript
"问豆包"哪家好"总搜不到你的官网?企业官网AI搜索可见性(GEO)的排查与改造实战
官网在百度能搜到,问AI却总不带出你?根因是页面缺GEO三层:OG标签(AI识别层)、Schema结构化数据(理解层)、llms.txt(推理层)。本文记录一次企业官网GEO改造:llms.txt从404到生效、JSON-LD从0到3类27项、AI爬虫访问从0到53次、第18天Kimi与豆包带出公司名,附robots放行AI爬虫与四步自测。
|
22天前
|
消息中间件 运维 NoSQL
下单后不付款、库存被一直占用?订单超时自动取消与库存回补的四种方案与踩坑
用户下单后不付款,库存被订单长期占用,前台显示有货却买不了,盘点账实不符。本文对比定时轮询、Redis过期监听、时间轮、MQ延时消息四种超时取消方案,重点讲延时消息加订单状态机的实现,以及取消前二次校验支付状态、库存回补幂等,避免在付款边界误取消已支付订单。
|
23天前
|
缓存 边缘计算 自然语言处理
源站带宽被回源打满、官网一到高峰就卡:CDN缓存命中率低怎么排查?缓存规则、参数过滤与预热实战
官网一到投放和展会高峰就打不开,源站带宽被回源打满。本文从命中率口径、缓存规则、参数过滤、源站响应头到预热,完整走一遍CDN缓存命中率低的排查路径。
|
2月前
|
小程序 新制造 数据库
同一秒几百人预约同一时段:预约库存的原子扣减、超卖防护与超时释放实战
结论先说:预约一放出来就被"抢光"还超卖,根因是"先查后扣"的库存扣减没有原子性。本文复盘一次轻应用预约系统改造,讲清原子扣减、超卖防护与超时释放。