大家好,我有个朋友叫小王,是一名苦逼程序猿,平日工作兢兢业业。某一天午休前,偶然一看日期,小王大惊失色,明天竟是女友生日!!!
这段时间全心工作忘了重要日子,满心焦虑自责。想着给女友惊喜弥补过错,急忙打开手机备忘录准备礼物,想起女友想要 iPad 刷剧,但钱包不鼓,决定买二手的。
打开多个二手软件,中意的不能明天送到,能明天送到的又不中意。同事张三见状推荐转转 APP,说能满足需求,支持查看预计送达时间,还支持次日、隔日和三日达筛选。
小王如见曙光,立刻下载转转APP,五分钟就下单了满意的 iPad 。
第二天上班的时候接到快递电话,喜不自禁。下班后带着礼物回家给女友过生日,从此过上了幸福的生活。
上面一个小故事,便于大家了解履约时效是什么,这次主要介绍履约时效在转转侧的落地方案。
章节列表
1 履约时效是什么 ? 2 转转履约时效的落地方案 2.1 预计送达时间是怎么设计的 ? 2.2 如何支撑万级QPS ? 2.3 次日达、隔日达以及三日达的筛选底层逻辑以及技术实现 3 结语
1 履约时效是什么?
履约时效,简称Promise,对于消费者来说,可以让他们更好地规划自己的时间和需求,知道自己购买的商品能够在特定时间内到达,有助于消费者做出合理的决策,并减少等待的焦虑。现在各大电商平台都有展示,形式如下:
2 转转履约时效的落地方案
如下图,在转转 APP 的商品列表页、商品详情页、确认下单页、商品筛选项等等都会有履约时效的身影,由此可见,日常获取商品的预计送达时间的QPS很高,大促时足以破万。
2.1 预计送达时间是怎么设计的 ?
为了方便大家理解,拿小王举个例子,假设小王假期要从北京回趟老家郑州,当天从北京到郑州的高铁只有三趟,分别是以下三个车次。
如果小王想要赶上在家吃晚饭,那就必须要坐上13:10从北京西站出发16:20到郑州东站的G521次列车,那小王怎样才能坐上这趟车呢 ?如下图:
小王只要在11:20之前开始做午饭,就可以赶上13:10的高铁。
对于用户在转转看到的“某一时间点前支付,某天送达”是一样的道理,先看一张用户在转转下单到签收的全流程的简图(如下):
分为订单、OMS、WMS和TMS四个模块:
订单模块:用户支付后会产生订单,订单会下发给OMS创建出库单。 OMS模块:产生出库单后,OMS会根据策略下发不同的仓储系统(WMS),创建WMS发货单。 WMS模块:产生发货单后,仓储人员会操作商品出库,等待揽收。 TMS模块:转转跟其他物流公司合作,每天在仓库有几个固定的时间点进行揽收。
综上所述,转转预计送达时间的计算逻辑为:
根据支付后每个系统节点的流转时间,计算出用户支付后多久被物流揽收。 根据揽收时间,调用物流公司获取预计送达时间(这一步类比小王回家的故事中的高铁)。
举个例子:
得出:
今日7:30前下单,明天14:00送达。 今日12:00前下单,明天22:00送达。 今日 16:30前下单,后天11:00送达。
2.2 如何支撑万级QPS
首先看一次不做任何策略的获取预计送达时间的接口的耗时情况,如下图:
很明显耗时最高的地方是根据揽收时间获取预计送达时间,其逻辑如下:
如果用户每次请求都这么处理,显然是有很大问题的,所以我们选择了一个稳妥的方案处理这个点,那就是预热,如下图:
每天T+1根据每个仓的每个揽收时间计算到全国任意一个区的预计送达时间,存入自己的Redis中,那么新的根据揽收时间获取预计送达时间的逻辑如下:
然后我们将其他耗时的地方通过增加一二级缓存优化,如下表格:
查询描述 | 优化方案 | 说明 |
---|---|---|
用户地址转换 | 本地缓存 | 城市名称和code 的映射关系发放入本地缓存 |
商品寻仓 | Redis | 请求一次放入Redis |
仓库信息填充 | 本地缓存 | 仓库信息为低频变化数据,数据量少,可以放入本地缓存 |
匹配揽收时间 | Redis | 配置修改是次日生效,每天定时刷入Redis |
最终,一次获取预计送达时间的接口耗时如下:
2.3 次日达、隔日达以及三日达的筛选底层逻辑以及技术实现
想要筛选有哪些商品支持次日达的含义就是筛选哪些仓支持次日达,所以我们要做两件事如下图:
在商品标记上每个商品所在的仓库站点。 提供到全国任意地址支持次日达的仓站点的能力。
如上图,到北京能够支持次日达的商品为SKUA、SKUE、SKUF。
难点
我们可以想像一下,对于用户小王来筛选次日达的时候,系统不可能实时计算每个仓到小王所在地址的送达时间,判断是否次日达,假设我们有3000个仓,每个仓有三个揽收时间,那么每次筛选就要计算9000次预计送达时间,肯定不可行。
如何解决
结合上文预热预计送达时间,在其预热任务基础上增加预热时效类型,如下图:
那存储结构怎么做?
如下图,以全国所有的省市区作为每个key,value是list对象存到redis中。
当前存储结构检索效率有提升但非最优,从redis拿list后需内存过滤次日达或隔日达。可将时效类型抽取到key上为:省市区 + 时效类型,虽key数量增加但value元素减少,能使用户检索时响应更快,如下图:
细心的同学应该发现了,这样仍需内存过滤,因有时间条件。如当前 11 点,上图首个次日达中仅青岛中心仓支持,此存储结构欠佳,需进一步改进,如下图。
变动点:
存储结构由原来的key-list转变为key-zset,把所有仓的所有批次(最晚支付时间)聚合成一个zset,最晚支付时间作为score,仓库信息作为value生成倒排索引。 原本的存储结构是针对某个收件区的次日达,每个仓下有哪些最晚支付时间支持;现在的存储结构是针对某个区的次日达,每个时间下有哪些仓支持。
这样做的好处:用户来筛选的时候定位到key,通过当前筛选时间,range一下当前zset,取出所有比当前筛选时间大的score,然后内存里去重一下即可直接获得当前支持的仓,并且避免了大key的问题,每次操作redis只是range一个范围,不取全量。
3 结语
转转履约时效系统的构建,是对用户体验的高度重视与不懈追求的体现。从精准的预计送达时间计算逻辑,到应对高并发的巧妙技术方案,再到不断优化的次日达等筛选功能,转转始终致力于为用户打造一个高效、可靠的购物环境。
在电商行业飞速发展的当下,履约时效已成为关键竞争力之一。未来,我们有理由相信,转转将持续完善履约时效系统,以更优质的服务满足用户日益增长的需求,在电商舞台上绽放更加耀眼的光芒。
DailyMart是一个基于 DDD 和Spring Cloud Alibaba的微服务商城系统,采用SpringBoot3.x以及JDK17。旨在为开发者提供集成式的学习体验,并将其无缝地应用于实际项目中。该专栏包含领域驱动设计(DDD)、Spring Cloud Alibaba企业级开发实践、设计模式实际应用场景解析、分库分表战术及实用技巧等内容。如果你对这个系列感兴趣,可在本公众号回复关键词 DDD 获取完整文档以及相关源码。