资料总结 资料总结
首页
go
java
云原生
  • mysql
  • redis
  • MongoDB
  • 设计模式详解
  • 数据结构与算法
  • 前端
  • 项目
  • 理论基础
  • 运营
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

linghui Wu

一只努力学飞的鱼
首页
go
java
云原生
  • mysql
  • redis
  • MongoDB
  • 设计模式详解
  • 数据结构与算法
  • 前端
  • 项目
  • 理论基础
  • 运营
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • IM系统
  • IM设计和实现
  • 聊天机器人
  • 直播系统
  • 分布式日志追踪系统
  • 入侵防御检测系统
  • 自动化监控报警系统
  • 云平台
  • 低代码平台
  • 电商
    • 表的设计
    • 商品展示
    • 缓存
      • 双写一致性
      • 最终一致性方案
      • 实时一致性方案
      • 对象大小
      • 高并发缓存失效
      • 代码lock
      • 代码tryLock
      • zk锁
      • 缓存击穿
      • 缓存穿透
      • 缓存雪崩
      • 架构
      • 二级缓存
    • 布隆过滤器
    • OpenResty(3层缓存)
    • 多级缓存
      • 商品搜索
      • 秒杀
    • 表结构
    • 业务设计
    • 流程
    • 返场
    • 防止机器刷单
    • 库存相关
    • 核心问题
    • 主要接口
      • 确认下单流程
      • 秒杀下单提交
      • 数据库同步操作
      • zk/redis分布锁
      • 预售库存
      • 库存处理
      • 处理未支付的订单
      • 异步订单查询接口
      • 接口幂等性
      • RocketMQ消息零丢失
      • 多种压测方案
      • 降级开关
      • 限流&降级&拒绝服务
    • 限流
      • 限流算法
      • 前端限流
      • 接入层nginx限流
      • 网关限流
      • 应用层限流
      • 热点参数限流
      • 热点探测功能
    • 降级
      • 分类
      • 熔断降级
    • 拒绝服务
      • Sentinel提供的系统规则限流
    • 总结
      • 分布式日志系统
      • 架构组件
  • 产品杂记
  • 项目
wulinghui
2022-02-24
目录

电商

容量规划、架构设计、数据库设计、缓存设计、框架选型、数据迁移 方案、性能压测、监控报警、领域模型、回滚方案、高并发、分库分表

# 常见术语

  • PV: Page view,即网站被浏览的总次数
  • UV: Unique Vister的缩写,独立访客
  • CR: ConversionRate的缩写,是指访问某一网站访客中,转化的访客占全访客的比例 (订单转化率=有效订单数/访客数)
  • SPU: Standard Product Unit (标准化产品单元),SPU是商品信息聚合的最小单位,是 一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
  • SKU: Stock keeping unit(库存量单位) SKU即库存进出计量的单位(买家购买、商家进 货、供应商备货、工厂生产都是依据SKU进行的),在服装、鞋类商品中使用最多最普遍。 例如纺织品中一个SKU通常表示:规格、颜色、款式。SKU是物理上不可分割的最小存货单 元。

# 淘宝十年

  • 1.0 : LAMP + mysql 读写分离
  • 1.1 : LAP + ES + ORACLE
  • 2.0 : java + 2个ORACLE + ES
  • 2.1 : java(多个server) + 多个ORACLE + ES + 分布式缓存 + CDN + 分布式文件存储
  • 3.0 : 分布式,拆服务了。
  • 4.0 : 云原生 ,微服务、容器化部署、DevOps

# 分布式Session (opens new window)

  • session Sticky : ip负载到固定服务器
  • Session Replication : session 复制,开销大
  • Session数据集中存储 : Session数据不保存到本机而且存放到一个集中存储的地方
  • Cookie Based : 所有 session 数据放在 cookie 中

# 商品详细页

资料 -> 图片处理 -> 发布 -> 维护 -> 下架

# 表的设计

商品+分类+品牌+属性(spu级别)+规格(SKU)

# 商品展示

  • 静态化处理 : FreeMarker 适合小流量架构,可以放到CDN上缓存,但是更新商品时间需要所有都替换。
  • 前后端分离: vue + 接口 , 接口中加2级缓存 (jvm + redis),一般的可以实现。但是热点商品还是可能会把接口冲挂,触发熔断的操作。
  • 3级缓存 : 前端缓存OpenResty(lua+nginx) + 接口2级缓存。 (京东的实现)

# 缓存

# 双写一致性

在秒杀后台把价格修改之后,如何同步到缓存中

# 最终一致性方案

设置过期时间来解决

# 实时一致性方案

  • 交易canal监控binlog。
  • 这个适合不频繁的操作,否则也会有性能问题。

# 对象大小

  • 利用grpc的protobuffer压缩对象大小放到redis中。
  • 放到redis的内存也少了
  • 也减轻网络压力
  • 同时也支持其他语言来读这缓存,可扩展性也强。

# 高并发缓存失效

  • 现象: 加了缓存,统一时间来的数据过大,并发导致,同一时刻出现缓存失效,大量数据到数据库了
  • 利用分布式锁锁住查询数据库和redis.set操作。

# 代码lock

try{
    lock.lock();
    // ....
}finally{
    lock.unlock();
}
1
2
3
4
5
6

# 代码tryLock

try{
   // 这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 
   if (lock.tryLock(0,5,SECONDS)) { // 等待时间=0,等待时间=5,时间单位SECONDS.
                // 执行sql查数据
   }else{
       			//因为 非公平的,线程可能还没给redis.set 这里只能保证可能。
                //方案一:  sleep 再 redis.get 递归调用
   }
   //方案二: TODO 改进可以改成公平的锁.或者再上面tryLock(1,sec)等一下再拿。 
}finally{
    // 判断被锁住,且是自己锁的。
    if ( lock.isHeldByCurrentThread() && lock.isLocked() )
                lock.unlock();
}
// 缺点: 2次redis的查询。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# zk锁

  • 优点: 强一致性锁,解决redis集群锁同步不成功问题。
  • 缺点: 性能下降
  • 原理: 临时顺序节点。有序节点、临时节点、事件监听3个去中的。
  • 流程: 创建一个节点下的临时顺序节点,如果不是第一个就监听上一个节点,上个节点释放就排队到前面去了。 类似于公平锁。

# 缓存击穿

  • 热点数据单个key
  • 前置条件: 高并发请求过来
  • 定义: key过期,同一个过期的Key有大量的请求直接到DB上。

解决方案:

  1. 加锁 , 在未命中缓存时,通过加锁避免大量请求访问数据库
  2. 不允许过期,定时更新。物理不过期,也就是不设置过期时间。而是逻辑上定时在后台异步的更新数据。
  3. 采用二级缓存。L1缓存失效时间短,L2缓存失效时间长。二级缓存,redis发布/订阅监控set事件。

# 缓存穿透

  • 恶意攻击、访问不存在数据
  • 前置条件: 请求缓存和数据都没有的数据
  • redis和DB都没有,造成无效请求.

解决方案:

  1. 布隆过滤器
  2. 设置空对象进行缓存,(我们采用的就是这种)但它的 过期时间会很短,最长不超过五分钟

# 缓存雪崩

  • 同一时间失效,并发量大
  • 前置条件: 大量的key同时失效。
  • 间接的造成大量请求到达数据库。

解决方案:

  1. 非时点性数据(不火),使用失效的固定时间加上随机值。
  2. 时点性数据(热点数据),没有查到就加锁数据库查询,改成串行。

# 架构

  • 事前 : 缓存集群实现高可用
  • 事中 :使用Hystrix进行限流 & 降级
  • 事后 : 开启Redis持久化机制,尽快恢复缓存集群

# 二级缓存

  • guava的缓存使用: 1、设置最大容量 2、初始化容量 3、缓存过期
  • Jetcache 框架.

# 布隆过滤器

  • 项目初始化时,放到key不存在,就全量放到bitmap中.
  • 增加时,MQ添加进去。
  • 再接口中使用,判断并报错。

# OpenResty(3层缓存)

  • 基于nginx的可用lua脚本编码的web服务器,可以说是超级高效。
  • lua脚本内容 : 请求接口 + 缓存 + html模板。
  • 最理想情况就是 缓存+html模板,组装html给前端。
  • LRU算法:最近最少使用淘汰数据,最热的数据缓存

3层的功能

  • lua+nginx:过期时间比较短(以触发LRU),数据里小、访问量相对来很高 ,超热点商品,一般存活动页、置顶的、推荐的商品。
  • jvm本地缓存:过期时间比较长(防止雪崩和击穿),数据里很大、访问量相对来高 ,热点商品。
  • redis:过期时间一般长,数据里相对来说比较大、访问量相对来不高,一般商品。
  • db: 直接查,冷门数据。

# 多级缓存

  • 浏览器缓存,当页面之间来回跳转时走local cache,或者打开页面时拿着Last-Modified 去CDN验证是否过期,减少来回传输的数据量;
  • CDN缓存,用户去离自己最近的CDN节点拿数据,而不是都回源到北京机房获取数据, 提升访问性能
  • 服务端应用本地缓存,我们使用Nginx+Lua架构,使用HttpLuaModule模块的shared dict做本地缓存( reload不丢失)或内存级Proxy Cache,从而减少带宽;

# 商品搜索

索引???,数据建模

# 秒杀

# 表结构

  • 业务: 一个活动可以有多个场次,每个场次可以有多个商品进行秒杀
  • 表: 秒杀活动表,秒杀场次表,场次商品关系表

# 业务设计

营销中心 -> 交易中心 -> 会员中心 -> 商家后台 -> 小二后台 -> 网盟系统 -> h5/app/pc -> 秒杀系统 -> 商品中心

# 流程

查询商品 -> 创建订单 -> 扣减库存 -> 更新订单 -> 付款 -> 卖家发货

# 返场

再秒杀过去了几个小时,后再去做一个返场的活动。

意义:

  • 把业务上没有下单的订单取消,库存返回。
  • 技术中一些服务挂了,或者其他原因导致和实际库存不一致,也有时间做技术处理。
  • 也就是保证活动再久一点,尽可能把商品都卖掉,卖空。

# 防止机器刷单

  • 验证码的整体设计和实现。
  • 验证码不仅可以防刷,还可以有效延长下单请求的时长,更好的分散请求峰值。
  • 后端验证码框架: Happy Captcha

流程:

  1. 从Redis判断商品是否有秒杀活动.
  2. 发送后台请求申请验证码。后台返回验证码图片,并将验证码的计算结果保存到Redis
  3. 将memberId、producrId和验证码的值一起传入后台,判断验证码的正确性,Redis中的验证码要及时删除,正确后台返回一个token。这个token会传入到接下来的商品确认页面,同时会保存到Redis当中,表示当前用户有购买秒杀商品的资格。
  4. 在商品确认页面,会调用确认下单接口,进行库存和上面token的校验,具体看后面的接口。

为什么要把token拼凑到请求路径,再跳页面呢??

  • 为了安全,不参数传递减少了接口的暴力破解
  • 同时多次跳转也增加了难度。

提前发Token优化:

秒杀前设置一个预约活动。 在活动中提前发放 token。例如一个秒杀活动有20W个商品,那就可以预先准备200W个 token。用户进行预约时,只发放200W个Token,其他人也能预约成功,但是其实没有获得token,那后面的秒杀,直接通过这个token就可 以过滤掉一大部分人。相当于没有token的人都只预约了个寂寞。这也是 互联网常用的一个套路。

这个有个问题,时间长了知道这个规律了,宣传就没效果了。所以后期改成了下面的了。

后期优化:

  • 把这个token在秒杀页面做了自动获得这个token的操作。
  • 同时识别手机的机器码和判断是否被root了,就免去了验证码的操作了,但是这都是移动端做的,和我没关系。
  • 但是pc页面还是有验证码的操作,只是提示用app可以免验证码。

# 库存相关

  • 目的地 : redis的库存,mysql的库存,还有第三方服务仓库里面登记的库存。

# 核心问题

  • 并发读 : 漏斗式流量控制,对nginx、网关、jvm、redis、mysql 一层层的减少流量的到达。
  • 并发写 : MQ解耦、削峰、异步.

# 主要接口

核心的处理接口:

  • 价格计算: 营销中心等业务相关.
  • 库存处理: 高并发下会出现超卖问题,并发技术相关。
  • 以下重点针对库存的操作

# 确认下单流程

  • 代码流程: 校验数量、秒杀时间、再获得信息和用户会员积分等等信息。
  • 重点是校验: 校验是否有权限购买token -> 检查本地缓存售罄状态 -> 判断redis库存是否充足 -> 检查是否正在排队当中
  • 目的: 第一层流量接口拦截,本地售罄状态,减少网络IO, redis库存减少网络和磁盘IO.
  • 本地售罄状态在多个jvm之间同步: 不同步需要再去查redis ;用zk监听同步;用mq监听同步;用redis监听同步,性能最高(redis这种发布与订阅是没有ack的,发出去了不会管有没有收到,吞吐量相当来说就会提高,因为减少了通讯,那处理数据的能力就就会上升);

# 秒杀下单提交

  • 代码流程: 检查数量(同上校验) -> 获取产品信息 -> 验证秒杀时间是否超时(同上) -> 调用会员服务获取会员信息 -> 通过Feign远程调用会员地址服务 -> 预减库存(异步流程才需要这块,数据库/分布式锁不需要此操作) -> 生成下单商品信息 -> 库存处理

# 数据库同步操作

  • for update是MySQL提供的实现悲观锁的方式。获得用版本号在代码中来做乐观锁
  • 性能问题: 在不考虑行锁会表锁的情况下,他还会造成,数据库和java中都有大量线程阻塞等待,2个都容易都宕机。
  • 个数问题: 库存只有1个,小明下单要买2个,就会有问题,还是需要再业务层去校验。
  • 架构问题: 上面说到阻塞,1000个人来抢10商品,意味着990个请求是没有意义的。
  • 总结: 代码和业务简单,但是存在上面的几个问题。
  • 一般高并发项目是不会在数据库加锁的,他需要刷盘和是很费性能的,数据库特别脆弱的。

# zk/redis分布锁

  • 分2种,lock和trylock。
  • 不管lock和tryLock重点还是里面的操作时间长短,长的可以放到MQ做异步。
  • 分布式锁,底层一般都会有个子线程做续命操作。这个很费资源。
  • 还是会有上面的架构问题,出现。
  • gc和redis 也还是会有来不及关闭线程,导致各种崩盘。
  • 总结: 也并不能解决上面的3个问题,只是把锁的操作放到了redis,用来减轻mysql的压力。

# 预售库存

  • 如果先有库存就直接下单,如果没有库存就不能下单,报错返回给前端。
  • 这样做可以拦截大部分流量进入到数据库中,和释放jvm的线程了。

实现:

  • 但是预售库存怎么做的??? 可以用redis的原子自减(incr)操作。
  • 初始化(全量):redis中的初始数据,可以再bean的初始化同步一下库存。
  • 初始化(增量):分布式定时、其他接口额外处理(一般需要营销领导审批)。
  • 如果没库存了,我们还可以再jvm中设置本地缓存售罄状态,这样就可以不用去访问redis了。
  • 如果买了2个实际只有1个,还需要自增redis还原库存,最好还发送同步库存请求给延迟队列2分钟,去确保不会发生少卖现象。

# 库存处理

  • insert 很多表,同步的话,会有性能问题。
  • 所以改成MQ异步。
  • 订单状态也往redis存一份,以便查询。
  • 对数据库的优化有: 索引、分库分表、读写分离

好处:

  1. 异步下单可以分流、让服务器处理的压力变小、数据库压力减少
  2. 解耦的话,业务更加清晰。
  3. 天然的排队处理能力。
  4. 消息中间件有很多特性可以利用,比如订单取消。

# 处理未支付的订单

  • 添加完库存相关的订单表之后再给MQ发送延迟消息。
  • 再延迟消息消费端中,判断订单是否已支付,已支付就不做操作。
  • 未支付就做库存的回滚操作。

# 异步订单查询接口

  • 直接去redis查对应的状态,不需要jvm缓存。
  • 因为有到这里已经是下单的用户了。或者说是支付成功的用户了。
  • 重点要在库存操作、支付中,操作这个redis内容。

# 接口幂等性

概念咯

如果判定正确执行还是需要回滚,数据积累又如何避免??

  • redis incr
  • 数据库唯一主键
  • 去重表
  • 等等

# RocketMQ消息零丢失

  • 生产端:同步发送消息、重试机制、事务消息、状态
  • 服务端:刷盘存储、主从同步、 状态返回
  • 消费端:pull broker offset 消费端并且返回成功 偏移值,如果消费失败那条数据

# 多种压测方案

  • 线下压测,Apache ab,Apache Jmeter,这种方式是固定url压测,一般通过访问日志 收集一些url进行压测,可以简单压测单机峰值吞吐量,但是不能作为最终的压测结果,因 为这种压测会存在热点问题;
  • 线上压测,可以使用Tcpcopy直接把线上流量导入到压测服务器,这种方式可以压测出机 器的性能,而且可以把流量放大,也可以使用Nginx+Lua协程机制把流量分发到多台压测 服务器
  • 直接在页面埋点,让用户压测,此种压测方式可以不给用户返回内容

# 降级开关

  • 开关前置化,如apisix,在Nginx上做开关,请求就到不了后端,减少后端压力;

  • spring的降级组件。Hysix,setnel。

  • 可降级的Servlet3业务线程池隔离。

    从Servlet3开始支持异步模型,Tomcat7/Jetty8开始支持。我们可以把处理过程分解为一个个的事件。通过这种将请求划分为事件方式我们可以进行更多的控制。如,我们可以为不同的业务再建立不 同的线程池进行控制:即我们只依赖tomcat线程池进行请求的解析,对于请求的处理我 们交给我们自己的线程池去完成;这样tomcat线程池就不是我们的瓶颈,造成现在无法优化的状况。通过使用这种异步化事件模型,我们可以提高整体的吞吐量,不让慢速的A 业务处理影响到其他业务处理。慢的还是慢,但是不影响其他的业务。我们通过这种机制还可以把tomcat线程池的监控拿出来,出问题时可以直接清空业务线程池,另外还可以 自定义任务队列来支持一些特殊的业务。

# 限流&降级&拒绝服务

针对秒杀系统,在遇到大流量时,更多考虑的是运行阶 段如何保障系统的稳定运行,常用的手段:限流,降级,拒绝服务。

核心思想: 流量消峰,一是可以让服务端处理变得更加平稳,二是可以节省服务器的资源成本。 常见手段: 排队、答题、分层过滤

# 限流

  • 限流就是当系统流量达到瓶颈时,我们需要通过限制一部分流量来保护系统,并做到既可以人工执行开关,也支持自动化保护的措施。
  • 限流必然会导致一部分用户请求失败,因此在系统处理这种异常时一定要设置超时时间,防止因被限流的请求不能 fast fail(快速失 败)而拖垮系统。

# 限流算法

  • 计算器 :
  • 滑动窗口 : 根据响应速度来决定流量大小。
  • 令牌桶 : 解决流量突刺
  • 漏桶 : 均匀请求,排队

# 前端限流

js控制已售罄等等模式

# 接入层nginx限流

  • 限制连接数
  • 限制同一IP同一时间只允许有1个连接
  • 限制同一IP,平均访问 和 令牌 . (用来应对平均流量和突刺的流量就排队)

# 网关限流

  • gateway接入sentinel
  • 持久化配置改造的坑,网关规则实体转换、 json解析丢失数据
  • 维度: route维度(资源名)、自定义 API 维度(url匹配,有精确、前缀、正则)。
  • 阈值:QPS和线程数。
  • 流控方式: 失败和排队。

# 应用层限流

  • 思考: 在没有事先进行缓存预热的情况下,如何避免更多的请求直接访问到数据库?

  • 思路: 加锁?锁id吗?缓存雪崩呢?锁次数?锁多少次呢?修改代码?; 锁次数不就是信号量吗? 单机/多机 。

  • 利用sentinel的关联DB资源,排队、阈值控制QPS。是不是就能完成上面的了??

  • 注意编码式是基于代理的,spring代理失效的解决方案。

用sentinel的应用层限流就行了。下面大致介绍一下再应用层的模式:

  • 资源名,对应springmvs的应用入口;
  • 阈值,有QPS和线程数;
  • 流控方式: 失败、排队、预热; 预热是一点点的流量放大。
  • 流控模式: 直接、关联、链路; 关联就是以关联的为主,链路就是以起始的为主。

# 热点参数限流

缓存热点访问出现期间,应用层少数热点访问 key 产生大量缓存访问请求,冲击分布式缓存系统,大量占据 内网带宽,最终影响应用层系统稳定性;

  • 解决方案: 二级缓存、限流。

  • sentinel针对不同的请求参数做不同的限制。

  • 注意事项: 热点规则需要使用@SentinelResource("resourceName")注解;参数必须是7种基本数据类型才会生效

# 热点探测功能

上面说了二级缓存,是缓存所有redis中的数据吗??? 那肯定不是的应该只缓存热点的key,那么如何快速且准确的发现热点访问key???

  • 代理redis操作或者再封装缓存的API.
  • 再里面做阈值限定,每天超过1W次的key就是热点,
  • 同时配合配置中心,通信模块做管理和同步。
  • 热点探测一般再网关层做,公共的处理。

# 降级

降级就是当系统的容量达到一定程度时,限制(比如展示从20到5)或者关闭系统的某些非核心功能,从而把有限的资源保留给更核心的业务。

# 分类

  • 运维维度,自动化运维: 超时次数、失败次数、故障统计, 限流也算。
  • 运维维度,手动开关降级 : 限制某些服务访问,灰度发布。
  • 功能维度,读服务降级: 把页面静态化、缓存。
  • 功能维度,写服务降级: 同步转化成异步,先写缓存(比如预售库存)。
  • 系统维度,页面js降级、售罄按钮
  • 系统维度,接入层降级、Nginx降级。
  • 系统维度,应用层降级,编码实现是否关闭响应。

# 熔断降级

  • OpenFeign整合Sentinel,feign接口配置fallbackFactory

降级规则有

  • 策略: 慢调用比例、异常比例、异常数
  • 最大RT和最小RT、阈值。
  • 熔断时长: 用于检测恢复正常访问。

# 拒绝服务

拒绝服务可以说是一种不得已的兜底方案,用以防止最坏情况发生,防止因把服务器压跨而长时间彻底无法提供服务。当系统负载达 到一定阈值时,例如 CPU 使用率达到 90% 或者系统 load 值达到 2*CPU 核数时,系统直接拒绝所有请求,这种方式是最暴力但也最有 效的系统保护方式。

  • 利用nginx插件 (opens new window)设置过载保护
  • Sentinel提供的系统规则限流

# Sentinel提供的系统规则限流

  • Load 自适应(仅对 Linux/Unix­like 机器生效,默认)
  • CPU usage(1.5.0+ 版本)
  • 并发线程数
  • 入口 QPS

# 总结

一般再网关监控

  • cpu大于60%触发,扩容可能阻塞业务的对应节点。
  • cpu大于80%做限流。
  • 当cpu大于100%,开始做过载保护,拒绝服务请求。

服务服务之间慢服务太多,做各个请求的熔断降级。

# 分布式日志系统

见ELK中的日志系统。

# 架构组件

  • Nacos 注册中心
  • openfeign 服务间的调用实现
  • nacos 配置中心
  • gateway 网关服务
  • Skywalking 链路追踪
  • Elasticsearch 实时的分布式搜索和分析引擎
  • Logstash 解析 Trace ID,实时数据收集引擎
  • kibana 为Elasticsearch提供分析和可视化的 Web 平台
  • FileBeats 收集本地日志
  • Prometheus 监控采集
  • Grafana 展示和报警
编辑 (opens new window)
上次更新: 2023/01/24, 15:21:15
低代码平台
产品杂记

← 低代码平台 产品杂记→

最近更新
01
架构升级踩坑之路
02-27
02
总结
02-27
03
语法学习
02-27
更多文章>
| Copyright © 2021-2025 Wu lingui |
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式