本文转载自微信公众号《微科技》,作者微科技。转载本文请联系微科公众号。大家好,我是汤哥。作为程序员,发展方向大致可以分为两个方面:一个是业务架构,一个是技术架构(中间件方向)。以业务架构为核心关键词,主要针对不同的业务场景和业务规则,完成业务系统的搭建,为用户提供在线信息服务。现在说业务,有很多方向,比如:旅游、外卖、充电宝、O2O、内容、社交、生鲜、电商,不同的业务有不同的特点。面对这么多的业务领域,有没有什么通用的技术经验可以提炼出来,让我们应对成百上千。在这里,电商业务首当其冲。电子商务系统的复杂度非常高,对高并发、高性能、高可用性、高扩展性的要求很高。你在其他业务中可能遇到的问题,在电商系统中基本上都会遇到。作为一名开发人员,我希望成为几个业务领域的技术专家。最好先精通电商领域,有很强的参考价值。对你在其他业务领域拓展和熟悉个性化玩法会有很大帮助。那么,电子商务领域的技术架构普遍存在哪些问题呢?1.避免重复下单。当用户快速点击两次“提交订单”按钮时,浏览器会向后台发送两次创建订单的请求,最终会创建两个订单。完全一样的顺序。解决方案:解决方案是采用幂等机制,多次请求与一次请求效果相同。方案一:使用数据库本身的“唯一主键约束”,在插入订单记录时插入主键值。如果顺序重复,记录插入将失败。操作流程:引入服务生成“全球唯一订单号”。进入订单创建页面时,前端请求服务预先生成订单ID。提交订单时,除了业务参数外,请求参数中还必须包含预先生成的订单ID。方案二:前端由js脚本控制,无法解决用户请求刷新提交。也没有办法解决恶意提交。不推荐使用该程序,如果要使用,仅作为辅助程序。解决方案3:就前后的附加参数验证达成一致。当用户点击购买按钮时,渲染订单页面显示商品、送货地址、运费、价格等信息,同时页面会埋入Token信息。当用户提交订单时,后台业务逻辑会验证token。被认为是合理的要求。注意:同一个Token只能使用一次,使用后立即失效。{%csrf_token%}2.订单快照,降低存储成本商品信息可修改。用户下单后,为了更好的解决日后可能出现的销售纠纷,在创建订单时会同步保存一份商品详情信息,称为订单快照。许多用户会购买相同的产品。如果产品受欢迎,短时间内会有数万的订单。如果为每个订单创建一个快照,存储成本太高。另外,商品信息虽然支持修改,但毕竟是低频动作。我们可以理解为大部分订单的商品快照信息是相同的,除非用户在下单时修改过。如何实时识别修改动作是解决快照成本的关键。我们采用抽象比较的方法。创建订单时,首先检查产品信息摘要是否已经存在,如果不存在,将创建快照记录。订单详情将与产品的快照主键相关联。公共类DigestTest{publicstaticvoidencodeStr(Stringdata){StringencodeS=DigestUtils.md5Hex(data);System.out.println(encodeS);}publicstaticvoidmain(String[]args){Stringdata="netpin投连险是保险公司的保险产品,在互联网金融中还是很常见的。"+"比如京东天天赢、网易优钱钱++。这些保险弱化了保险的保障功能,降低了成本,从而提高了保险的理财功能,提高了理财收益。"+》投连险与银行结构性理财产品基本相同,信息披露程度不高。想象一下,投资起点低的银行理财产品。网上销售和投连险普遍受益4-6%,保本无保障。”+“经常有报道说保险公司的保障型长期投连险有投资损失,但是网购的短期投连险投资型投连险目前没有损失,而且基本上可以按照预期收入支付。”+“网销投连险的安全性和盈利性都比较中等,短期产品的风险系数不高。但在债券违约的大环境下,长期产品的安全性并没有多少保障。”+“好在保险公司没有跑路的风险,至少不会赔光本金。";encodeStr(data);}}因为订单快照是一个非核心操作,即使失败了,也不应该影响用户正常的购买流程,所以通常在异步流程中执行匹配函数,暂存用户想要购买的商品,分为三个动作:添加商品,查看列表,结算下单,技术设计不是特别复杂,存储的信息比较有限(用户id,商品id,sku_id,quantity,Addingtime)。这里主要关注用户体验层面的几个问题:添加购物车时,后台会检查用户是否未登录。大意是引导用户去跳转到登录页面,登录成功后,再添加一个购物车,多了一步操作会给用户一种强制感,体验会很差,有没有更好的办法呢,各位大平台细细体会比如京东、淘宝,你会发现即使没有登录也可以添加购物。切,这是怎么实现的?仔细想想,原理并不复杂。服务器端在用户登录时进行分支路由,当用户未登录时,会创建一个临时的Token作为用户的唯一标识,购物车数据挂载在Token下,避免相互影响考虑到购物车数据和设计的复杂性,会有一个临时的购物车表。当然,临时购物车表的数据量也不会太大,为什么呢?用户不用一直加购物车玩。当用户登录查看购物车时,服务器会从请求的cookie中查找购物车TokenID,检查临时购物车表中是否有数据,然后合并。进入官方购物车表。特别提示:临时购物车一定要存放在服务器上吗?不必要。一些架构师倾向于将数据存储在浏览器或APPLocalStorage中。毕竟这部分数据是不共享的。但它不够好,增加了设计的复杂性。客户端需要使用本地数据索引远程请求查看完整信息。如果是登录状态,需要添加数据合并逻辑。考虑到这两部分数据只是用户标识的不同,笔者还是建议统一存储在服务端。即使以后业务逻辑发生变化,也只需要改动一个地方。毕竟,自操作系统需要良好的可维护性。我们很担心。4、库存超卖常见的库存扣减方式有:订单减法:即买家下单时,从商品总库存中减去买家的采购数量。下单减库存是最简单的减库存方式,也是最精准的控制方式。下单时,通过数据库的交易机制直接控制商品的库存,不会出现超卖的情况。但是你要知道,有的人可能下单后不付款。付款减少库存:买家下单后,库存不会立即减少,而是在用户付款后实际减少库存,否则库存会一直留给其他买家。但是因为只是在付款的时候减少了库存,如果并发量比较高,可能会出现买家下单后无法付款的情况,因为商品可能已经被别人买了。预扣库存:这种方法比较复杂。买家下单后,库存会保留一定时间(如30分钟)。过了这个时间,库存会自动释放。发布后,其他买家可以继续购买。买家付款前,系统会检查订单库存是否有预留:如果没有预留,则再次尝试预留;存货不足(即扣缴不合格)的,不得补缴;如果预扣成功,则进行付款并实际减去库存。至于采用哪种减库存方式更多是业务层面的考虑,减库存的核心是在大并发请求时保证数据库中inventory字段的值不能为负。方案一:行级锁一般用在扣库存的场景。数据库引擎本身控制记录的锁定,保证数据库更新的安全,where语句的条件保证库存不会降为0。下面是可以有效控制超卖的场景。update...setamount=amount-1whereid=$idandamount-1>=0解决方法二:将数据库的字段数据设置为无符号整数,这样SQL语句就会报错减去后的库存字段小于零。5.商户发货,物流单更新ABA问题举个例子:商户发货,填写运单号,一开始填写123,后来发现不对,后来修改为456。此时,如果埋下伏笔一个特殊场景埋下的错误,我们来看一下细节:流程:启动“requestA”发货,调整订单服务接口,更新运单号123,但响应有点慢,时效性出去。这时商家发现运单号填写错误,发起“请求B”,更新运单号为456,订单服务也响应成功。此时“RequestA”触发重试,再次调用订单服务,更新运单号123,订单服务也响应成功。OrderServices保存的最后一个跟踪号是123。你弄错了吗!!!!那么有什么好的解决办法吗?很多人可能会说不用重试就好了。要知道重试机制是服务高可用的重要保障。许多重试是框架自动启动的。理想方案:在数据库表中引入一个额外的字段version,每次更新时判断表中的版本号与请求参数携带的版本号是否一致。updateordersetlogistics_num=#{logistics_num},version=#{version}+1其中order_id=1111和version=#{version}一致:触发更新。不一致:表示在此期间已经进行了数据更新,可能会出错,拒绝执行。6.为了更新账户余额,保证交易用户付款,我们需要从买家账户中扣除一定金额,再向卖家账户中添加一定金额。为了保证数据的完整性和可追溯性,在更换天平时,我们通常会插入一条流水记录。账户交易核心字段:交易ID、金额、交易双方账户、交易时间戳、订单号。注意:账户历史只能添加,不能修改或删除。序列号必须自动递增。以后系统对账的时候,我们只需要积累交易流水的明细数据即可。如果与余额不一致,一般会根据交易流程修复余额数据。虽然更新余额和记录流量是两个操作,但需要保证要么都成功,要么都失败。做生意。数据库的事务隔离级别有:ReadUncommitted(RU)、ReadCommitted(RC)、RepeatableRead(RR)和Serializable。常用的隔离级别是RC和RR,因为这两种隔离级别都可以防止脏读。当然,如果涉及多个微服务调用,就要用到分布式事务。分布式事务想想就很容易理解。就是将一个大的事务拆分成多个本地事务。本地事务还是由数据库自己的事务来解决。难点在于解决这个分布式一致性问题。借助重试机制,保证最终一致性是我们常用的解决方案。7、MySQL读写分离导致的数据不一致。大多数Internet业务需要的读取多于写入。为了提高数据库集群的吞吐性能,我们通常采用主从架构和读写分离。部署一个主库实例,客户端请求所有的写操作都写入到主库中,然后利用MySQL内置的主从同步功能做一些简单的配置,就可以将主库的数据同步到多个从库实例近乎实时,主从延迟很小,一般不超过1毫秒。客户端请求的读操作全部发送到从库,借助多实例集群提高读请求的整体处理能力。这个解决方案看似天衣无缝,但实际上有副作用。虽然主从同步几乎是实时的,但是还是有时间差的。主库中的数据刚刚被更新,但是数据还没有同步到从库中。后续读请求直接访问从库,看到的还是旧数据,影响用户体验。没有什么是完美的,从主同步也是如此。没有完美的解决方案,我们必须找到一个平衡点。下面以电商为例,看看如何从产品层面解决这个问题。为了实验的真实性,Tom哥特地在淘宝下了购物单。在订单确认页面,点击购买按钮进入支付页面:输入支付宝支付密码进入支付成功页面,其中有一个可以查看订单详情的入口。点击查看交易详情,跳转至真实订单详情页面,可查看订单支付状态(订单数据取自仓库)。你明白吗?支付成功后,我们并没有立即跳转到订单详情页面,而是添加了一个无关的中间页面(支付成功页面),一个是告诉你支付结果成功,钱没有丢,千万不要担心;此外,还可以添加一些推荐商品来吸引流量,提高网站的GMV。最重要的是,增加了一个缓冲期,为订单的主从数据库数据同步争取了更多的时间。可以说是一举多得,其他互联网业务也是类似的。你学会了另一个技巧吗?