一日,线上的环境的后端服务向ES发起的查询被拒绝,引发后了后端服务的异常。打开ES的日志,发现错误如下:

很明显,ES的search线程池队列太小了,queue capacity只有只有150,但ES查询的请求已经堆积了168个了,超出了线程池队列的限制,ES直接报异常拒绝了。查询官方文档

search线程池的类型是fixed_auto_queue_size。这个线程池的任务是做search,count,suggest这三类操作。这里先对fixed_auto_queue_size做研究,queue的默认线程池大小配置的是1000。那为什么我们队列大小只有150,于是我们查看线上是否对线程池做了专门的配置,果不其然,其它组的同事把配置改成了如下

奇怪的是,在我们查询量变大的时候,ES的队列大小没有变大,难不成max_queue的配置就是摆设。 这个配置是我们最新加上的,在之前没有发现这样的问题,说明默认值的队列大小1000是够用的,但一下子降低到150,足足降低了6倍大小,出现问题是可以理解。 那么为什么队列大小会改变呢,到了这里,只有研究文档了。

到这里,可以发现,队列的大小变化和这个所谓的利特尔定律有关系,这个是一种主要用于预估队列应该如何变化的算法,大白话来说: 我吃1碗饭要10分钟,那么给我50分钟,我可以干掉5碗米饭,这里的5碗米饭,就可以认为是我们的队列大小。50分钟,是我的目标时间。 到了这里,我们可以明白。如果在任务繁忙的时间内,我们有大量的查询和写入任务,每个节点的负载都比较高,同理,每个查询任务花费的时间都会提示,在target_response_time固定的情况下,队列的大小,就会缩小,由于我的队列最小值设置的是100,那么理论上,队里的最小值就会掉到min_queue_size的大小上去。

到了这里,我们已经定位到问题了,只需要将min_queue_size的值调大即可。这样我们的queue_size就变大了。

任然有三个问题,还不清楚,需要研究

  1. 为什么之前使用默认配置没有问题,修改配置后文档中明确说明线程池的队列初始大小是1000,但是最大值和最小值文档的默认值到底是多少
  2. 如果一个查询占据一个队列,最小队列大小也只能支撑100并发查询,但是实际上,当时时间段的业务没有那个量
  3. 关于参数auto_queue_frame_size具体含义,文档上面的描述不清除

要弄清楚第一个问题,就需要撸一下ES的源码了,我们线上系统的版本是6.4.2。这里我也下载了6.4.2版本的源码,把系统跑通后,利用系统默认配置,重启服务,发现在默认配置的情况下是 min_queue_size 默认值是 1000, max_queue_size默认值也是1000,如图。

在使用系统默认配置的情况下,fixed_auto_queue_size就是一个队列大小固定为1000的线程池,和fix类型的线程池相比几乎没有区别,同时search线程池的type必须指定为fixed_auto_queue_size,设定为其它类型的线程池,es启动的时候,会直接抛出异常。

第二个问题,每个线程池的队列大小是属于节点级别的,当时客户端发起一个查询时候,如果我们的es集群有三个节点,每个节点30分片,每次客户端发起的查询都在每个分片上面执行一次,平均每个节点就会发生30次查询,如果每个查询执行够慢的话,都堆积在队列里面,每个节点的search队列中就堆积了30个任务。回到问题2,在我们线上,我们的索引设置的是99个分片,分布在3个节点上面,平均每次查询落在每个节点上面就会占据线程池33个队列(假设足够慢),当我们线上search队列大小回退到150的时候,只能承受不到5个客户端的并发查询请求,这个性能肯定是不能忍受的。

第三个问题,就绕不过利特尔定律,这里继续白话利特尔定律,还是拿吃饭举例,我吃1碗饭要10分钟,但是在实际情况下,我不可能每碗饭都吃10分钟。当我肚子饿的时候,我吃的快,5分钟就吃完了。但如果我身体不舒服,一碗饭就要吃30分钟,因此为了预估我平均一碗饭要吃多少分钟,就需要多参考几次我吃饭的时间。auto_queue_frame_size就是具体参考几次。在源码中,按照auto_queue_frame_size默认值为2000, 在一个节点上,所有分片查询的每累计了2000次,es就会启动对search线程池队列大小的resize。回我我们具体的问题场景,客户端发起60次查询请求,我们99个分片(约等于100)都会执行一次,那么该节点会执行 60次查询*100个分片/3个节点,平均每个节点2000次的查询,达到了auto_queue_frame_size的大小,系统就会评估一下,队列的大小是否需要改变,具体改变多少,按照利特尔定律来设置,每次调整的值都是向上或者向下固定调整50。因此,官方文档要求我们这个值不要设置的过小,避免线程池队列大小的频繁改变。

这里也告诫我们,做配置之前,多检查一下,配置里面每个参数的具体含义。

03-05 15:13