滴滴出行架构
2012 年 6 月,程维在北京中关村的一间软件园办公室里,创办了滴滴出行。那时候,快的打车已经在杭州上线,滴滴和快的在接下来两年里展开了一场史诗级的补贴大战——烧掉了数十亿人民币,教育了整个中国市场什么是「网约车」。
补贴大战结束后,滴滴和快的在 2015 年合并,又在 2016 年合并了 Uber 中国,成为中国出行市场的绝对霸主。
但技术上的挑战远没有补贴大战那么简单:每天处理数千万订单,高峰期每秒处理数万次叫车请求,同时为百万司机和数亿用户提供匹配服务——这背后是一套极其复杂的实时调度系统。
公司画像
滴滴出行是全球最大的出行平台之一,覆盖中国 400 多个城市,拥有超过 5000 万注册司机,日均订单超过 3000 万。业务涵盖快车、专车、出租车、顺风车、代驾、货运等多个品类。
理解滴滴技术挑战的关键,在于它的实时调度特性:
- 秒级匹配:用户叫车后,等待时间超过 30 秒就会焦虑,调度系统必须在几秒内完成匹配。
- 供需动态波动:早晚高峰、雨雪天气、大型活动,供需关系在分钟级别内剧烈变化。
- 多目标优化:不只是让用户打到车,还要考虑司机收入、平台抽成、用户体验多个目标。
- 全局最优 vs 局部最优:每次匹配不仅考虑当前订单,还要考虑对后续订单的影响。
架构演进时间线
第一阶段:补贴大战的粗放期(2012-2014)
起步:简单派单
滴滴最初的功能只有一个:出租车叫车。用户发单,系统推送给附近司机,司机接单。
早期的派单逻辑极其简单——按距离排序,把订单推送给最近的司机:
// 早期派单:最近司机优先
public class SimpleDispatchService {
public DispatchResult dispatch(Order order) {
Location userLocation = order.getUserLocation();
// 1. 查询附近 5km 内的司机
List<Driver> nearbyDrivers = driverService.findNearby(
userLocation.getLat(),
userLocation.getLng(),
5000 // 5km
);
// 2. 按距离排序
nearbyDrivers.sort(Comparator.comparingDouble(
d -> distance(d.getLocation(), userLocation)));
// 3. 推送给最近的司机
for (Driver driver : nearbyDrivers) {
boolean accepted = pushToDriver(driver, order);
if (accepted) {
return DispatchResult.success(driver.getId());
}
// 司机拒单,继续推下一个
}
return DispatchResult.noDriverAvailable();
}
}
这个策略的问题很快暴露:只考虑距离,不考虑其他因素。司机的接单意愿、当前状态(是否在送客中)、是否顺路,都没有被考虑。
第二阶段:规模化与重构(2014-2016)
合并后的技术整合
2015 年滴滴快的合并,2016 年合并 Uber 中国,每次合并都带来巨大的技术整合压力:
- 两家公司的技术栈完全不同(滴滴用 Java,快的用 Python)
- 两家公司的用户和司机数据需要合并
- 系统需要支撑更大的并发量
这个阶段,滴滴做了全面的微服务化改造,统一技术栈到 Java。
订单状态机
出行订单是一个典型的状态机——每个订单从创建到完成,要经历一系列明确的状态转换。
// 订单状态机
public class OrderStateMachine {
public static enum OrderStatus {
CREATED, // 用户下单
DISPATCHING, // 系统派单中
DRIVER_ACCEPTED, // 司机接单
DRIVER_ARRIVED, // 司机到达
TRIP_STARTED, // 行程开始
TRIP_COMPLETED, // 行程结束
CANCELLED, // 取消
CLOSED // 订单关闭(已付款)
}
// 状态转换规则
public boolean canTransition(OrderStatus from, OrderStatus to) {
Map<OrderStatus, Set<OrderStatus>> transitions = Map.of(
CREATED, Set.of(DISPATCHING, CANCELLED),
DISPATCHING, Set.of(DRIVER_ACCEPTED, CANCELLED),
DRIVER_ACCEPTED, Set.of(DRIVER_ARRIVED, CANCELLED),
DRIVER_ARRIVED, Set.of(TRIP_STARTED, CANCELLED),
TRIP_STARTED, Set.of(TRIP_COMPLETED),
TRIP_COMPLETED, Set.of(CLOSED),
CANCELLED, Set.of(),
CLOSED, Set.of()
);
return transitions.get(from).contains(to);
}
// 执行状态转换
public void transition(Order order, OrderStatus to) {
if (!canTransition(order.getStatus(), to)) {
throw new IllegalStateException(
"Cannot transition from " + order.getStatus() + " to " + to);
}
OrderStatus from = order.getStatus();
order.setStatus(to);
order.setUpdatedAt(System.currentTimeMillis());
// 持久化
orderRepository.save(order);
// 发布领域事件
eventBus.publish(new OrderStatusChangedEvent(order.getId(), from, to));
}
}
Redis 缓存热点数据
叫车场景的读操作远多于写操作——用户每次刷新界面,都在查询附近司机。滴滴大量使用 Redis 缓存热点数据:
// 司机位置缓存:Redis GEO
@Service
public class DriverLocationService {
private static final String GEO_KEY = "driver:locations";
private static final String AVAIL_KEY = "driver:available:";
// 司机位置上报(每 5 秒一次)
public void updateLocation(Long driverId, double lng, double lat) {
// GEOADD:更新司机地理位置
redisTemplate.opsForGeo().add(GEO_KEY, new Point(lng, lat), driverId.toString());
// 标记可用:带 2 分钟过期时间
String availKey = AVAIL_KEY + driverId;
redisTemplate.opsForValue().set(availKey, "1", Duration.ofMinutes(2));
}
// 查询附近可用司机
public List<Driver> findNearbyAvailable(double lng, double lat, double radiusMeters) {
// 1. 查询附近所有司机
Circle circle = new Circle(new Point(lng, lat),
new Distance(radiusMeters, MetricsUnit.METERS));
GeoResults<GeoLocation<String>> results =
redisTemplate.opsForGeo().radius(GEO_KEY, circle);
if (results == null) return Collections.emptyList();
// 2. 过滤出可用司机
return results.getContent().stream()
.filter(r -> {
String key = AVAIL_KEY + r.getContent().getName();
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
})
.map(r -> {
Long driverId = Long.parseLong(r.getContent().getName());
return driverService.findById(driverId);
})
.collect(Collectors.toList());
}
}
第三阶段:智能化调度(2016-2019)
机器学习派单:综合评分
2016 年,滴滴上线了机器学习派单系统,不再只按距离排序,而是综合考虑多个因素计算每个司机的派单分数。
// 机器学习派单:综合评分
@Service
public class MLDispatchService {
@Autowired private MLModel rankModel;
@Autowired private DriverService driverService;
@Autowired private DemandService demandService;
public DispatchDecision dispatch(Order order) {
Location pickup = order.getPickupLocation();
// 1. 获取候选司机(附近 5km 内)
List<Driver> candidates = driverService.findNearby(
pickup.getLat(), pickup.getLng(), 5000);
// 2. 构造特征
List<CandidateFeature> features = candidates.stream()
.map(driver -> buildFeature(driver, order))
.collect(Collectors.toList());
// 3. 模型打分(批量预测,节省 RPC 调用)
List<Double> scores = rankModel.predict(features);
// 4. 选择分数最高的司机
int bestIdx = 0;
double bestScore = scores.get(0);
for (int i = 1; i < scores.size(); i++) {
if (scores.get(i) > bestScore) {
bestScore = scores.get(i);
bestIdx = i;
}
}
// 5. 推送并确认
return dispatchToDriver(order, candidates.get(bestIdx));
}
private CandidateFeature buildFeature(Driver driver, Order order) {
return CandidateFeature.builder()
// 距离特征
.distanceToPickup(distance(driver.getLocation(), order.getPickupLocation()))
.distanceToDestination(distance(driver.getLocation(), order.getDestination()))
// 供需特征
.supplyDemandRatio(demandService.getSupplyDemandRatio(order.getPickupLocation()))
.currentDriverDensity(driverService.countNearby(order.getPickupLocation(), 3000))
// 司机特征
.driverAcceptRate(driver.getAcceptRate())
.driverRating(driver.getRating())
.driverCancellationRate(driver.getCancellationRate())
.driverActiveHours(driver.getActiveHoursToday())
// 顺路特征
.detourDistance(calculateDetour(driver, order))
.directionMatch(calculateDirectionMatch(driver, order))
.build();
}
}
需求预测与动态调度
滴滴的需求预测系统,会预测接下来 30 分钟内各区域的出行需求,提前引导司机前往热点区域,减少用户的等待时间。
// 需求预测:提前引导司机
@Service
public class DemandForecastService {
// 预测未来 30 分钟的出行需求(按网格)
public Map<GridCell, Double> forecastDemand30min() {
Map<GridCell, Double> forecast = new HashMap<>();
for (GridCell cell : allCells) {
// 历史数据:上周同期、同昨天同期
double historyAvg = getHistoryAverage(cell);
// 实时修正:当前需求相对历史的倍数
double realTimeFactor = getRealTimeFactor(cell);
// 外部因素:天气、节假日、大型活动
double externalFactor = externalFactorService.getFactor(cell);
// 综合预测
double forecastValue = historyAvg * realTimeFactor * externalFactor;
forecast.put(cell, forecastValue);
}
return forecast;
}
// 发送引导:告诉司机哪些区域需求高
public void sendGuidance(Long driverId) {
Map<GridCell, Double> forecast = forecastDemand30min();
// 找出预测需求最高的前 3 个区域
List<GridCell> hotSpots = forecast.entrySet().stream()
.sorted(Map.Entry.<GridCell, Double>comparingByValue().reversed())
.limit(3)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!hotSpots.isEmpty()) {
notificationService.pushGuidance(driverId, hotSpots);
}
}
}
第四阶段:自动驾驶融合调度(2019-至今)
自动驾驶与有人驾驶的混合调度
2019 年后,滴滴开始布局自动驾驶(Robotaxi)。这带来了全新的技术挑战:如何让自动驾驶车辆和有人驾驶车辆共存于同一个调度平台。
两者的调度逻辑差异很大:
- 有人驾驶:需要考虑司机体验、收入公平、拒单意愿
- 自动驾驶:不考虑体验,但有电量限制、区域限制、速度限制
滴滴的解决方案是统一的调度引擎,根据不同的车辆类型和当前状态,选择最优的派单策略。
架构启示
启示一:派单策略比技术选型更重要
滴滴早期用的是 PHP + MySQL,后来才换成 Java + HBase。但真正提升用户体验的,不是技术栈升级,而是派单策略的智能化。
机器学习派单上线后,用户的平均等待时间缩短了 15%,司机的接单率提升了 10%。这些改进不需要换数据库,只需要换一个算法。
建议:在投入资源做技术架构升级之前,先审视业务策略是否有优化空间。很多时候,策略优化比技术优化的 ROI 更高。
启示二:供需匹配是出行平台的核心
出行平台的核心矛盾是供需错配——用户叫车的时候往往没车,有车的时候往往没用户。
滴滴的解决方案是动态调节:
- 价格调节:高峰期涨价,吸引更多司机出车
- 引导调节:提前预测热点,引导司机前往
- 拼车调节:一个司机同时接多个乘客,提升利用率
建议:供需错配是平台型业务的本质问题,没有一劳永逸的解决方案,需要持续优化。
启示三:状态机是复杂业务的核心
出行订单的状态转换非常复杂,涉及用户、司机、平台三个参与方。清晰的状态机设计,是防止状态错乱的关键。
建议:对于涉及多个参与方、有复杂状态转换的业务(如订单、支付、工单),从第一天就设计好状态机,不要用 if-else 硬编码状态逻辑。
术语表
总结
滴滴的技术演进,始终围绕一个核心命题:如何在秒级时间内,把最合适的司机匹配给最需要的乘客。
演进脉络:
- 2012-2014:简单派单,最近司机优先
- 2014-2016:微服务化,Redis GEO,订单状态机
- 2016-2019:机器学习派单,需求预测,拼车优化
- 2019-至今:自动驾驶融合调度,多车型混合派单
核心技术亮点:
- 机器学习派单:综合距离、供需、司机特征多维度评分
- 需求预测:基于历史 + 实时 + 外部因素,提前引导司机
- Redis GEO:高并发查询附近司机,每秒处理数十万次
- 订单状态机:清晰的状态转换规则,防止状态错乱
对普通项目的启发:
- 派单策略优化往往比技术升级的 ROI 更高
- 供需错配是平台型业务的本质问题,需要持续优化
- 复杂业务要用状态机管理,不要用 if-else 硬编码
- 实时性要求高的系统,要做好 Redis 等缓存层的容量规划