京东到家订单派发的技术实战
作者:网友投稿 时间:2018-11-06 21:02
达达-京东到家作为优秀的即时配送物流平台,实现了多渠道的订单配送,包括外卖平台的餐饮订单、新零售的生鲜订单、知名商户的优质订单等。为了提升平台的用户粘性,我们需要兼顾商户和骑士的各自愿景:商户希望订单能够准时送达,骑士希望可以高效抢单。那么在合适的时候提升订单定制化的曝光率,是及时送物流平台的核心竞争力之一。
本文将描述订单派发系统从无到有的系统演进以及方案设计与关键要点,为大家在解决相关业务场景上提供一个案例参考。
订单派发架构演进
在公司发展的初期,我们的外卖订单从商户发单之后直接出现在抢单池中,3公里之内的骑士能够看到订单,并且从订单卡片中获取配送地址、配送时效等关键信息。这种暴力的显示模式,很容易造成骑士挑选有利于自身的订单进行配送,从而导致部分订单超时未被配送。这样的模式,在一定程度上导致了商户的流失,同时也浪费了骑士的配送时间。
从上面的场景可以看出来,我们系统中缺少一个订单核心调度者。有一种方案是选择区域订单的订单调度员,由调度员根据骑士的接单情况、配送时间、订单挤压等实时情况来进行订单调度。这种模式,看似可行,但是人力成本投入太高,且比较依赖个人的经验总结。
核心问题已经出来了:个人的经验总结会是什么呢?
骑士正在配送的订单的数量,是否已经饱和
骑士的配送习惯是什么
某一阶段的订单是否顺路,骑士是否可以一起配送
骑士到店驻留时间的预估
...
理清核心问题的答案,我们的系统派单便成为了可能。基于以上的原理,订单派发模式就可以逐渐从抢单池的订单显示演变成系统派单。

我们将会记录商户发单行为、骑士配送日志及运行轨迹等信息,并且经过数据挖掘和数据分析,获取骑士的画像、骑士配送时间的预估、骑士到店驻留时间的预估等基础信息;使用遗传算法规划出最优的配送路径...经过上述一系列算法,我们将在骑士池中匹配出最合适的骑士,进而使用长连接(Netty)不间断的通知到骑士。
随着达达业务的不断迭代,订单配送逐渐孵化出基于大商户的驻店模式:基于商户维护一批固定的专属骑士,订单只会在运力不足的时候才会外发到抢单池中,正常情况使用派单模式通知骑士。

订单派发模型
订单派发可以浅显的认为是一种信息流的推荐。在订单进入抢单池之前,我们会根据每个城市的调度情况,先进行轮询N次的派单。大概的表现形式如下图:

举例有笔订单需要进行推送,在推送过程中,我们暂且假设一直没有骑士接单,那么这笔订单会每间隔N秒便会进行一次普通推荐,然后进入抢单池。
从订单派发的流程周期上可以看出来,派发模型充斥着大量的延迟任务,只要能解决订单在什么时候可以进行派发,那么整个系统 50% 的功能点就能迎刃而解。
我们先了解一下经典的延迟方案:
1. 数据库轮询
通过一个线程定时的扫描数据库,获取到需要派单的订单信息
优点:开发简单,结合quartz即可以满足分布式扫描
缺点:对数据库服务器压力大,不利于项目后续发展
2. JDK的延迟队列 - DelayQueue
DelayQueue是Delayed元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。队列中对象的顺序按到期时间进行排序。
优点:开发简单,效率高,任务触发时间延迟低
缺点:服务器重启后,数据会丢失,要满足高可用场景,需要hook线程二次开发;宕机的担忧;如果数据量暴增,也会引起OOM的情况产生
3. 时间轮 - TimingWheel
时间轮的结构原理很简单,它是一个存储定时任务的环形队列,底层是由数组实现,而数组中的每个元素都可以存放一个定时任务列表,列表中的每一项都表示一个事件操作单元,当时间指针指向对应的时间格的时候,该列表中的所有任务都会被执行。 时间轮由多个时间格组成,每个时间格代表着当前实践论的跨度,用tickMs代表;时间轮的个数是固定的,用wheelSize代表;整个时间轮的跨度用interval代表,那么指针转了一圈的时间为:
interval = tickMs * wheelSize




