一、百万级并发优化实战 #
1.1. 背景 #
- 主场景为认证、资源列表下发、环境上报、资源访问鉴权
1.2. 如何优化点分析 #
本地集群瓶颈
- 模拟高峰流量,分析到是cpu占用过高,然后是数据库瓶颈,然后是内存
1.3. 优化过程 #
1) api级别短时效缓存 #
- 业务处理多个地方调用接口查询数据库,对同一个数据进行多次查询不能每一次都读io
- 对接口进行api级别缓存,业务代码可直接调用数据库接口查询,接口内部会对查询数据进行缓存
2) 数据预处理 #
- 对于每次都需要进行复杂转换和计算的大数据,提前进行预处理储存到缓存中,每次只需要读取结果即可
3) 运行时数据缓存(时间局部性) #
- 对于登陆访问过程中通过不同api查询同样的数据,将数据和对应id进行缓存
- 第一个api调用查询数据库然后缓存,后续直接通过id查询即可,不需要再查询数据库
4) 非核心业务降频排队 #
- 非核心业务可能会占用数据库并加锁,如果是io密集型,将其隔离并进行低优先级排队处理,防止阻塞核心业务
5) 连接池分离 #
- 不同业务的redis和数据库的连接池分离,核心业务和非核心业务分离,防止非核心业务占用核心业务连接池导致无法访问
6) 数据库写操作有区分按不同优先级同/异步落盘 #
- 使用代理服务进行sql语句插入,可以进行批量插入和数据可视化
- 仅针对非强实时性要求的数据
- 可以有日志和重试可靠性处理
7) 合并api请求数据 #
- 当某个请求过程中,api可以复用下发数据,减少api调用次数
- 不止客户端到服务端的数据复用,服务端内部的链路也同样可以复用数据
8) 高峰期分析 #
- 对于真实场景高峰期的cpu分析,找到是否数据库索引存在问题
- 对于需要计算类型的,提前进行预处理缓存,防止索引没命中导致的扫表
1.4. 优化效果 #
- 一个主节点带两个工作节点,40w并发15min登陆上线完成,cpu占用不超过30%
二、微博场景 #
2.1. twitter推送 #
场景 #
- 用户发布tweet,关注者在主页可以查看到其发布的消息,按照时间线排序
- 目标,3000w关注者5s内完成发布接收
方案 #
方案1
- 使用关系型数据库,每个用户发tweet插入到全局tweet库
- 每个关注者查询全局tweet库,根据自己关注的用户进行查询,获取到按照时间线排序的结果
方案2
- 使用redis对每个用户维护一个时间线缓存,类似邮箱
- 发布者发布tweet后,查询关注者,将tweet插入到所有关注者的时间线缓存中
对比
方案1 | 方案2 | |
---|---|---|
优点 | 实现简单,发布tweet写入少 | 读负载降低,只需要读取缓存即可 |
缺点 | 全局tweet的读负载压力与日俱增 | 实现复杂,发布tweet写入多个关注者缓存中,大V情况下写入负载很高 |
结论 #
- twitter最后使用方案2的稳定实现,但是对于大V,使用方案1。使用混合方案可以达到很好的表现
三、秒杀系统 #
参考 https://blog.csdn.net/flynetcn/article/details/120228586#comments_30579499
秒杀系统是针对短时间大量并发场景所考虑的系统,如果系统本身性能就存在优化点,不在这里考虑范围,参考上面的百万并发优化
1. 电商系统架构 #
- 负载均衡层使用nginx,nginx的最大并发预估为10W+,万为单位
- 应用层使用tomcat,最大并发预估为800,百为单位
- 持久层
- redis缓存的并发预估为5W,万为单位
- mysql的最大并发预估为1000,千为单位
如何扩容 #
- 系统扩容,水平扩容和垂直扩容,增加设备机器数量或配置
- 缓存,本地缓存或集中式缓存,减少网络IO
- 读写分离,增加机器并行处理能力
2. 秒杀系统特点 #
2.1. 业务特点 #
12306举例,春运访问量特别大,平常访问量很平缓,春运期间会出现访问量瞬时激增
小米秒杀系统,10点开售商品,10点前访问量比较平缓,10点出现并发量瞬时突增的现象
所以秒杀系统的特点
- 限时、限量、限价
- 活动预热,开始前用户可以查看活动信息
- 持续时间短,购买人数庞大,商品快速卖完
2.2. 技术特点 #
- 瞬时并发量高,大量用户同一时间抢购商品
- 读多写少,由于商品数量少,但是购买用户多,所以大量查询而购买少
- 流程简单,下单减库存
3. 秒杀系统流程 #
- 准备阶段,系统预热,由于用户不断刷新秒杀界面,一定程度上可以将一些数据存储到缓存中进行预热
- 秒杀阶段,这个阶段会瞬时产生高并发流量,对系统资源造成巨大冲击,需要做好系统防护
- 结算阶段,完成秒杀后的数据处理工作,数据一致性,异常处理,商品回仓处理等
对于这种短时间大量请求的系统来说,扩容不太合适,因为大部分场景下扩容的机器在闲置,仅部分场景下才需要这么大的流量请求
4. 秒杀系统方案 #
考虑几个措施提升系统的性能
4.1. 异步解耦 #
整体流程拆解,核心流程通过队列进行控制
4.2. 限流防刷 #
控制整体流量,提高请求门槛,防止刷单,避免系统资源耗尽
4.3. 资源控制 #
整体流程中对资源调度进行控制,扬长避短
由于应用层的并发量比缓存少很多,而且比负载均衡层也少很多,考虑在负载均衡层访问缓存,避免到应用层的损耗
并发量太高也可以将用户请求放到队列进行处理,弹出排队界面
5. 秒杀系统时序图 #
5.1. 同步下单流程 #
@startuml
participant user
box server
participant nginx
participant service
database redis
database mysql
end box
user->nginx: 1. 发起秒杀请求
nginx->service:
service->service: 验证码是否正确
service->service: 活动是否结束
service->service: 是否黑名单(拦截器统计访问频次)
service<->mysql: 真实库存是否足够
service<->redis: 扣减缓存中的库存
service->service: 计算秒杀价格
service->user
user->nginx: 2. 提交订单
nginx->service
service->mysql: 订单入库
service->mysql: 扣减真实库存
service->user
@enduml
大部分网上的秒杀系统都是这个方案,在设备性能高且用户量不大的情况下,此方案是可以的。但是放到小米、淘宝、天猫、京东等秒杀和12306的场景,这个系统明显会被玩死
5.2. 异步下单流程 #
@startuml
participant user
box server
participant nginx
participant service
database redis
database mysql
end box
user->nginx: 1. 发起秒杀请求
nginx<->redis: 验证码是否正确
nginx<->nginx: 是否限流
note over nginx
这里可以做限流判断,如果出售1000个商品
消息队列存在1000个请求,后续请求直接返回已售完
end note
nginx->nginx: 加入消息队列
nginx->user: 发送排队界面
nginx->service
loop
user->nginx: 2. 短轮询探测是否获得秒杀资格
nginx->redis: 查询是否生成Token
nginx->user
end
alt 异步执行
service->service: 活动是否结束
service->service: 是否黑名单(拦截器统计访问频次)
service<->redis: 扣减缓存中的库存
service->redis: 生成Token
end
alt Token过期
service->redis: 回滚秒杀商品库存
end
user->nginx: 3. 秒杀结算
nginx<->redis: 验证下单Token
nginx->service
service->service: 加入秒杀购物车
service->user
user->nginx: 4. 提交订单
nginx->service
service->mysql: 订单入库
service->mysql: 扣减真实库存
service->redis: 删除Token
service->user
@enduml
这里我们把限流前置,将高并发的削峰前置到请求阶段,业务层的流量就不是很高了。如果同步里面将限流放到业务层会让业务层压力大增,很有可能限流无法正常运行
6. 高并发“黑科技”与制胜奇招 #
假设使用redis做缓存,redis的并发量在5W左右,而商城需要支持并发到100W左右,全部打到redis上会导致redis挂掉,如何解决这个问题。
6.1. 分库 #
在Redis存储秒杀商品的库存数量可以分割来存储,对于每个id加上数字标识来存储。对key进行hash运算时,得到的结果不一样,大概率不在同一个槽位中。这样可以提升Redis处理请求的性能和并发量
6.2. 移花接木 #
秒杀场景中,秒杀商品被瞬间抢购一空。如果用户再进行请求,转到业务层处理,再由业务层访问缓存或数据库,而业务层并发访问量是以百为单位的,大量请求积压也会降低并发度
解决这个问题我们将商品扣减判断放到负载均衡层,使用lua脚本直接计算是否存在库存,不存在直接返回已售完,不再经过业务层处理。