源 / 文/ 做为一名程序员,发展方向大致可以分为两个方面:一个是业务架构,一个是技术架构(中间件方向)。 业务架构,取其核心关键词,主要是围绕这不同的业务场景、业务规则,完成业务系统的落地建设,为用户提供在线化的信息服务。 既然说到业务,那方向可就多了去了,如:出行、外卖、充电宝、O2O、内容、社交、生鲜、电商,不同的业务有不同的特点。 面对这么多的业务域,有没有通用技术经验可以抽取,让我们可以以一应百。 这里,首推电商业务,电商系统的复杂性很高,对高并发、高性能、高可用、高扩展,等方面要求很高。你在其他业务中可能遇到的问题,在电商系统中基本都会遇到。 作为开发,希望自己成为某几个业务领域的技术专家,最好能先精通电商领域,有很强的借鉴意义。对于你后续拓展熟悉其他业务领域的个性化玩法有很大帮助。 那么,电商领域的技术架构有哪些常见问题? 一、避免重复下单用户快速点了两次 “提交订单” 按钮,浏览器会向后端发送两条创建订单的请求,最终会创建两条一模一样的订单。 解决方案: 解决方案就是采用幂等机制,多次请求和一次请求产生的效果是一样的。 方案一: 利用数据库自身特性 “主键唯一约束”,在插入订单记录时,带上主键值,如果订单重复,记录插入会失败。 操作过程:
方案二: 前端通过js脚本控制,无法解决用户刷新提交的请求。另外也无法解决恶意提交。 不建议采用该方案,如果想用,也只是作为一个补充方案。 方案三: 前后约定附加参数校验。 当用户点击购买按钮时,渲染下单页面,展示商品、收货地址、运费、价格等信息,同时页面会埋上 注意:同一个
补充: 关于幂等的处理,更多解决方案可以看这两篇文章 二、订单快照,减少存储成本商品信息是可以修改的,当用户下单后,为了更好解决后面可能存在的买卖纠纷,创建订单时会同步保存一份商品详情信息,称之为订单快照。 同一件商品,会有很多用户会购买,如果热销商品,短时间就会有上万的订单。如果每个订单都创建一份快照,存储成本太高。另外商品信息虽然支持修改,但毕竟是一个低频动作。我们可以理解成,大部分订单的商品快照信息都是一样的,除非下单时用户修改过。 如何实时识别修改动作是解决快照成本的关键所在。我们采用摘要比对的方法。创建订单时,先检查商品信息摘要是否已经存在,如果不存在,会创建快照记录。订单明细会关联商品的快照主键。
由于订单快照属于非核心操作,即使失败也不应该影响用户正常购买流程,所以通常采用异步流程执行。 三、购物车,混合存储购物车是电商系统的标配功能,暂存用户想要购买的商品。分为添加商品、列表查看、结算下单三个动作。 技术设计并不是特别复杂,存储的信息也相对有限(用户id、商品id、sku_id、数量、添加时间)。这里特别拿出来单讲主要是用户体验层面要注意几个问题: 添加购物车时,后端校验用户未登录,常规思路,引导用户跳转登录页,待登录成功后,再添加购物车。多了一步操作,给用户一种强迫的感觉,体验会比较差。有没有更好的方式? 如果细心体验京东、淘宝等大平台,你会发现即使未登录态也可以添加购物车,这到底是怎么实现的? 细细琢磨其实原理并不复杂,服务端这边在用户登录态校验时,做了分支路由,当用户未登录时,会创建一个临时 当然,临时购物车表的数据量并不会太大,why?用户不会一直闲着添加购物车玩,当用户登录后,查看自己的购物车,服务端会从请求的cookie里查找购物车 特别说明: 临时购物车是不是一定要在服务端存储?未必。 有架构师倾向前置存储,将数据存储在浏览器或者
考虑到这两部分数据只是用户标识的差异性,所以作者还是建议统一存到服务端,日后即使业务逻辑变更,只需要改一处就可以了,毕竟自运营系统,良好的可维护性也需要我们非常关注的。 四、库存超卖常见的库存扣减方式有:
至于采用哪一种减库存方式更多是业务层面的考虑,减库存最核心的是大并发请求时保证数据库中的库存字段值不能为负数。 方案一: 通常在扣减库存的场景下使用行级锁,通过数据库引擎本身对记录加锁的控制,保证数据库的更新的安全性,并且通过
方案二: 设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时 SQL 语句会报错。 五、商家发货,物流单更新 ABA 问题举个例子: 商家发货,填写运单号,开始填了 123,后来发现填错了,然后又修改为 456。 此时,如果就为某种特殊场景埋下错误伏笔,具体我们来看下 过程:
是不是犯错了!!!! 那么有什么好的解决方案吗? 很多人可能会说,不重试不就可以了,要知道 理想的解决方案: 数据库表引入一个额外字段
六、账户余额更新,保证事务用户支付,我们要从买家账户减掉一定金额,再往卖家增加一定金额,为了保证数据的 账户流水核心字段:流水ID、金额、交易双方账户、交易时间戳、订单号、
后续,系统对账时,我们只需要对交易流水明细数据做累计即可,如果出现和余额不一致情况,一般以交易流水为准来修复余额数据。
数据库的事务隔离级别有: 常用的隔离级别是 RC 和 RR ,因为这两种隔离级别都可以避免脏读。 当然,如果涉及多个微服务调用,会用到分布式事务 分布式事务,细想下也很容易理解,就是将 七、MySQL读写分离带来的数据不一致问题互联网业务大部分都是 部署一个主库实例,客户端请求 客户端请求的 这个方案看似天衣无缝,但实际有个 副作用 主从同步虽然近乎实时,但还是有个 任何事情都不是完美的,从主同步也是一样,没有完美的解决方案,我们要找到其中的平衡取舍点。 我们以电商为例,看看如何从
在下单确认页面,点击购买按钮,进入了支付页面 输入支付宝支付密码,进入支付成功页面,页面有查看订单详情的入口。 点击 看懂了吗? 我们在支付成功后,并没有立即跳到 可谓一举多得,其他互联网业务也是类似道理。 是不是又学了一招 😊😊😊 八、历史订单,归档根据二八定律,系统绝大部分的性能开销花在20%的业务。数据也不例外,从数据的使用频率来看,经常被业务访问的数据称为热点数据;反之,称之为冷数据。 在了解的数据的冷、热特性后,便可以指导我们做一些有针对性的性能优化。这里面有业务层面的优化,也有技术层面的优化。比如:电商网站,一般只能查询3个月内的订单,如果你想看看3个月前的订单,需要访问历史订单页面。 实现思路: 1、冷热数据区分的标准是什么?要结合业务思考,可能要找产品同学一块讨论才能做决策,切记不要拍脑袋。以电商订单为例:
2、如何触发冷热数据的分离
3、如何实现冷热数据分离,过程大概分为三步:
4、如何使用冷热数据
九、订单分库分表,多维度查询如果电商网站的订单数过多,我们一般会想到 但是查询维度很多 1、买家,查询 2、查看订单详情,需要根据 3、卖家,查询 而订单分表只有一个分表键,如何满足多维度 SQL 操作呢? 我们一般是基于买家维度来设计,下图是 一个订单号 19 位,我们会发现同一个用户不同订单的最后 6 位都是一样的,没错,那是用户id的后6位。 这样,上文中 至于 end ![]() |
|
来自: 昵称10087950 > 《JAVA》