V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yyyfor
V2EX  ›  程序员

关于系统瓶颈的面试问题

  •  2
     
  •   yyyfor · 2021-02-01 21:09:43 +08:00 · 4303 次点击
    这是一个创建于 1151 天前的主题,其中的信息可能已经有所发展或是发生改变。

    求教一下各位大佬,

    面试老是被问到系统瓶颈的问题,如果现在正常运行的系统,要改造提高 10(甚至 100)倍的 qps,会遇到哪些问题、瓶颈?

    关于这类的问题,应该如何去分析和描述?

    45 条回复    2021-02-03 10:43:26 +08:00
    Kirsk
        1
    Kirsk  
       2021-02-02 00:38:31 +08:00 via Android
    鬼知道 具体问题具体分析 稀里糊涂的提问的人也不知道 走过场看面试者对不对自己胃口=你猜猜看我最爱吃什么
    yyyfor
        2
    yyyfor  
    OP
       2021-02-02 09:08:43 +08:00
    @Kirsk 昨天遇到的问题还算具体的。redis 每秒十万个 get 请求,会不会有问题? 如果没有问题,每秒请求量继续增加,问增加十倍或者一百倍,制约请求量增加的瓶颈因素是什么?
    Kirsk
        3
    Kirsk  
       2021-02-02 09:20:35 +08:00 via Android
    @yyyfor 假设是单纯承载容量 那就是集群横向扩展了
    yyyfor
        4
    yyyfor  
    OP
       2021-02-02 09:23:47 +08:00
    @Kirsk 面试官已经假设是在一台机器上了。我当时觉得应该从 cpu 、内存、io 去分析,但是具体分析的过程不知道如何表述比较准确
    Kirsk
        5
    Kirsk  
       2021-02-02 09:27:31 +08:00 via Android
    @yyyfor 有思路就行了 剩下是上手干活去解决的
    yyyfor
        6
    yyyfor  
    OP
       2021-02-02 09:49:14 +08:00
    @Kirsk 面试官想要具体的分析过程😂比如 cpu 、内存、io 哪个最有可能最先成为瓶颈? 然后卡了好久。感觉面试官想要从具体的数据量级去分析这个问题
    lewis89
        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 的存储体系结构来分析,或者从网络层去分析,就差不多了
    qwer666df
        8
    qwer666df  
       2021-02-02 10:07:41 +08:00
    @lewis89 #7 请教一下, 昨天有一个类似的问题, 关于一个活动用户发短信的场景 他意思一个线程只能处理 10w 个任务, 然后系统如果这个时候只开了 5 个线程, 也就是 50w 任务, 但是活动太过火爆了之后. 只能突然来了几百万的请求, 这时候怎么处理, 不考虑服务器手动加脚本的操作
    lewis89
        9
    lewis89  
       2021-02-02 10:12:29 +08:00
    @qwer666df #8 一开始就要考虑复杂均衡跟平滑扩容吧,而且这种任务没有顺序依赖,很容易分片解决就好了
    lewis89
        10
    lewis89  
       2021-02-02 10:18:49 +08:00
    @qwer666df #8 所有的并发, 如果没有数据竞争或者顺序依赖的情况,都可以通过平滑扩容以及负载均衡来解决,nginx 可以通过监测你服务的响应时间把 这些请求进行负载均衡处理的,实际上每台机器或者线程处理的数量不会太大

    高并发读从来都不是问题,如果不要求强一致性,加副本 配合 分片路由算法,没有什么问题是加机器不能解决的,
    真正的问题是 高并发读的时候 如何解决写的问题,写的话 如何保障副本跟主节点的数据同步问题,并发读写才是要命的,这个时候要么 要求主从强一致性 那就是选 CP ,要么要求主从弱一致性,或者存在延迟同步的情况,此时就是 AP
    yyyfor
        11
    yyyfor  
    OP
       2021-02-02 10:24:08 +08:00
    @lewis89 受教了,谢谢老哥
    qwer666df
        12
    qwer666df  
       2021-02-02 10:30:24 +08:00
    @lewis89 #10 如果是单机咋是不是就没办法了 昨天没有想过加机器的办法.... 我以为他是让我在单机上面考虑
    lewis89
        13
    lewis89  
       2021-02-02 10:34:11 +08:00
    @yyyfor #6 另外 Redis 本身 AOF 也是会丢失数据的,如果每一个写操作 都 fsync 到硬盘,那么每次 redis 的写操作就退化到磁盘的写入速度了,虽然内核文件系统会 buffer 你的写操作,然后合并成连续写 来提升性能,但是也顶不住你每次写操作都 fsync 写入硬盘的操作,毕竟磁盘的磁头 寻道,要从外面的圈移动到里面的圈,从寻道的物理角度来讲,他的速度是很非常慢的,所以机械硬盘只适合连续读写,在 4K 随机读写的场景下,机械硬盘就是个渣渣,被 SSD nvme 硬盘吊打的
    lewis89
        14
    lewis89  
       2021-02-02 10:38:17 +08:00
    @qwer666df #12 不可能单机的,单机 CPU 都打满了,你再怎么优化都没用,通常来讲,所有的服务都是无状态的,服务本身不存储任何状态,这样高峰的时候,我们可以通过扩展集群实例数量来提升吞吐量,但是大部分互联网的业务其实瓶颈都在数据库,所以一般都会采取 水平拆分的操作 来提升写的并发能力,因为写的话 会分片写入多个 MySQL 实例,此时单机写入的压力就会降低,总体的写入性能就会提升。
    qwer666df
        15
    qwer666df  
       2021-02-02 10:40:09 +08:00
    @lewis89 #14 受教了, 现在业务代码写的多, 还真不懂怎么 "扩展集群实例数量", 谢谢老哥
    lewis89
        16
    lewis89  
       2021-02-02 10:43:57 +08:00 via iPhone
    @qwer666df 扩展集群实例很简单的,一开始考虑好怎么负载均衡 然后用 k8s 加实例就好了,现在基本上这些都是运维做的,但是程序员要考虑负载均衡的算法,避免单个实例负载很大的情况
    lewis89
        17
    lewis89  
       2021-02-02 10:49:24 +08:00
    @qwer666df #15 要随时能扩充服务实例,首先还是要考虑 服务的无状态化,例如以前单机的 session 就不能用了,session 得存放到 redis 里面,不然你扩充的实例 不知道 session 在哪里,用 token 令牌的现在也只能存到 redis 里面去,所有的服务本身不保存任何状态,这样就可以平滑的加机器 减少机器的数量.. 当然这种集群扩充减少大多只能解决 CPU 密集型问题,对于瓶颈在数据库 IO 的问题 没法解决
    liuxu
        18
    liuxu  
       2021-02-02 10:58:59 +08:00
    @qwer666df
    @lewis89

    #8 属于削峰问题,正确的方法是入消息队列,前端线程只做任务入消息队列,后端线程消费任务。前端 cpu 爆了的话只能横向扩展机器,由于只入消息队列,所以是可扩展的。
    lewis89
        19
    lewis89  
       2021-02-02 11:14:46 +08:00
    @liuxu #18 如果我不削峰呢?我就要实时性,你这边 5 个线程,可能人家发送短信的 IO 可能根本都没打满,削峰只有 IO 被打满的情况下 不得已才考虑的一种情况,而且代码的主流程 是没法削峰的,发优惠券发金豆这种你削峰就削峰了,用户不会在意到账的及时性,像饿了么这种下订单的主流程,你怎么削峰?进消息队列?人家几秒钟的等待或者看到 提示错误就准备打开美团外卖了。
    lewis89
        20
    lewis89  
       2021-02-02 11:17:37 +08:00
    @liuxu #18 另外饿了么下订单这种主流程 只能做分表去提升并发写的能力,然后做冷热分离去提升读的能力,否则用户一看,订单没下成功,饿肚子了 分分钟就打开你对手美团的 APP 去下单了
    murmur
        21
    murmur  
       2021-02-02 11:26:03 +08:00
    全年提升 100 倍 qps 也就阿里腾讯能有这个级别
    其余的第一时间考虑不是错峰么,能错峰为啥要全年 qps 拉这么满
    最典型的例子,12306 优化了多少年,最终不还是靠错峰解决的问题,现在的双十一 双十二也都是错峰抢购
    yuedingwangji
        22
    yuedingwangji  
       2021-02-02 11:34:18 +08:00
    @lewis89 大佬呀
    liuxu
        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 分配到不同子集群。
    liuxu
        24
    liuxu  
       2021-02-02 11:56:19 +08:00
    @lewis89 你不用发这么多问号,不同业务有不同的处理办法。#8 的业务是发送短信,短信任务入队列削峰是正确的方式,现代短信发送一遍是 http 做接口调用,不仅有单机 100k 接收请求问题,还有单机 100k 发送 http 请求问题。
    lewis89
        25
    lewis89  
       2021-02-02 11:58:56 +08:00
    @liuxu #23 关键人家只有 5 个线程,并发能力只有 5..
    lewis89
        26
    lewis89  
       2021-02-02 12:00:11 +08:00
    @liuxu #23 5 个线程并发能打垮的系统,我还没见过 .. 我的垃圾树莓派 pi 都能 web 读并发到 100 以上
    liuxu
        27
    liuxu  
       2021-02-02 12:03:33 +08:00
    @lewis89 根据你说的饿了么订单这类及时任务,拆分业务,分表,是唯一选择。可以从 DNS,LB,业务才分,hash 分表,数据库集群解决,我没说有问题。
    liuxu
        28
    liuxu  
       2021-02-02 12:05:53 +08:00
    @lewis89 线程和并发不一定是 1 对 1 关系,同步任务是 1 对 1,现代 aio 下,协程 epoll 可以做到单线程多并发。
    togou
        29
    togou  
       2021-02-02 12:25:29 +08:00
    看傻了
    lewis89
        30
    lewis89  
       2021-02-02 12:54:04 +08:00
    @liuxu #22 另外 bbr 这种快启动也不是万能的,最好的办法还是根据包的大小来设置窗口跟窗口扩大缩小的策略,不过大部分场景 基本上都是小包..
    lewis89
        31
    lewis89  
       2021-02-02 13:06:34 +08:00
    @liuxu #27 有几个人会自己用 非阻塞式 IO 配合 epoll 关键字 自己手写多路复用,而且还要考虑写的 buffer 满了一大堆问题,另外用异步的更少见,有的异步甚至就是用线程池模拟出来的,一般默认 5 个线程就是 5 个阻塞式的 IO
    liuxu
        32
    liuxu  
       2021-02-02 13:47:39 +08:00   ❤️ 1
    @lewis89 bbr 当让不是万能的,满负荷下 bbr 还可能降低发包能力,但不至于自己设置窗口扩大缩小的策略吧,能改窗口,又觉得没几个人能手写 epoll,看来你是写 C/C++,做驱动开发的吧。应用层开发不会动 tcp 协议层,现代所有业务开发语言都支持异步和协程,很简单的语法糖。你说的手写异步模型超纲了,再说下去我就要开始改电路,处理 INT 中断,做真异步了,告辞
    young1lin
        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 、业务 /数据 /系统隔离、七种负载均衡办法,面试官应该想听到的是这些。
    lewis89
        34
    lewis89  
       2021-02-02 14:36:46 +08:00
    @young1lin #32 ryzen 的服务器版本还没流行吧,怎么 NUMA 已经成为标配了.. 不过多个物理 CPU 的服务器 确实存在 NUMA 的问题..
    OldCarMan
        35
    OldCarMan  
       2021-02-02 14:54:04 +08:00
    不错不错,感觉看了场高端局交流比赛。
    Lemeng
        36
    Lemeng  
       2021-02-02 15:07:53 +08:00
    问到了
    pavelpiero
        37
    pavelpiero  
       2021-02-02 15:26:40 +08:00
    lesismal
        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
    boobo
        39
    boobo  
       2021-02-02 17:54:16 +08:00
    mark 一下先...
    janxin
        40
    janxin  
       2021-02-02 18:46:46 +08:00
    这是思路问题,先看问题出在哪里然后根据问题对应解决。

    常用方案是知识储备,分析解决问题是经验思路
    razertory
        41
    razertory  
       2021-02-02 19:07:38 +08:00
    首先你的网卡要支持足够的带宽,在确认 Max 带宽的情况下,你要会计算这个能承载多少 IOPS 。QPS 只是一个非常片面的指标。一个系统要健康稳定高效运转需要从各个维度去监控。那种以提升 QPS 做问题的面试官通常两类

    1. 懂得太少,只会拿 QPS 做考点
    2. 以 QPS 做切入点,和你探讨延伸各个方向的问题。
    lewis89
        42
    lewis89  
       2021-02-02 19:22:02 +08:00
    @janxin #39 对的,确实是要基础知识储备足够,然后解决方案跟思路 只是一个顺延展开基础理论知识的过程
    cassyfar
        43
    cassyfar  
       2021-02-02 19:40:02 +08:00
    100 万 QPS 的项目做过,直接上云,master-slave 就能跑,注意的点就是,

    不要跨区读缓存;作好 replication ;缓存读不出来的时候不要立马更新缓存;随机 TTL 避免缓存雪崩。

    最后我真不知道假设在单机上的意义何在。。。
    liuxu
        44
    liuxu  
       2021-02-03 09:32:03 +08:00
    @cassyfar #43 100 万 qps,具体什么业务,还是你们被人 cc 了

    其实全贴没人假设必须是在单机
    cassyfar
        45
    cassyfar  
       2021-02-03 10:43:26 +08:00
    @liuxu 电商和聊天都有做过,基础架构。

    我看第四楼 LZ 提到的,面试官已经假设到一台机器上。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   954 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 21:18 · PVG 05:18 · LAX 14:18 · JFK 17:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.