ikkhksvu 发表于 2024-8-31 10:02:06

Linux 网络性能的 15 个优化意见!

<span style="color: black;"><strong style="color: blue;"><span style="color: black;">来自公众号:<span style="color: black;"><span style="color: black;">研发</span>内功修炼</span></span></strong></span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">大众</span>好,我是飞哥!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">近期</span>我出了一本非常受欢迎的新书<span style="color: black;">──</span>《<strong style="color: blue;">深入理解Linux网络</strong>》。在这本书中<span style="color: black;">咱们</span>深入地讨论了<span style="color: black;">非常多</span>内核网络模块<span style="color: black;">关联</span>的问题。讨论了一个网络包是<span style="color: black;">怎样</span>从网卡到达用户进程的,聊了同步阻塞和多路复用 epoll,<span style="color: black;">亦</span><span style="color: black;">仔细</span>讨论了数据是<span style="color: black;">怎样</span>从进程发送出去的等等一系列深度的网络工作原理。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">这本书首发当日就登上了京东的科技类销量日冠军,刚上市三个星期就<span style="color: black;">已然</span><span style="color: black;">起始</span>了第三次的加印,<span style="color: black;">能够</span>说是非常的热门。</span><span style="color: black;">倘若</span>你能看完这本书,你就会正和庖丁<span style="color: black;">同样</span>,从今日往后<span style="color: black;">咱们</span>看到的<span style="color: black;">亦</span><span style="color: black;">再也不</span>是<span style="color: black;">全部</span>的 Linux (整头牛)了,而是内核的内部各个模块(筋⻣肌理)。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">那样</span>具备了对网络的深刻的理解之后,<span style="color: black;">咱们</span>在性能方面有<span style="color: black;">那些</span>优化手段可用呢?我<span style="color: black;">这儿</span>给出<span style="color: black;">有些</span><span style="color: black;">研发</span><span style="color: black;">或</span>运维中的性能优化<span style="color: black;">意见</span>。这些<span style="color: black;">意见</span>都是从书中摘录的。<span style="color: black;">不外</span>要<span style="color: black;">重视</span>的是,每一种性能优化<span style="color: black;">办法</span>都有它适用<span style="color: black;">或</span>不适用的应用场景。你应当<span style="color: black;">按照</span>你当前的项目<span style="color: black;">状况</span>灵活来<span style="color: black;">选取</span>用<span style="color: black;">或</span><span style="color: black;">不消</span>。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>1:<span style="color: black;">尽可能</span>减少不必要的网络 IO</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">我要给出的<span style="color: black;">第1</span>个<span style="color: black;">意见</span><span style="color: black;">便是</span>不必要用网络 IO 的<span style="color: black;">尽可能</span><span style="color: black;">不消</span>。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">是的,网络在现代的互联网世界里承载了很重要的角色。用户<span style="color: black;">经过</span>网络请求线上服务、服务器<span style="color: black;">经过</span>网络读取数据库中数据,<span style="color: black;">经过</span>网络构建能力无比强大分布式系统。网络很好,能降低模块的<span style="color: black;">研发</span>难度,<span style="color: black;">亦</span>能用它搭建出更强大的系统。<span style="color: black;">然则</span>这不是你<span style="color: black;">乱用</span>它的理由!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">原由</span>是即使是本机网络 IO 开销仍然是很大的。先说发送一个网络包,<span style="color: black;">首要</span>得从用户态切换到内核态,花费一次系统调用的开销。进入到内核以后,又得经过冗长的协议栈,这会花费不少的 CPU 周期,最后进入环回设备的“驱动程序”。接收端呢,软中断花费不少的 CPU 周期又得经过接收协议栈的处理,最后唤醒<span style="color: black;">或</span><span style="color: black;">通告</span>用户进程来处理。当服务端处理完以后,还得把结果再发过来。又得来这么一遍,最后你的进程<span style="color: black;">才可</span>收到结果。你说麻烦不麻烦。<span style="color: black;">另一</span>还有个问题<span style="color: black;">便是</span>多个进程协作来完成一项工作就必然会引入<span style="color: black;">更加多</span>的进程上下文切换开销,这些开销从<span style="color: black;">研发</span>视角来看,做的其实都是无用功。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">上面<span style="color: black;">咱们</span>还分析的只是本机网络 IO,<span style="color: black;">倘若</span>是跨<span style="color: black;">设备</span>的还得会有双方网卡的 DMA 拷贝过程,以及两端之间的网络 RTT 耗时延迟。<span style="color: black;">因此</span>,网络虽好,但<span style="color: black;">亦</span><span style="color: black;">不可</span>随意<span style="color: black;">乱用</span>!</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>2:<span style="color: black;">尽可能</span>合并网络请求</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在可能的<span style="color: black;">状况</span>下,尽可能地把多次的网络请求合并到一次,<span style="color: black;">这般</span>既节约了双端的 CPU 开销,<span style="color: black;">亦</span>能降低多次 RTT <span style="color: black;">引起</span>的耗时。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">咱们</span>举个实践中的例子可能更好理解。假如有一个 redis,里面存了每一个 App 的信息(应用名、包名、版本、截图等等)。你<span style="color: black;">此刻</span>需要<span style="color: black;">按照</span>用户安装应用列表来<span style="color: black;">查找</span>数据库中有<span style="color: black;">那些</span>应用比用户的版本更新,<span style="color: black;">倘若</span>有则提醒用户更新。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">那样</span>最好不要写出如下的代码:</span></p>&lt;?<span style="color: black;">
      <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">php&nbsp;</p><span style="color: black;">for</span><span style="color: black;">(安装列表&nbsp;as&nbsp;包名)</span>
    </span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">{</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">&nbsp;&nbsp;redis-&gt;get(包名)</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">&nbsp;&nbsp;...</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">}</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">上面这段代码功能上实现上没问题,问题在于性能。据<span style="color: black;">咱们</span>统计现代用户平均安装 App 的数量在 60 个<span style="color: black;">上下</span>。那这段代码在运行的时候,每当用户来请求一次,你的服务器就需要和 redis 进行 60 次网络请求。总耗时最少是 60 个 RTT 起。更好的<span style="color: black;">办法</span>是应该<span style="color: black;">运用</span> redis 中<span style="color: black;">供给</span>的批量获取命令,如 hmget、pipeline等,经过一次网络 IO 就获取到所有想要的数据,如图。</span></p><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkYskCRKYvGG55ffW7V2uDyKRdAHCALfRiaD3U1uzk2khKFdet9ic3ViaicQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;">
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>3:调用者与被调用<span style="color: black;">设备</span>尽可能<span style="color: black;">安排</span>的近<span style="color: black;">有些</span></h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在前面的章节中<span style="color: black;">咱们</span>看到在握手一切正常的<span style="color: black;">状况</span>下, TCP 握手的时间基本取决于两台<span style="color: black;">设备</span>之间的 RTT 耗时。虽然<span style="color: black;">咱们</span>没办法彻底去掉这个耗时,<span style="color: black;">然则</span><span style="color: black;">咱们</span>却有办法把 RTT 降低,那<span style="color: black;">便是</span>把客户端和服务器放的足够的近<span style="color: black;">有些</span>。<span style="color: black;">尽可能</span>把<span style="color: black;">每一个</span>机房内部的数据请求都在本地机房<span style="color: black;">处理</span>,减少跨地网络传输。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">举例,假如你的服务是<span style="color: black;">安排</span>在北京机房的,你调用的 mysql、redis最好都<span style="color: black;">位置于</span>北京机房内部。<span style="color: black;">尽可能</span>不要跨过千里万里跑到广东机房去请求数据,即使你有专线,耗时<span style="color: black;">亦</span>会大大<span style="color: black;">增多</span>!在机房内部的服务器之间的 RTT 延迟大概<span style="color: black;">仅有</span>零点几毫秒,同地区的<span style="color: black;">区别</span>机房之间大约是 1 ms 多<span style="color: black;">有些</span>。但<span style="color: black;">倘若</span>从北京跨到广东的话,延迟将是 30 - 40 ms <span style="color: black;">上下</span>,几十倍的上涨!</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>4:内网调用不要用外网域名</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">假如说你所在负责的服务需要调用兄弟<span style="color: black;">分部</span>的一个搜索接口,假设接口是:"http://www.sogou.com/wq?key=<span style="color: black;">研发</span>内功修炼"。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">那既然是兄弟<span style="color: black;">分部</span>,那很可能这个接口和你的服务是<span style="color: black;">安排</span>在一个机房的。即使<span style="color: black;">无</span><span style="color: black;">安排</span>在一个机房,<span style="color: black;">通常</span><span style="color: black;">亦</span>是有专线<span style="color: black;">达到</span>的。<strong style="color: blue;"><span style="color: black;">因此</span>不要直接请求 www.sogou.com, 而是应该<span style="color: black;">运用</span>该服务在<span style="color: black;">机构</span>对应的内网域名</strong>。在<span style="color: black;">咱们</span><span style="color: black;">机构</span>内部,每一个外网服务都会配置一个对应的内网域名,我相信你们<span style="color: black;">机构</span><span style="color: black;">亦</span>有。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">为何</span>要这么做,<span style="color: black;">原由</span>有以下几点</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">1)<strong style="color: blue;">外网接口慢</strong>。本来内网可能过个交换机就能达到兄弟<span style="color: black;">分部</span>的<span style="color: black;">设备</span>,非得上外网兜一圈再回来,时间上肯定会慢。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">2)<strong style="color: blue;">带宽成本高</strong>。在互联网服务里,除了<span style="color: black;">设备</span>以外,<span style="color: black;">另一</span><span style="color: black;">一起</span>很大的成本<span style="color: black;">便是</span> IDC 机房的出入口带宽成本。两台<span style="color: black;">设备</span>在内网不管<span style="color: black;">怎样</span>通信都不<span style="color: black;">触及</span>到带宽的计算。<span style="color: black;">然则</span>一旦你去外网兜了一圈回来,行了,一进一出<span style="color: black;">所有</span>要缴带宽费,你说亏不亏!!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">3)<strong style="color: blue;">NAT 单点瓶颈</strong>。<span style="color: black;">通常</span>的服务器都<span style="color: black;">无</span>外网 IP,所以<span style="color: black;">想要</span>请求外网的资源,必须要经过 NAT 服务器。<span style="color: black;">然则</span>一个<span style="color: black;">机构</span>的机房里几千台服务器中,承担 NAT 角色的可能就<span style="color: black;">那样</span>几台。它很容易<span style="color: black;">作为</span>瓶颈。<span style="color: black;">咱们</span>的业务就遇到过好几次 NAT 故障<span style="color: black;">引起</span>外网请求失败的情形。NAT <span style="color: black;">设备</span>挂了,你的服务可能<span style="color: black;">亦</span>就挂了,故障率大大<span style="color: black;">增多</span>。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>5:<span style="color: black;">调节</span>网卡 RingBuffer 大小</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在 Linux 的<span style="color: black;">全部</span>网络栈中,RingBuffer 起到一个任务的收发中转站的角色。<span style="color: black;">针对</span>接收过程来讲,网卡负责往 RingBuffer 中写入收到的数据帧,ksoftirqd 内核线程负责从中取走处理。只要 ksoftirqd 线程工作的足够快,RingBuffer 这个中转站就不会<span style="color: black;">显现</span>问题。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">但是<span style="color: black;">咱们</span>设想一下,假如某一时刻,瞬间来了<span style="color: black;">尤其</span>多的包,而 ksoftirqd 处理<span style="color: black;">不外</span>来了,会<span style="color: black;">出现</span>什么?<span style="color: black;">此时</span> RingBuffer 可能瞬间就被填满了,后面再来的包网卡直接就会丢弃,不做任何处理!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkfqTshFY2YR7LZQDKC8608oNowSLbNG6qa8dK9Ee81LWQc9zeTib1t7A/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">经过</span> ethtool 就<span style="color: black;">能够</span>加大 RingBuffer 这个“中转仓库”的<span style="color: black;">体积</span>。。</span></p><span style="color: black;">#&nbsp;ethtool&nbsp;-G&nbsp;eth1&nbsp;rx&nbsp;4096&nbsp;tx&nbsp;4096</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkicDXQPeysibT7yWt3iadJU9A5pdorMQFLoyV2H1QBolTNE8J2xNDVIa1A/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p><span style="color: black;"><span style="color: black;">这般</span>网卡会被分配更大一点的”中转站“,<span style="color: black;">能够</span><span style="color: black;">处理</span>偶发的瞬时的丢包。<span style="color: black;">不外</span>这种<span style="color: black;">办法</span>有个小副<span style="color: black;">功效</span>,那<span style="color: black;">便是</span>排队的包<span style="color: black;">太多</span>会<span style="color: black;">增多</span>处理网络包的延时。<span style="color: black;">因此</span>应该让内核处理网络包的速度更快<span style="color: black;">有些</span>更好,而不是让网络包傻傻地在 RingBuffer 中排队。<span style="color: black;">咱们</span>后面会再介绍到 RSS ,它<span style="color: black;">能够</span>让<span style="color: black;">更加多</span>的核来参与网络包接收。</span>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>6:减少内存拷贝</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">假如你要发送一个文件给<span style="color: black;">另一</span>一台<span style="color: black;">设备</span>上,<span style="color: black;">那样</span>比较<span style="color: black;">基本</span>的做法是先调用 read 把文件读出来,再调用 send 把数据把数据发出去。<span style="color: black;">这般</span>数据需要频繁地在内核态内存和用户态内存之间拷贝,如图 9.6。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkHHhNfovoBqB8yic9k3icGBb3MDUoNzciaSGjNlT1j0vXVOF6HlAp3esGw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">日前</span>减少内存拷贝<span style="color: black;">重点</span>有两种<span style="color: black;">办法</span>,分别是<span style="color: black;">运用</span> mmap 和 sendfile 两个系统调用。<span style="color: black;">运用</span> mmap 系统调用的话,映射进来的这段<span style="color: black;">位置</span>空间的内存在用户态和内核态都是<span style="color: black;">能够</span><span style="color: black;">运用</span>的。<span style="color: black;">倘若</span>你发送数据是发的是 mmap 映射进来的数据,则内核直接就<span style="color: black;">能够</span>从<span style="color: black;">位置</span>空间中读取,<span style="color: black;">这般</span>就节约了一次从内核态到用户态的拷贝过程。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkp74cONt3RZZZAAAWgaOTQhicDgyKXBTh76sjtsMXUcmynGiaMeaiadQBQ/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p><span style="color: black;"><span style="color: black;">不外</span>在 mmap 发送文件的方式里,系统调用的开销并<span style="color: black;">无</span>减少,还是<span style="color: black;">出现</span>两次内核态和用户态的上下文切换。<span style="color: black;">倘若</span>你只是想把一个文件发送出去,而不关心它的内容,则<span style="color: black;">能够</span>调用<span style="color: black;">另一</span>一个做的更极致的系统调用 - sendfile。在这个系统调用里,彻底把读文件和发送文件给合并起来了,系统调用的开销又省了一次。再<span style="color: black;">协同</span>绝大<span style="color: black;">都数</span>网卡都支持的"分散-收集"(Scatter-gather)DMA 功能。<span style="color: black;">能够</span>直接从 PageCache 缓存区中 DMA 拷贝到网卡中。<span style="color: black;">这般</span>绝大部分的 CPU 拷贝操作就都省去了。</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUktOOkKcm4TSDvxOcPJVJL85t62vyOLGbNL0N0mRsz35QR62D90icW5Jw/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>7:<span style="color: black;">运用</span> eBPF 绕开协议栈的本机 IO</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>你的业务中<span style="color: black;">触及</span>到<span style="color: black;">海量</span>的本机网络 IO <span style="color: black;">能够</span><span style="color: black;">思虑</span>这个优化<span style="color: black;">方法</span>。本机网络 IO 和跨机 IO 比较起来,确实是节约了驱动上的<span style="color: black;">有些</span>开销。发送数据不需要进 RingBuffer 的驱动队列,直接把 skb 传给接收协议栈(经过软中断)。<span style="color: black;">然则</span>在内核其它组件上,可是一点都没少,系统调用、协议栈(传输层、网络层等)、设备子系统<span style="color: black;">全部</span>走 了一个遍。连“驱动”程序都走了(虽然<span style="color: black;">针对</span>回环设备<span style="color: black;">来讲</span>这个驱动只是一个纯软件的虚拟出来的东东)。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>想用本机网络 IO,<span style="color: black;">然则</span>又不想频繁地在协议栈中绕来绕去。<span style="color: black;">那样</span>你<span style="color: black;">能够</span>试试 eBPF。<span style="color: black;">运用</span> eBPF 的 sockmap 和 sk redirect <span style="color: black;">能够</span>绕过 TCP/IP 协议栈,而被直接发送给接收端的 socket,业界<span style="color: black;">已然</span>有<span style="color: black;">机构</span>在这么做了。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkyT4yaoiaZJJlujhQVf6VD4komc8gZiaMK49icnrp1f912AT19u45S4wrA/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>8:<span style="color: black;">尽可能</span>少用 recvfrom 等进程阻塞的方式</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在<span style="color: black;">运用</span>了 recvfrom 阻塞方式来接收 socket 上数据的时候。每次一个进程专⻔为了等一个 socket 上的数据就得被从 CPU 上拿下来。<span style="color: black;">而后</span>再换上另一个 进程。等到数据 ready 了,<span style="color: black;">睡觉</span>的进程又会被唤醒。总共两次进程上下文切换开销。<span style="color: black;">倘若</span><span style="color: black;">咱们</span>服务器上需要有<span style="color: black;">海量</span>的用户请求需要处理,那就需要有<span style="color: black;">非常多</span>的进程存在,<span style="color: black;">况且</span><span style="color: black;">一直</span>地切换来切换去。<span style="color: black;">这般</span>的缺点有如下这么几个:</span></p><span style="color: black;"><span style="color: black;">由于</span><span style="color: black;">每一个</span>进程只能<span style="color: black;">同期</span>等待一条连接,<span style="color: black;">因此</span>需要<span style="color: black;">海量</span>的进程。</span><span style="color: black;">进程之间互相切换的时候需要消耗<span style="color: black;">非常多</span> CPU 周期,一次切换大约是 3 - 5 us <span style="color: black;">上下</span>。</span><span style="color: black;">频繁的切换<span style="color: black;">引起</span> L1、L2、L3 等高速缓存的效果大打折扣</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">大众</span>可能以为这种网络 IO 模型很少见了。但其实在<span style="color: black;">非常多</span>传统的客户端 SDK 中,<span style="color: black;">例如</span> mysql、redis 和 kafka 仍然是沿用了这种方式。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>9:<span style="color: black;">运用</span>成熟的网络库</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">运用</span> epoll <span style="color: black;">能够</span><span style="color: black;">有效</span>地管理海量的 socket。在服务器端。<span style="color: black;">咱们</span>有<span style="color: black;">各样</span>成熟的网络库进行<span style="color: black;">运用</span>。这些网络库都对 epoll <span style="color: black;">运用</span>了<span style="color: black;">区别</span>程度的封装。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">首要</span><span style="color: black;">第1</span>个要给<span style="color: black;">大众</span>参考的是 Redis。老版本的 Redis 里单进程<span style="color: black;">有效</span>地<span style="color: black;">运用</span> epoll 就能支持每秒数万 QPS 的高性能。<span style="color: black;">倘若</span>你的服务是单进程的,<span style="color: black;">能够</span>参考 Redis 在网络 IO 这块的源码。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>是多线程的,线程之间的分工有<span style="color: black;">非常多</span>种模式。<span style="color: black;">那样</span>哪个线程负责等待读 IO 事件,哪个线程负责处理用户请求,哪个线程又负责给用户写返回。<span style="color: black;">按照</span>分工的<span style="color: black;">区别</span>,又衍生出单 Reactor、多 Reactor、以及 Proactor 等多种模式。<span style="color: black;">大众</span><span style="color: black;">亦</span>不必头疼,只要理解了这些原理之后<span style="color: black;">选取</span>一个性能不错的网络库就<span style="color: black;">能够</span>了。<span style="color: black;">例如</span> PHP 中的 Swoole、Golang 的 net 包、Java 中的 netty 、C++ 中的 &nbsp;Sogou Workflow 都封装的非常的不错。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>10:<span style="color: black;">运用</span> Kernel-ByPass 新技术</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>你的服务对网络<span style="color: black;">需求</span>确实<span style="color: black;">尤其</span>特<span style="color: black;">尤其</span>的高,<span style="color: black;">况且</span><span style="color: black;">各样</span>优化<span style="color: black;">办法</span><span style="color: black;">亦</span>都用过了,<span style="color: black;">那样</span><span style="color: black;">此刻</span>还有终极优化大招 -- Kernel-ByPass 技术。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">内核在接收网络包的时候要经过很⻓的收发路径。在这<span style="color: black;">时期</span>牵涉到<span style="color: black;">非常多</span>内核组件之间的协同、协议栈的处理、以及内核态和用户态的拷贝和切换。Kernel-ByPass 这类的技术<span style="color: black;">方法</span><span style="color: black;">便是</span>绕开内核协议栈,自己在用户态来实现网络包的收发。<span style="color: black;">这般</span><span style="color: black;">不仅</span>避开了繁杂的内核协议栈处理,<span style="color: black;">亦</span>减少了频繁了内核态用户态之间的拷贝和切换,性能将发挥到极致!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">日前</span>我所<span style="color: black;">晓得</span>的<span style="color: black;">方法</span>有 SOLARFLARE 的软硬件<span style="color: black;">方法</span>、DPDK 等等。<span style="color: black;">倘若</span><span style="color: black;">大众</span>感兴趣,<span style="color: black;">能够</span>多去<span style="color: black;">认识</span>一下!</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><img src="https://mmbiz.qpic.cn/mmbiz_png/BBjAFF4hcwrXUoWVbHSG80ibNxjChtjUkGibp3vHPPoUfTmiarzCIgFL3AvobdIRu53uvLupAsSZREEEJXatoLHhg/640?wx_fmt=png&amp;tp=webp&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" style="width: 50%; margin-bottom: 20px;"></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>11:配置充足的端口范围</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">客户端在调用 connect 系统调用发起连接的时候,需要先<span style="color: black;">选取</span>一个可用的端口。内核在<span style="color: black;">选择</span>端口的时候,是采用从可用端口范围中某一个随机位置<span style="color: black;">起始</span>遍历的方式。<span style="color: black;">倘若</span>端口不充足的话,内核可能需要循环撞<span style="color: black;">非常多</span>次<span style="color: black;">才可</span>选上一个可用的。这<span style="color: black;">亦</span>会<span style="color: black;">引起</span>花费<span style="color: black;">更加多</span>的 CPU 周期在内部的哈希表<span style="color: black;">查询</span>以及可能的自旋锁等待上。<span style="color: black;">因此呢</span>不要等到端口用尽报错了才<span style="color: black;">起始</span>加大端口范围,<span style="color: black;">况且</span>应该一<span style="color: black;">起始</span>的时候就保持一个比较充足的值。</span></p><span style="color: black;">#&nbsp;vi&nbsp;/etc/sysctl.conf</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">net.ipv4.ip_local_port_range&nbsp;=&nbsp;5000&nbsp;65000</p><span style="color: black;">#&nbsp;sysctl&nbsp;-p&nbsp;&nbsp;//使配置生效</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>端口加大了仍然<span style="color: black;">不足</span>用,<span style="color: black;">那样</span><span style="color: black;">能够</span><span style="color: black;">思虑</span>开启端口 reuse 和 recycle。<span style="color: black;">这般</span>端口在连接断开的时候就不需要等待 2MSL 的时间了,<span style="color: black;">能够</span>快速回收。开启这个参数之前需要<span style="color: black;">保准</span> tcp_timestamps 是开启的。</span></p><span style="color: black;">#&nbsp;vi&nbsp;/etc/sysctl.conf</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">net.ipv4.tcp_timestamps&nbsp;=&nbsp;1</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">net.ipv4.tcp_tw_reuse&nbsp;=&nbsp;1</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">net.ipv4.tw_recycle&nbsp;=&nbsp;1</p><span style="color: black;">#&nbsp;sysctl&nbsp;-p</span>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>12:小心连接队列溢出</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">服务器端<span style="color: black;">运用</span>了两个连接队列来响应来自客户端的握手请求。这两个队列的长度是在服务器 listen 的时候就确定好了的。<span style="color: black;">倘若</span><span style="color: black;">出现</span>溢出,很可能会丢包。<span style="color: black;">因此</span><span style="color: black;">倘若</span>你的业务<span style="color: black;">运用</span>的是短连接且流量比<span style="color: black;">很强</span>,<span style="color: black;">那样</span><span style="color: black;">必定</span>得学会观察这两个队列<span style="color: black;">是不是</span>存在溢出的<span style="color: black;">状况</span>。<span style="color: black;">由于</span>一旦<span style="color: black;">显现</span><span style="color: black;">由于</span>连接队列<span style="color: black;">引起</span>的握手问题,<span style="color: black;">那样</span> TCP 连接耗时都是秒级以上了。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">针对</span>半连接队列, 有个简单的办法。那<span style="color: black;">便是</span>只要<span style="color: black;">保准</span> tcp_syncookies 这个内核参数是 1 就能<span style="color: black;">保准</span>不会有<span style="color: black;">由于</span>半连接队列满而<span style="color: black;">出现</span>的丢包。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">针对</span>全连接队列<span style="color: black;">来讲</span>,<span style="color: black;">能够</span><span style="color: black;">经过</span> netstat -s 来观察。netstat -s 可查看到当前系统全连接队列满<span style="color: black;">引起</span>的丢包统计。但该数字记录的是总丢包数,<span style="color: black;">因此</span>你需要再借助 watch 命令动态监控。</span></p><span style="color: black;">#&nbsp;watch&nbsp;netstat&nbsp;-s&nbsp;|&nbsp;grep&nbsp;overflowed</span>160&nbsp;<span style="color: black;">times</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">&nbsp;the&nbsp;listen&nbsp;queue&nbsp;of&nbsp;a&nbsp;socket&nbsp;overflowed&nbsp;//全连接队列满<span style="color: black;">引起</span>的丢包</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>输出的数字在你监控的过程中变了,那说明当前服务器有<span style="color: black;">由于</span>全连接队列满而产生的丢包。你就需要加大你的全连接队列的⻓度了。全连接队列是应用程序调用 listen时传入的 backlog 以及内核参数 net.core.somaxconn 二者之中较小的那个。<span style="color: black;">倘若</span>需要加大,可能两个参数都需要改。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>你手头并<span style="color: black;">无</span>服务器的权限,只是<span style="color: black;">发掘</span>自己的客户端机连接某个 server <span style="color: black;">显现</span>耗时长,想定位一下<span style="color: black;">是不是</span>是<span style="color: black;">由于</span>握手队列的问题。那<span style="color: black;">亦</span>有间接的办法,<span style="color: black;">能够</span> tcpdump 抓包查看<span style="color: black;">是不是</span>有 SYN 的 TCP Retransmission。<span style="color: black;">倘若</span>有偶发的 TCP Retransmission, 那就说明对应的服务端连接队列可能有问题了。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>13:减少握手重试</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在 6.5 节<span style="color: black;">咱们</span>看到<span style="color: black;">倘若</span>握手<span style="color: black;">出现</span><span style="color: black;">反常</span>,客户端<span style="color: black;">或</span>服务端就会<span style="color: black;">起步</span>超时重传机制。这个超时重试的时间间隔是翻倍地增长的,1 秒、3 秒、7 秒、15 秒、31 秒、63 秒 ......。<span style="color: black;">针对</span><span style="color: black;">咱们</span><span style="color: black;">供给</span>给用户直接<span style="color: black;">拜访</span>的接口<span style="color: black;">来讲</span>,重试<span style="color: black;">第1</span>次耗时 1 秒多<span style="color: black;">已然</span>是严重影响用户体验了。<span style="color: black;">倘若</span>重试到第三次以后,<span style="color: black;">特别有</span>可能某一个环节<span style="color: black;">已然</span>报错返回 504 了。<span style="color: black;">因此</span>在这种应用场景下,<span style="color: black;">守护</span>这么多的超时次数其实<span style="color: black;">无</span>任何意义。倒不如把<span style="color: black;">她们</span>设置的小<span style="color: black;">有些</span>,尽早放弃。其中客户端的 syn 重传次数由 tcp_syn_retries <span style="color: black;">掌控</span>,服务器半连接队列中的超时次数<span style="color: black;">是由于</span> tcp_synack_retries 来<span style="color: black;">掌控</span>。把它们两个调成你想要的值。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>14:<span style="color: black;">倘若</span>请求频繁,请弃用短连接改用长连接</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>你的服务器频繁请求某个 server,<span style="color: black;">例如</span> redis 缓存。和<span style="color: black;">意见</span> 1 比起来,一个更好一点的<span style="color: black;">办法</span>是<span style="color: black;">运用</span>长连接。<span style="color: black;">这般</span>的好处有</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">1)<strong style="color: blue;">节约了握手开销</strong>。短连接中每次请求都需要服务和缓存之间进行握手,<span style="color: black;">这般</span>每次都得让用户多等一个握手的时间开销。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">2)<strong style="color: blue;">规避了队列满的问题</strong>。前面<span style="color: black;">咱们</span>看到当全连接<span style="color: black;">或</span>半连接队列溢出的时候,服务器直接丢包。而客户端呢并不知情,<span style="color: black;">因此</span>傻傻地等 3 秒才会重试。要<span style="color: black;">晓得</span> tcp 本身并不是专门为互联网服务设计的。这个 3 秒的超时<span style="color: black;">针对</span>互联网用户的体验影响是致命的。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">3)<strong style="color: blue;">端口数<span style="color: black;">不易</span>出问题</strong>。端连接中,在释放连接的时候,客户端<span style="color: black;">运用</span>的端口需要进入 TIME_WAIT 状态,等待 2 MSL的时间<span style="color: black;">才可</span>释放。<span style="color: black;">因此</span><span style="color: black;">倘若</span>连接频繁,端口数量很容易<span style="color: black;">不足</span>用。而长连接就固定<span style="color: black;">运用</span><span style="color: black;">那样</span>几十上百个端口就够用了。</span></p>
    <h3 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">意见</span>15:TIME_WAIT 的优化</h3>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">非常多</span>线上服务<span style="color: black;">倘若</span><span style="color: black;">运用</span>了短连接的<span style="color: black;">状况</span>下,就会<span style="color: black;">显现</span><span style="color: black;">海量</span>的 TIME_WAIT。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">首要</span>,我想说的是<span style="color: black;">无</span>必要见到两三万个 TIME_WAIT 就恐慌的不行。从内存的⻆度来<span style="color: black;">思虑</span>,一条 TIME_WAIT 状态的连接仅仅是 0.5 KB 的内存<span style="color: black;">罢了</span>。从端口占用的<span style="color: black;">方向</span><span style="color: black;">来讲</span>,确实是消耗掉了一个端口。但假如你下次再连接的是<span style="color: black;">区别</span>的 Server 的话,该端口仍然<span style="color: black;">能够</span><span style="color: black;">运用</span>。<span style="color: black;">仅有</span>在所有 TIME_WAIT 都聚集在和一个 Server 的连接上的时候才会有问题。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">那怎么<span style="color: black;">处理</span>呢? 其实办法有<span style="color: black;">非常多</span>。<span style="color: black;">第1</span>个办法是按上面<span style="color: black;">意见</span>开启端口 reuse 和 recycle。&nbsp;第二个办法是限制 TIME_WAIT 状态的连接的最大数量。</span></p><span style="color: black;">#&nbsp;vi&nbsp;/etc/sysctl.conf</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">net.ipv4.tcp_max_tw_buckets&nbsp;=&nbsp;32768</p><span style="color: black;">#&nbsp;sysctl&nbsp;-p</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">倘若</span>再彻底<span style="color: black;">有些</span>,<span style="color: black;">亦</span><span style="color: black;">能够</span>干脆直接用⻓连接代替频繁的短连接。连接频率大大降低以后,自然<span style="color: black;">亦</span>就<span style="color: black;">无</span> TIME_WAIT 的问题了。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">好了,以上<span style="color: black;">便是</span>飞哥为<span style="color: black;">大众</span>准备的网络性能<span style="color: black;">关联</span>的 15 条<span style="color: black;">意见</span>。<span style="color: black;">更加多</span>网络性能<span style="color: black;">关联</span>的底层原理<span style="color: black;">能够</span><span style="color: black;">仔细</span>阅读《深入理解Linux网络》。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">--- EOF ---</span></p><span style="color: black;"><span style="color: black;"><strong style="color: blue;"><span style="color: black;">举荐</span>↓↓↓</strong></span></span>




j8typz 发表于 2024-10-7 05:17:25

你的言辞如同繁星闪烁,点亮了我心中的夜空。

nqkk58 发表于 7 天前

大势所趋,用于讽刺一些制作目的就是为了跟风玩梗,博取眼球的作品。

nqkk58 发表于 18 小时前

在遇到你之前,我对人世间是否有真正的圣人是怀疑的。
页: [1]
查看完整版本: Linux 网络性能的 15 个优化意见!