前言
如果让我设计一个配置中心,最先想到的两个核心功能:一个是如何将配置存储下来,另一个是怎么能够实时的获取到最新的配置;最简单的方式我们可以直接利用现有的一些中间件:Zookeeper、Redis等;
Zookeeper
: 本身提供了持久化功能,同时客户端可以监听某个节点,节点数据变更,可以实时推送给客户端;Redis
: Redis也提供了持久化的方案,同时可以通过psubscribe提供的订阅功能做到配置的实时推动;
以上两个中间件在客户端进行连接的时候,其实建立的是长连接;长连接就是客户端和服务端建立连接后连接是保持的,这样服务端可以很容易的把最新的配置推送给客户端.
数据交互模式
常见的数据交互模式:Push模式和Pull模式
- Push模式:服务端主动推送数据给客户端,上面说到的Zookeeper和Redis就是这种模式; 这种模式实时性很高,对客户端来说也简单,接收处理消息即可;缺点就是服务端不知道客户端处理消息的能力,可能会导致数据积压,同时也增加了服务端的工作量,影响服务端的性能;
- Pull模式: 拉取模式,即客户端主动去服务端拉取数据,主动权在客户端,拉取数据,然后处理数据,再拉取数据,一直循环下去,具体拉取数据的时间间隔不好设定,太短可能会导致大量的连接拉取不到数据,太长导致数据接收不及时;
可以发现两种模式各有优缺点;Apollo既没有使用Push模式也没有使用Pull模式,而是使用了长轮询的数据交互模式;
- 长轮询模式:通过客户端和服务端的配合,达到主动权在客户端,同时也能保证数据的实时性;长轮询本质上也是轮询,只不过对普通的轮询做了优化处理,服务端在没有数据的时候并不是马上返回数据,会 hold 住请求,等待服务端有数据,或者一直没有数据超时处理;
长轮询模式其实在很多中间件中被广泛使用比如:RocketMQ、Kakfa、Nacos等;当然具体每个中间件是如何实现自己的长轮询方案是不一样的,本文重点介绍的是Apollo如何利用Servlet3.0中提供的异步请求处理机制来实现自己的长轮询;下面先看一下Servlet3.0的异步处理机制原理.
Servlet异步处理
Servlet 3.0开始支持异步处理请求, 在接收到请求之后Servlet线程可以将耗时的操作委派给另一个线程来完成, 这样Servlet线程就可以被释放出来,可以去接收其他的请求,可以提高系统的吞吐量;为了更加清楚的了解异步处理,我们需要了解一下线程模型,下面以常用的容器Tomcat为例,来看一下Tomcat的线程模型;
Tomcat线程模型
Unix系统I/O模型主要包含以下五种类型:同步阻塞I/O、同步非阻塞I/O、I/O多路复用、信号驱动I/O和异步I/O;
目前主流使用的是I/O多路复用模型,像以高性能著称的Netty,以及下面要介绍的Tomcat都是用来此模型;此模型依赖非阻塞Channel,可以 通过一个线程来管理多个数据通道(Channel)的状态,极大的提供了性能; 当然此模型下也演变出多个变种包括:单线程模型、多线程模型、主从多线程模型, 这里就不展开讲了。
Tomcat的NioEndpoint主要包括:Acceptor
、Poller
、SocketProcessor
和Executor
几个组件,大致关系如下图所示:
- Acceptor:默认启动一个线程通过
ServerSocketChannel
监听连接请求,同时往Selecotr中注册读事件; - Poller: 内部其实就是一个选择器
Selector
,选择哪些SocketChannel
已经准备好读写了,这里一般会启动多个pollerThread,也就是我们常见的多Selector模型; - Executor:执行器线程池,默认最大可以创建200个线程,是真正处理I/O读写的地方,这个线程很大程度上也影响了系统的吞吐量;其实异步处理也就是释放这里面的线程;
- SocketProcessor:可以理解就是处理I/O读写的任务,被
Executor
来调度;