求教一下各位大佬,
面试老是被问到系统瓶颈的问题,如果现在正常运行的系统,要改造提高 10(甚至 100)倍的 qps,会遇到哪些问题、瓶颈?
关于这类的问题,应该如何去分析和描述?
1
Kirsk 2021-02-02 00:38:31 +08:00 via Android
鬼知道 具体问题具体分析 稀里糊涂的提问的人也不知道 走过场看面试者对不对自己胃口=你猜猜看我最爱吃什么
|
2
yyyfor OP @Kirsk 昨天遇到的问题还算具体的。redis 每秒十万个 get 请求,会不会有问题? 如果没有问题,每秒请求量继续增加,问增加十倍或者一百倍,制约请求量增加的瓶颈因素是什么?
|
4
yyyfor OP @Kirsk 面试官已经假设是在一台机器上了。我当时觉得应该从 cpu 、内存、io 去分析,但是具体分析的过程不知道如何表述比较准确
|
6
yyyfor OP @Kirsk 面试官想要具体的分析过程😂比如 cpu 、内存、io 哪个最有可能最先成为瓶颈? 然后卡了好久。感觉面试官想要从具体的数据量级去分析这个问题
|
7
lewis89 2021-02-02 10:02:00 +08:00 30
@yyyfor #4
问 redis,10 万请求的,这个时候制约 redis 的是单核心问题吧,因为单线程模型最多能打满一个 CPU,上集群就可以打满多个 CPU 实例,然后上集群就要涉及到热点 key 的问题,如果 100 万的 key get 操作 90%路由到一个 redis 实例上 ,那么又回到老问题,你的 hash 算法是否合理,真实的业务场景要不要解决热点 key 的问题,甚至可以在 redis 的路由上再开发一套 ip 随机 负载均衡的 分片,把 redis 的 get 操作路由到不同的 slave 节点上,此时多个 slave 副本又要考虑 CAP 的问题,是保证强一致性 所有的 slave 副本跟 master 节点保障 强一致性,那么就是 CP 系统 此时就是牺牲高可用,如果是要高可用,那就是异步复制 AP 系统,牺牲强一致性 来保障高可用 /高并发读,此时 master slave 副本肯定会存在数据不一致的问题,另外 redis 本身是写入内存的,如果你想通过写入硬盘 AOF 这种操作 将日志写入这些低速设备来保障最终一致性也是不可行的,因为低速设备(持久化)本身就会拖慢系统的响应速度。 另外瓶颈的问题,首先可以从网络层分析,是否存在 TCP SYN 超时大量 socket 描述符没有被回收的情况,因为 C10K 的问题我模拟过,默认的 ubuntu 设置 存在 fd 描述符数量上限以及 socket 超时描述符未被回收的情况,如果是大量的短连接频繁创建销毁会触发这个问题。 然后就是考虑 TCP 的拥塞问题,是否可以考虑使用快启动算法,来避免滑动窗口缩小,然后导致原本 2-3 个报文就搞定的事情,结果因为拥塞的问题 多好几个 ip 报文才发送出去。 其余的瓶颈可以查看机械硬盘的 IO 看是否有大量的随机读写 拖慢了机械硬盘的速度,导致大量的 CPU 空闲,然后所有进程都在等待磁盘 IO 内存方面可以排查页交换的问题,是否存在频繁的内存页从低速磁盘设备换出到物理内存,一般都是不建议服务器使用 swap 空间 CPU 飙升的话,可以看是不是 C10K 大量线程被频繁唤醒,然后频繁进入内核态上下文切换带来的非常大的开销 性能瓶颈的话其实还是从 X86 的存储体系结构来分析,或者从网络层去分析,就差不多了 |
8
qwer666df 2021-02-02 10:07:41 +08:00
@lewis89 #7 请教一下, 昨天有一个类似的问题, 关于一个活动用户发短信的场景 他意思一个线程只能处理 10w 个任务, 然后系统如果这个时候只开了 5 个线程, 也就是 50w 任务, 但是活动太过火爆了之后. 只能突然来了几百万的请求, 这时候怎么处理, 不考虑服务器手动加脚本的操作
|
10
lewis89 2021-02-02 10:18:49 +08:00
@qwer666df #8 所有的并发, 如果没有数据竞争或者顺序依赖的情况,都可以通过平滑扩容以及负载均衡来解决,nginx 可以通过监测你服务的响应时间把 这些请求进行负载均衡处理的,实际上每台机器或者线程处理的数量不会太大
高并发读从来都不是问题,如果不要求强一致性,加副本 配合 分片路由算法,没有什么问题是加机器不能解决的, 真正的问题是 高并发读的时候 如何解决写的问题,写的话 如何保障副本跟主节点的数据同步问题,并发读写才是要命的,这个时候要么 要求主从强一致性 那就是选 CP ,要么要求主从弱一致性,或者存在延迟同步的情况,此时就是 AP |
13
lewis89 2021-02-02 10:34:11 +08:00
@yyyfor #6 另外 Redis 本身 AOF 也是会丢失数据的,如果每一个写操作 都 fsync 到硬盘,那么每次 redis 的写操作就退化到磁盘的写入速度了,虽然内核文件系统会 buffer 你的写操作,然后合并成连续写 来提升性能,但是也顶不住你每次写操作都 fsync 写入硬盘的操作,毕竟磁盘的磁头 寻道,要从外面的圈移动到里面的圈,从寻道的物理角度来讲,他的速度是很非常慢的,所以机械硬盘只适合连续读写,在 4K 随机读写的场景下,机械硬盘就是个渣渣,被 SSD nvme 硬盘吊打的
|
14
lewis89 2021-02-02 10:38:17 +08:00
@qwer666df #12 不可能单机的,单机 CPU 都打满了,你再怎么优化都没用,通常来讲,所有的服务都是无状态的,服务本身不存储任何状态,这样高峰的时候,我们可以通过扩展集群实例数量来提升吞吐量,但是大部分互联网的业务其实瓶颈都在数据库,所以一般都会采取 水平拆分的操作 来提升写的并发能力,因为写的话 会分片写入多个 MySQL 实例,此时单机写入的压力就会降低,总体的写入性能就会提升。
|
16
lewis89 2021-02-02 10:43:57 +08:00 via iPhone
@qwer666df 扩展集群实例很简单的,一开始考虑好怎么负载均衡 然后用 k8s 加实例就好了,现在基本上这些都是运维做的,但是程序员要考虑负载均衡的算法,避免单个实例负载很大的情况
|
17
lewis89 2021-02-02 10:49:24 +08:00
@qwer666df #15 要随时能扩充服务实例,首先还是要考虑 服务的无状态化,例如以前单机的 session 就不能用了,session 得存放到 redis 里面,不然你扩充的实例 不知道 session 在哪里,用 token 令牌的现在也只能存到 redis 里面去,所有的服务本身不保存任何状态,这样就可以平滑的加机器 减少机器的数量.. 当然这种集群扩充减少大多只能解决 CPU 密集型问题,对于瓶颈在数据库 IO 的问题 没法解决
|
18
liuxu 2021-02-02 10:58:59 +08:00
|
19
lewis89 2021-02-02 11:14:46 +08:00
@liuxu #18 如果我不削峰呢?我就要实时性,你这边 5 个线程,可能人家发送短信的 IO 可能根本都没打满,削峰只有 IO 被打满的情况下 不得已才考虑的一种情况,而且代码的主流程 是没法削峰的,发优惠券发金豆这种你削峰就削峰了,用户不会在意到账的及时性,像饿了么这种下订单的主流程,你怎么削峰?进消息队列?人家几秒钟的等待或者看到 提示错误就准备打开美团外卖了。
|
20
lewis89 2021-02-02 11:17:37 +08:00
@liuxu #18 另外饿了么下订单这种主流程 只能做分表去提升并发写的能力,然后做冷热分离去提升读的能力,否则用户一看,订单没下成功,饿肚子了 分分钟就打开你对手美团的 APP 去下单了
|
21
murmur 2021-02-02 11:26:03 +08:00
全年提升 100 倍 qps 也就阿里腾讯能有这个级别
其余的第一时间考虑不是错峰么,能错峰为啥要全年 qps 拉这么满 最典型的例子,12306 优化了多少年,最终不还是靠错峰解决的问题,现在的双十一 双十二也都是错峰抢购 |
22
yuedingwangji 2021-02-02 11:34:18 +08:00
@lewis89 大佬呀
|
23
liuxu 2021-02-02 11:51:30 +08:00 1
@lewis89
#7 我补充一点,不知道对不对 限制单机,100k 的 get 。 由于 redis 从内存读,所以内存没有限制。 主要是 cpu,假如单机 cpu 不够的话此问题无解,除非升级 cpu 。 带宽问题需要保证,一个 get 10 字节,100k * 10 = 1MB/s = 10Mbps,100 个字节就需要 100Mbps 。 linux 可使用端口一般默认为 32768 - 60000 之间,可以通过 sysctl(/etc/sysctl.conf)修改 net.ipv4.ip_local_port_range 增加更大范围,1024-65535 。但这样会存储一个问题,假如 3306 被 redis 分配掉,你再想启动 mysql,只能手动释放这个端口,然后启动 mysql 。这里只是举例,这种负载情况自然不建议单机起多服务。 由于大量短连接会造成系统大量 TIME_WAIT 的端口,导致端口不可用,可以通过 sysctl(/etc/sysctl.conf)的 net.ipv4.tcp_tw_reuse 来快速重用。 大量链接下系统 nofile 会快速占用,debian 系默认通常为 1024,通过修改 ulimit(/etc/security/limits.conf )的 nofile 解决,需要注意的是它是用户的 pam 限制,需要给启动 redis 的用户增加此选项,可直接修改全局文件全部增加即可。 关于 redis 磁盘落地问题,使用三星的 1T pcie 4.0 接口的 nvme ssd,顺序读写基本在 3GB/s 以上,随机 4k 读写可达到 500k 以上,是读和写,感谢时代。 网络拥塞问题,debian 系可以直接修改 /etc/sysctl.conf 文件的 net.core.default_qdisc=fq 和 net.ipv4.tcp_congestion_control=bbr 来抢占发包。rhel 系自行升级内核到 4.10 以上,我说的就是辣鸡 centos 。 由于现在 aio 一般使用 epoll,C10K 导致大量上下文切换,可以使用最新 linux 内核 5.1 以上,使用 io_uring,根据有关评测,系统调用只有 epoll 的 1/10 。 同时回答楼主,扩展到 10 倍或者 100 。做横向集群扩展的时候,压力在 LB 的 hash 均匀分配和性能上。同时可拆分业务,通过 LB 或者 DNS 分配到不同子集群。 |
24
liuxu 2021-02-02 11:56:19 +08:00
@lewis89 你不用发这么多问号,不同业务有不同的处理办法。#8 的业务是发送短信,短信任务入队列削峰是正确的方式,现代短信发送一遍是 http 做接口调用,不仅有单机 100k 接收请求问题,还有单机 100k 发送 http 请求问题。
|
26
lewis89 2021-02-02 12:00:11 +08:00
|
27
liuxu 2021-02-02 12:03:33 +08:00
@lewis89 根据你说的饿了么订单这类及时任务,拆分业务,分表,是唯一选择。可以从 DNS,LB,业务才分,hash 分表,数据库集群解决,我没说有问题。
|
28
liuxu 2021-02-02 12:05:53 +08:00
@lewis89 线程和并发不一定是 1 对 1 关系,同步任务是 1 对 1,现代 aio 下,协程 epoll 可以做到单线程多并发。
|
29
togou 2021-02-02 12:25:29 +08:00
看傻了
|
30
lewis89 2021-02-02 12:54:04 +08:00
@liuxu #22 另外 bbr 这种快启动也不是万能的,最好的办法还是根据包的大小来设置窗口跟窗口扩大缩小的策略,不过大部分场景 基本上都是小包..
|
31
lewis89 2021-02-02 13:06:34 +08:00
@liuxu #27 有几个人会自己用 非阻塞式 IO 配合 epoll 关键字 自己手写多路复用,而且还要考虑写的 buffer 满了一大堆问题,另外用异步的更少见,有的异步甚至就是用线程池模拟出来的,一般默认 5 个线程就是 5 个阻塞式的 IO
|
32
liuxu 2021-02-02 13:47:39 +08:00 1
@lewis89 bbr 当让不是万能的,满负荷下 bbr 还可能降低发包能力,但不至于自己设置窗口扩大缩小的策略吧,能改窗口,又觉得没几个人能手写 epoll,看来你是写 C/C++,做驱动开发的吧。应用层开发不会动 tcp 协议层,现代所有业务开发语言都支持异步和协程,很简单的语法糖。你说的手写异步模型超纲了,再说下去我就要开始改电路,处理 INT 中断,做真异步了,告辞
|
33
young1lin 2021-02-02 13:49:39 +08:00
我做个补充。
看你现有的系统是哪种了,单体,前后端分离单体,SOA 还是微服务架构。 Redis 在 Get 小数据时,十万并发是勉强能支撑住的,如果是 Get 超大的对象,那可能就不行了。要增加十倍或者一百倍就有些骚操作了。用 Codis 或者官方提供的 Cluster 方案,将原来的数据拆分到多个 Redis 实例上。Redis 6 的多线程就是增加了接受请求的线程而已,不是以前的复用同一个线程,Set 和 Get 操作还是以前的单线程执行。一般现在都是 NUMA 架构,然后你可以进行绑核这种骚操作。 我建议你看下《微服务架构设计模式》这本书,X 、Y 、Z 轴扩展。应用水平扩展,拆分成服务,最好是无状态服务(当然这是最好的情况)水平扩展没什么障碍,Z 轴就是根据用户请求来进行绑定某些机器,例如粘性 session 。 其实还有前端的 Cache 、DNS 、登录的无状态化、动静分离、F5 、LVS 、一致性 Hash 、业务 /数据 /系统隔离、七种负载均衡办法,面试官应该想听到的是这些。 |
34
lewis89 2021-02-02 14:36:46 +08:00
@young1lin #32 ryzen 的服务器版本还没流行吧,怎么 NUMA 已经成为标配了.. 不过多个物理 CPU 的服务器 确实存在 NUMA 的问题..
|
35
OldCarMan 2021-02-02 14:54:04 +08:00
不错不错,感觉看了场高端局交流比赛。
|
36
Lemeng 2021-02-02 15:07:53 +08:00
问到了
|
37
pavelpiero 2021-02-02 15:26:40 +08:00
@OldCarMan +1
|
38
lesismal 2021-02-02 16:31:59 +08:00
真热闹,我也补充一些吧
前面的各位都只是说到丢给 redis,但是单就 redis 也有击穿、穿透、雪崩各种说法,1s 几十万全丢给 redis,也只能说各位逮到个好玩意就往死里操,还是太暴力太浪费了 缓存和持久层其实都还是局部性原理的范畴,既然局部性原理,就再进一步,内存缓存,内存缓存这块跟 redis 放一块的话也有多种实现方式,比如: 1. 热点 key 的数据,定时或者发布订阅或者其他什么更新机制,服务节点 load 到自己的内存,请求来了直接返回自己内存缓存的 2. 同 key 访问加锁串行化,上一个请求回来后把结果带回来,其他等待锁的先检查是否有结果了,有了就直接拿结果、不落到 redis 了,相当于合并了到 redis 的请求。这个过程当然也可以结合或者改成内存缓存,比如内存的 1s 过期,内存没有、再 redis 、持久层之类的 高配点的机器,如果不是大 key 、value,几十万 qps 没啥压力,我自己的 i7 PC 测自己的 arpc 都能 40 多万 qps |
39
boobo 2021-02-02 17:54:16 +08:00
mark 一下先...
|
40
janxin 2021-02-02 18:46:46 +08:00
这是思路问题,先看问题出在哪里然后根据问题对应解决。
常用方案是知识储备,分析解决问题是经验思路 |
41
razertory 2021-02-02 19:07:38 +08:00
首先你的网卡要支持足够的带宽,在确认 Max 带宽的情况下,你要会计算这个能承载多少 IOPS 。QPS 只是一个非常片面的指标。一个系统要健康稳定高效运转需要从各个维度去监控。那种以提升 QPS 做问题的面试官通常两类
1. 懂得太少,只会拿 QPS 做考点 2. 以 QPS 做切入点,和你探讨延伸各个方向的问题。 |
43
cassyfar 2021-02-02 19:40:02 +08:00
100 万 QPS 的项目做过,直接上云,master-slave 就能跑,注意的点就是,
不要跨区读缓存;作好 replication ;缓存读不出来的时候不要立马更新缓存;随机 TTL 避免缓存雪崩。 最后我真不知道假设在单机上的意义何在。。。 |