主进程收到SIGNUP信号时,会执行如下操作:
-
重新加载配置,并派生一组新的工作进程,这组新的工作进程立即开始接受连接并处理流量;
-
指示旧的工作进程正常退出,工作进程停止接收新连接,当前的每个请求处理完成之后,旧的工作进程就会关掉,一旦所有的连接关闭,工作进程就将退出。
这种重新加载配置过程可能导致CPU的内存使用量小幅度提升,但是这个性能牺牲是值得的。
3.5、优雅的升级
Nginx的二进制升级过程也实现了不停服的效果。
升级过程与政策重新加载配置的方法类型,新的Nginx主进程与原始主进程并行运行,他们共享监听套接字,两个进程都处于活动状态,他们各自的工作进程都在处理流量,然后可以可以指示旧的Master和Worker进程正常退出。
3.5、Nginx的优势
在每个请求一个进程,阻塞式的连接方法中,每个连接都需要大量额外的资源开销,并且会导致频繁的上下文切换;可以尽可能消耗少的内存,每个连接几乎没有额外的开销,Nginx进程数可以设置为CPU核心数,上下文切换相对较少。
那么问题来了,我们自己写网络程序的时候,有没有可以帮助我们提高网络性能的程序框架呢?有,那就是大名鼎鼎的Netty,接下来就来说他。
4、Netty
=======
4.1、Netty主从Reactor模式
Netty也不例外,是基于Reactor模型设计和开发的。
Netty采用了主从Reactor模式,主Reactor只负责建立连接,获取已连接套接字,然后把已连接套接字的IO事件转给从Reactor线程进行处理。
我们先来大致讲讲Netty中的几个与Reactor有关的抽象概念:
-
Selector:可以理解为一个Reactor线程,内部会通过IO多路复用感知事件的发生,然后把事件交代给Channel进行处理;
-
Channel:注册到Selector中的对象,代表Selector监听的事件,如套接字读写事件;
具体上,Netty抽象出了以下模型进行实现Reactor主从模式:
Netty基于Pipeline管道的模式来处理Channel事件,从Netty的使用API中也可以了解到。
4.2、Netty主从Reactor+Worker线程池模式
为了降低具体业务逻辑对从Reactor的影响,我们可以单独把业务逻辑处理放到一个线程池中处理,这样无论是对于监听套接字的事件处理,还是对于已连接套接字事件的处理,都不会因为业务处理程序而导致阻塞了,如下图所示,更详细的说明参考我的博客 IT宅(itzhai.com) 或者公众号 Java架构杂谈(itread) 中的文章更新 网络编程范式:高性能服务器就这么回事 | C10K,Event Loop,Reactor,Proactor :
我们可以通过创建一个 DefaultEventExecutorGroup 线程池来处理业务逻辑。
大致程序框架如下图所示:
// 声明一个bossGroup作为主Reactor,本质是一个线程池,每个线程是一个EventLoop
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 声明一个workerGroup作为从Reactor,本质是一个线程池,每个线程是一个EventLoop
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 构建业务处理Group
DefaultEventExecutorGroup defaultEventExecutorGroup =
new DefaultEventExecutorGroup(10,
new ThreadFactory() {
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, “BusinessThread-” + this.threadIndex.incrementAndGet());
}
});
try {
// 创建服务端启动类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
…
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 在pipeline中添加自定义的handler
ch.pipeline().addLast(defaultEventExecutorGroup, new BizHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
Netty是基于NIO的,而Java中的NIO是JDK1.4开始支持,内部是基于IO多路复用实现的,具体的实现思路不再详细说,底层都是IO复用技术,通过Channel借助于Buffer处理感知到的IO事件。
有了NIO为啥还要有Netty?
这是两个不同层次的东西,NIO只是一个IO类库,实现同步非阻塞IO,而Netty是基于NIO实现的高性能网络框架,基于主从Reactor设计的。
NIO类库API复杂,需要处理多线程编程,自己写Reactor模式,并且客户端断线重连、半包读写,失败缓存、网络阻塞和异常码流等问题处理起来难度很大。而Netty对于NIO遇到的这些问题都做了很好的封装,主要优点体现在:
-
API使用简单;
-
封装度高,功能强大,提供多种编码解码器,解决了TCP拆包粘包问题;
-
基于Reactor模式实现,性能高,无需再自己实现Reactor了;
-
商用项目多,经历过很多考验,社区活跃…
5、Redis
=======
相信大家已经听过无数遍“Redis是单线程的”这句话了, Redis真的是单线程的吗,又是如何支撑那么大的并发量 ,并且运用到了这么多的互联网应用中的呢?
其实,Redis的单线程指的是Redis内部会有一个主处理线程,充分利用了非阻塞、IO多路复用模型,实现的一个Reactor架构。但是在某些情况下,Redis会生成线程或者子进程来执行某些比较繁重的任务。
5.1、Redis线程模型
还是那个Reactor模型,只不过我们再次踏入了不同的国界,于是又出现了一种新的的表述方式。
Redis基于Reactor模型开发了网络事件处理器,这个处理器被称为文件事件处理器。不过叫什么不重要,重要的是原理都是一样的。以下是Redis的线程模型:
这个图基本上涵盖了Redis进程处理的主要事情:
-
客户端A发起请求建立连接,监听套接字Server Socket建立连接之后,产生一个AE_READABLE事件;
-
该事件被IO多路复用处理,放入事件队列,最终被文件事件分派器分派给了连接应答处理器进行处理:连接应答处理器处理新连接,将FD1套接字的AE_READABLE事件与命令请求处理器关联起来。
-
客户端A最终在客户端生成一个已连接套接字FD1;
-
客户端A发送命令请求,产生一个AE_READABLE事件,该事件被IO多路复用处理,放入事件队列,最终被文件事件分派器分派给了命令请求处理器进行处理:命令请求处理器执行客户端FD1套接字命令操作,得到结果,将结果写入到套接字的回复缓冲区中,准备好响应给客户端;同时将FD1套接字的AE_WRITABLE事件与命令回复处理器关联;
-
当FD1套接字准备好写的时候,会产生一个AE_WRITABLE事件,该事件被IO多路复用处理,放入事件队列,最终被事件分派器分派给了命令回复处理器进行处理:命令回复处理器把结果输出响应给客户端的FD1已连接套接字;然后将FD1套接字的AE_WRITABLE事件跟命令回复处理器解除关联。
大致一个交互流程就这样完成了,是不是很简单呢。
5.2、为啥Redis单线程也这么高效?
前面已经讲了这么多Reactor模式的好处,相信大家心里也有个底了,大致总结下:
-
Redis是纯内存操作的,所以处理速度非常快,这同时也跟Redis高效的数据结构有关,不过本文重点是讲网络相关的,数据结构不展开讲;
-
Redis的瓶颈不在CPU,而是在内存和网络;
-
单线程反而避免了上下文切换的开销。
对于开发人员来说,最关注的一点就是:单线程降低了开发的复杂度,再也不需要处理各种静态条件了,就连Hash的惰性Rehash,Lpush等线程不安全的命令都可以进行无锁编程了。
5.3、Redis真的是单线程的吗?
我再问一句大家,Redis真的是单线程的吗,从Reactor模型上来说,单线程肯定会存在瓶颈的;
为此,Redis引入了多线程机制。
Redis 4.0初步引入多线程
在Redis 4.0中,Redis开始使具有更多线程。这个版本仅限于在后台删除对象,其中包括非阻塞的删除操作。UNLINK操作,只会将键从元数据中删除,并不会立刻删除数据,真正的删除操作会在一个后台线程异步执行。
Redis 6.0真正引入多线程
虽然基于Reactor模型,单线程也可以支持很大的并发量,但是要是IO读写多了,待处理的已连接套接字多了,需要执行的命令也多了,那么,单线程依旧是瓶颈,这个时候我们就要引入主从Reactor模型,甚至主从Reactor模型+Worker线程池了。
在Redis 6.0中,如果要开启多线程,可以进行设置:
io-threads 线程数
io-threads-do-reads yes // 默认IO线程只会用于写操作,如果要在读操作和协议解析的时候启用IO线程,则可以设置该选项为yes,但是Redis团队声称它并没有多大帮忙
不过呢,Redis为了避免产生线程并发安全的问题,在执行命令阶段仍然是单线程顺序执行的,只是在网络数据读写和协议解析阶段才用到了多线程。
为了进一步了解这个特性,我们可以阅读以下 redis.conf配置文件的说明。在这里,这个特性被命名为: THREADED I/O ,下面是翻译整理自里面的一些说明。
THREADED I/O
Redis大多是单线程的,但是有一些线程操作,例如UNLINK,执行缓慢的I/O访问等是在后台线程上执行的操作。
将io-threads设置为1只会像传统一样只启用单线程。
使用8个以上的线程不会有太大帮助,并且建议实际存在性能问题的时候才使用IO线程,否则就没有必要使用了。
启用IO线程后,我们仅将IO线程用于写操作,即对write(2)系统调用进行线程化并将客户端缓冲区传输到套接字。 但是,也可以使用以下配置指令通过以下方式启用读取线程和协议解析:
io-threads-do-reads yes
通常,线程读取没有太大帮助。
Redis用的是类似单线程版的Reactor + IO线程池(Worker线程池),不过与我们前面提到的单线程Reactor + Worker线程池模式有所不同,再回顾下Reactor + Worker线程池模式:
Redis是在所谓的Reactor线程(主线程)中把IO读事件一批一批地交给IO线程池进行读取,读取完毕之后,统一执行所有请求的命令,然后才是一次性把所有请求的响应写到socket,如下图所示:
等待队列中的待处理时间平均分给每个IO线程,IO线程池只是负责IO读写和解析数据,IO线程池充分利用了CPU多核处理的能力,提高了IO读写速度。
Redis 6.0真的是单线程的吗?
6、Tomcat
========
作为一个Java程序员,怎么能不认识Tomcat呢,Tomcat的线程模型又是怎样的?不用往下看,我们都能猜出Tomcat肯定会利用Reactor模式来优化网络处理,不过这个优化过程却是跟随者技术的发展慢慢演变的。
6.1、Tomcat整体架构
Tomcat是HTTP服务器,同时还是一个Servlet容器,可以执行Java Servlet,并将JavaServer Pages(JSP)和JavaServerFaces(JSF)转换为Java Servlet。
我们先来看看Tomcat各个组件的整体架构。Tomcat采用了分层和模块化的体系结构,如下所示,这个结构有点像套娃,一层套一层的,这也同时是Tomcat server.xml配置文件的层级结构:
Server是顶层组件,代表着一个Tomcat实例,在配置文件中一般如下:
…
Server下面可以包含多个Service,每个服务都有自己的Container和Connector。
- 6.1.1、Container
Container用于管理各种Servlet,处理Connector传过来的Request请求。
大家可以看到,Container内部若隐若现的好像还有内幕…是的,上图中我把内幕隐藏起来了,接口Container内部,我们可以看到这样的结构:
-
Container最顶层是Engine,一个Service只能有一个Engine,用来管理多个站点;
-
Host代表一个站点,一个Engine下可以有多个Host;
-
Context代表一个应用,一个Host下面可以有多个应用;
-
Wrapper封装Servlet,每个应用都有很多Servlet,这个是大家最熟悉的了。
-
6.1.2、Connector
Connector用于处理请求,处理Socket套接字,把原始的网络数据包装成Request对象给Container进行处理,并封装Response对象用于响应套接字输出。
如上图,一个Service可以有多个Connector, 每个Connector实现不同的连接协议,通过不同的端口提供服务。
这里已经看到我们要关注的重点了,是的,Connector就是处理网络的关键模块,这个模块的效率直接决定了Tomcat的性能!!!
接下来,我们打开Connector潘多拉的盒子,看看里面究竟有什么不可告人的秘密。
话不多说,我直接上图,这么爽快不断附图片的博客还真不多,IT宅(itzhai.com)的Java架构杂谈 算一个,重点来了,这里我们先列出传统的BIO运行模型的组件图:
其中 ProtocolHandler 中主要的组件有:
EndPoint
Processor
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
知其然不知其所以然,大厂常问面试技术如何复习?
1、热门面试题及答案大全
面试前做足功夫,让你面试成功率提升一截,这里一份热门350道一线互联网常问面试题及答案助你拿offer
2、多线程、高并发、缓存入门到实战项目pdf书籍
3、文中提到面试题答案整理
4、Java核心知识面试宝典
覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
MQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入**
[外链图片转存中…(img-0HX7n65v-1712578609928)]
[外链图片转存中…(img-qkBmzZ67-1712578609928)]
[外链图片转存中…(img-46H9D0sP-1712578609928)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算