美团本地生活架构
2010 年 3 月,王兴创办了美团。那时候Groupon 的团购模式正在美国爆火,王兴把团购引入中国,成为中国团购网站的开创者。
但团购只是起点。美团用十年时间,从一个团购网站演变成了中国最大的本地生活服务平台——涵盖外卖、到店、酒旅、买菜、单车、充电宝等多个业务。日订单峰值超过 6000 万单,骑手超过 100 万,商家超过 900 万。
理解美团技术挑战的关键,在于它的本地生活服务特性:
- LBS(Location Based Service):所有业务都跟地理位置强相关,搜索结果、配送路线都要根据用户位置实时计算。
- 供给侧分散:没有自营商品或司机,而是连接商家和用户,需要协调的是数十万商家和百万骑手。
- 高峰低谷明显:午晚高峰(11-13点、17-19点)和平时流量差距可达 10 倍以上。
- 多角色系统:用户、商家、骑手、BD(地推人员),每个角色的数据和业务逻辑完全不同。
架构演进时间线
第一阶段:团购时代的单体架构(2010-2013)
LBS 的基本问题
团购模式的技术核心是 LBS(基于位置的服务)——用户在某个位置,希望看到附近的团购优惠。
早期实现很简单:商家入驻时填写地址经纬度,用户搜索时按距离排序:
-- 查找附近商家的 SQL(简化版)
SELECT id, name, ST_Distance_Sphere(
location, ST_GeomFromText('POINT(116.408 39.904)')) AS distance
FROM merchants
WHERE city = '北京'
AND category = '餐饮'
HAVING distance < 5000 -- 5km 范围内
ORDER BY distance
LIMIT 20;
这个查询的问题:当商家数量增加到几十万时,MySQL 的空间索引在高性能要求下变得不够用。
第二阶段:外卖扩张与 O2O 实时化(2013-2016)
为什么团购架构撑不住外卖
2013 年美团上线外卖,外卖的实时性要求远高于团购:
- 团购:用户搜索 → 商家列表 → 下单(延迟容忍度高)
- 外卖:用户搜索 → 商家列表 → 下单 → 商家接单 → 骑手取餐 → 配送 → 用户收货(延迟要求高)
外卖的核心是实时配送,每一步都需要及时响应。
智能调度系统:骑手匹配
美团配送的调度系统(internally called "TMS"),是外卖系统中最复杂的部分之一。当用户下单后,系统需要在几秒钟内匹配最合适的骑手。
// 配送调度:骑手匹配算法
@Service
public class DispatchService {
@Autowired private MerchantStore merchantStore;
@Autowired private RiderService riderService;
@Autowired private DistanceService distanceService;
public DispatchDecision dispatch(Long orderId, Location deliveryAddress) {
Merchant merchant = merchantStore.findByOrder(orderId);
Location merchantLocation = merchant.getLocation();
// 1. 找出附近可用的骑手(10km 范围内)
List<Rider> nearbyRiders = riderService.findNearby(
merchantLocation.getLat(),
merchantLocation.getLng(),
10000 // 10km
);
// 2. 过滤出正在等单的骑手(不是正在配送中的)
nearbyRiders = nearbyRiders.stream()
.filter(Rider::isIdle)
.collect(Collectors.toList());
if (nearbyRiders.isEmpty()) {
return DispatchDecision.noRiderAvailable();
}
// 3. 并行计算每个骑手的配送成本
List<RiderCost> riderCosts = nearbyRiders.parallelStream()
.map(rider -> calculateCost(rider, merchant, deliveryAddress))
.collect(Collectors.toList());
// 4. 选择最优骑手(综合考虑距离、骑手当前负载、时间窗口)
RiderCost best = riderCosts.stream()
.min(Comparator.comparingDouble(RiderCost::getTotalCost))
.orElseThrow();
// 5. 绑定订单
riderService.assignOrder(best.getRiderId(), orderId);
return DispatchDecision.success(best.getRiderId(), best.getEstimatedTime());
}
private RiderCost calculateCost(Rider rider, Merchant merchant,
Location deliveryAddress) {
// 距离成本:骑手到商家的距离
double distToMerchant = distanceService.distance(
rider.getLocation(), merchant.getLocation());
// 配送成本:商家到用户地址的距离
double distToUser = distanceService.distance(
merchant.getLocation(), deliveryAddress);
// 时间成本:骑手当前等单时间(等得越久越优先派单)
long waitTime = System.currentTimeMillis() - rider.getLastDeliveryTime();
// 综合成本
double totalCost = distToMerchant * 1.0
+ distToUser * 0.5
- waitTime * 0.1; // 负号:等得越久成本越低
return new RiderCost(rider, distToMerchant, distToUser,
waitTime, totalCost);
}
}
第三阶段:配送网络优化(2016-2019)
多点配送:骑手同时接多单
美团配送的核心优化是骑手同时接多单——骑手不是一次只送一单,而是一次取多个商家的多份餐,按最优路线依次配送。
这让配送效率提升了 40% 以上,但调度复杂度急剧上升:
// 多点配送:路径规划
@Service
public class MultiStopDispatcher {
// 骑手一次接 N 单,按最优路线配送
public DeliveryPlan planMultiStopRoute(Rider rider, List<Order> orders) {
Merchant firstPickup = orders.get(0).getMerchant();
Location currentPos = rider.getLocation();
List<DeliveryStop> plan = new ArrayList<>();
for (Order order : orders) {
// 1. 前往商家取餐
DeliveryStop pickup = new DeliveryStop();
pickup.setType(StopType.PICKUP);
pickup.setLocation(order.getMerchant().getLocation());
pickup.setOrderId(order.getId());
pickup.setEstimatedArrival(
calculateArrivalTime(currentPos, pickup.getLocation()));
plan.add(pickup);
currentPos = pickup.getLocation();
// 2. 前往用户地址送餐
DeliveryStop dropoff = new DeliveryStop();
dropoff.setType(StopType.DROPOFF);
dropoff.setLocation(order.getDeliveryAddress());
dropoff.setOrderId(order.getId());
dropoff.setEstimatedArrival(
calculateArrivalTime(currentPos, dropoff.getLocation()));
dropoff.setTimeWindow(order.getLatestAcceptableTime()); // 用户可接受的最晚时间
plan.add(dropoff);
currentPos = dropoff.getLocation();
}
// 3. 优化路线顺序(贪心算法 + 2-opt 局部优化)
plan = optimizeRouteOrder(plan);
// 4. 验证时间窗口约束
validateTimeWindows(plan);
return new DeliveryPlan(rider.getId(), plan);
}
// 2-opt 局部优化:交换路径中两条边的中点,看是否能缩短总距离
private List<DeliveryStop> optimizeRouteOrder(List<DeliveryStop> plan) {
double bestDistance = calculateTotalDistance(plan);
for (int i = 1; i < plan.size() - 2; i++) {
for (int j = i + 1; j < plan.size() - 1; j++) {
List<DeliveryStop> newPlan = twoOptSwap(plan, i, j);
double newDistance = calculateTotalDistance(newPlan);
if (newDistance < bestDistance) {
plan = newPlan;
bestDistance = newDistance;
}
}
}
return plan;
}
}
实时需求预测
美团配送系统在用户下单前,就开始预调度骑手——根据历史数据和实时情况,预测接下来 30 分钟的订单分布,提前安排骑手到热门区域等待。
// 需求预测:提前 30 分钟预调度骑手
@Service
public class DemandPredictionService {
@Autowired private HistoryOrderStore orderHistory;
// 每 5 分钟跑一次预测
@Scheduled(fixedRate = 5 * 60 * 1000)
public void predictAndPreposition() {
// 1. 按 1 公里网格统计历史订单分布
Map<GeoHash, Integer> demandDistribution = predictDemand30min();
// 2. 获取当前骑手分布
Map<GeoHash, Integer> riderDistribution = riderService.getCurrentDistribution();
// 3. 计算供需差
Map<GeoHash, Double> gapMap = new HashMap<>();
for (GeoHash cell : demandDistribution.keySet()) {
double demand = demandDistribution.get(cell);
double supply = riderDistribution.getOrDefault(cell, 0);
gapMap.put(cell, demand - supply);
}
// 4. 发送预调度指令:引导骑手前往需求高的区域
for (Map.Entry<GeoHash, Double> entry : gapMap.entrySet()) {
if (entry.getValue() > 5) { // 需求超过供给 5 单
sendPrepositioningDirective(entry.getKey(), entry.getValue());
}
}
}
// 需求预测模型:基于历史数据 + 当前实时数据
private Map<GeoHash, Integer> predictDemand30min() {
LocalDateTime now = LocalDateTime.now();
// 获取同时间(昨天/上周同期)的历史数据
LocalDateTime[] historySlots = {
now.minusDays(1),
now.minusDays(7),
now.minusDays(14)
};
Map<GeoHash, Integer> prediction = new HashMap<>();
for (GeoHash cell : allCells) {
int totalHistory = 0;
for (LocalDateTime slot : historySlots) {
totalHistory += orderHistory.getCount(cell, slot);
}
int avg = totalHistory / historySlots.length;
// 加上实时趋势修正(当前 30 分钟的实时流量)
double trendFactor = getRealTimeTrendFactor(cell);
prediction.put(cell, (int) (avg * trendFactor));
}
return prediction;
}
}
第四阶段:平台化与本地超脑(2019-至今)
本地超脑:统一调度引擎
2019 年,美团发布了「本地超脑」系统,将原本分散的调度能力统一到一个平台。本地超脑的核心是一个每秒可决策数百万次的实时调度引擎。
本地超脑的技术特点:
- 实时计算:订单从创建到分配骑手,延迟 < 3 秒
- 全局优化:不只是优化单个订单,而是优化整个配送网络的总效率
- 弹性扩容:高峰期自动扩容,低谷期缩容,节省成本
架构启示
启示一:LBS 是本地生活服务的基础能力
美团所有业务的核心能力都是 LBS:搜索附近商家、计算配送距离、规划骑手路线。
LBS 的关键技术点:
- 空间索引:GeoHash、Quadtree、R-Tree,用于高效查询地理空间数据
- 距离计算:Haversine 公式(球面距离),或投影距离(平面近似)
- Geofence:定义虚拟地理边界,触发区域事件(如进入配送范围)
启示二:供给侧优化比需求侧优化更重要
大多数平台优化的思路是「让用户更快找到商品」。但美团的经验是供给侧优化更重要——骑手预调度、多点配送、路径优化,这些供给侧改进带来的效率提升远超搜索排序优化。
建议:在做需求侧优化之前,先审视供给侧是否还有提升空间。
启示三:实时性有代价,要分优先级
美团的调度系统有几套不同的时效要求:
- 核心链路(订单分配骑手):< 3 秒,必须优先保障
- 预调度(提前安排骑手):< 30 秒,可接受延迟
- 需求预测:< 5 分钟,离线计算即可
建议:不要把所有需求都做成实时。先区分优先级,再决定技术方案。
术语表
总结
美团的技术演进,始终围绕一个核心命题:如何在秒级时间内,把百万骑手和千万订单精准匹配在一起。
演进脉络:
- 2010-2013:团购时代,LBS 基础能力
- 2013-2016:外卖扩张,O2O 实时化,骑手调度起步
- 2016-2019:多点配送,路径优化,需求预测,配送网络优化
- 2019-至今:本地超脑,统一调度引擎,实时决策
核心技术亮点:
- 智能调度:综合距离、等待时间、订单负载的骑手匹配算法
- 多点配送:骑手同时接多单,2-opt 路径优化提升 40% 效率
- 需求预测:基于历史数据和实时趋势,提前 30 分钟预调度骑手
- 本地超脑:每秒百万次决策,< 3 秒完成订单分配
对普通项目的启发:
- LBS 能力是本地生活服务的基础,要优先建立
- 供给侧优化往往比需求侧优化更有价值
- 实时性有代价,按优先级分开处理
- 多点配送的路径优化有成熟算法,不要自己造轮子