第6周:GusLift 如何实时匹配乘车
Source: Dev.to
匹配房间抽象
我们并不运行一个全局池。我们按地点、日期和出发时间进行分区。房间键的形式如下:
Westie:mon:08:00该字符串成为 Cloudflare Durable Object (DO) 的标识,它是一个实时内存进程,拥有该时段的所有实时状态。所有在周一上午 8 点从 Westie 出发的用户共享同一个房间。10:30 出发?则是完全不同的房间。这使得每个实例都保持小且专注。匹配房间不需要也不关心其他出发窗口的情况。
进入正确的房间
当用户打开应用时,Cloudflare Worker 处理请求。它进行身份验证,解析用户的行程,生成时段键,并将 WebSocket 连接转发到相应的 DO。Worker 是无状态的——纯路由器。所有有趣的状态都保存在它指向的对象中。
DO 追踪的内容
DO 维护四类状态:
drivers— 驾驶员 ID → 剩余座位的映射riders_waiting— 请求搭乘的乘客的 FIFO 队列pending_matches— 驾驶员已选但尚未确认的乘客connections— 实时 WebSocket 句柄,每位用户一个
每一次状态变化都会广播给所有已连接的客户端。前端完全镜像 DO 所持有的内容,除此之外没有其他信息。
流程
- 驾驶员上线 → 发送
driver_online并附带座位数 → 在房间中注册,并广播给所有人。 - 乘客请求座位 → 发送
rider_request→ 加入队列。 - 驾驶员挑选乘客 → 该乘客从
riders_waiting移到pending_matches,并在其 socket 上收到match_request。 - 乘客有 30 秒时间接受。
- 未响应 → 乘客返回队列。
- 接受 → 座位数递减,行程写入 Postgres,房间状态对所有人更新。
并发事件——例如两个驾驶员同时选择同一乘客——能够优雅处理,因为 DO 以单线程模型一次执行一条消息,消除了显式锁的需求。
为什么使用 Durable Objects
无服务器平台与 WebSocket 默认配合不佳:无服务器函数是无状态且短暂的,而 WebSocket 需要持久状态。DO 提供了持久的、单线程的进程,拥有跨事件存活的内存状态。对于负载集中在早晨 20 分钟窗口的校园应用来说,房间在需要时启动,空闲时休眠,消除了闲置基础设施和连接交接问题。
剩余工作
尚未解决的开放问题:
- 驾驶员在匹配被接受后取消。
- 出发窗口结束后从未被清理的房间。
- 对 socket 事件的速率限制。
这些挑战在原则上相对直接,只是尚未纳入最初的实现中。