高并发系统实战派

购买链接

高并发系统的认知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 一、什么是高并发系统
1. 高并发:大流量冲击
2. 指标:QPS(每秒请求数)、TPS(每秒事务数)、PV(访问量)、UV(独立访客)
3. 单体、分布式、微服务 ===> No Silver Bullet(没有银弹)
# 二、两个高并发系统
1. “秒杀”
1. 秒杀特性
2. 秒杀设计原则
- 数据要少
- 请求要少
- 路径要短
- 尽量异步
- 避免单机
3. 动静分离
4. 热点数据
5. 流量分层:CDN层、反向代理层、后端服务层、数据库层(行锁优化,一行变三行)
6. 库存扣减:
× 下单后扣减库存:别人恶搞你,就是大量下单,但是不支付。
× 支付后扣减库存:大量用户同时抢,会出现超卖。(这种除非后续商家进行补货)
√ 预扣减库存:下单后,锁库存一段时间(如:30min);超时则释放库存;付款时,校验有效期,有效则支付,无效则再锁库存,锁失败则“库存不足”。
=> 扣减:update stock set num = $new_num where pro_id = $pro_id and num = $old_num
=> 优化:利用缓存 + 异步处理
2. 社会化治理
------------------------------------------------------------
高并发实战
2022-11-05 周六
1~39页(上午1~19,晚上20~39,看的好困)

39~61(第一篇看完,现在2022-11-06 00.24了。)
=>没想到案例2之后,突然就nacos源码,事务xa 事务消息。
【因为之前的基础,现在看这个,巨快,又学习了2阶段、3阶段基于xa=强一致;tcc服务端,强耦合;事务消息最终一致性,基于base理论;】

搭建生产级系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 三、生产级系统框架设计细节
1. 幂等性
- 幂等场景:重复点击/页面回退 导致再次提交;网络异常,框架重试;
- 数据库幂等:
非幂等 insert into(id, money) values(1, 100);
幂等 select money from user where id = 1;
幂等 update user set money = money + 100 where id = 1;
非幂等 update user set money = 200 where id = 1;
幂等 delete from user where id = 1;
- 避免重复提交
- 全局唯一ID:雪花id
- token + redis
1. 进入订单确认页面,获取 token,返回 UUID,把 UUID写入 Redis并设过期时间。
2. 点击提交订单,token当做参数,传入下单接口
3. 下单接口:判断token存在redis,删token,处理后续业务;不存在redis,表示重复请求;
=> 注意:token获取,对比,删除(原子操作,可使用 lua脚本)
- 数据库悲观锁、乐观锁、分布式锁等...
- 避免ABA问题:版本控制(版本比较并自增)
update order set price = 80, version = version + 1 where oder = 10001 and version = 1;
2. 接口参数校验 @Validated
3. 统一异常处理 @RestControllerAdvice + @ExceptionHandler
4. 统一封装 Response
5. 异步任务 @EnableAsync + @Async
6. DTO + DO互相转换
7. RESTful API
8. Swagger 可视化API文档
# 四、快速部署
1. 正向代理、反向代理
2. Nginx使用
------------------------------------------------------------
2022-11-19 周六
61~135(第四章看完,现在 2022-11-19 12.44了。看的贼快,)

(主键 雪花id;token+redis lua获取删除;)
(乐观锁版本号)
(正方向代理)

116,通透。正向代理,反向代理。中介,二房东。(2022-11-19 12.27 周六,在游乐场,小唠看别人枪,自己也哭哭咽咽的要。)
  • 正向代理、反向代理
    在这里插入图片描述

生产环境监测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 五、生产环境监测

1. 平均负载
2. 上下文切换
- 进程上下文切换
- 线程上下文切换
- 中断上下文切换
3. CPU瓶颈
4. CPU性能优化方法论
陶攀峰:算法优化、异步处理、多线程注意上下文切换、利用好缓存
5. 定位和处理内存泄漏问题
6. JVM内存模型、Java如何在JVM中运行、JVM优化思路
------------------------------------------------------------
2022-11-20 12.14
(136页开始,155页结束)
平均负载,活跃进程平均数。(正在使用cpu,等待cpu,等待io不可被中断)
cpu使用率,
cpu上下文切换,
1,进程上下文切换
2,线程上下文切换
3,中断上下文切换

vmstat 5
每隔5秒输出一次。
  • TOP命令,CPU 飙高,CPU架构,进程上下文
    在这里插入图片描述

应用集群化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 六、应用集群化

1. 什么是集群?
高可用集群
一个鸡掰,另外一个还可以上,避免单机故障。
高性能集群
负载均衡集群 => 例如:一个web服务部署多个节点,使用负载均衡器来请求路由。
计算集群 => 例如:需要节点连接到一起来执行任务,来突破单节点的算力瓶颈。
存储集群
紧耦合架构:将数据划分为小块,将它们分别存储多个服务器中。
=> 例如:Redis hash slot
松耦合架构:每个节点都可以存储所有数据,类似主从架构,但每个节点之间会进行一定的同步。
=> 例如:Kafka 集群同步分区数据
2. 集群有什么好处?
- 扩展性 => 可以增加节点,使多台性能低、价格便宜的服务器连接一起(集群扩展成本最低)
- 可用性 => 上面已经说了,避免单机故障。“一个鸡掰,另外一个还可以上,避免单机故障”
- 易管理 => 节点挂了,自动下线,暂时不提供服务;节点恢复,自动上线,继续提供服务;(不需要人工干预)
3. 集群和分布式有什么区别?
- 集群系统:同一业务部署多个服务器上,同时对外提供服务。
- 分布式系统:同一业务被拆分成多个子业务,分别部署在不同的服务器上;这些子业务共同协作一件事,让用户感觉像是一个系统提供服务。

所以说,多台服务器同时提供服务的系统,可以是集群系统,也可以是分布式系统,并不是说把多台服务器堆在一起就是分布式系统。
=> MySQL 的主从架构、双主架构都不属于分布式系统(都是集群系统)。

举个栗子(如下图):
1. 公司初期,由于业务简单,只需要一个全栈工程师就可以完成所有业务开发工作。(单机架构)
2. 随着公司发展,业务增加,一个工程师完成不了。又招聘了一个全栈工程师来做新业务。(这两个工程师就是集群架构)
3. 随着公司继续发展,当前的全栈工程师不能满足公司业务发展的速度了。
这时进行拆分,分别招聘前端、后端、测试等。于是,这些工程师互相协助,共同完成公司业务开发。(这些工程师就是分布式系统)
4. Nginx 搭建与负载均衡配置
5. Linux服务器机器:配置hosts、设置SSH免密登录
  • 单机架构、集群架构、分布式架构
    在这里插入图片描述
    167页
    【日期标记】2022-11-23 07:49:26 以上同步完成

缓存设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 七、缓存设计

1. 缓存分类
- 访问位置
- 本地缓存:客户端缓存、CDN缓存、反向代理缓存(Nginx)、应用程序缓存(Guava Cache、Map、Set)
- 远程缓存:Redis
- 存储介质:内存型缓存、持久化型缓存
2. 缓存的坏处
- 增加系统复杂度;
- 部署及运维成本高;
- 数据不一致问题;
3. 缓存的读写策略
- 业界最常用的:Cache Aside Pattern
=> 读:先读cache,命中直接返回;未命中,读db,写cache。
=> 写:更新db,删cache。
问题1:“先更新db,再更新cache”
更新db 50
更新db 80
更新cache 80
更新cache 50
问题2:“先删cache,再更db”
删cache
读cache,未命中
查db 80
写cache 80
更新cache 50
4. 多级缓存
CPU也有多级缓存:L1、L2、L3
CPU的频率非常快,快到内存跟不上它,结果就是“在处理器时钟周期内,CPU需要经常等待主存”,造成资源浪费。
多级缓存可以解决CPU运行速度与内存读写速度不匹配的问题。

用户请求 -> 客户端缓存 -> CDN缓存 -> 反向代理缓存 -> 远程缓存 -> 应用程序缓存 -> 数据库
- HTTP缓存
强制缓存:根据过期时间来判断缓存是否到期
协商缓存:根据标识判断资源是否更新
5. 高并发,如何找到热点key?
为何找到热点key,为了防止并发,可能一瞬间来了大量请求。
=> Redis单节点抗5w并发,此时某个热点 key一下来了50w请求,就会有可能宕机。
=> (Redis hash slot 可能导致一个热点key只在一个节点上)

探测热点key
- 离线计算:比较简单,只是效果不太精确,不能自动发现
- 实时计算:可使用大数据的流式计算,spark、flink来统计数据的访问次数。
举个栗子:
日志收集器收集后端服务日志,同步到 Kafka,Flink实时计算Kafka,统计次数,如果热点key,发到 Zookeeper,
后端再监听 Zookeeper,如果有变更,会去DB读取热点数据,写入到本地缓存中。(这样集中式的缓存就分散到了后端节点)
6. 数据不一致问题
- 全量同步:准备好热点的key,进行全量同步至缓存。
- 增量同步:如 阿里巴巴的 Canal框架来读取 Binlog来同步至缓存。
7. 缓存淘汰问题
- 缓存数据到达最大缓存:FIFO、LFU、LRU
- 没有达到最大缓存:
- 定时删除:设置定时任务,规定时间检查并删除过期key(占CPU时间,影响服务器响应时间和吞吐量)
- 惰性删除:使用时,判断key是否到期。不过期,直接返回;过期,则删除;(浪费太多内存,有内存泄漏风险)
- 定期删除:每隔一段时间删除一次(以上两者的折中方案,需要合理设置执行频率和时长)
=> 执行太频繁,或 执行时间长 => 退化成定时删除。
=> 执行太少,或 执行时间短 => 退化成惰性删除。

Redis:惰性 + 定期(两者配合使用,合理地在使用CPU和内存浪费取得平衡)

182页


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
8. 缓存组件的选择
- Memcached 本地缓存,重启丢失
- Redis 的原理

Redis 是基于 ANSI C语言编写,k-v存储组件,所有数据都在内存中,可以用作缓存、数据库和消息中间件。

# (1)Redis 的特性
redis k-v存储组件,支持8种核心数据类型,每一种都有相应指令。性能高,单线程:10w QPS。
- 持久化:可保存到磁盘,重启不丢失
- 数据备份:支持 Master-Slave模式
- 原子性:所有操作都是原子性的,同时还支持对几个操作合并成原子性执行(LUA脚本)。
- 集群特性:可以自动或手动将 key按 hash算法分散到不同节点,容量不足,可以使用迁移指令把其中一部分key迁移到其他节点。

# (2)高性能
Redis基于 Epoll事件模型开发,可进行非阻塞网络I/O。
所有操作都在内存,单线程处理,不存在竞争,所以无锁,也无上下文开销。

除主进程外,还会有以下3种场景开辟子进程处理其他任务。
- 收到 BGREWRITEAOF(background rewrite aof)命令时,fork子进程,子进程写入重建数据库的所有命令。
=> (实际实现:会读取数据库所有数据类型的数据,把数据映射为 相关的set命令,追加到 AOF。)
=> (例如:mysql select之后,再把 select的结果映射为 insert语句保存)
=> 写入结束时,子进程通知父进程,父进程会将新增的写操作追加到新的 AOF文件中替换老的 AOF文件,并且重命名。
=> https://redis.io/commands/bgrewriteaof/
- 当收到 bgsave 命令时,fork子进程,子进程将内存中的所有数据通过快照的方式持久化到磁盘(RDB文件)。
- 需要全量复制时,fork子进程,子进程将数据库快照保存到RDB文件。写完 RDB文件后,Master节点会把 RDB文件发给 Slave节点,
=> 并且后续新的写指令都同步给 Slave节点。
=>(实际实现:Slave节点会作为Server,Master作为Client,Master作为 Server收到用户写命令后,会作为 Client发送给 Slave)

Redis主进程中,除了用于网络I/O和命令操作的主线程外,还有 3个辅助的 BIO线程(如下图):
- 将 AOF缓存数据刷盘的 fsync线程
- 关闭文件的 close线程
- 清理对象的 clear线程(上面我们所说的定期删除策略)

Redis在启动时,会将 3个BIO线程(fsync线程、close线程、clear线程)都启动。
启动之后,这 3个线程进入休眠状态以等待任务的到来。
需要执行的相关类型后台任务时,Redis会先构建一个 bio_job 结构以记录任务参数,然后将 bio_job尾插到任务队列,接着唤醒 BIO线程(即开始执行任务)。

# (3)Redis集群管理的 3种方式
- Client分片访问:进行缓存时,Client 对key进行hash计算,然后根据取模结果来选择 Redis的实例
- 使用代理:在Redis前面增加一层Proxy(代理),把路由策略、后端的Redis状态维护工作都放到Proxy中进行,Client直接访问Proxy。
=> 若后端Redis需要变更,则只需要修改 Proxy配置即可(陶攀峰:我没理解)
- Redis集群:从Redis 3.0开始引入槽(slot),Redis集群中一共有 16384个槽,每个key都会通过 CRC16算法校验后对 16384取模,取模的结果决定存放哪个槽中。
=> 后面访问时,先对key进行hash计算找到对应的槽(slot),然后访问槽(slot)所在的Redis实例。
=> (扩缩容:在线通过 cluster setslot 和 migrate指令将slot中所有的key迁移到其他节点)
  • Redis 主进程中的四个线程
    在这里插入图片描述

187页
【日期标记】2022-11-23 09:51:43 以上同步完成


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
9. 缓存分布的设计
1. 分布算法
- 取模算法
=> 实现:缓存key做hash,对总节点数取模。
=> 缺点:增加/减少节点,取模结果会变化,从而造成缓存失效。
- 一致性hash算法
=> 解决问题:增加/减少节点造成命中率下降(缓存失效)。
2. 读写访问
- Client读写:直接进行读写,节点变更要通知所有Client。
- Proxy读写:读写路由,缓存分布算法和节点变更都由Proxy处理。(性能损失,多了一层Proxy)
- Redis常见数据类型
- string字符串
=> 场景:常见k-v,计数之类的(如:点赞数、粉丝数等)
- hash哈希:value类似Java HashMap
=> 场景:存储对象。例如:一个用户信息对象,用户对象包括姓名、年龄、手机号码等。
- list列表:双向字符串链表
=> 场景:构建消息队列、社交热门信息等。
- set集合:string类型无序集合,不重复
=> 场景:社交系统中,用户关注的人放入一个集合中,所有粉丝存在另外一个集合中,
Redis提交两个集合的交集、并集、差集,可以非常方便实现共同关注、共同喜好、二度好友,
- sorted set有序集合:也被称为zset,string类型集合,不重复,按照分数从小到大排序
=> 场景:排行榜、实时刷新榜单;学生成绩,某个成绩范围内的学生;带权重的队列;
- bitmap位图:二进制表示的byte数组,只有0和1两个数字。每个bit的位置就是offset偏移量;存储成本非常低;
=> 场景:系统中的新闻、信息流设置的标签(如:军事、娱乐、视频、图片、文字等),使用bitmap存储这些标签,所有标签的bit位设置为1;
在线用户及活跃用户;
- geo地理位置
=> 简介:如微信附近的人,美团/饿了么附近的美食,滴滴打车附近的车等,都需要地理位置信息进行搜索。
=> 实现:geo是基于 sorted set封装实现的。储存某个点位置时,Redis先利用Geohash算法将二维经纬度映射编码成一堆52位整数值,
将位置名称、经纬度编码 score 作为键值对储存到分类 key对应的 sorted set中。
=> 为什么转一维?geo类型利用 Geohash算法将二维的经纬度转为一维的整数值,为了方便对地理位置进行查询、测距和范围搜索。
=> 场景:查询某个地方的某个具体位置、当前位置到目的地之间的距离,以及附近的人、餐厅和电影院等。
- hyperLogLog基数统计
=> 基数是什么? 比如数据集 {1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集为 {1, 3, 5, 7, 8},
基数(不重复元素的个数)为5。基数估算就是在可接受的误差范围内快速计算基数。
=> 简介:对巨大数量的元素做统计时,利用hyperLogLog只需要很小的内存即可完成。
hyperLogLog不保存元数据,只记录待统计元素的估算数量,这个估算有0.81%标准差的近似值。
在大多数业务场景中,对海量数据而言,不足 1%的误差是可以接受的。
=> 场景:统计过程中不记录独立元素,占内存非常少,非常适合统计海量数据。
大型系统中,统计每日、每月的UV(独立访客数),或统计海量用户搜索的独立词条数。
  • 取模算法、一致性hash算法
    在这里插入图片描述

1
10. Redis主从:读写分离

关于主从复制、读写分离,之前也有提到过。参考这里:主从复制,读写分离

  • Redis 读写分离、主从复制
    在这里插入图片描述

1
11. 缓存雪崩
  • 缓存雪崩
    在这里插入图片描述

1
12. 缓存穿透
  • 缓存穿透
    在这里插入图片描述

1
13. Redis集群
  • Redis集群
    在这里插入图片描述

1
14. 实战:朋友圈点赞、查找附近的人
  • 朋友圈点赞、查找附近的人
    在这里插入图片描述

212页
【日期标记】2022-11-24 09:36:51 以上同步完成(未整理)
【日期标记】2022-11-25 10:20:53 以上同步完成

(因为之前有看过《Redis设计与实现》,在读这一块可以很快,但是我还是慢慢理解了一下里面不一样的点,比如:GEO二维变一位 52位整数。MOVED 不同处理 Dummy客户端,Smart客户端。…等)
(读到这里,我确定的认为,这是一本好书,讲的不晦涩,也很深入一点,还讲到了一些实操,以及实际会遇到的问题点,还有对比总结和分析,)

第八章:存储系统设计

1
2
3
4
5
6
7
1. 池化技术
解决创建、销毁,导致开销性能过大问题。
2. 线程池
- 解决生命周期系统开销问题,少量线程执行大量任务
- 根据配置控制线程数量,不够就创建,太多就回收,避免线程浪费
- 统一管理,管理队列和线程,也利于统计(统计已执行过的任务数量)
3. 协程池(如下图)
  • 协程:应用程序创建
    在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
主从复制:基于Binlog来同步,进行数据重放

为什么要读写分离?
大量的读要是一致的,可以加缓存。
如果每个人的用户都不一样的话,就会大量的读请求去查数据库。

读写分离
- 硬编码
- 第三方组件:Sharding-JDBC
- 代理中间件:MyCat
  • MySQL数据库 主从复制
    在这里插入图片描述
  • 读写分离、数据不一致(主从延迟)
    在这里插入图片描述

1
分库分表
  • 数据库 分库分表
    在这里插入图片描述

239页
【日期标记】2022-11-30 09:39:33 以上同步完成(未整理)
【日期标记】2022-12-02 11:04:27 以上同步完成

第九章:搜索引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
倒排索引
- 正排索引:文档找是否有关键词
- 倒排索引:关键词找是否有关联文档
索引与分片
- 1索引,多分片(扩展写入性能,不可变);1分片,多副本(扩展读取性能,可变);
- 分片:hash取模
- 副本
主分片(写入,不可扩展) ===> 重建分片需重新索引,比较耗时,一般不采取
副本分片(同步主,不可写,可读,可扩展)

性能优化
- cache大一点内存:增大命中率
- 数据预热:提前放入
- 冷热分离:不同索引
- 避免join:设计文档类型要避免复杂查询,复杂查询代码中做
- 避免大分页:一页页查询,避免跳转任意一页

256页
【日期标记】2022-12-01 09:02:20 以上同步完成

第十章:消息中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
消息幂等
生产者:没必要,如果非要做,可以 <producer_id, msg_last_id>,生产消息与最后一条消息对比(相等,丢弃;不相等,存储);
消费者:消息消费后,入库,消费时,看库中是否有此消息(有,不处理;无,处理);
结合业务做幂等:
- 乐观锁:数据库增加version版本号字段
- 写数据库:根据主键查询,有更新,无插入
- redis天然幂等,无需担心
- 数据库唯一键约束,插入重复会报错

消息顺序性
单Consumer,分多个内存队列,每个内存队列只分配给一个线程

消息积压
优化消费者代码,提升消费者数量,消费者最大为 Partition数量。
=> 可以使用每个消费者对应一个多线程来提升消费速度
=> 也可以增加 Partition,再增加 Consumer数量
  • RocketMQ 架构原理
    在这里插入图片描述

281页
【日期标记】2022-12-02 08:21:12 以上同步完成


第十一章:微服务系统拆分
299页(不打算记录)


第十二章:API网关

1
2
3
4
zuul => servlet 阻塞式I/O
SpringCloud Gateway => Reactive + Netty 非阻塞式I/O
- 路由,过滤,断言
- redis限流

315页

第十三章:高并发设计

1
2
3
4
5
6
7
8
9
10
11
高并发通用设计
1. 负载均衡(分散流量至多服务节点)
2. 分布式缓存(数据库压力过大,缓存抗流量)
3. DB读写分离(读多写少,读和写分开,读可以扩展)
4. DB分库分表(单库有瓶颈,多库分别在多数据库节点,分摊请求减少压力;单表数据上限 => 多表来分摊数据)
5. NoSQL(非关系型数据库)、MQ(异步,提升吞吐,削峰填谷)、ES(复杂搜索)
6. 微服务(服务拆分,部分服务复用)

性能提升
- 垂直伸缩 => 单机提升性能(更快的CPU,更多核CPU,更大内存,更快网卡,更多磁盘)
- 水平伸缩 => 多个服务器组成分布式集群(系统性能无极限)
  • 高并发设计
    在这里插入图片描述
    326页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
这四章,看的贼快,感觉没太大用途!

第十四章:秒杀系统
341页

第十五章:社会化治理系统
358页

第十六章:运维
373页

第十七章:监控
388页
【日期标记】2022-12-02 09:28:08 以上同步完成

聊天结束