城市电竞陪玩调度系统实战:从派单算法到多端协同的架构拆解

城市电竞陪玩调度系统实战:从派单算法到多端协同的架构拆解

城市电竞陪玩调度系统,本质是一套「带实时位置与技能属性的双边撮合平台」:一端是发布陪玩需求的用户,另一端是在线可接单的陪玩师。它和同城跑腿、代驾在调度层面同源,但匹配维度更多------不止看距离,还要看游戏品类、段位区间、语音/线下场景、时段偏好与服务评价。本文结合 Spring Boot + MyBatis Plus + MySQL + UniApp + Vue/ElementUI 这套常见技术组合,拆解一套可落地的调度系统该怎么设计与实现。

一、先厘清业务边界:城市电竞陪玩调度系统要解决什么

把需求拆成四层,后面的技术选型才有依据:

  1. 发现层:用户按城市、游戏(如 MOBA、FPS、棋牌)、服务形式(线上语音/同城线下)筛选陪玩师。
  2. 调度层:订单创建后,系统在候选池中筛选并派单,支持抢单、派单、竞价三种模式。
  3. 履约层:接单后进入会话(IM/语音房间),同步开始时间、时长、战绩截图等凭证。
  4. 治理层:评价、申诉、风控(虚拟号码、异常行为识别、安全提醒)。

其中调度层是系统的心脏,也是新手容易写崩的地方------常见问题是把「查询附近的人」直接写成全表扫描,或者用数据库行锁硬扛并发派单。

二、技术选型与整体架构

沿用知识库中同类同城系统的成熟组合,分层如下:

层次 技术 说明
后台服务 Spring Boot + MyBatis Plus 单体起步,按调度、订单、IM、结算拆分模块
数据库 MySQL 订单与陪玩师主数据
缓存/位置 Redis(GEO + 分布式锁) 在线状态、地理位置、幂等控制
实时通信 WebSocket / Netty 派单推送、IM 消息
用户端 UniApp(Vue 语法) 一套代码发布小程序 / H5 / 公众号 / App
管理后台 Vue + ElementUI 调度监控、账号审核、订单干预

多端复用的关键是把「端能力差异」收敛到一层适配:小程序拿不到后台定位时降级为手动选点,App 端则启用持续定位上报。业务代码只依赖统一的 LocationProvider 接口。

三、调度引擎:从「附近的人」到「合适的人」

1. 位置索引用 Redis GEO,不用 MySQL 的经纬度计算

MySQL 里 WHERE 里写三角函数算距离,数据量上万后基本不可用。正确做法是陪玩师每次心跳上报位置时写入 Redis GEO:

java 复制代码
// 位置上报:陪玩师心跳
public void reportLocation(Long providerId, double lng, double lat) {
    String key = "geo:provider:" + cityCode;
    redisTemplate.opsForGeo().add(key, new Point(lng, lat), String.valueOf(providerId));
    // 在线状态单独用 String + TTL,避免僵尸账号
    redisTemplate.opsForValue().set("online:provider:" + providerId, "1", 90, TimeUnit.SECONDS);
}

// 候选集查询:3 公里内、多 50 人
public List<Long> findCandidates(String cityCode, double lng, double lat) {
    Circle circle = new Circle(new Point(lng, lat),
            new Distance(3, Metrics.KILOMETERS));
    GeoResults<RedisGeoCommands.GeoLocation<String>> results =
            redisTemplate.opsForGeo().radius("geo:provider:" + cityCode,
                    circle, RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                            .includeDistance().sortAscending().limit(50));
    return results.getContent().stream()
            .map(r -> Long.valueOf(r.getContent().getName()))
            .collect(Collectors.toList());
}

2. 多因子打分排序

拿到候选集后,再做一轮加权打分。权重建议做成配置项,便于运营侧调整:

java 复制代码
public double score(Provider p, Order o, double distanceKm) {
    double distanceScore = 1.0 / (1 + distanceKm);            // 距离越近越高
    double skillScore    = p.getGames().contains(o.getGameId()) ? 1.0 : 0.0;
    double rankScore     = matchRank(p.getRankLevel(), o.getRankRange());
    double loadScore     = 1.0 / (1 + p.getTodayOrderCount()); // 均衡接单量
    double creditScore   = normalize(p.getCreditScore());      // 评价与完单率

    return 0.35 * distanceScore + 0.25 * skillScore
         + 0.15 * rankScore + 0.15 * loadScore + 0.10 * creditScore;
}

排序后取 Top N 进入派单队列,而不是直接「谁先抢到算谁」------后者在高峰期会造成严重的接单倾斜。

3. 派单的幂等与锁

一个订单同一时刻只能有一个有效接单方,这是典型的并发控制问题。推荐三段式:

  • Redis 分布式锁锁订单号,SET order:lock:{id} value NX EX 10
  • 加锁成功后校验订单状态机(待接单 → 已接单),非法流转直接拒绝;
  • 用 Lua 脚本保证「校验 + 写入」原子性,避免锁过期后的临界区穿透。

订单状态机建议显式定义,禁止业务代码随意改 status 字段:

复制代码
CREATED → DISPATCHING → ACCEPTED → SERVING → FINISHED
                     ↘ CANCELED / TIMEOUT

四、实时通信与消息可靠性

派单的体验取决于推送是否及时。WebSocket 长连接是标配,但要处理三个工程问题:

  1. 连接与用户映射 :网关层维护 userId → channel 的本地表,配合 Redis Pub/Sub 做多实例广播,避免单机内存表在扩容后失效。
  2. 消息不丢:推送只作为「提醒」,客户端收到后必须回源查询订单详情;同时服务端记录消息表,支持离线补偿拉取。
  3. 超时重派:订单 15 秒内无人接单,进入下一轮候选,同时给用户一个「正在扩大范围」的反馈,而不是静默等待。

IM 部分注意内容审核前置:文本走敏感词过滤,图片走内容安全接口,未过审的消息不落库直接拦截。

五、安全与合规:同城线下场景的必备设计

线上陪玩风险较低,一旦涉及同城线下,安全模块必须做进架构里,而不是事后补丁:

  • 虚拟号码:双方在前 30 分钟通过中间号联系,订单结束后自动解绑,避免真实号码泄露。
  • 一键报警与行程共享:服务开始后向紧急联系人推送服务时段与位置轨迹。
  • 行为风控:同一设备多账号、短时间高频取消、异常接单集中在同一地点等,打标签进入人工复核队列。
  • 账号审核:陪玩师入驻需提交游戏战绩截图与实名信息,审核状态与接单权限强绑定。

这些能力在知识库里同类同城系统中通常以「安全中心」的形式统一承载,技术上就是一套规则引擎 + 事件表,不必过度设计。

六、数据库与性能的几个落地建议

  • 订单表按 city_code 分片或按时间归档,历史订单冷热分离;
  • 陪玩师列表页的筛选条件组合多,热点查询走 Redis 缓存,写操作后主动失效;
  • 战绩、聊天记录等大字段单独建表,主表保持窄行,利于索引效率;
  • 所有对外接口做分页上限,避免 limit 被无限放大拖垮数据库。

常见问题(FAQ)

Q1:城市电竞陪玩调度系统和普通外卖派单系统在技术上差别大吗?

骨架一致,都是「位置检索 + 候选排序 + 并发接单控制」。差异在匹配因子更多(技能、段位、服务形式),以及需要 IM 与语音房间等履约能力,调度层本身可以复用同一套抽象。

Q2:小团队初期一定要用 Redis GEO 吗?

如果陪玩师在线规模在数百以内,MySQL 加空间索引也能跑。但只要涉及跨城市、频繁位置更新,建议直接上 Redis GEO,迁移成本远低于后期重构。

Q3:如何避免同一订单被多人同时接单?

核心是「单键锁 + 状态机校验 + 原子写入」三件套,并保证锁的过期时间大于业务处理时间,业务侧再做一次乐观锁版本号兜底。

Q4:多端(小程序 / H5 / App)如何减少重复开发?

用 UniApp 统一业务层与请求层,把定位、支付、推送、分享等平台差异封装成条件编译的适配模块,页面与状态管理保持单套实现。

相关推荐
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】SQLite基础概念与C的API开发
网络·c++·人工智能·学习·架构
张彦峰ZYF1 小时前
AI时代Java工程师能力地图:哪些会被替代,哪些更加值钱
人工智能·架构·被替代的不是岗位而是任务·生成成本塌陷,验证成本没降·上下文闭合度·可判定性、可逆性·责任可归属性
mldong1 小时前
改了工作流引擎的流程定义,在跑的审批单有的跟着变、有的不变
后端·架构
鹿角片ljp10 小时前
LeetCode 64:最小路径和复盘|二维 DP 与 ACM 模式完整写法
java·数据结构·算法
泡海椒11 小时前
PDF 表格样式优化:jquick-pdf 边框、圆角、背景色
java·开发语言·pdf
景熙552312 小时前
15.Java 8 Stream 流入门到实战
java·开发语言·数据结构
wno70412 小时前
Spring Security权限控制
java·python·spring