作者:empeliu,腾讯TEG后台开发工程师ElasticSearch是一款分布式开源搜索分析引擎,以其强大的功能和易用性被应用于众多业务场景.在生产环境中使用ES时,如果不进行优化,可能无法保证服务的稳定性。目前我们在使用ES作为计费平台的基础组件为微信支付提供服务时遇到了这个问题。本文从当前业务场景出发,分析ES稳定性不满足要求的原因,并给出相应的解决方案。一、背景微信支付的计费系统方便用户获取交易记录。针对不同的用户群体,账单也分为三类:个人账单:针对普通用户群体,这类用户的特点是基数大,单个用户数据量小。使用计费系统的主要目的是获取列表和基本统计??数据;商户账单:针对商户用户群,这类用户的特点是基数小,单个用户的数据量非常大。查询条件;业务账单:针对普通用户和商户之间的用户群,比如微信商户或者面对面的小商户,使用账单系统主要是获取列表,丰富统计功能;目前计费平台提供微信支付的这三类账单的写入、存储和查询服务,基本结构如下:计费平台主要包括两部分:逻辑端:业务端直接接入模块,主要是为了降低业务接入成本,提高接入效率;存储端:包括ES和接入层ESProxy,接入层屏蔽了索引划分机制,方便上层使用;目前的微信支付对整体质量的要求非常高,体现在99.99%的可用率,计费平台也需要达到或超过这个要求。但是如果ES和系统环境没有优化,读写成功率达不到要求。个人账单ES索引场景,写入成功率为99.85%,读取成功率为99.95%,这里急需优化。2.内存回收慢优化问题分析针对读写成功率低的问题,我们首先排查存储侧访问层ESProxy超时故障。接入层本身出现问题后,问题的根源基本锁定在了ES节点上。通过进一步确认ES节点负载(如下图),机器会出现CPU抖动,抖动时上层会超时,说明读写成功率低是CPU抖动造成的,所以我们的重点是解决CPU抖动问题。那么是什么原因导致了ES节点的CPU抖动呢?首先我们先判断CPU抖动时系统在做什么。根据之前的经验,很有可能是ES热线程或者GC引起的。但是在分析CPU抖动,用户和系统进程的比值时,用户进程的CPU比值基本没有变化,但是系统进程的CPU却增加了很多。由于ES热线程或者GC是用户进程,所以排除这里的影响。通过系统相关的统计和perf,得到如下现象:系统抖动时,系统正在扫描大量可回收内存。当系统不断回收内存时,系统分配内存失败。通过这三个现象,我们也得出了一个结论,CPU抖动是由内存不足引起的。在优化方案明确了抖动问题的原因后,我们接下来的优化方向是保证有足够的空闲内存,避免内存不断回收导致CPU抖动。解决内存不足的问题,首先要确认系统当前的内存分布情况。具体数据如下:进一步分析如下:ES节点内存主要由JVM和PageCache内存占用JVM内存由java独占,这部分内存不会被PageCache回收内存由运行维护系统,这部分内存是可以回收的。一般情况下,如果系统内存不足,内核可以通过回收PageCache的内存来提供足够的空闲内存,即不会出现内存不足的情况;反之,如果当前内存不足,则说明PageCache没有被正常回收,所以对于内存优化,重点关注PageCache回收问题。关于PageCache回收的问题,首先我们要弄清楚是什么因素导致PageCache不能及时回收的。其中MMap可能会导致PageCache无法正常回收。原因是应用程序会在MMap之后引用这部分内存,而内核在回收内存时会忽略这部分。记忆。ES节点默认读取文件的方式是MMap。整体内存关系如下:既然MMap方式会导致PageCache不能及时回收,自然要考虑其他方式来代替MMap访问文件。NIO在Java中可以使用对应的内存关系如下:NIO用于访问文件,PageCache内存只由操作系统维护,内核可以及时回收PageCache,保证足够的内存使用,解决了内存不足问题和CPU抖动问题,从而提高读写成功率;但是使用NIO访问文件也有一个问题,就是数据会多拷贝一次,会导致比MMap方式延迟更高。经过测试,发现延迟会高出30%左右。这个结果也不是我们想要的,所以我们考虑将两者结合起来,目的是加快内存回收,减少延迟。延迟低,低频使用Nio方式,可以加快内核回收PageCache)。优于MMap使用MMap+Nio组合上线后,现网对应的写入成功率从99.85%提升到99.99%。3、高阶内存优化问题分析系统运行一段时间后,现网成功率逐渐下降,从99.99%下降到99.97%,接入层对应的超时失败也增多因此。有了之前的经验,我们相应地检查了ES节点的负载,发现仍然存在CPU抖动(如下图)。考虑到之前优化过内存回收慢的问题,此时应该是新问题导致的CPU抖动,所以接下来的优化点还是解决抖动。和前面分析CPU抖动问题一样,我们先确认一下CPU抖动系统是干什么的。通过perf分析,如下图:采样结果可以明确,CPU抖动的时候,系统在进行内存碎片化(也就是有compact_zone()等函数调用),也就是说高此时系统级内存不足。为了进一步验证当前高层内存是否不足,通过cat/proc/buddyinfo查看当前系统空闲内存分布情况,如下图:分析以上数据,可以得出结论:目前空闲内存约4G,86%的内存为0级内存,大于等于2阶的高位内存占比仅为4%左右。到这里,验证了当前空闲内存基本是碎片化的。碎片化内存示意图如下:优化方案明确当前问题后,接下来的关键是考虑将碎片化内存变为连续内存。在上一篇文章中,我们明确了当前ES节点的内存主要由两部分组成,即JVM内存和PageCache内存,而在我们的现网环境中,这两部分内存基本是独立的(目前的实况网络机器内存有两个NODE,每个NODE占物理内存的一半,其中JVM和PageCache分布在不同的NODE上),也就是说我们只能优化PageCache之间的内存碎片,这样才能满足我们的需求;相应的优化过程如下:具体分为两步:1.释放内存:释放PageCache内存,保证新的空闲内存尽可能的连续。具体处理措施为echo1>/proc/sys/vm/drop_cache2.预留一定的空闲内存:目的是避免内存不断的申请和回收导致再次出现严重的内存碎片。具体解决办法是限制PageCache的大小(这里要看tlinux的实现)。具体命令为echo36>/proc/sys/vm/pagecache_limit_ratio优化效果已经通过上面的优化过后,系统空闲内存分布如下:此时空闲内存约为4G,但是high-Tier2或更高端的内存占95%左右,也就是目前高端内存已经很充足了,机器的CPU几乎没有Jitter(如下图)。在现网进行相应调整后,读写成功率提升效果如下:写入成功率由99.85%提升至99.999%,读取成功率由99.95%提升至99.999%。为满足需求,进行了以下优化措施:1、内存回收慢优化:优化ES文件读取方式,加快内存回收,降低内存回收的CPU消耗;2、高端内存不足优化:整理碎片内存,保证高端内存充足,减少内存整理时的CPU消耗;经过上述优化措施后,ES系统的读写成功率达到99.999%,超出了当前的可用性要求,保证了ES在生产环境中的稳定性。参考:1.NodeHotthreadsAPI2,PhysicalPageAllocation3,DescribingPhysicalMemory
