系统性能调优思路整理

本文详细阐述了基础设施优化技巧,包括CPU缓存命中率提升、内存分配效率、哈希表与磁盘优化,以及系统网络优化的广播组播技术、C10M事件驱动和TCP握手优化。此外,还介绍了应用层编解码和分布式系统的优化策略。
摘要由CSDN通过智能技术生成

一、基础设施优化

1、提升 CPU 缓存的命中率

CPU 缓存分为数据缓存指令缓存

  • 按顺序访问数据(操作连续内存):利用数据缓存,提高读数据缓存的命中率。
  • 有规律的条件分支(如数据集先排序再处理):利用指令缓存,提高读指令缓存的命中率。
  • 数据按缓存行大小填充/对齐(通常为64字节):防止伪共享,提高并发处理能力和缓存命中率。
  • 对于多核CPU,如果缓存命中率很高,可以考虑进行CPU绑定。

伪共享:假设cache line是64字节,我们在一个64字节的并且和cache line 对齐后的内存中放入两个4字节的整数A和B,然后线程a和b分别访问A和B,在内存层面的语义是这两个线程分别独享一块内存区域,操作时互不干扰,但是在缓存cache line层面他们是共享一个cache line的,是一个"原子的数",这就是伪共享。
       缓存层面的伪共享的一致性由硬件保证,对程序员透明,也就是对这个"原子数"的操作不用显式加锁,但是伪共享会降低程序效率。

2、提升内存分配效率

进程申请内存的速度会受到内存池的影响,隐藏的内存池如下:

其中不同的 C 库内存池,都有它们最适合的应用场景,例如 :TCMalloc 对多线程下的小内存分配特别友好,每个线程独立分配内存,无须加锁,所以速度更快;而Ptmalloc2 则对各类尺寸的内存申请都有稳定的表现,更加通用。

在栈中分配内存比内存池中堆分配内存要快很多。

3、使用哈希表管理亿级数据

作为索引,哈希表是第一选择。由于哈希表基于数组实现,数组可根据下标随机访问任意元素,即下标乘以元素大小,再加上数组的首地址,就可以获得目标访问地址,直接获取数据。因此查找时间复杂度为O(1),不会随着业务规模增长而变化。

位图,哈希表的变种,限制每个哈希桶只有 1 个比特位,消耗的空间少。常用于解决缓存穿透的问题,以及快速判断对象是否存在。(布隆过滤器也可判断状态)

由于哈希表不支持范围查询与遍历,如果业务需要,可以考虑B树。

  • 如何降低 / 解决哈希冲突?

1、由于生产环境需要考虑容灾,如将哈希表原地序列化为文件,保证新进程快速恢复哈希表。相较于拉链法,开放寻址法更擅长序列化数据。

2、注重内存的节约使用。数亿条数据会放大节约下的点滴内存,再把它们用于提升哈希数组的大小,就可以通过降低装载因子来减少哈希冲突。

3、优化哈希函数。(集群的负载均衡正是用到了这个思想)

4、磁盘优化技术

磁盘是主机中最慢的硬件之一,常常是性能瓶颈,所以优化它能获得立竿见影的效果。

  • 零拷贝提升文件传输性能:

为什么读取磁盘文件时,一定要做上下文切换呢?

服务器提供文件传输功能,需要将磁盘上的文件读取出来,通过网络协议发送到客户端,然而读取磁盘或者操作网卡都由操作系统内核完成,内核权限最高。对于执行的read和write系统调用,一定会发生2次上下文切换。(用户态到内核态间切换)

零拷贝示意图

零拷贝技术:它是操作系统提供的新函数,同时接收文件描述符和 TCP socket 作为输入参数,这样执行时就可以完全在内核态完成内存拷贝,既减少了内存拷贝次数,也降低了上下文切换次数。

零拷贝技术基于 PageCache(磁盘高速缓存)。

1、PageCache 缓存了最近访问过的数据,提升了访问缓存数据的性能。

2、PageCache协助 IO 调度算法实现了 IO 合并与预读(这也是顺序读比随机读性能好的原因),进一步提升了零拷贝的性能。

异步 IO + 直接 IO:高并发场景处理大文件时,应当使用异步 IO+直接 IO 来替换零拷贝技术。(可以设定一个文件大小阈值)


知识点补充:

常见网络IO模型:

  • 同步阻塞IO(BIO)
  • 同步非阻塞IO(NIO)
  • IO多路复用
  • 异步非阻塞(AIO)

其中前三种为同步模型,AIO为异步;BIO是最简单、最常见的IO模型(Linux下默认所有socket为blocking的);IO多路复用可参考文章什么是IO多路复用?用来解决什么问题?如何实现?

零拷贝根据应用场景有不同实现方式:

1、sendfile:直接从磁盘读到内核发送到网卡,不需经过用户态到内核态的拷贝转换。

2、mmap:虚拟内存映射到用户空间,需要保证内存与磁盘的数据一致性。


5、实现高并发服务的思路

1、使用多线程,为每一个请求分配一个线程来执行。弊端:单个线程消耗内存过多,没有足够的内存创建几万线程实现并发;线程切换导致上下文切换,耗费CPU资源。

2、异步编程,实现用户态的请求切换。异步化依赖 IO 多路复用机制的同时,还需要把阻塞方法改为非阻塞方法(IO多路复用+回调函数)。处理基于 TCP 的应用层协议时,一个请求的处理代码必须被拆分到多个回调函数中,由异步框架在相应的事件生成时调用它们。弊端:代码书写难度大,易出错。

3、使用协程,协程可看作用户态的线程。与异步框架不同点在于,协程把异步化中的两段函数封装成一个阻塞的协程函数。在该函数执行时,由协程框架完成协程之间的切换,协程是无感知的。弊端:由于一个线程可以包含多个协程,如果协程触发了线程的切换就会导致该线程上的所有协程都阻塞,所以需要使用生态完善的协程,如GO语言天然支持协程。python 3内置库Asyncio支持原生协程,但生态不够完善,基于aiohttp可实现一些小的服务。

二、系统网络优化

1、一对多通讯提升局域网传输效率

广播:对局域网内所有主机发送消息

组播:对部分主机发送消息

相比于TCP协议需要建立连接,UDP协议无需建立连接,所以我们常用 UDP 协议发送广播。

广播性能高的原因:1、交换机直接转发给接收方,要比从发送方到接收方的传输路径更短。2、原本需要发送方复制多份报文再逐一发送至各个接受者的工作,被交换机完成了,这既分担了发送方的负载,也充分使用了整个网络的带宽。

组播——更精准的定向广播:网络 API 中的 setsockopt 函数可以通过 IGMP 协议,向虚拟组中添加或者删除 IP 地址。当路由器支持 IGMP 协议时,组播就可以跨越多个网络实现更广泛的一对多通讯。

广播和组播能够充分地使用全网带宽,在更关注及时性、对丢包不敏感的流媒体直播中更有应用前景。

2、C10M实现之事件驱动

早些年提到的C10K,是指服务器同时处理1万个TCP连接,近来我们更希望单台服务器的并发能力可以达到 C10M,也就是同时可以处理 1 千万个 TCP 连接。

什么是事件:从网络中接收到一个报文,就可能产生一个事件,进而触发回调函数执行。由于常见的HTTP协议是基于TCP实现的,在TCP报文中有两种事件类型:读事件和写事件。

TCP 连接建立时,会在客户端产生写事件,在服务器端产生读事件。连接关闭时,则会在被动关闭端产生读事件。在连接上收发消息时,也会产生事件,其中发送消息前的写事件与内核分配的缓冲区有关。

获取事件:多路复用epoll 函数使得进程可以高效地收集到事件。

  1. 把需要监控的 socket 传给内核(epoll_ctl 函数),它仅在连接建立等有限的时机调用;
  2. 收集事件(epoll_wait 函数)便不用传递 socket 了,这样就把 socket 的重复传递改为了一次传递,降低了性能损耗。

一个事件的时间不易过长,对于处理事件代码分为以下三类:

  • 对于计算任务,可以将请求放在独立线程中完成或者把请求拆分成多段,放慢该请求处理时间保证其他请求及时处理。
  • 读写磁盘,需要将大文件的读取拆分成多份,每份仅有几十KB,降低单次操作的耗时。
  • 通过网络访问上游服务,必须将阻塞socket改造成非阻塞 socket,用事件驱动方式处理请求。比如 Memcached 的官方 SDK 是用阻塞 socket 实现的,Nginx正确的访问方式,是使用第三方提供的 ngx_http_memcached_module 模块,它用非阻塞 socket 重新封装了 SDK。

3、提升TCP三次握手性能

TCP 是一个可以双向传输的全双工协议,所以需要经过三次握手才能建立连接。三次握手在一个 HTTP 请求中的平均时间占比在 10% 以上,在网络状况不佳、高并发或者遭遇 SYN 泛洪攻击等场景中,如果不能正确地调整三次握手中的参数,就会对性能有很大的影响。

客户端优化

  • 当客户端通过发送SYN发起握手时,可以通过tcp_syn_retries控制重发次数;
  • 当服务器的 SYN 半连接队列溢出后,SYN 报文会丢失从而导致连接建立失败。我们可以通过 netstat -s 给出的统计结果判断队列长度是否合适,进而通过 tcp_max_syn_backlog 参数调整队列的长度;
  • 服务器回复 SYN+ACK 报文的重试次数由 tcp_synack_retries 参数控制,网络稳定时可以调小它。
  • 为了应对 SYN 泛洪攻击,应将 tcp_syncookies 参数设置为 1,它仅在 SYN 队列满后开启 syncookie 功能,保证连接成功建立。

服务端优化

  • 服务器收到客户端返回的 ACK 后,会把连接移入 accept 队列,等待进程调用 accept 函数取出连接。
  • 如果 accept 队列溢出,默认系统会丢弃 ACK,也可以通过 tcp_abort_on_overflow 参数用 RST 通知客户端连接建立失败。
  • 如果 netstat 统计信息显示,大量的 ACK 被丢弃后,可以通过 listen 函数的 backlog 参数和 somaxconn 系统参数提高队列上限。

4、提升TCP四次挥手的性能

四次挥手只涉及两种报文:FIN 和 ACK。

为什么建立连接是三次握手,而关闭连接需要四次挥手?

建立连接三次握手为了保证client到server和server到client双向通道都可达;结束连接四次挥手,除了彼此确认双向通道关闭,还为服务端状态变成关闭提供异步等待时间。

 

 

三、应用层编解码优化

四、分布式系统优化

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值