京东售后系统架构设计:专治多端并发、数据不一致的臭毛病

标签: | 发表时间:2022-10-28 08:45 | 作者:
出处:https://dbaplus.cn
前言

 

通过阅读本文,您将了解到一个售后系统应该具备的一些能力、在整个上下游系统中的定位、基本的系统架构,以及针对售后业务场景中常见问题的解决方案。

 

一、核心价值

 

京东到家售后系统作为逆向流,强依赖京东到家业务域,目前涵盖了:退款、退货、换货、维修等四大类场景,并且为用户与商家提供申诉、仲裁场景支持,为计费与结算系统提供逆向金额数据支持。

 

售后系统业务结构:

 

 

售后系统上下游依赖:

 

 

二、系统架构

 

售后系统使用的就是基础的三层架构。应用层有不同身份的三个端入口,服务层提供了一些业务支持和数据支持,数据层目前使用到了MySQL和Redis以及ElasticSearch。当然还有一些中间件使用,比如rpc框架,zk配置中心,worker分布式定时任务,jmq消息。还有完善的基础设施,统一监控和日志采集。

 

 

三、业务形态

 

当正向订单履约完成后,如订单中商品有缺件、错件、质量等问题可以发起售后申请。目前申请售后支持用户端、商家端、到家客服发起。用户端申请需要根据不同责任方分配到商家或者客服审核。商家端只能选择商家责任原因申请售后,然后自动审核通过。客服代用户申请售后和用户端一致,流转到商家或客服审核。

 

用户端申请售后流程:

 

 

 1. 申请售后

 

1)多端操作并发场景下问题

 

2)售后商品拆分信息如何获取

 

在正向订单履约完成后一定的时效内,可以通过用户端,商家端,运营端基于订单中商品选择性申请售后。当接收到一个售后单提交申请,售后这边会依赖订单数据,拆分数据来构建售后单详情数据。那么对于多端申请售后入口,我们怎么能保证订单中商品不会被重复申请呢?申请时我们使用了redis分布式锁。

 

售后申请场景下分布式锁需要注意点:

 

①不同的入口使用相同的key,这里我们通过前缀加订单号来区分,来保证对同一订单加锁。

 

②加入过期时间,比如第一个申请获取到锁,如果释放锁异常,这里只需要等到超时时间自动过期,防止死锁。

 

③等待锁时间,同一个订单多个入口同时申请售后,如果获取不到锁就进入等待,直到获取到锁或者等待超时后退出。

 

④使用uuid来保证token唯一性,每次都释放自己当前请求锁。

 

我们保证了同一时间只能有一个订单下的售后能够申请,接下来就是组装售后单详情数据。一个完整的售后单数据来源于订单详情和拆分详情。

 

通过从订单详情中取用户基础信息,订单信息,商家门店信息来保存到售后单主表中。根据申请选择的商品skuid从订单商品详情中获取对应商品基础信息保存到售后商品表中。接下来就是比较重要的售后商品拆分信息,这个数据来源于拆分系统。先了解下拆分数据结构:

 

 

可以看到,拆分系统会根据订单中所有商品把金额拆分到每一件商品上,并且通过num_下标来区分。当选择订单中某个商品发起售后我们是怎么去找到这个商品对应的拆分信息呢?

 

我们通过sku_promotionType(商品+促销类型)来区分不同的商品拆分信息,然后通过记录num商品下标来确定找到哪一个商品。

 

比如下面的场景:

 

假设订单中购买了3个正价A商品,1个促销A商品。

 

①第一次申请一个正价A售后。这时售后系统会记录一个售后单,对应售后详情为商品A。从拆分获取sku_A_正价_num0信息并记录到售后商品拆分详情表。

 

②再申请一个正价A和一个促销A售后。这里售后会发现此订单已申请过一个正价A,记录的是sku_A_正价_num0。这时就会去取拆分的 sku_A_正价_num1这条数据。

 

③第二次申请售后对应一个新售后单,商品详情记录为sku_A_正价,sku_A_促销。商品拆分记录数据为:sku_A_正价_num1,sku_A_促销_num0。

 

初步了解了售后商品获取对应拆分数据的逻辑,这时如果同一个订单中购买了相同促销的A商品,但是价格不一样怎么办呢?按照上面获取逻辑,获取的售后商品金额就会出现多退或者少退情况。

 

比如下面的捆绑促销:

 

A+B捆绑销售,A金额3元。A+C捆绑销售,此时A金额2元。这时拆分的数据结构为:sku_A_捆绑_num0价格3元,sku_A_捆绑_num0价格2元。此时如果两个A都申请了售后,我们再按照sku_promotionType去获取拆分那么永远获取的都是第一个的金额。因此针对这种特殊的促销场景,我们在原有获取拆分维度基础上又增加了一个价格。

 

区分维度:sku_promotionType_price(商品+促销类型+价格)

 

上面的方案可以满足各种不同促销场景的售后,但是针对称重退差订单申请售后还会适用么?

 

称重退差订单含义:当正向订单拣货时,商家发现实际拣货的称重品和售卖规格有误差,此时可以发起退差单把差额的钱退给用户。之后订单正常履约,订单完成后用户也可以申请售后。此时再申请售后退给用户的钱就应该是减去退差后的部分。

 

比如下面的场景:

 

假设一个订单中买了2个原价A+1个促销价A,原价3元,促销价2元,整单共8元。拣货时发现A商品实际重量比标重少,退差1元,此时退差单中会记录商品A退差金额,退差重量。这时选择正价A发起售后申请,售后系统就需要根据实际重量获取退差商品金额,然后计算实际退款金额。这时我们又在原来的基础上增加了一个重量维度。

 

sku_promotionType_price_weight(商品+促销类型+价格+重量)

 

系统都是为了业务来服务的,随着业务变更场景的增多,我们的架构也在演变。目前所有的计算拆分逻辑都封装成统一方法,统一入口,未来再增加不同促销,或者其他业务都可以很友好的支持。

 

 2. 审核售后

 

1)多条件复杂查询性能问题

 

当售后单申请成功后,会根据审核方分配给商家或者客服审核。这里涉及到两个列表查询,一个是运营端客服使用,一个是商家端根据商家账号权限来展示可操作的售后单列表。最初我们的售后单表数据并不是很大,随着业务品类扩增以及用户量的增加遇到了一些问题。

 

①数据库频繁报警,慢SQL,影响其他业务

 

②商家运营反馈售后单列表查询过慢,影响审核效率。

 

通过分析慢SQL日志,我们根据查询字段增加索引来提高查询速率。由于支持各种查询场景过多,目前主表中已经建立了20多个索引。而且基于业务的发展需要支持查询的时间区间也会更长。主表的数据量一直在增长,还是会遇到查询性能问题,过多的索引对于售后单流程中变化更新也有一定的影响。

 

因为ES是基于倒排索引实现的搜索,配合分词器在文本模糊搜索上表现比较好,使用的业务场景广泛,因此我们考虑把售后单数据同步到ES中,列表查询走ES。

 

基于我们目的是为了解决查询问题,每次操作业务都会根据主键再查询一次mysql库详情,数据迁移同步方案如下:

 

 

①存量数据如何同步?

 

  • 首先增加一个开关来控制操作是走mysql还是es。先关闭开关然后通过批量同步接口,根据主键id范围区间查询把存量数据分批同步到ES中。

 

  • 打开开关,这时如果有新的售后单数据,通过MQ异步同步到ES中,同时把开关打开前产生的一部分数据同步到ES中。

 

  • 最后再通过count总数校验下数据是否全部同步。

 

②如何保证数据同步一致性?

 

  • 涉及到同步数据,难免就会有数据不一致问题。从售后单申请到售后单状态变更,提交事务后每个节点都会发送一个需要同步的MQ消息。

 

  • 接收到消息后通过主键id查询mysql获取售后单详情。然后全量字段同步到ES中。

 

  • 这样不管先消费哪个节点的MQ,同步的数据都是实时查询的数据库,以此来保证每次同步的数据都是当时最新数据。

 

③数据延迟怎么处理?

 

  • MQ消费有延时,就有可能造成ES和mysql中数据状态不一致问题。我们只是为了解决查询性能问题,因此所有复杂查询都是查的ES数据,但当商家或者客服操作售后单时会根据主键查询mysql售后单详情,然后执行审核操作。

 

  • 针对所有的业务操作后端也增加了前置状态校验,来屏蔽这种数据延时带来的问题。

 

没有最好的方案,只有最适用自己业务的方案。当然现在也有一些工具类插件可以支持不同的同步方案,比如cancel基于binlog的同步以及CloudCanal。我们的目的是为了解决查询效率问题,因此选择了上面的同步方案。

 

 3. 售后退货

 

1)合单召唤物流配送方案

 

退货退款售后单,商家或平台审核通过后,需要退回订单中货物。这里就需要与达达交互,召唤配送员走逆向取件流程。在创建运单召唤达达配送前售后这边会有一个合单逻辑。

 

 

①合单思想

 

  • 订单完成后申请售后可以分多次申请,每次可以选择不同数量的商品。

 

  • 如果用户同一个订单中商品分多次售后都申请为退货,那么在售后单审核通过后这些售后的商品都需要配送员送回商家。

 

  • 这里为了提升用户多次退货体验,也同时为了节约配送成本。因此就需要有一个合单逻辑,同一订单下的售后单退货只需召唤一次物流配送即可。

 

②合单逻辑

 

  • 合单worker定时扫描待召唤物流的售后单,当到达用户预计取件开始时间前10分钟就会触发需要合单的任务。

 

  • 合单任务会根据订单号获取此订单下所有需合单的售后单,然后获取预计取件开始时间最近的售后单。

 

  • 依据最近上门取件开始时间来创建物流运单。

 

③创建运单

 

  • 创建运单前需要前置状态校验,只处理待退货售后单。然后组装订单下用户基本信息,需要合单的所有售后单商品信息以及累计重量,创建运单。

 

  • 运单接口根据订单号做幂等处理,重复调用会返回相同的运单号。

 

④接收结果

 

通过监听运单状态消息,来同步更新配送员信息。

 

⑤异常重试

 

  • 针对合单任务失败数据,记录失败标识,等待下次合单worker执行。

 

  • 记录失败次数,如果超过失败最大次数,跳过合单并预警处理。避免一直合单失败的数据影响正常合单业务数据。

 

 4. 售后退款

 

1)退款准确性问题

 

 

通过上面的流程图了解了售后单审核退款到退款结束的一个过程。那么我们都做了哪些来保证审核退款的售后单金额是正确的呢?

 

①增加分布式锁

 

商家角色审核退款可以通过商家中心、商家端APP、系统对接接口。同时客服端也可以通过运营平台审核退款。

 

因为这里也涉及多端操作,所以这里的锁主要为了防止重复审核退款。

 

审核退款时已经确定是售后单维度,每个售后单只能审核退款一次,所以这里的key维度是售后单维度。并且获取不到锁直接抛出失败,提示业务异常。

 

②单行商品合法性校验

 

为什么要做单行商品合法性校验呢?可以看下下面这个场景:

 

假设当前订单购买了1个A商品和2个B商品,A商品单价10元,B商品单价15元,整单金额40元。申请售后接口参数为:

 

  •  
  skuList:[{"skuCount":1,"skuName":"skuA","procotionType":"1"},{"skuCount":1,"skuName":"skuA","promotionType":"1"}]

 

系统对接的商家通过到家开放平台发布的售后接口创建售后单,由于开放平台入口面对的是所有商家,每个商家系统对接能力不一样,可以看出订单中只买了1个A商品,但是传了两遍。正常我们的做法是解析入参list,然后校验每一行商品的合法性。查询当前订单已申请商品个数,以及订单中总商品个数,然后与当前审核售后单商品个数做比较。但是循环比较等于比较了两次,每次个数都是1。而且由于2个商品A总额小于订单总额,所以即使有后面的台账总额校验,还是会造成多退情况。因此这里需要根据当前申请商品总数加已申请此商品总数与订单中商品总数做校验。

 

③订单台账金额校验

 

  • 订单台账金额校验,是最后一道校验,校验的维度不同,是获取每一项支付明细剩余可退金额。

 

  • 校验当前要退售后单金额与台账余额比较,必须小于等于台账余额。

 

④异步退款结果

 

  • 审核退款后,通过异步接收退款mq来更新退款状态。

 

  • 退款成功通知下游依赖系统。

 

总结

 

逆向售后的业务是依赖于正向订单的,随着正向单不同场景玩法的增加,售后需要支持的场景也在增多,我们也在不断的迭代进步。在这当中也遇到了一些需要解决和完善的问题,比如售后系统没有自己的网关,这样会造成业务逻辑维护多处,业务不闭环。整个售后业务中各种不同场景下逻辑配置都不同,我们也在规划通过模板引擎配置做到智能化。最后也非常欢迎大家留言交流,共同进步。

 

作者丨姚飞涛

相关 [京东 系统架构 设计] 推荐:

京东售后系统架构设计:专治多端并发、数据不一致的臭毛病

- -
通过阅读本文,您将了解到一个售后系统应该具备的一些能力、在整个上下游系统中的定位、基本的系统架构,以及针对售后业务场景中常见问题的解决方案. 京东到家售后系统作为逆向流,强依赖京东到家业务域,目前涵盖了:退款、退货、换货、维修等四大类场景,并且为用户与商家提供申诉、仲裁场景支持,为计费与结算系统提供逆向金额数据支持.

网购秒杀系统架构设计

- - 企业架构 - ITeye博客
秒杀活动只是网站营销的一个附加活动,这个活动具有时间短,并发访问量大的特点,如果和网站原有应用部署在一起,必须会对现有业务造成冲击,稍有不慎可能导致整个网站瘫痪. 用户在秒杀开始前,通过不停刷新浏览器页面以保证不会错过秒杀,这些请求如果按照一般的网站应用架构,访问应用服务器、连接数据库,会对应用服务器和数据库服务器造成极大的负载压力.

O2O供应链系统架构设计

- - 美团技术团队
本文是美团技术沙龙第一期, O2O技术架构与实践上的分享内容. 请在微信搜索“美团技术团队”关注我们的公众账号,了解更多活动信息. 英国知名供应链专家Martin Christopher曾经说过一句非常深刻的话:“21世纪的竞争不是企业和企业之间的竞争,而是供应链和供应链之间的竞争. 在风云变幻、寡头纷争的O2O战场,美团屡出重拳并步步为营,战绩不俗.

灰度发布系统架构设计

- - 掘金 架构
互联网产品需要快速迭代开发上线,又要保证质量,保证刚上线的系统,一旦出现问题可以很快控制影响面,就需要设计一套灰度发布系统. 灰度发布系统的作用,可以根据配置,将用户的流量导到新上线的系统上,来快速验证新的功能,而一旦出现问题,也可以马上的修复,简单的说,就是一套A/B Test系统. 灰度发布允许带着bug上线,只要bug不是致命的,当然这个bug是不知道的情况下,如果知道就要很快的改掉.

Netflix系统架构设计方案

- - DockOne.io
【编者的话】Netflix是全球最大的在线视频网站之一,它是怎么设计的呢. 这篇文章介绍了Netflix系统架构的设计方案. 原文: Netflix System Architecture. 我们来讨论一下如何设计Netflix. 相信每个人都会通过某些网站或应用在线追剧或者看电影,而Netflix是我最喜欢的在线视频网站,不过今天我不推荐任何电影,相反,我想展示的是Netflix背后令人惊艳的系统逻辑.

解剖Twitter:Twitter系统架构设计分析

- flychen50 - 互联网的那点事
随着信息爆炸的加剧,微博客网站Twitter横空出世了. 用横空出世这个词来形容Twitter的成长,并不夸张. 从2006年5月Twitter上线,到2007年12月,一年半的时间里,Twitter用户数从0增长到6.6万. 又过了一年,2008年12月,Twitter的用户数达到5百万. Twitter网站的成功,先决条件是能够同时给千万用户提供服务,而且提供服务的速度要快.

海尔电商峰值系统架构设计最佳实践

- - 博客园_知识库
  多数电商平台都会经历相似的过程,流量和业绩每年以几倍至十几倍的速度增长,每年都要接受几次大规模、全方位的系统检阅,例如双11、周年庆等购物狂欢节,期间流量和订单可能是日常的十几倍甚至几十倍,产生的峰值对平台形成极其强烈的冲击,对电商平台的架构带来巨大的考验. 因此,对电商平台的规划和架构工作不仅要高瞻远瞩,而且要细致入微,否则将导致平台无法满足高速增长的业务发展,细微处的失误也可能造成严重后果,不仅影响业务指标的实现,还可能导致对系统进行重新架构,劳时费力又伤钱.

基于Hadoop的Clearinghouse系统架构设计

- - CSDN博客架构设计推荐文章
1 Clearinghouse(数据交换中心)介绍.        Clearinghouse(数据交换中心)是随着异构组织之间共享空间数据而产生的,它的目标是建立一个虚拟空间数据机制,用来收集空间数据的元数据和发布服务,以便高效的获取空间数据,同时利用空间数据提供决策支持. 通常建立Clearinghouse的基本途径是通过一套元数据标准,收集各个组织中空间数据的元数据,通过服务接口帮助用户确定存在哪些数据,以及获取这些数据的方式等.

分布式会话跟踪系统架构设计与实践

- - 美团点评技术团队
本文整理自美团点评技术沙龙第08期:大规模集群的服务治理设计与实践. 美团点评技术沙龙由美团点评技术团队主办,每月一期. 每期沙龙邀请美团点评及其它互联网公司的技术专家分享来自一线的实践经验,覆盖各主要技术领域. 目前沙龙会分别在北京、上海和厦门等地举行,要参加下一次最新沙龙活动. 赶快关注微信公众号“美团点评技术团队”.