入门架构——单机高性能

clipboard.png

协做方式前端

在高并发场景中,必需要让服务器同时维护大量请求链接,多是一个服务进程建立另外一个进程,也多是一个服务线程去建立另外一个线程,但链接结束后进程或线程就销毁了,这是一个巨大的浪费json

一个天然的想法就是经过建立一个进程/线程池从而达到资源复用,一个进程/线程能够处理多个链接性能优化

那么如何处理多个链接?服务器

同步阻塞网络

clipboard.png

一个请求占用一个进程处理,先等待数据准备好,而后从内核向进程复制数据,最后处理完数据后返回数据结构

若是一个进程处理一个请求,再来请求再开进程,虽然会有CPU在等待IO时的浪费和进程数量限制,但仍是能够作到必定的高性能。若是一个进程处理多个链接,那么其余链接会在第一个链接致使的IO操做时被阻塞,这样没法作到高性能,因此不会选择该模式实现高性能多线程

同步非阻塞架构

clipboard.png

进程先将一个套接字在内核中设置成非阻塞再等待数据准备好,在这个过程当中反复轮询内核数据是否准备好,准备好以后最后处理数据返回并发

一个进程处理一个请求不太实际,一个进程处理多个请求的性能上限会更高,因此简单的处理同步阻塞中的阻塞问题的方式就是一个进程轮询多个链接,但轮询是有CPU开销的,且若是一个进程有成千上万的链接时效率很低,也不会选择该模式实现高性能框架

I/O多路复用

clipboard.png

至关于对同步非阻塞的优化版本,区别在于I/O多路复用阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。换句话说,轮询机制被优化成通知机制,多个链接公用一个阻塞对象,进程只须要在一个阻塞对象上等待,无需再轮询全部链接

当某条链接有新的数据能够处理时,操做系统会通知进程,进程从阻塞状态返回,开始处理业务,这是高性能的基础,但仍不算高效,由于让一个进程/线程进行select是不够的,还须要某种机制来分配进程/线程去负责监听、处理数据这个两个过程才能实现高性能

Reactor

I/O多路复用结合线程池就是Reactor

Reactor的核心包括Reactor(监听和分配事件)和处理资源池(负责处理事件),具体实现能够多变,体如今:

  • Reactor的数量能够变化
  • 处理资源池的数量能够变化,能够是单个进程/线程,也能够是多个进程/线程

单Reactor单进程/线程

clipboard.png

Reactor对象经过select监控链接事件,收到事件后经过dispatch分发
若是是创建链接,交给Acceptor处理,经过accept接收链接,建立一个Handler来处理链接后续的事件
若是是否是创建链接事件,交给以前创建链接阶段建立的对应的Handler处理
优势是简单,没有进程间通讯、竞争,缺点是只有一个进程,没法发挥多核CPU性能,且Handler上处理某个链接的业务时,整个进程没法处理任何其余事件

因此适用场景很少,适合于业务处理很是快的场景,如Redis

单Reactor多线程

clipboard.png

与单Reactor单进程/线程在于Handler只负责响应事件,业务处理交给Processor,且Processor会在独立的子线程中处理,而后将结果发给主进程的Handler处理

优势是充分发挥了多核CPU的能力,缺点是多线程数据共享复杂,且Reactor承担全部事件的监听和响应,高并发会成为瓶颈

多Reactor多进程/线程

clipboard.png

为了解决单Reactor多线程的问题,这个模式的区别:

父进程的select监听到链接创建事件后经过Acceptor将新的链接分配给子进程
子进程的Reactor将新的链接加入本身的链接队列进行监听,并建立一个Handler用于处理链接的事件
当有新的事件发生,子Reactor会调用链接的Handler
Handler完成read->业务处理->send的业务流程
看起来比单Reactor多线程更复杂,但实现更简单,由于:

父进程只负责接收并创建新链接,子进程只负责业务处理
父子进程之间的交互只有父进程把链接交给子进程,子进程不须要把结果返回给父进程
Nginx、Memcache、Netty使用的就是该模式

Proactor

Reactor是同步非阻塞的网络模型,由于真正的read和send这样的IO操做都须要用户进程同步操做,若是把IO操做改成异步就能进一步提高性能,这就是Proactor

clipboard.png

初始化器Initiator负责建立通知组件Proactor和处理器Handler,而且都注册到内核
内核负责处理注册请求,并完成IO操做
内核完成IO操做后通知Proactor
Proactor回调到Handler
Handler完成业务处理,Handler也能够注册新的Handler到内核
理论上Proactor的效率高于Reactor,让IO操做与计算重叠,但要实现真正的异步IO,须要操做系统支持,Windows支持而Linux不完善

实践方式

以上是操做系统或Nginx或高性能服务器软件已经帮咱们解决了,咱们在编码的时候除非达到了代码的性能极限,通常不须要担忧这方面

因此下面谈到的是一些做为开发人员,为了提高单体服务的性能而须要注意的地方

高性能的代码

性能

选用高性能的框架。好比Java方面考虑用Netty,Go方面考虑用Gin
代码细节。这块是与咱们最息息相关的了,如何写出高性能代码,每种语言都有本身的最佳实践,反而这里没办法讲到,须要平常学习积累。好比字符串拼接效率如何最高?哪一个数据结构适合在某个业务场景使用?
IO细节。因为磁盘的读写速度远低于CPU、内存,因此对磁盘的读写每每会严重拖慢性能,好比写日志,不注意的话可能本地写了一份日志文件,控制台也在输出日志信息,另一个文件上传流也在写入信息,那么log会成倍地拖慢速度,因此须要统一日志输出方式,好比只往日志收集流中写入到EFK系统中查看

单体服务器压测

写出了自认为高性能的代码?赶忙来压测试一遍,压测就一个目的:寻找瓶颈

在接近于生产环境下的机器作压测才是最真实的,还须要使用专门的压测机来避免环境的影响,最简单的方式是经过ab工具测试QPS是多少,同时检测CPU、内存、网络流量是否达到了瓶颈,而后再根据瓶颈,寻找解决方案,这就是大致压测以及优化的思路,单体应用的压测还挺简单,至于集群的压测就须要考虑更多,往后再说

最近我对一个服务进行了压测,QPS是1200,而且是跑在3台虚拟机上的,瓶颈在于CPU,因此很明显单体服务的性能过低或者是总路由出现了转发问题,这里不考虑后者,咱们先分析这个服务的接口是拿来干什么的,这个接口仅仅作了一件事,从Redis获取数据,转发给前端,这里也不考虑Redis的性能问题,那么就多是在处理数据的时候性能过低。因此同事将返回的json压缩了一下,从40kb压缩到了20kb,QPS直接提高到2500。这就是一个简单的压测后调优的例子,还能够参考这里。

合适的服务器

规格

若是你用过云服务,那么确定会在启动实例的时候被强迫去选择一个规格的实例,以下

clipboard.png

那么请根据你的服务是哪一种性能须要,选择对应的服务器呢,固然还要考虑你滴钱包够不够

配置

在Linux平台上,在进行高并发TCP链接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是由于系统为每一个TCP链接都要建立一个socket句柄,每一个socket句柄同时也是一个文件句柄)。可以使用ulimit命令查看系统容许当前用户进程打开的文件数限制

相似的,对Linux系统配置也会影响到性能的参数须要格外注意,但也须要听从一个方式:按需调整

感谢您耐心看完的文章

顺便给你们推荐一个Java技术交流群:710373545里面会分享一些资深架构师录制的视频资料:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多!

相关文章
相关标签/搜索