当前位置: 首页 > 科技观察

vivo全球商城:电商平台通用取货码设计

时间:2023-03-14 12:28:57 科技观察

作者|vivo官网商城开发团队-周龙健一、背景随着O2O线上线下业务的不断拓展,电商平台也在逐步完善交易-相关信息产品功能。在最新的需求版本中,为了进一步提升用户体验,业务方规划了取货码生成和订单验证相关的逻辑。目的是让在线用户在支付后可以到店自提或安排导购。送货。在日常生活中,我们对取码核销等功能的使用体验大多来自于:看电影前取票、吃饭后出示优惠券码、快递柜取包裹等。有一些相似的特点。例如:取货码的长度比较短,比十多二十位、几位的订单号更便于记忆和输入;除了数字取件码外,还提供二维码,方便终端扫码验证。取件码使用起来非常简单,但就像一座“冰山”,简单的外表下隐藏着严谨的设计和一丝不苟的逻辑。可以说,麻雀虽小,五脏俱全。本文介绍的设计也比较有意思,按照这个思路可以实现市面上大部分验证码的生成,同时也可以满足商家的SaaS化。是比较一般的能力。在这里,我与您分享整个设计。2、简单系统的单台业务如果业务量小,店铺客流量相对较小,平台规模还没有形成,比如个体户的系统。那么取货码或者优惠券码的实现就比较简单了,跟订单共用一张横向大表或者用扩展表关联订单即可,这个阶段不需要过度设计。表格的设计如下图所示:不过需要注意的是,一般的订单号都比较长,一般在十位或者二十位(当然也有比较短的订单号,如果订单号比较短,取货码也可以用订单号)我们假设订单号有18位,取货码有8位,即订单号取值范围远大于取货码,所以在订单号的生命周期内,取货码有很大概率会重复。解决方案相对简单。我们只需要保证在任何情况下,没有被注销的数字代码不重复,也就是被注销的数字代码可以被回收。那么拾取码的生成逻辑就很清晰了。以下伪代码用于模拟真实的实现逻辑:伪代码实现for(;;){step1获取随机码:Stringcode=this.getRandomCode();step2执行SQL:SELECTCOUNT(1)FROMorder_mainWHEREcode=${code}ANDwrite_off_status=0;step3判断是否可以插入:if(count>0){continue;}step4执行数据写入:UPDATEorder_mainSETcode=${code},qr_code=${qrCode},write_off_status=0WHEREorder_no=${orderNo}}*注意:这里step2和step4不是原子操作,有并发问题。在实际应用中,最好使用分布式锁来锁定操作。3、复杂平台的分库分表业务通过简单的单表设计,我们可以窥见取货代码的大致实现逻辑。然而,当我们将一个简单的解决方案实施到一个大型项目中时,我们需要考虑很多方面,设计需要更加精细。基于SaaS的电商平台会比简单的单表业务复杂很多。重点是:SaaS产品涉及门店多、订单量大,需要设计大容量存储。所以,订单表基本上都是分库分表,显然作为订单附带的取货码表也必须使用相同的策略;B端和C端用户的体验很重要,服务端接口的设计需要充分考虑健壮性,提升最基本的重试和容错能力;取件码的要求可能不同,取件码的设计需要具有通用性和个性化的配置属性。3.1详细设计取货码表的设计建议采用与订单一致的分库分表策略。优点是:和订单一样,支持大量订单行的存储;使用同一个分库分表因子查询比较方便(例如:open_id,member_id)。在考虑实现方式时,我们遇到了第一个讨论点,即取货码是“门店唯一”还是“全球唯一”?3.2门店唯一性方案刚刚开始考虑使用类似餐厅取餐码的逻辑,保证取餐码在每家门店保持唯一。类似于下图所示的交互,图中用户A和用户B的取件码相同,用户A和B去各自对应的门店完成验证,整个交易流程就结束了。但这要保证用户A和B能够在各自订单所属的店铺正确完成验证。显然,这个方案是有风险的!如下图的情况,用户A和B也可以正常注销,但是订单已经整理好了,原本属于用户A的订单已经被用户B取消了。出现这种问题的本质原因是纯数字代码不能携带用户标识。虽然可以在注销前通过人为验证身份来避免,但仍然是高风险的系统设计,所以只针对店铺的解决方案不可取!3.3全局唯一方案全局唯一方案风险低,但实现难度稍大。核心问题是如何确定随机生成的取件码是全局唯一的。当然,如果系统本身依赖于ES等存储介质,可以在插入ES之前查询一下。但是查询和编写ES对于实时接口来说有点重,不像直接查数据库表那么直接。假设一个业务方分4库4表,共16张表,取件码长度确定为8位,那么在多数据库的Mysql中如何查询和保证全局唯一性多个表?遍历表格的方式绝对不可取!为了解决以上问题,我们可以在设计时针对取件码的布局做一些文章,分以下几步进行详细说明:步骤①:8位取件码可以分为两部分areas,“乱码区”+“库表位置”,如下图示例:Step②:乱码区暂不介绍,看看接下来的2位库表是如何映射到16表的由4个库和4个表组成。这里也有两套方案:【方案一】可以选择2位库表的第一位作为库号,最后一位作为库号。优点是映射比较简单,但是容量不够大。如果分库或分表>9,扩容会有点麻烦。如下图所示,我们将末尾的“12”逻辑映射为“1号库中编号为2的表”;[方案2]将库4和表4的二维结构转化为一维,以0为初值递增,(0库,0表)→00,(0库,1表)→01。..,(3库3表)→15。好处是容量增加了,最多支持99表,不受库或表条件限制。缺点是映射逻辑写起来比较麻烦,但这不是问题。经过对取货码的简单整理,我们就完成了取货码到仓库表的映射逻辑,解决了取货码的访问问题。其实仔细想想,其实全局唯一性的问题就解决了。我们只需要保证前6个随机码在单表中是唯一的。理论上支持未验证状态的单表范围:000000~999999条记录,容量足够。关键是我们将多库多表查询简化为只跑一条SQL,大大提高了效率。3.4方案实施中遇到的问题由于本文是对完整的SaaS方案的介绍,在实施过程中或多或少会遇到一些问题。下面给出实践中遇到的三个典型问题,并给出一些解决方案:[问题1]Math.random()生成的6位随机码与表中的重复,如何处理?【解决】其实重复的情况有两种:可能是表中已经存在相同号码的未验证取件码;另一种情况是其他事务在运行,恰好有一个分布式事务锁定了相同的Numericcodes(概率小,但有可能)。这两种情况的出现,都需要我们优雅地重试!大致思路如下伪代码://step1根据分库分表因子获取数据库表号,userCode-用户号,tenantId-租户号Stringsuffix=getCodeSuffix(userCode,tenantId);//step2批量获取6位随机码for(inti=1;i<=5;i++){//批量获取随机数。每次重试,取2的指数量进行过滤,相比于暴力执行for循环ListtempCodes=getRandomCodes(2<{0..3}.order_pick_up_0$->{0..3}#配置库数据库的计算逻辑Strategy:hint:algorithmClassName:com.xxx.xxxxx.xxx.service.impl.DbHintShardingAlgorithm#配偶表的计算逻辑tableStrategy:hint:algorithmClassName:com.xxx。xxxxx.xxx.service.impl.DbHintShardingAlgorithm...//java代码try(HintManagerhintManager=HintManager.getInstance()){hintManager.addDatabaseShardingValue("order_code"/**取码表*/,DbHintShardingAlgorithm.calDbShardingValue(tenantId,代码));hintManager.addTableShardingValue("order_code"/**取码表*/,DbHintShardingAlgorithm.calTabShardingValue(tenantId,code));Objectxxx=xxxMapper.selectOne(queryDTO);}【注意】这里介绍一种程序化的方案,优点是配置简单,灵活,缺点是代码多说一点,其实ShardingSphere也支持注解的方式,大家可以自行研究;第一种比较灵活,体现在自己实现的“DbHintShardingAlgorithm.calDbShardingValue(tenantId,code)”方法中。这个方法可以自己定义,所以我们入参可以是普通的分仓分表因子,也可以是取货码自定义的“入库表位置”字段,非常灵活。【问题三】如何实现更强的扩展性,适用于SaaS平台和不同的业务场景?【解决】细心的朋友应该注意到“tenantId”字段,这是租户的代码,在实际代码中会透传。我们可以通过这个字段对不同的租户(或者业务方)进行不同的配置,比如:取码长度,取码排列方式,取码映射库的策略表位置等配置,只要将骨干逻辑进一步抽象,采用策略模式进行个性化编码即可。4.总结在实现取货代码逻辑的时候,发现网上优惠券代码的解决方案和技术文章比较少。当时就萌生了写一篇文章分享的想法。其实我相信大部分公司做的大概都差不多,即使采用其他方案,也能得到相同的结果。这篇文章整体只介绍了一个思路,这个思路类似于订单分库分表的简化版,但这就是神奇之处。其实,我们也可以将一些常用的技术方案应用到不同的应用场景中,大胆地做一些尝试,走更多从未想过的路!