分布式 MQ 可靠性与任务调度系统设计 一句话理解MQ 可靠性不是只靠 MQ 本身而是靠生产端确认、Broker 刷盘复制、消费端业务成功后确认、失败补偿、死信告警和业务幂等一起兜住任务调度系统也不是只会派车而是状态机、并发抢占、幂等和异常恢复的组合。一、MQ 不丢消息怎么讲面试里不要只说“加 ACK”要按三段讲生产端确认 Broker 刷盘/复制 消费端业务成功后确认 重试/死信/补偿 幂等1. 生产端防丢以 RocketMQ 为例同步发送调用send()等SendResult只有SEND_OK才能认为 Broker 已确认接收。异步发送通过SendCallback.onSuccess/onException拿结果异步不是没有确认风险在于回调异常没处理、进程提前退出、超时结果未知。单向发送sendOneway()不等结果不适合订单、库存、AGV 任务这类可靠业务。如果发送失败或结果未知生产端要重试或写本地消息表后续由定时任务补偿发送。因为超时重试可能带来重复消息所以消费端必须做幂等。2. Broker 端防丢RocketMQ Broker 侧看两个维度刷盘和主从复制。SYNC_FLUSH 保证 Master 本机落盘 SYNC_MASTER 保证 Slave 也同步完成。ASYNC_FLUSH写入 PageCache 后可能就返回成功Broker 宕机且还没刷盘时可能丢。SYNC_FLUSH消息写入 CommitLog 并刷盘成功后再返回可靠性更高。ASYNC_MASTERMaster 写成功后返回Slave 可能还没同步Master 故障切换时可能丢。SYNC_MASTER主从同步完成后再返回可靠性更高但吞吐和延迟更差。面试要补一句可靠性和性能要取舍核心业务可以提高可靠等级普通日志类消息可以接受异步。3. 消费端防丢消费者收到消息不等于业务成功。正确顺序是收到消息 - 执行业务 - 写库/状态推进成功 - 返回 CONSUME_SUCCESS如果刚收到就返回CONSUME_SUCCESS后续业务异常或服务宕机Broker 不会再投递看起来就像消息丢了。失败时应返回RECONSUME_LATER或抛异常让 Broker 重试超过次数进入死信队列再由告警和人工/自动补偿处理。二、重复消费与幂等设计MQ 常见语义是至少一次投递所以重复消费是正常情况不是异常情况。常见重复来源消费者业务执行成功但确认失败。消费超时Broker 认为没有成功。网络抖动、服务重启、Rebalance。生产端发送超时后重试实际第一条已经成功写入 Broker。解决方式是业务幂等。1. 创建防重创建任务、创建订单、创建待办时用业务唯一键防重比如requestId externalTaskId wmsTaskNo数据库层加唯一索引不能只靠“先查再插”。因为并发下两个线程可能同时查不到再同时插入。2. 状态流转防重状态流转类操作用状态机 条件更新UPDATEtaskSETstatusDISPATCHINGWHEREtask_id?ANDstatusWAITING_DISPATCH;影响 1 行说明当前线程抢占成功继续执行。影响 0 行说明已经被处理或状态已变化直接按幂等成功返回或结束。这套回答特别适合 AGV/RCS因为任务本身就天然有状态机。三、本地消息表、Inbox 与 RocketMQ 事务消息1. 本地消息表是什么本地消息表是业务侧 Outbox核心不是“防重复”而是“可靠发送 补偿”。1. 开启本地事务 2. 写业务表 3. 写本地消息表 WAIT_SEND 4. 提交本地事务 5. 发送 MQ 6. 发送成功标记 SENT 7. 失败保持 WAIT_SEND/FAILED 8. 定时任务扫描补发它相当于业务系统自己保存的一本“我应该发什么消息”的台账。MQ 短暂不可用、发送失败或服务宕机后可以根据这张表补发。2. Inbox 是什么Outbox 保证消息发出去Inbox 保证消息不被重复处理。消费端可以用messageId、bizId、taskId eventType建消费记录或唯一约束。重复消息来了发现已经处理过就直接返回成功。3. RocketMQ 事务消息解决什么RocketMQ 事务消息解决的是 Producer 本地事务和消息投递一致性。1. Producer 发送 Half Message 2. Half Message 对 Consumer 不可见 3. Producer 执行本地事务 4. 成功 - COMMIT_MESSAGE 5. 失败 - ROLLBACK_MESSAGE 6. 未知 - Broker 回查 Producer回查由 Broker 发起但回查逻辑由 Producer 实现。Producer 的checkLocalTransaction不能看内存要查数据库里的业务表、事务日志或本地消息表判断本地事务到底成功没有。重要边界事务消息不保证消费者业务成功。消费者业务仍要靠重试、死信、补偿和幂等。四、顺序消息怎么讲RocketMQ 保证的是队列级顺序不是默认全局顺序。AGV/RCS 里任务事件应该按taskId分队列同一个 taskId - 同一个 MessageQueue - 顺序消费这样同一个任务的创建、派发、执行、完成事件就不会乱序。不要追求全局顺序因为全局顺序通常意味着所有消息进入一个队列会严重影响吞吐和扩展性。面试表达重点是按业务键保证局部顺序。五、限流、熔断、降级一句话记忆限流防冲垮熔断防拖垮降级保核心。限流入口少放请求进来防止系统被瞬时流量打爆。熔断下游持续失败时暂时停止调用它快速失败或走 fallback避免调用方也被拖垮。降级完整功能不可用时返回简化结果、缓存结果或默认结果保证核心链路可用。令牌桶和漏桶令牌桶系统按固定速率生成令牌请求拿到令牌才能执行。空闲时令牌可以积累所以允许一定突发流量。漏桶请求先进桶里排队系统按固定速率流出处理更像匀速削峰。AGV 任务“不丢”不能靠令牌桶天然保证而要靠任务表、队列、状态机和补偿机制保证。限流只是控制调度入口或下发频率。六、AGV/RCS 任务调度系统设计1. 业务背景WMS/MES 下发搬运任务RCS 接收任务后选择车辆、派发任务、监控执行过程并把完成、异常、取消等结果回传上游。2. 核心对象任务表task_id external_task_id source_point target_point priority status assigned_vehicle_id retry_count create_time update_time车辆表vehicle_id status current_point battery load_status current_task_id last_heartbeat_time其中taskId是系统内部分布式 IDexternalTaskId/wmsTaskNo是上游业务幂等键。3. 状态机CREATED - WAITING_DISPATCH - DISPATCHING - DISPATCHED - EXECUTING - FINISHED异常状态包括FAILED、CANCELLED、ABNORMAL。状态机的价值是防止乱跳比如一个已经FINISHED的任务不能再被重新派发。4. 多实例并发抢占任务抢占UPDATEtaskSETstatusDISPATCHING,scheduler_instance?,update_timeNOW()WHEREtask_id?ANDstatusWAITING_DISPATCH;车辆抢占UPDATEvehicleSETstatusBUSY,current_task_id?WHEREvehicle_id?ANDstatusIDLE;通过影响行数判断是否抢占成功避免多个调度实例派同一任务或同一车辆。5. 异常恢复DISPATCHING这类中间态要有超时补偿如果超过一定时间没有下游派发记录或车辆绑定可以恢复为WAITING_DISPATCH或标记失败重试。EXECUTING阶段不能简单回滚因为车辆可能已经在路上。要结合车辆心跳、位置上报、任务进度和告警判断必要时暂停任务、释放资源、重新规划路径或人工介入。