分布式存储

电商系统如何设计

业务需求:
1.这个系统(或者是功能)是给哪些人用的?
2.这些人使用这个系统来解决什么问题?

UML中的用例图——需求分析时需要画的第一张图:
在这里插入图片描述

业务流程——购物:
在这里插入图片描述

根据流程划分模块:
购物流程涉及的模块:商品、购物车、订单、支付和库存
在这里插入图片描述

整个电商系统涉及的模块:
UML中的包图:
在这里插入图片描述

创业篇

创建和更新订单时,如何保证数据准确无误?

1.如何避免重复下单?(插入数据)

场景:1.用户重复点击“提交订单”按钮 ----- 前端页面上防止用户重复提交表单
2.网络错误会导致重传, RPC 框架、网关都会有自动重试机制,导致重复请求

解决方案:让你的订单服务具备幂等性。
难点:对于订单服务来说,它怎么知道发过来的创建订单请求是不是重复请求呢?
思路:我们可以利用数据库的“主键唯一约束”特性,在插入数据的时候带上主键,来解决创建订单服务的幂等性问题。
做法:我们给订单系统增加一个“生成订单号”的服务,这个服务没有参数,返回值就是一个新的、全局唯一的订单号。
在用户进入创建订单的页面时,前端页面先调用这个生成订单号服务得到一个订单号,在用户提交订单的时候,
在创建订单的请求中带着这个订单号,这个订单号也是我们订单表的主键。
在这里插入图片描述
还有一点需要注意的是,如果是因为重复订单导致插入订单表失败,订单服务不要把这个错误返回给前端页面。否则,就有可能出现这样的情况:用户点击创建订单按钮后,页面提示创建订单失败,而实际上订单却创建成功了。正确的做法是,遇到这种情况,订单服务直接返回订单创建成功就可以了。

2.如何解决 ABA 问题?(更新数据)

场景:1.数据库的更新操作,本身就具备天然的幂等性;---- 比如订单状态更新为已支付,那无论执行多次,还是已支付状态。
2.在并发环境下,有可能会产生ABA问题。----比如请求1更新运单号666,请求2更新运单号888,变更666—>888,但是请求1由于没 有收到响应进行了重试,又将运单号更新回了666,变更888—>666; 666—>888—>666
在这里插入图片描述
解决方案:订单主表增加一列,列名可以叫 version,每次查询订单的时候,版本号需要随着订单数据返回给页面。
页面在更新数据的请求中,需要把这个版本号作为更新请求的参数,再带回给订单更新服务。
订单服务在更新数据的时候,需要比较订单当前数据的版本号,是否和消息中的版本号一致,
如果不一致就拒绝更新数据。如果版本号一致,还需要再更新数据的同时,把版本号+1。
“比较版本号、更新数据和版本号 +1”,这个过程必须在同一个事务里面执行。
在这里插入图片描述
通过这个版本号可以判断这个期间有没有其他人修改过这条订单数据。

流量大、数据多的商品详情页系统如何设计?

商品详情页需要保存的数据:
在这里插入图片描述
这里面,右边灰色的部分,来自于电商的其他系统,我们暂且不去管这些,左边彩色部分,都是商品系统需要存储的内容。

商品基本信息的存储 ---- Redis + 数据库

分析:1.商品的基本信息包括主副标题、价格、颜色等一些商品最基本、主要的属性,字段不算太多。
2.这些属性都是固定的,不太可能会因为需求或者不同的商品而变化。
3.这部分数据也不会太大。
结论:在数据库中建一张表来保存商品的基本信息,使用前置缓存(Redis、Memcached等)帮助数据抵挡绝大部分的读请求。
注意:一定要记得保留商品数据的每一个历史版本。因为商品数据是随时变化的,但是订单中关联的商品数据,必须是下单那个时刻
的商品数据。你可以为每一个历史版本的商品数据保存一个快照,可以创建一个历史表保存到 MySQL 中,也可以保存到一些 KV
存储中。

保存商品参数 ---- MongoDB

分析: 商品参数例如电脑的内存大小、手机的屏幕尺寸、酒的度数、口红的色号等等,和商品的基本属性一样,都是结构化的数据。但麻烦
的是,不同类型的商品,它的参数是完全不一样的。

方案1:每个类别分别建一张表。
比如说,建一个电脑参数表,里面的字段有 CPU 型号、内存大小、显卡型号、硬盘大小等等;
再建一个酒类参数表,里面的字段有酒精度数、香型、产地等等。
适用场景:如果说,品类比较少,在 100 个以内,用几十张表分别保存不同品类的商品参数,这样做也是可以的。

方案2:使用MongoDB,对于商品参数信息,数据量大、数据结构不统一,这些 MongoDB 都可以很好的满足。 ------ 我们选这种方案。

保存图片和视频 ----- 对象存储(七牛云、AWS 的 S3、 MinIO、OSS)

分析: 一般的存储方式都是,在数据库中只保存图片视频的ID 或者 URL,实际的图片视频以文件的方式单独存储。
云服务厂商的对象存储大多都提供了客户端 API,可以在 Web 页面或者 App 中直接访问而不用通过后端服务来中转。
这样,App 和页面在上传图片视频的时候,直接保存到对象存储中,然后把对应 key 保存在商品系统中就可以了。
最有用的是,缩放图片和视频转码,你只要把图片和视频丢到对象存储中,就可以随时获得任意尺寸大小的图片,
视频也会自动转码成各种格式和码率的版本,适配各种 App 和场景。

商品介绍 ----- 静态化

分析:商品介绍在商详页中占得比重是最大的,商品介绍的文本,一般都是随着商详页一起静态化,保存在 HTML 文件中。
商详页静态化之后,把商详页放到离用户最近的 CDN 服务器上, 让商详页访问更快。至于商品价格、促销信息等这些需要频繁变动
的信息,不能静态化到页面中,可以在前端页面使用 AJAX 请求商品系统动态获取。
在这里插入图片描述

购物车系统的存储如何设计?

购物车系统的功能,主要就三个:1.把商品加入购物车;2.购物车列表页 3.发起结算下单
支撑购物车的这几个功能,对应的存储模型应该怎么设计?
很简单,只要一个**“购物车”实体**就够了。
主要属性:SKUID(商品 ID)、数量、加购时间和勾选状态。

注意点:1. 如果未登录,需要临时暂存购物车的商品;
2. 用户登录时,把暂存购物车的商品合并到用户购物车中,并且清除暂存购物车;
3. 用户登陆后,购物车中的商品,需要在浏览器、手机 APP 和微信等等这些终端中都保持同步。

如何设计“暂存购物车”的存储?

根据需要选型:

  1. Cookie 存储
  2. LocalStorage 存储

存储的数据格式:
在这里插入图片描述

如何设计“用户购物车”的存储?

根据需要选型:

1.存储在Mysql
在这里插入图片描述
注意,需要在 user_id 上建一个索引,因为查询购物车表时,都是以 user_id 作为查询条件来查询的。

2.存储在Redis

以用户 ID 作为 Key,用一个 Redis 的HASH 作为 Value 来保存购物车中的商品。
在这里插入图片描述

账户余额总是对不上账,怎么办?

最小化的账户系统数据结构:
在这里插入图片描述
在设计账户流水时,有几个重要的原则必须遵守,最好是用技术手段加以限制:
1.流水记录只能新增,一旦记录成功不允许修改和删除。即使是由于正当原因需要取消一笔已经完成的交易,也不应该去删除交易流水。正确的做法是再记录一笔“取消交易”的流水。
2.流水号必须是递增的,我们需要用流水号来确定交易的先后顺序。

使用数据库事务来保证数据一致性

事务的 ACID 四个基本特性;

事务的隔离级别:
在这里插入图片描述

两种不常用:
第一种 RU 级别,实际上就是完全不隔离。每个进行中事务的中间状态,对其他事务都是可见的,所以有可能会出现“脏读”。
第四种“序列化”级别,具备完美的“隔离性”和“一致性”,性能最差,也很少会用到。

两种常用:
RC 和 RR 两种。
这两种隔离级别都可以避免脏读,能够保证在其他事务中是不会读到未提交事务的数据。
只要你的事务没有提交,那这个事务对数据做出的更新,对其他会话是不可见的,它们读到的还是你这个事务更新之前的数据。

RC 和 RR 唯一的区别在于“是否可重复读”。
在一个事务执行过程中,它能不能读到其他已提交事务对数据的更新,如果能读到数据变化,就是“不可重复读”,否则就是“可重复读”。
RC:
在同一个事务内两次读取同一条数据,读到的结果可能会不一样,这就是“不可重复读”。
RR:
在一个事务进行过程中,对于同一条数据,每次读到的结果总是相同的,无论其他会话是否已经更新了这条数据,这就是“可重复读”。

幻读:
比如我们在会话A准备插入id=1000的记录,查询当前数据库不存在id=1000的数据,那判断可以安全插入数据,但在这个时候,会话B抢先插入了一条id=1000的记录,当会话A插入的时候,报“报主键冲突错误”,但是由于事务的隔离性,它执行查询的时候,却查不到id=1000的记录,就像出现了“幻觉”一样。

分布式事务

分布式事务的解决方案有很多,比如:2PC、3PC、TCC、Saga 和本地消息表等等,各有各的使用场景。

2PC:订单与优惠券的数据一致性问题

我们用订单和优惠券的例子来说明一下,如何用 2PC 来解决订单系统和促销系统的数据一致性问题。
订单系统需要:
1. 在“订单优惠券表”中写入订单关联的优惠券数据;
2. 在“订单表”中写入订单数据。
订单系统内两个操作的一致性问题可以直接使用数据库事务来解决。
所谓的二阶段指的是准备阶段提交阶段。在准备阶段,协调者分别给订单系统和促销系统发送“准备”命令,订单和促销系统收到准备命令之后,开始执行准备操作。等两个系统都准备好了之后,进入提交阶段。提交阶段就比较简单了,协调者再给这两个系统发送“提交”命令,每个系统提交自己的数据库事务,然后给协调者返回“提交成功”响应,协调者收到所有响应之后,给客户端返回成功响应,整个分布式事务就结束了。
在这里插入图片描述

异常情况:
在这里插入图片描述

如果准备阶段成功,进入提交阶段,这个时候就“只有华山一条路”,整个分布式事务只能成功,不能失败。

只有在需要强一致、并且并发量不大的场景下,才考虑使用 2PC

本地消息表:订单与购物车的数据一致性问题

本地消息表非常适合解决这种分布式最终一致性的问题。

本地消息表的实现思路是这样的,订单服务在收到下单请求后,正常使用订单库的事务去更新订单的数据,并且,在执行这个数据库事务过程中,在本地记录一条消息。这个消息就是一个日志,内容就是“清空购物车”这个操作。因为这个日志是记录在本地的,这里面没有分布式的问题,那这就是一个普通的单机事务,那我们就可以让订单库的事务,来保证记录本地消息和订单库的一致性。完成这一步之后,就可以给客户端返回成功响应了。

然后,我们再用一个异步的服务,去读取刚刚记录的清空购物车的本地消息,调用购物车系统的服务清空购物车。购物车清空之后,把本地消息的状态更新成已完成就可以了。异步清空购物车这个过程中,如果操作失败了,可以通过重试来解决。最终,可以保证订单系统和购物车系统它们的数据是一致的。

这里面,本地消息表,你可以选择存在订单库中,也可以用文件的形式,保存在订单服务所在服务器的本地磁盘中,两种方式都是可以的,相对来说,放在订单库中更简单一些。消息队列 RocketMQ 提供一种事务消息的功能,其实就是本地消息表思想的一个实现。使用事务消息可以达到和本地消息表一样的最终一致性,相比我们自己来实现本地消息表,使用起来更加简单,你也可以考虑使用。

如何用Elasticsearch构建商品搜索系统

ES 本质上是一个支持全文搜索的分布式内存数据库,特别适合用于构建搜索系统。ES 之所以能有非常好的全文搜索性能,最重要的原因就是采用了倒排索引。倒排索引是一种特别为搜索而设计的索引结构,倒排索引先对需要索引的字段进行分词,然后以分词为索引组成一个查找树,这样就把一个全文匹配的查找转换成了对树的查找,这是倒排索引能够快速进行搜索的根本原因。但是,倒排索引相比于一般数据库采用的 B 树索引,它的写入和更新性能都比较差,因此倒排索引也只是适合全文搜索,不适合更新频繁的交易类数据。

Mysql如何更安全地做数据备份和恢复?

1.全量备份: 在 MySQL 中,你可以使用mysqldump命令来执行全量备份。
2.增量备份: MySQL 自带了 Binlog,就是一种实时的增量备份

数据恢复: 通过定期的全量备份,配合 Binlog,我们就可以把数据恢复到任意一个时间点。

配置 MySQL HA 实现高可用

高可用依赖的是数据复制,数据复制的本质就是从一个库备份数据,然后恢复到另外一个库中去。
在这里插入图片描述

SQL是如何在执行器中执行的?

访问数据库超时

  1. 根据故障时段在系统忙时,推断出故障是跟支持用户访问的功能有关。
  2. mysql CPU达到100%,大部分情况和慢sql有关
  3. 根据系统能在流量峰值过后自动恢复这一现象,排除后台服务被大量请求打死的可能性。
  4. 根据 CPU 利用率曲线的规律变化,推断出可能和定时任务有关。

怎么避免写出慢SQL?

一、评估数据量

影响 MySQL 处理能力的因素很多,比如:服务器的配置、数据库中的数据量大小、MySQL 的一些参数配置、数据库的繁忙程度等等。

我的经验数据,一般一台 MySQL 服务器,平均每秒钟执行的 SQL 数量在几百左右,就已经是非常繁忙了。

在开发阶段,衡量一个 SQL 查询语句查询性能的手段是,估计执行 SQL 时需要遍历的数据行数。遍历行数在百万以内,可以认为是安
全的 SQL,百万到千万这个量级则需要仔细评估和优化,千万级别以上则是非常危险的。为了减少慢 SQL 的可能性,每个数据表的行
数最好控制在千万以内。

查询语句尽可能多的用索引,更新语句尽可能少的用索引。

对于复杂的查询,最好使用 SQL 执行计划,事先对查询做一个分析。在 SQL 执行计划的结果中,可以看到查询预估的遍历行数,命中哪
些索引。执行计划也可以很好地帮助你优化你的查询语句。----- EXPLAIN

SQL是如何在数据库中执行的?

数据库的服务端,可以划分为执行器 (Execution Engine) 和 存储引擎 (Storage Engine) 两部分。
执行器负责执行计算,存储引擎负责保存数据。

SQL------> AST(抽象语法树)------> 逻辑执行计划------>物理执行计划------>完成数据的查询。

MySQL如何应对高并发----使用缓存保护MySQL

Redis:
key可以加一个统一前缀,比如:“orders:888888”;
value可以选择可读性好的JSON作为序列化方式,也可以选择性能更好且更节省内存的二进制序列化方式。

缓存更新方式:Cache Aside 模式
在这里插入图片描述

海量数据篇

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值