后端存储实战课 - 学习笔记
电商
01 订单
订单系统最重要的是:数据不能错。
- 下单如何保证幂等:可以先提供一个生成订单号的服务,下单时,将该号传过来。该订单号作为数据库主键来保证不会重复插入相同的数据。
- 更新订单的ABA问题:可以增加一个版本号字段,
update orders set xxx=yyy, version = version +1 where id = 111 and version = 222;
才更新,有点像CAS
02 商品详情页
商品详情主要包含了基本信息(标题,副标题,价格等),商品参数(电脑的型号,CPU,显卡型号\粉底液的色号,防晒指数等等),商品介绍和图片以及视频。
存储的思路大致是:基本信息放到MySQL的一张表里, 商品参数放到MongoDB, 前置做一个缓存如Reids,使用Cache Aside策略。图片视频使用对象存储(CDN加速), 商品页面介绍保存静态HTML(Nginx, CDN加速), 配合前端的AJAX动态获取信息。
Cache Aside:
Read/Write Through:
03 购物车
根据用户使用购物车的过程体验,可以把购物车划分为:1. 存在客户端的“暂存购物车” 和 2.存在服务端的“用户购物车”。
- 暂存购物车,主要是当用户没有登录,进行操作时,需要保存在客户端的。一般可以用Cookie或者LocalStorage来实现
- 用户购物车,主要是当用户登录了之后,不管使用什么客户端,如微信,app,网页等等都能看到自己购物车的信息。一般可以使用Redis或者MySQL来实现。Redis主要就是快,但是可能丢数据,对统计查询不太友好。MySQL比较慢,但是数据可靠,可以有丰富的查询方式。总体还是推荐MySQL。
04 账户
冗余数据
账户系统其实有非常多的冗余数据,这里的冗余数据并不是指同一份数据的多个地方的拷贝,而是含有一致性信息的冗余数据,如交易流水和账户余额,账户余额可以通过交易流水来计算得出。
正常情况是不建议使用冗余数据的,一个是浪费存储,一个是维护他们的一致性很麻烦。但是很多业务不得不维护冗余数据,例如上述的交易流水和账户余额。对账户余额的查询很多,不可能每次都去计算交易流水。而只存账户余额,又不能解决如果出现意外情况如余额被篡改进行追查。
保证冗余数据一致性
使用事务(事务最初就是来保证交易的,他们都是一个英语,叫做transaction)。
05 分布式事务
分布式事务的解决方案:2PC, 3PC, TCC, Saga和本地消息等
2PC 两阶段提交
有一个事务协调者,第一个阶段:协调者向各个系统发送准备指令,各个系统收到后,执行事务但不提交,执行后告诉协调者“我准备好啦”,协调者收到所有系统“准备好啦”的回复后,进入第二个阶段:发送提交指令,各个系统收到提交指令后将刚刚执行但未提交的事务提交,提交完成后,告诉协调者“我提交成功啦”,协调者收到所有反馈后,告诉客户端该事务ok了。
如果在准备阶段,有任何异常发生,包括没收到某个系统的回信,协调者将发起回滚通知,让所有系统回滚刚才的事务。
2PC是数据强一致性的设计,性能不高,因为会阻塞服务端的线程和数据库的会话。所以只有在要求强一致性且并发量不大的场景下才考虑2PC。
本地消息表
订单与购物车的数据一致,需求:只要保证数据最终一致就行了,如购物车数据在提交订单后晚几秒钟更新完全可以接受。
本地消息表:下单时,通过订单系统的事务,保证下单数据更新和记录一条消息(“更新购物车”)的原子性。这是一个普通的单机事务,完成后就可以给客户端返回了。
然后再通过异步的消息消费,消费失败进行重试。可以用RocketMQ提供的事务消息的功能。本地消息表的使用前提是异步操作的部分不能有资源依赖(当然了)。例如下单时除了更新订单系统,还要更新库存,那更新库存就不能异步做,因为下单的前提是库存得有货。
06 商品搜索
通过ElasticSearch(支持全文搜索的分布式内存数据库)构建商品搜索系统。首先,搜索的核心需求是全文匹配。ES使用的是:倒排索引,这个倒排索引长啥样,如下图:
ES在插入商品信息时,会对该信息进行分词,然后再做成这样的倒排索引。