ElasticSearch中碰到的C10K问题
Elasticsearch基于Netty解决C10K问题背后的原理是JAVA NIO中的IO多路复用机制,涉及到三大"组件":SelectableChannel、Selector、SelectionKey。普通的"一请求一线程"方式,有一个线程负责accept请求,请求accepted后返回Channel,然后新建一个线程负责处理Channel上的IO事件。显然当请求量达到C10K时,就得创建10K个线程,这对于一台服务器是不可接受的。
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (port));
ssc.configureBlocking (false);
while (true) {
System.out.println ("Waiting for connections");
SocketChannel sc = ssc.accept( );
if (sc == null) {
// no connections, snooze a while
Thread.sleep (2000);
}else{
Socket socket = sc.socket();// an accetped request
//一请求一线程方式:new Thread processing sc.socket()
//或者采用线程池方式:ExecutorService.execute(...) processing sc.socket()
}
}
这时候,有人就会提出:accepted连接之后,也可以不创建新线程,使用线程池来处理Channel上的IO事件。有一个线程负责accept请求,请求accepted后返回Channel,然后从线程池中取出一个线程负责处理Channel上的IO事件。这种方式只是当线程池中某个线程处理完Channel上的IO事件后,线程复用,又可以让它处理最新accepted的请求(这里不再new Thread了),但是当线程池中线程被耗尽(在10K的请求量下,线程池中有1w个线程吗?)时,此时也无能为力了。
这种模型表示如下:(参考网友的图:)
既然采用线程池并没有解决C10K问题,线程池中的线程数量也是有限的,当有大量的IO请求时,IO事件一般都伴随着阻塞操作,这些阻塞操作占用了一个线程,但因为IO阻塞,线程就会被挂起,此时CPU却很空闲。
而假设此时线程池中又没有空闲线程了(要么正在执行业务逻辑、要么IO阻塞操作挂起了),此时就会看到:服务器的CPU利用率并不高,但是却无法接受新的连接请求,这也是为什么在故障检查时发现CPU利用率并不高,但是日志中却有大量被拒绝的连接。
CPU处理的事件有两种类型:IO密集型、CPU密集型。假设CPU的核数为16核,针对IO密集型任务,线程池中的线程数量可以开到64个、128个...(当然不能无限制地达到几万个...),正是因为IO密集型任务有阻塞操作,多开线程可以增加任务处理数量,从而提高CPU的吞吐量和利用率。而对于CPU密集型任务,线程池中线程数量一般设置为17(CPU核数加1),因为CPU密集型任务,几乎不会阻塞,一直在占用CPU运行,这时线程池中创建大量线程反而会使CPU实际利用率(吞吐量)下降了,因为线程上下文切换消耗了大量系统资源。《JAVA并发编程实践》中提到了CPU核数与线程数量之间的关系。
继续分析,既然线程池的方式也不能解决C10K问题,这里候就轮到IO多路复用机制了。(这里引用了Netty中的EventLoopGroup)
原生 JAVA NIO处理、Netty处理的区别就是:Netty中把Channel上发生的IO事件的处理交给了EventLoopGroup来处理,EventLoopGroup实质是个ScheduledThreadPoolExecutor,它管理着若干EventLoop线程,EventLoop在各种文档/资料中有一个专业名称:I/O 事件线程。
这里提个问题:为什么Netty里面建议:不要使用EventLoopGroup处理IO阻塞操作,而是自己创建线程池,把IO阻塞操作代理给自己创建的线程池处理?
IO多路复用机制为什么能解决C10K问题?下面详细分析why?
当新请求到来时,有一个单独的线程负责accept请求,请求 accepted 后返回一个Channel,"使用"Selector在Channel上注册它感兴趣的事件,就是与前面2种方式的本质区别。这样,不管请求量有多大(C10K的请求量),Server 都能够将之accepted,然后仅仅只是在创建的Channel上注册了感兴趣的事件而已(真正的IO事件可能尚未发生)。
通过Selector轮询,检查哪个Channel上注册的事件发生了,如果事件发生了,才"开动"线程去处理(这个线程可以来自EventLoopGroup线程池,也可以是自己 new Thread ,也可以是自已 new 一个ThreadPool中的线程)。这就是IO多路复用机制原理。所以,真正解决C10K问题的原因是基于Selector的IO多路复用机制。
// Allocate an unbound server socket channel
ServerSocketChannel serverChannel = ServerSocketChannel.open( );
// Get the associated ServerSocket to bind it with
ServerSocket serverSocket = serverChannel.socket( );
// Create a new Selector for use below
Selector selector = Selector.open( );
// Set the port the server channel will listen to
serverSocket.bind (new InetSocketAddress (port));
// Set nonblocking mode for the listening socket
serverChannel.configureBlocking (false);
// Register the ServerSocketChannel with the Selector
serverChannel.register (selector, SelectionKey.OP_ACCEPT);
while (true) {
// This may block for a long time. Upon returning, the selected set contains keys of the ready channels.
int n = selector.select();
if (n == 0) {
continue; // nothing to do
}
// Get an iterator over the set of selected keys
Iterator it = selector.selectedKeys().iterator( );
// Look at each key in the selected set
while (it.hasNext( )) {
SelectionKey key = (SelectionKey) it.next( );
// Is a new connection coming in?
if (key.isAcceptable( )) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
registerChannel (selector, channel,SelectionKey.OP_READ);
sayHello (channel);
}
// Is there data to read on this channel?
if (key.isReadable( )) {
readDataFromSocket (key);
}
//.....
在IO多路复用机制下,Server accepted 连接后返回一个Channel,并在Channel上注册感兴趣的事件(比如读操作对应着读事件)。在实际TCP连接中,建立了连接并不代表就立即发送数据了,IO多路复用基于Selector轮询(epoll),只有当数据发送过来了,底层OS把事件"通知"给Selector,数据就绪后,才"开动"EventLoopGroup中的EventLoop线程去处理数据。(readiness selection),这样Server处理C10K的连接就成为可能了。如下图:每个Socket(Channel)上的相应的事件都注册到Selector,然后有一个线程轮询Selector selector.select()
,当某个Socket上的事件发生了时,再进行相应处理。
只是在原生的JAVA NIO下,我们需要自己编写代码如何处理每个就绪选择的事件。而基于Netty,已经帮我们封装好了这些处理逻辑,每个Channel上的事件直接交由EventLoopGroup处理,示例图如下:
在这里EventLoopGroup至关重要,因为已就绪的IO事件是交给它来处理的(take EventLoop-n and bind EventLoop-n to Channel),如果EventLoopGroup中的线程执行某种"阻塞"操作(EventLoop-n process IO),那就会影响能够处理已就绪的IO事件数量,进而影响Server能接受/处理多少连接。因此,可以自己再创建一个线程池,把阻塞操作交给该线程池执行,就能保证EventLoopGroup高效地处理已发生的IO事件而不发生阻塞。
实际应用
Kafka Borker处理Client的请求是基于Reactor模式
- acceptor 线程监听Client的连接请求。
- 请求建立后,生成SocketChanel(可理解为Client与Broker之间发消息通道),processor 线程将SocketChannel上的发生的"事件"放到一个请求队列中(queued.max.requests参数),processor 线程 就是 IO事件线程,而IO事件线程最好是不能阻塞的。
- KafkaRequestHandler线程池,这是真正的执行业务逻辑处理的线程。processor线程将 业务逻辑处理(如可能发生的IO阻塞操作)代理给KafkaRequestHandler线程池来处理。该线程池中线程数量由broker参数 num.io.threads指定。
个人理解,可能有错误。
参考资料:
- 一文理解Netty模型架构
- 《Java NIO》
- 《Netty in action》
- ElasticSearch6.3.2 Transport模块和ThreadPool模块的源码
- JAVA IO 以及 NIO 理解
- music of ...
原文:https://www.cnblogs.com/hapjin/p/10895932.html
ElasticSearch中碰到的C10K问题的更多相关文章
- Elasticsearch中Mapping
映射(Mapping) 概念:创建索引时,可以预先定义字段的类型以及相关属性.从而使得索引建立得更加细致和完善.如果不预先设置映射,会自动识别输入的字段类型. 官方文档(字段数据类型):https:/ ...
- 如何在Elasticsearch中安装中文分词器(IK+pinyin)
如果直接使用Elasticsearch的朋友在处理中文内容的搜索时,肯定会遇到很尴尬的问题--中文词语被分成了一个一个的汉字,当用Kibana作图的时候,按照term来分组,结果一个汉字被分成了一组. ...
- elasticsearch中常用的API
elasticsearch中常用的API分类如下: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作,查看索引信息等 查看API: ...
- 在Elasticsearch中查询Term Vectors词条向量信息
这篇文章有点深度,可能需要一些Lucene或者全文检索的背景.由于我也很久没有看过Lucene了,有些地方理解的不对还请多多指正. 更多内容还请参考整理的ELK教程 关于Term Vectors 额, ...
- elasticsearch中的API
elasticsearch中的API es中的API按照大类分为下面几种: 文档API: 提供对文档的增删改查操作 搜索API: 提供对文档进行某个字段的查询 索引API: 提供对索引进行操作 查看A ...
- 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作(二)
CSSDesk body { background-color: #2574b0; } /*! zybuluo */ article,aside,details,figcaption,figure,f ...
- 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作
http://www.cnblogs.com/wgp13x/p/4934521.html 内容一样,样式好的版本. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据 ...
- ElasticSearch中的简单查询
前言 最近修改项目,又看了下ElasticSearch中的搜索,所以简单整理一下其中的查询语句等.都是比较基础的.PS,好久没写博客了..大概就是因为懒吧.闲言少叙书归正传. 查询示例 http:// ...
- elasticsearch中的mapping映射配置与查询典型案例
elasticsearch中的mapping映射配置与查询典型案例 elasticsearch中的mapping映射配置示例比如要搭建个中文新闻信息的搜索引擎,新闻有"标题".&q ...
随机推荐
- 设计模式之(十一)代理模式(Proxy)
软件开发行业有一个观点:任务问题都可以添加一个中间层来解决.代理模式也是这个思想下的产物. 首先看下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.就是把类委托给另外一个类,用这个类来控 ...
- zookeeper知识点总结
1. 关于ZooKeeper集群服务器数: ZooKeeper 官方确实给出了关于奇数的建议,但绝大部分 ZooKeeper 用户对于这个建议认识有偏差.一个 ZooKeeper 集群如果要对外提供可 ...
- Matlab空对象模式
在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查.Null 对象不是检查空值,而是反应一个不做任何动作的关系.这样的 Null 对象也可以在数据不可用 ...
- 人大金仓KCI
#include "bin/libkci.h" static void exit_nicely(KCIConnection *conn) { KCIConnectionDestor ...
- linux echo命令颜色显示
echo命令颜色显示: echo: -n: 不换行. -e:让转移符生效. \t(tab) \n (换行) 实例: $ echo -e "\033[34mabcd\03 ...
- 【转载】UNICODE与ASCII的区别
原文地址:https://blog.csdn.net/lx697/article/details/5914417 最近的项目涉及到了国际化的问题,由于之前并没有接触到UNICODE编码,因此,在项目期 ...
- InterpretML 微软可解释性机器学习包
InterpretML InterpretML: A Unified Framework for Machine Learning Interpretability https://github.co ...
- Linux传输指令-scp
本地传到远程 scp -rf bt.plist cjp@centos:~/下载 从远程下载到本地 scp -rf cjp@centos:~/下载 ~ 参数详解 参数 描述 -a 尽可能将档案状态.权限 ...
- WTL 9.0的变化 - atlctrls.h
atlctrls.h中是对控件的封装. 第1249行增加: void GetMargins(UINT& nLeft, UINT& nRight) const { ATLASSERT(: ...
- python scapy中sniffer的用法以及过滤器
Sniff方法定义: sniff(filter="",iface="any", prn=function, count=N) 1.filter的规则使用 Ber ...