怎么提高服务器的并发处理能力

55次阅读
没有评论

共计 5940 个字符,预计需要花费 15 分钟才能阅读完成。

这篇文章主要介绍怎么提高服务器的并发处理能力,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

什么是服务器并发处理能力一台服务器在单位时间里能处理的请求越多,服务器的能力越高,也就是服务器并发处理能力越强
有什么方法衡量服务器并发处理能力 1. 吞吐率
吞吐率,单位时间里服务器处理的最大请求数,单位 req/s
从服务器角度,实际并发用户数的可以理解为服务器当前维护的代表不同用户的文件描述符总数,也就是并发连接数。
服务器一般会限制同时服务的最多用户数,比如 apache 的 MaxClents 参数。
这里再深入一下,对于服务器来说,服务器希望支持高吞吐率,对于用户来说,用户只希望等待最少的时间,显然,双方不能满足,所以双方利益的平衡点,就是我们希望的最大并发用户数。
2. 压力测试
有一个原理一定要先搞清楚,假如 100 个用户同时向服务器分别进行 10 个请求,与 1 个用户向服务器连续进行 1000 次请求,对服务器的压力是一样吗?
实际上是不一样的,因对每一个用户,连续发送请求实际上是指发送一个请求并接收到响应数据后再发送下一个请求。
这样对于 1 个用户向服务器连续进行 1000 次请求, 任何时刻服务器的网卡接收缓冲区中只有 1 个请求,而对于 100 个用户同时向服务器分别进行 10 个请求,服务器的网卡接收缓冲区最多有 100 个等待处理的请求,显然这时的服务器压力更大。
压力测试前提考虑的条件并发用户数: 指在某一时刻同时向服务器发送请求的用户总数 (HttpWatch) 总请求数请求资源描述请求等待时间 (用户等待时间) 用户平均请求的等待时间服务器平均请求处理的时间

硬件环境

压力测试中关心的时间又细分以下 2 种: 用户平均请求等待时间(这里暂不把数据在网络的传输时间,还有用户 PC 本地的计算时间计算入内)

服务器平均请求处理时间

用户平均请求等待时间主要用于衡量服务器在一定并发用户数下,单个用户的服务质量;而服务器平均请求处理时间就是吞吐率的倒数。
一般来说,用户平均请求等待时间 = 服务器平均请求处理时间 * 并发用户数怎么提高服务器的并发处理能力
1. 提高 CPU 并发计算能力
服务器之所以可以同时处理多个请求,在于操作系统通过多执行流体系设计使得多个任务可以轮流使用系统资源。
这些资源包括 CPU,内存以及 I /O. 这里的 I / O 主要指磁盘 I /O, 和网络 I /O。
多进程 多线程
多执行流的一般实现便是进程,多进程的好处可以对 CPU 时间的轮流使用,对 CPU 计算和 IO 操作重叠利用。这里的 IO 主要是指磁盘 IO 和网络 IO,相对 CPU 而言,它们慢的可怜。
而实际上,大多数进程的时间主要消耗在 I / O 操作上。
现代计算机的 DMA 技术可以让 CPU 不参与 I / O 操作的全过程,比如进程通过系统调用,使得 CPU 向网卡或者磁盘等 I / O 设备发出指令,然后进程被挂起,释放出 CPU 资源,等待 I / O 设备完成工作后通过中断来通知进程重新就绪。
对于单任务而言,CPU 大部分时间空闲,这时候多进程的作用尤为重要。CPU 是怎么认识代码的?推荐大家看下。
多进程不仅能够提高 CPU 的并发度。其优越性还体现在独立的内存地址空间和生命周期所带来的稳定性和健壮性,其中一个进程崩溃不会影响到另一个进程。
但是进程也有如下缺点:fork()系统调用开销很大: prefork 进程间调度和上下文切换成本: 减少进程数量庞大的内存重复:共享内存

IPC 编程相对比较麻烦

减少进程切换
当硬件上下文频繁装入和移出时,所消耗的时间是非常可观的。可用 Nmon 工具监视服务器每秒的上下文切换次数。
为了尽量减少上下文切换次数,最简单的做法就是减少进程数,尽量使用线程并配合其它 I / O 模型来设计并发策略。
还可以考虑使用进程绑定 CPU 技术,增加 CPU 缓存的命中率。若进程不断在各 CPU 上切换,这样旧的 CPU 缓存就会失效。
减少使用不必要的锁
服务器处理大量并发请求时,多个请求处理任务时存在一些资源抢占竞争,这时一般采用“锁”机制来控制资源的占用。到底什么是重入锁,推荐大家看下。
当一个任务占用资源时,我们锁住资源,这时其它任务都在等待锁的释放,这个现象称为锁竞争。
通过锁竞争的本质,我们要意识到尽量减少并发请求对于共享资源的竞争。
比如在允许情况下关闭服务器访问日志,这可以大大减少在锁等待时的延迟时间。要最大程度减少无辜的等待时间。
这里说下无锁编程,就是由内核完成这个锁机制,主要是使用原子操作替代锁来实现对共享资源的访问保护。
使用原子操作时,在进行实际的写操作时,使用了 lock 指令,这样就可以阻止其他任务写这块内存,避免出现数据竞争现象。原子操作速度比锁快,一般要快一倍以上。
例如 fwrite(), fopen(),其是使用 append 方式写文件,其原理就是使用了无锁编程,无锁编程的复杂度高,但是效率快,而且发生死锁概率低。
关注微信公众号:Java 技术栈,在后台回复:多线程,可以获取我整理的 N 篇最新 Java 多线程教程,都是干货。
考虑进程优先级
进程调度器会动态调整运行队列中进程的优先级,通过 top 观察进程的 PR 值
考虑系统负载
可在任何时刻查看 /proc/loadavg, top 中的 load average 也可看出
考虑 CPU 使用率
除了用户空间和内核空间的 CPU 使用率以外,还要关注 I /O wait, 它是指 CPU 空闲并且等待 I / O 操作完成的时间比例(top 中查看 wa 的值)。
2. 考虑减少内存分配和释放
服务器的工作过程中,需要大量的内存,使得内存的分配和释放工作尤为重要。
可以通过改善数据结构和算法复制度来适当减少中间临时变量的内存分配及数据复制时间,而服务器本身也使用了各自的策略来提高效率。
例如 Apache, 在运行开始时一次申请大片的内存作为内存池,若随后需要时就在内存池中直接获取,不需要再次分配,避免了频繁的内存分配和释放引起的内存整理时间。
再如 Nginx 使用多线程来处理请求,使得多个线程之间可以共享内存资源,从而令它的内存总体使用量大大减少。
另外,Nginx 分阶段的内存分配策略,按需分配,及时释放,使得内存使用量保持在很小的数量范围。
另外,还可以考虑共享内存。
共享内存指在多处理器的计算机系统中,可以被不同中央处理器(CPU)访问的大容量内存,也可以由不同进程共享,是非常快的进程通信方式。
但是使用共享内存也有不好的地方,就是对于多机器时数据不好统一。
shell 命令 ipcs 可用来显示系统下共享内存的状态,函数 shmget 可以创建或打开一块共享内存区,函数 shmat 将一个存在的共享内存段连接到本进程空间, 函数 shmctl 可以对共享内存段进行多种操作,函数 shmdt 函数分离该共享内存。
3. 考虑使用持久连接
持久连接也为长连接,它本身是 TCP 通信的一种普通方式,即在一次 TCP 连接中持续发送多分数据而不断开连接。
与它相反的方式称为短连接,也就是建立连接后发送一份数据就断开,然后再次建立连接发送下一份数据,周而复始。
是否采用持久连接,完全取决于应用特点。
从性能角度看,建立 TCP 连接的操作本身是一项不小的开销,在允许的情况下,连接次数越少,越有利于性能的提升; 尤其对于密集型的图片或网页等小数据请求处理有明显的加速所用。
HTTP 长连接需要浏览器和 web 服务器的共同协作,目前浏览器普遍支持长连接,表现在其发出的 HTTP 请求数据头中包含关于长连接的声明,如下:Connection: Keep-Alive
主流的 web 服务器都支持长连接,比如 apache 中,可以用 KeepAlive off 关闭长连接。
对于长连接的有效使用,还有关键一点在于长连接超时时间的设置,即长连接在什么时候关闭吗?
Apache 的默认设置为 5s, 若这个时间设置过长,则可能导致资源无效占有,维持大量空闲进程,影响服务器性能。
4. 改进 I /O 模型
I/ O 操作根据设备的不同分为很多类型,比如内存 I /O, 网络 I /O, 磁盘 I /O。详解 Java 中 4 种 I/O 模型,推荐大家看下。
对于网络 I / O 和磁盘 I /O, 它们的速度要慢很多,尽管使用 RAID 磁盘阵列可通过并行磁盘磁盘来加快磁盘 I / O 速度,购买大连独享网络带宽以及使用高带宽网络适配器可以提高网络 I / O 的速度。
但这些 I / O 操作需要内核系统调用来完成,这些需要 CPU 来调度,这使得 CPU 不得不浪费宝贵的时间来等待慢速 I / O 操作。
我们希望让 CPU 足够少的时间在 i / O 操作的调度上,如何让高速的 CPU 和慢速的 I / O 设备更好地协调工作,是现代计算机一直探讨的话题。各种 I / O 模型的本质区别在于 CPU 的参与方式。
DMA 技术
I/ O 设备和内存之间的数据传输方式由 DMA 控制器完成。在 DMA 模式下,CPU 只需向 DMA 下达命令,让 DMA 控制器来处理数据的传送,这样可以大大节省系统资源。
异步 I /O
异步 I / O 指主动请求数据后便可以继续处理其它任务,随后等待 I / O 操作的通知,这样进程在数据读写时不发生阻塞。
异步 I / O 是非阻塞的,当函数返回时,真正的 I / O 传输已经完成,这让 CPU 处理和 I / O 操作达到很好的重叠。
I/ O 多路复用
epoll 服务器同时处理大量的文件描述符是必不可少的,若采用同步非阻塞 I / O 模型,若同时接收 TCP 连接的数据,就必须轮流对每个 socket 调用接收数据的方法,不管这些 socket 有没有可接收的数据,都要询问一次。
假如大部分 socket 并没有数据可以接收,那么进程便会浪费很多 CPU 时间用于检查这些 socket 有没有可以接收的数据。
多路 I / O 就绪通知的出现,提供了对大量文件描述符就绪检查的高性能方案,它允许进程通过一种方法同时监视所有文件描述符,并可以快速获得所有就绪的文件描述符,然后只针对这些文件描述符进行数据访问。
epoll 可以同时支持水平触发和边缘触发,理论上边缘触发性能更高,但是代码实现复杂,因为任何意外的丢失事件都会造成请求处理错误。
epoll 主要有 2 大改进:epoll 只告知就绪的文件描述符,而且当调用 epoll_wait()获得文件描述符时,返回并不是实际的描述符,而是一个代表就绪描述符数量的值,然后只需去 epoll 指定的一个数组中依次取得相应数量的文件描述符即可。这里使用了内存映射 (mmap) 技术,这样彻底省掉了这些文件描述符在系统调用时复制的开销。

epoll 采用基于事件的就绪通知方式。其事先通过 epoll_ctrl()注册每一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似 callback 的回调机制,当进程调用 epoll_wait()时得到通知

关于 IO 模型,可以参考笔者前面写的相关文章 Java NIO.2;关于 epoll,可以参考笔者前面写的文章 select、poll 和 epoll 简介。
Sendfile
大多数时候,我们都向服务器请求静态文件,比如图片,样式表等。
在处理这些请求时,磁盘文件的数据先经过内核缓冲区,然后到用户内存空间,不需经过任何处理,其又被送到网卡对应的内核缓冲区,接着再被送入网卡进行发送。
Linux 提供 sendfile()系统调用,可以讲磁盘文件的特定部分直接传送到代表客户端的 socket 描述符,加快了静态文件的请求速度,同时减少 CPU 和内存的开销。
适用场景:对于请求较小的静态文件,sendfile 发挥的作用不那么明显,因发送数据的环节在整个过程中所占时间的比例相比于大文件请求时小很多。
内存映射
Linux 内核提供一种访问磁盘文件的特殊方式,它可以将内存中某块地址空间和我们指定的磁盘文件相关联,从而对这块内存的访问转换为对磁盘文件的访问。这种技术称为内存映射。
多数情况下,内存映射可以提高磁盘 I / O 的性能,无须使用 read()或 write()等系统调用来访问文件,而是通过 mmap()系统调用来建立内存和磁盘文件的关联,然后像访问内存一样自由访问文件。
缺点:在处理较大文件时,内存映射会导致较大的内存开销,得不偿失。
直接 I /O
在 linux 2.6 中,内存映射和直接访问文件没有本质差异,因为数据需要经过 2 次复制,即在磁盘与内核缓冲区之间以及在内核缓冲区与用户态内存空间。
引入内核缓冲区的目的在于提高磁盘文件的访问性能,然而对于一些复杂的应用,比如数据库服务器,它们为了进一步提高性能,希望绕过内核缓冲区,由自己在用户态空间实现并管理 I / O 缓冲区,比如数据库可根据更加合理的策略来提高查询缓存命中率。
另一方面,绕过内核缓冲区也可以减少系统内存的开销,因内核缓冲区本身就在使用系统内存。
Linux 在 open()系统调用中增加参数选项 O_DIRECT, 即可绕过内核缓冲区直接访问文件, 实现直接 I /O。
在 Mysql 中,对于 Innodb 存储引擎,自身进行数据和索引的缓存管理,可在 my.cnf 配置中分配 raw 分区跳过内核缓冲区,实现直接 I /O。
5. 改进服务器并发策略
服务器并发策略的目的,是让 I / O 操作和 CPU 计算尽量重叠进行,一方面让 CPU 在 I / O 等待时不要空闲,另一方面让 CPU 在 I / O 调度上尽量花最少的时间。
一个进程处理一个连接,非阻塞 I /O
这样会存在多个并发请求同时到达时,服务器必然要准备多个进程来处理请求。其进程的开销限制了它的并发连接数。
但从稳定性和兼容性的角度,则其相对安全,任何一个子进程的崩溃不会影响服务器本身,父进程可以创建新的子进程;这种策略典型的例子就是 Apache 的 fork 和 prefork 模式。
对于并发数不高(如 150 以内)的站点同时依赖 Apache 其它功能时的应用选择 Apache 还是可以的。
一个线程处理一个连接,非阻塞 IO
这种方式允许在一个进程中通过多个线程来处理多个连接,一个线程处理一个连接。Apache 的 worker 模式就是这种典型例子,使其可支持更多的并发连接。不过这种模式的总体性能还不如 prefork,所以一般不选用 worker 模式。推荐阅读:14 个 Java 并发容器。
一个进程处理多个连接,异步 I /O
一个线程同时处理多个连接,潜在的前提条件就是使用 IO 多路复用就绪通知。
这种情况下,将处理多个连接的进程叫做 worker 进程或服务进程。worker 的数量可以配置,如 Nginx 中的 worker_processes 4。
一个线程处理多个连接,异步 IO
即使有高性能的 IO 多路复用就绪通知,但磁盘 IO 的等待还是无法避免的。更加高效的方法是对磁盘文件使用异步 IO,目前很少有 Web 服务器真正意义上支持这种异步 IO。
6. 改进硬件环境还有一点要提及的是硬件环境,服务器的硬件配置对应用程序的性能提升往往是最直接,也是最简单的方式,这就是所谓的 scale up。这里不做论述。

以上是“怎么提高服务器的并发处理能力”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注丸趣 TV 行业资讯频道!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计5940字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)