【架构入门 – 高性能篇】单机高性能
协作方式
在高并发场景中,必须要让服务器同时维护大量请求连接,可能是一个服务进程创建另一个进程,也可能是一个服务线程去创建另一个线程,但连接结束后进程或线程就销毁了,这是一个巨大的浪费
一个自然的想法就是通过创建一个进程/线程池从而达到资源复用,一个进程/线程可以处理多个连接
那么如何处理多个连接?
同步阻塞
一个请求占用一个进程处理,先等待数据准备好,然后从内核向进程复制数据,最后处理完数据后返回
如果一个进程处理一个请求,再来请求再开进程,虽然会有CPU在等待IO时的浪费和进程数量限制,但还是可以做到一定的高性能。如果一个进程处理多个连接,那么其他连接会在第一个连接导致的IO操作时被阻塞,这样无法做到高性能,所以不会选择该模式实现高性能
同步非阻塞
进程先将一个套接字在内核中设置成非阻塞再等待数据准备好,在这个过程中反复轮询内核数据是否准备好,准备好之后最后处理数据返回
一个进程处理一个请求不太实际,一个进程处理多个请求的性能上限会更高,所以简单的处理 同步阻塞 中的阻塞问题的方式就是一个进程轮询多个连接,但轮询是有CPU开销的,且如果一个进程有成千上万的连接时效率很低,也不会选择该模式实现高性能
I/O多路复用
相当于对 同步非阻塞 的优化版本,区别在于 I/O多路复用 阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。换句话说,轮询机制被优化成通知机制,多个连接公用一个阻塞对象,进程只需要在一个阻塞对象上等待,无需再轮询所有连接
当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始处理业务,这是高性能的基础,但仍不算高效,因为让一个进程/线程进行select是不够的,还需要某种机制来分配进程/线程去负责监听、处理数据这个两个过程才能实现高性能
Reactor
I/O多路复用结合 线程池 就是 Reactor
Reactor的核心包括Reactor(监听和分配事件)和处理资源池(负责处理事件),具体实现可以多变,体现在:
- Reactor的数量可以变化
- 处理资源池的数量可以变化,可以是单个进程/线程,也可以是多个进程/线程
单Reactor单进程/线程
- Reactor对象通过select监控连接事件,收到事件后通过dispatch分发
- 如果是建立连接,交给Acceptor处理,通过accept接收连接,创建一个Handler来处理连接后续的事件
- 如果是不是建立连接事件,交给之前建立连接阶段创建的对应的Handler处理
优点是简单,没有进程间通信、竞争,缺点是只有一个进程,无法发挥多核CPU性能,且Handler上处理某个连接的业务时,整个进程无法处理任何其他事件
所以适用场景不多,适合于业务处理非常快的场景,如Redis
单Reactor多线程
与 单Reactor单进程/线程 在于Handler只负责响应事件,业务处理交给Processor,且Processor会在独立的子线程中处理,然后将结果发给主进程的Handler处理
优点是充分发挥了多核CPU的能力,缺点是多线程数据共享复杂,且Reactor承担所有事件的监听和响应,高并发会成为瓶颈
多Reactor多进程/线程
为了解决 单Reactor多线程 的问题,这个模式的区别:
- 父进程的select监听到连接建立事件后通过Acceptor将新的连接分配给子进程
- 子进程的Reactor将新的连接加入自己的连接队列进行监听,并创建一个Handler用于处理连接的事件
- 当有新的事件发生,子Reactor会调用连接的Handler
- Handler完成read->业务处理->send的业务流程
看起来比 单Reactor多线程 更复杂,但实现更简单,因为:
- 父进程只负责接收并建立新连接,子进程只负责业务处理
- 父子进程之间的交互只有父进程把连接交给子进程,子进程不需要把结果返回给父进程
Nginx、Memcache、Netty使用的就是该模式
Proactor
实践方式
高性能的代码
性能 日志
合适的服务器
规格 配置
参考
号外号外
最近在总结一些针对 Java 面试相关的知识点,感兴趣的朋友可以一起维护~