我们在谈论高并发的时候究竟在谈什么?看这一篇彻底明白并发原理

老挝首都2022-07-06  20

高并发进程上下文切换的目录是什么线程上下文切换协同上下文切换性能测试结果汇总欢迎关注作者,优质文章在这里等你。

文章来源:

谢谢你的精彩文章。作者从操作系统原理的角度深入解释了什么是高并发。

笔者看过相关文章,讲解的很清楚,值得一读。

什么是高并发?高并发性是互联网分布式系统架构的性能指标之一。通常指的是单位时间内系统可以同时处理的请求数量。简单地说,它是QPS(每秒查询数)。那么,当我们谈论高并发性时,我们在谈论什么呢?

高并发到底是什么?

结论如下:高并发的基本表现是单位时间内系统可以同时处理的请求数量,

高并发的核心是对CPU资源的有效挤压。

比如我们开发一个叫md5 Exhaustion的应用,每个请求都会携带一个md5加密的字符串,最后系统会枚举所有的结果并返回原来的字符串。此时,我们的应用场景或应用业务是CPU密集型而不是io密集型的。此时CPU已经在做有效的计算,甚至可以把CPU利用率运行到满负荷。这个时候,我们再谈高并发就没有意义了。

(当然我们可以通过加机器,也就是加CPU来提高并发。这是所有正常猿类都知道的无厘头方案。谈加机没有意义。没有加机器解决不了的高并发。如果有,说明你加的机器还不够多!)

对于大多数互联网应用来说,CPU不是也不应该是系统的瓶颈。大多数时候,系统的状态是CPU在等待I/O(硬盘/内存/网络)读/写操作的完成。

这时候可能有人会说,我看系统监控,内存和网络都正常,但是CPU利用率满了。为什么?

这是一个好问题。我后面会举实际例子,再次强调上面提到的四个字‘有效压榨’。这四个字将包围这篇文章的全部内容!控制变量法一切都是相互关联的。我们在说高并发的时候,系统的每一个环节都要与之匹配。我们先来回顾一个经典的C/S HTTP请求流程。

如图中的序列号所示:

我们将检查DNS服务器的解析,请求将到达负载平衡集群。负载平衡服务器将根据配置的规则请求分配给服务层。服务层也是我们业务的核心层。可能会有PRC和MQ等的一些调用。这里,然后数据将通过缓存层持久化并返回给客户端。为了实现高并发性,我们需要负载平衡、服务层、缓存层和持久层具有高可用性和高性能。即使在第五步,我们也可以通过压缩静态文件来做优化。我们可以在这里每一层写几本书。

本文主要讨论服务层,也就是红线圈出来的部分。忘记数据库和缓存的影响。

高中知识告诉我们,这叫受控变量法。

并发网络编程模型的演化历史

并发一直是服务器端编程的一个重点和难点问题。为了优化系统的并发性,从最初的Fork进程开始,到进程池/线程池,到epoll事件驱动(Nginx,node.js反人类回调),再到协进程。

从上面可以清楚的看到,整个进化的过程就是压榨CPU有效性能的过程。

什么?不明显?

先说上下文切换。在说上下文切换之前,先把两个名词的概念说清楚。

并行:两个事件同时完成。

并发:两个事件在同一时间段交替发生。从宏观上看,这两个事件都发生了。

线程是操作系统调度的最小单位,进程是资源分配的最小单位。因为CPU是串行的,所以对于单核CPU来说,同一时间必须只有一个线程占用CPU资源。所以Linux作为一个多任务(进程)系统,会频繁切换进程/线程。

在每个任务运行之前,CPU需要知道在哪里加载,在哪里运行。这些信息存储在操作系统的CPU寄存器和程序计数器中。这两样东西叫做CPU上下文。

进程由内核管理和调度,进程的切换只能在内核状态下进行。因此,用户空之间的资源如虚拟内存、堆栈、全局变量,内核空之间的状态如内核堆栈、寄存器等称为进程上下文。

如前所述,线程是操作系统调度的最小单位。同时,线程会共享父进程的虚拟内存、全局变量等资源,所以父进程的资源加上它自己的私有数据在线就叫做线程的上下文。

对于线程的上下文切换,如果同一个进程的线程共享资源,会比多个进程之间的切换消耗更少的资源。

现在更容易解释进程和线程之间的切换会导致CPU上下文切换和进程/线程上下文切换。这些上下文切换将消耗额外的CPU资源。

再进一步说协同学的语境切换。那么,协同学是否不需要语境切换呢?可以,但是不会引起CPU上下文切换和进程/线程上下文切换,因为这些切换都是在同一个线程中,也就是在用户状态下。你甚至可以简单的理解为协调上下文之间的切换就是在你的程序中移动指针,CPU资源仍然属于当前线程。

如果需要深入了解,可以深入看看Go的GMP模型。

最终的影响是,协同过程进一步挤压了CPU的有效利用率。

回到开头的问题

这时候可能有人会说,我看系统监控,内存和网络都正常,但是CPU利用率满了。为什么?注意,本文讲到CPU利用率的时候,肯定会加上“有效”这个词作为定语。当CPU利用率满负荷运行时,很多时候其实是做了很多低效的计算。

以“世界上最好的语言”为例,典型的PHP-FPM的CGI模式。每个HTTP请求:

会读取数百个框架的php文件,

将重新建立/释放MYSQL/REIDS/MQ MQ连接,

将动态地重新解释、编译和执行PHP文件,

会在不同的php-fpm进程中直接连续切换。

php的这种CGI运行模式,从根本上决定了它在高并发时灾难性的表现。

发现问题往往比解决问题更难。当我们在讲高并发的时候,理解了我们在讲什么,我们就会发现,高并发和高性能不是被编程语言所限制,而只是被你的思想所限制。

发现问题,解决问题!当我们能有效挤压CPU性能的时候,能达到什么效果?

让我们来看看php+swoole服务之间的性能差异。

性能比较前的准备

什么是swole swole?swole是基于事件的高性能异步什么是并发网络通信引擎Netty?Netty是JBOSS提供的java开源框架。Netty提供了一个异步、事件驱动的网络应用程序框架和工具,用于快速开发高性能、高可靠性的网络服务器和客户端程序。单台机器可以达到的最大连接数。

每个TCP连接由四个属性标识:本地ip、本地端口、远程ip和远程端口。

本地端口由16位组成,因此本地端口的最大数量是2 ^ 16 = 65535。

远程端口由16位组成,因此远程端口的最大数量是2 ^ 16 = 65535。

同时,在linux的底层网络编程模型中,对于每个TCP连接,操作系统都会维护一个与之对应的文件描述符(fd)文件,fd的数量限制可以通过ulimt -n命令查看和修改。在测试之前,我们可以执行命令:ulimit -n 65536来将这个限制修改为65535。

因此,在不考虑硬件资源限制的情况下,

当地最大的。

最大遥控次数+∞ =无限制~ ~。

PS:实际上有些预留端口会被操作系统占用,所以本地连接数实际上是达不到理论值的。

性能比较

为每个资源测试一个docker容器,1G内存+双核CPU

Docker-compose的排列如下:

# Java 8版本:" 2.2 "服务:java8: container_name: "java8 "主机名:" java8" image: "java:8 "卷:- /home/cg/MyApp:/ MyApp端口:- "5555:8080 "环境:-tz =亚洲/上海working _ dir:/MyApp CPU:2 CPU set:0,1 mem _ limit:1024m mem swap _ limit:1024m mem _ reservation:1024m tty:true # PHP 7-SW version:" 2phpuse Swoole \ Server使用Swoole\

公共静态void main(String[] args)引发异常{ //配置SSL。最终SslContext sslCtxif(SSL){ SelfSignedCertificate SSC = new SelfSignedCertificate();SSL CTX = sslcontextbuilder . for server(SSC . certificate()、ssc.privateKey())。build();} else { sslCtx = null} //配置服务器。EventLoopGroup boss group = new NioEventLoopGroup(2);EventLoopGroup worker group = new NioEventLoopGroup();请尝试{ server bootstrap b = new server bootstrap();b .选项(ChannelOption。SO_BACKLOG,1024);b .群体(老板群体、工人群体)。通道(nioserversocketchannel.class)。处理程序(新的日志处理程序(loglevel.info))。childhandler(新进程很好。

端口551代表PHP服务。

端口555代表Java服务。

压力测量工具结果对比:ApacheBench (ab)ab命令:Docker Run-RM Jordi/a b-K-C1000-n 100000/

在1000个并发Http请求的基准测试中,

Java+netty压力测试结果:

PHP+swoole的压力测量结果:

服务QPS响应时间毫秒(最大,最小)内存(MB) Java+netty84042.11 (11,25) 600+PHP+swoole87222.98 (9,25) 30+

Ps:上图选取了三次压力测量下的最佳结果。

总的来说性能差别不大。PHP+swoole的服务甚至略好于java+netty,尤其是在内存占用方面。Java用600 MB,PHP只用30MB。

这是什么意思?如果没有IO闭锁操作,协调切换将不会发生。

这只能说明多线程+epoll模式有效挤压CPU性能,你甚至可以用PHP写出高并发高性能的服务。

性能对比——见证奇迹的时刻

其实上面的代码并没有表现出协同学的优秀性能,因为整个请求中并没有阻塞操作,但是往往我们的应用程序都伴随着文档读取、DB连接等各种阻塞操作。下面我们来看一下添加阻断操作后的压力测试结果。

在Java和PHP代码中,我添加了sleep(0.01) // second的代码来模拟0.01秒的系统调用阻塞。

该代码将不会再次发布。

进行所有压力测试大约需要10分钟。。。

带IO阻塞操作的PHP+swoole的压力测量结果:

服务QPS响应时间毫秒(最大,最小)内存(MB) Java+netty1562.69 (52,160) 100+PHP+swoole9745.20 (9,25) 30+

从结果可以看出,基于协同学的php+ swoole服务的QPS比Java+netty服务高6倍。

当然,这两个测试代码都是官方演示中的源代码,肯定有很多配置可以优化。优化后,效果肯定会好很多。

再想一想,为什么官方默认的线程/进程数不多设置一点?

进程/线程的数量不是越多越好。正如我们之前讨论的,当进程/线程切换时,将会消耗额外的CPU资源,尤其是在用户模式和内核模式之间切换时!

总结这些压力测试结果,我不是说Java。我的意思是,只要你明白高并发的核心是什么,找到这个目标,不管你用什么编程语言,只要你对CPU利用率进行有效优化(连接池、守护进程、多线程、协程、选择轮询、epoll事件驱动),你也可以构建一个高并发、高性能的系统。

现在,当我们谈论高性能时,您明白我们在谈论什么了吗?

想法永远比结果重要!

您的转发+关注是对作者最大的支持。欢迎关注。如果你对大厂的架构设计,BAT等厂商的面试问题解读,编程语言理论或者互联网圈的趣闻轶事感兴趣,请关注作者。没毛病。干货文章都在这里。

转载请注明原文地址:https://juke.outofmemory.cn/read/620229.html

最新回复(0)