[特殊字符]实战:使用 Canal + MQ + ES + Redis + XXL-Job 打造高性能地理抢单系统

📚目录

  1. 项目背景

  2. 技术栈总览

  3. 详细流程分析

    • 3.1 Canal监听MySQL Binlog

    • 3.2 MQ中转传递订单变化

    • 3.3 Elasticsearch存储并查询附近订单

    • 3.4 Redis高性能抢单+Lua防止抢单冲突

    • 3.5 XXL-Job定时任务处理

  4. 完整系统流程图

  5. 总结

一、项目背景

针对类似外卖、跑腿、上门维修等业务场景,存在大量订单需要快速分配给周边服务人员。为了保证抢单速度快、系统高可用,我们设计了一套基于:

  • MySQL主库 + Canal监听Binlog

  • MQ异步推送订单变更

  • Elasticsearch进行地理位置查询

  • Redis加速抢单并防止重复抢单

  • XXL-Job异步修正状态

的完整解决方案。


二、技术栈总览

技术 用途
Canal 监听数据库Binlog变化
MQ (RocketMQ) 订单变更异步推送
Elasticsearch 地理位置检索订单
Redis 高并发缓存抢单、Lua保证原子操作
XXL-Job 定时任务处理订单

三、详细流程分析

3.1 Canal监听MySQL Binlog

目的 :实时感知订单表(如:order)中数据变化(新建/更新/删除)。

步骤:

  1. Canal连接MySQL,模拟一个Slave。

  2. 订阅需要监听的表,比如:

    复制代码
    instance.filter.regex = db_name.order
  3. 把监听到的变更事件,转换成统一格式,推送到MQ。

注意:

  • Canal要有正确的binlog起始点。

  • reset master后要删除Canal的meta.dat文件。

Canal配置关键项:

复制代码
canal.instance.master.address=127.0.0.1:3306
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.filter.regex=order.*

3.2 MQ中转传递订单变化

目的:削峰填谷,异步解耦,解放数据库压力。

  • Canal将每条订单变更消息发送到MQ,例如RocketMQ的OrderChangeTopic。

  • 消费端程序监听此Topic,异步拉取消息,处理数据同步到ES和Redis。

RocketMQ发送格式示例(JSON):

复制代码
{
  "eventType": "UPDATE",
  "orderId": "123456",
  "status": "NEW",
  "longitude": 113.2644,
  "latitude": 23.1291
}

3.3 Elasticsearch存储并查询附近订单

目的:让服务人员可以基于地理位置检索周边5km内未被抢的订单。

订单结构设计(Mapping):

复制代码
{
  "mappings": {
    "properties": {
      "orderId": { "type": "keyword" },
      "status": { "type": "keyword" },
      "location": { "type": "geo_point" }
    }
  }
}

Java (ES 8+ Lambda Client) 地理查询示例:

复制代码
SearchResponse<OrderDoc> response = client.search(s -> s
    .index("orders")
    .query(q -> q
        .bool(b -> b
            .must(m -> m.match(mq -> mq.field("status").query("NEW")))
            .filter(f -> f.geoDistance(gd -> gd
                .field("location")
                .distance("5km")
                .location(l -> l.latlon(23.1291, 113.2644))
            ))
        )
    ),
OrderDoc.class
);

注意:

  • 地理位置需要保存为geo_point格式。

  • 查询时注意单位(km、m)。


3.4 Redis高性能抢单+Lua防止抢单冲突

目的:防止高并发下,出现多个服务人员抢到同一订单的问题。

抢单流程:

  1. 抢单时直接查Redis,避免访问MySQL。

  2. 使用Lua脚本,原子性判断是否已被抢。

Lua抢单脚本示例:

复制代码
if redis.call("get", KEYS[1]) == false then
    redis.call("set", KEYS[1], ARGV[1])
    return 1
else
    return 0
end

Java调用Lua脚本示例:

复制代码
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(luaScriptContent);
redisScript.setResultType(Long.class);

Long result = stringRedisTemplate.execute(
    redisScript,
    Collections.singletonList("order:123456"),
    "TAKEN"
);

if (result == 1L) {
    // 抢单成功
} else {
    // 已被抢
}

注意:

  • KEY设计为order:{orderId}

  • 失效时间根据业务设置,比如订单未支付自动失效。


3.5 XXL-Job定时任务处理

目的:在服务人员未支付、超时未接单等情况下,自动取消订单,释放资源。

常见定时任务:

  • 每分钟扫描未支付的订单,关闭超时订单。

  • 抢单后超时未接单,订单重新入池。

XXL-Job处理示例:

复制代码
@XxlJob("orderTimeoutHandler")
public void orderTimeoutHandler() {
    List<String> timeoutOrderIds = orderService.findTimeoutOrders();
    for (String orderId : timeoutOrderIds) {
        orderService.cancelOrder(orderId);
    }
}

调度配置:

  • 调度周期:每1分钟

  • 失败重试:3次

  • 超时时间:30秒


四、完整系统流程图


五、总结

通过这一套组合:

  • Canal保证数据库变化实时捕捉

  • MQ解耦数据同步和订单更新

  • ES支撑复杂地理位置查询

  • Redis + Lua保障高并发原子抢单

  • XXL-Job负责后置状态修正

整套抢单系统实现了:

✅ 高可用

✅ 高性能

✅ 地理范围精准控制

✅ 数据一致性

适合各类跑腿、外卖、上门维修等业务场景。

相关推荐
东风破_12 分钟前
从 Neo4j 到 GraphRAG:用 Text-to-Cypher 构建图检索 RAG
人工智能·后端
Wang's Blog21 分钟前
Java框架 SpringCloud 快速入门: 实现 Feign 最佳实践(抽取方式)
java·spring cloud
MandalaO_O1 小时前
IDEA 开发(快捷键 + 调试 + 序列化)
java·ide·intellij-idea
xiaoqiMikko2 小时前
JVM 线上排查实战(七):jps 看不见它、jstack 连不上它,可它明明活得好好的
java·jvm
llqbzllll2 小时前
Spring AI 工具调用不是反射一下就结束:用 2.0.1 跑通失败恢复与调用上限
人工智能·后端
狼爷3 小时前
Rust/Go/Java/Python/PHP 大比拼:负载下后端框架到底差多少?
java·后端·编程语言
念何架构之路3 小时前
zap扩展生态与总结
java·前端·数据库
mantou1323 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
程序员老赵3 小时前
Docker 部署 DeepSeek Harness:轻松搭建局域网里的 AI Agent 平台
后端·ai编程·deepseek
第七页独白3 小时前
汽车零件厂如何通过 QMS 真正落地 IATF 16949——QMS软件系统:品质检验-内审稽核-8d客诉管理:全星质量管理软件系统
java·前端·数据库