前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >由Elasticsearch的API命令,引发的金融业生产故障

由Elasticsearch的API命令,引发的金融业生产故障

作者头像
jeanron100
发布2021-12-03 20:04:37
6801
发布2021-12-03 20:04:37
举报

序言

图示:Elasticsearch目前在DB-Engine综合排名第8

Elasticsearch博大精深,提供了非常丰富的应用场景功能,也提供了丰富的API命令操作,有些API非常好用,有的API用一用就要出大事,防不胜防。

以下围绕某客户一次客户端应用程序错用Cluster State命令展开,从问题定位到问题解决,记录自己的过程与方法,还有一些心得总结(注:具体客户信息不便透漏,以下部分图片信息仅为示意图)。

一、案例介绍

1、项目背景概要介绍

客户用ES来解决日常的数据查询与存储,利用ES非常好的横向扩展特性,满足多种场景应用。

ES集群版本属于5.6.x,已经超出Elastic官方支持的版本,集群节点数不到10个,节点硬件配置属于均衡一致性,标准的性能型合理范围硬件配置,应用于业务系统提供查询或者更新等,日常都有提供定期巡检,基本正常稳定,不过一直侧重的是集群运维本身,很少会深入到应用端程序去巡检分析,业务线太多,经费有限。

ES集群设置并未采用Master与Data分离设计,所有节点均是同等角色权限。ES集群数据量也是属于正常支撑能力范围,集群索引的分片数量在2w以上,注意这个数字,后面会基于此来突破寻找问题。

客户端应用程序基于Spring data elasticsearch开发框架,引用Trasnport-client直连模式,中间未采用任何代理Proxy负载产品。

客户端应用程序部署了多个实例,按照2的倍数增加,需要做一些“大量”数据的写入与查询,瞬间并发的操作ES集群,无论怎么操作,ES的并发数并不高,但应用端程序就是运行不快,这很不合常理也不合事宜,常识告诉我这不正常。

图示:客户公司的应用项目架构示意图,客户端采用Transport-client直连模式

2、问题现象与事故原因

客户端应用程序只要开始运行,ES集群立刻会慢起来,但查看ES集群整体资源消耗,ES所在节点CPU/MEM/硬盘IO并不高,其数据节点甚至非常低,远远没有达到节点的瓶颈。

经过细细观察发现,集群的Master节点流量特别高,其余节点数据流量都传给Master节点,超出了Master单节点网络IO的极限,这显然不正常。但在生产环境上并不容易排查。后面经过在本地开发环境模拟压测,终于确定了问题来源,是客户端的某个集群管理操作 API引起的。

最终,找到了 Cluster State Api 统计命令引起的,客户端应用程序每次做实际业务前,都会调用这个API命令获取集群一些索引与Mapping信息,由于客户端是采用多线程设计,且部署多个实例,只要并发数高,所有流量必然打到Master节点,造成整个集群响应慢。由于整个集群有近2w个分片,执行一次State 命令,需要统计汇总集群所有索引分片信息,并最终汇总到Master节点,造成实际Master节点网络IO超过极限。

图示:cluster state api返回内容,索引越多,返回内容越大。

二、排查过程&分析手段

以上我们快速讲了一下问题现象与事故原因, 下面重点聊一下问题排查过程以及采用的工具方法思维等。

特别说明,由于是远程协作分析排查以及客户公司的行业特性,外部不能直接操作远程任何节点,必须通过客户公司运维人员间接指导操作排查,过程稍微有点曲折。

1、服务端集群排查

首先是ES集群问题排查,这是很正常直接的思维,在问题爆发点排查(ps:类似警察先赶到案发现场,采集证据)。由于集群版本是5.6.x,一些原因并未启用官方的XPack功能,所以也就没有自带的Kibana监控可视化报表,国内早期多数用户都是采用开源免费版本搭建集群,客户公司基于Zabbix搭建有集群节点的基础指标监控,总算是有一个,非常难得。

1)zabbix监控分析

很快基于Zabbix展开各个ES机器节点指标监控分析,按照常规套路,分析了CPU、内存、磁盘IO等一切正常,并不高。这就非常令人困惑,明明集群响应很慢,客户端应用程序开发人员也如实反应,部署的实例非常少,所以初次分析下来,并未发现明显定位到问题。

接着分析节点网络IO,貌似发现了问题点,其中Master节点流量特别高,超出了网卡的极限;按照此套路,继续查看了其它数据节点网络IO,也比较高,然后观察发现,数据节点的网络IO流量累计起来刚好等于Master节点网络IO流量,终于算是找到了一个突破口。

注意,基于Zabbix虽然能监控各个独立节点的网络流量,但并不能看到节点之间网络流量,以及应用客户端与服务端之间,这很不友好,若有一张全网络流量图,将会更容易定位分析问题。

图示:Zabbix 网络流量示意图,仅仅能快速查看单独节点

2)iftop监控分析

借助Zabbix算是找到了突破口,接下来就需要一张网络流量关联图。此时第一个想到的是 Elastic Stack Packetbeat 网络IO流量包可视化监控监控,瞬间与客户公司讨论,能否安装配置,显然这个过程太慢,需要申请更多资源等麻烦,时间预计来不及。后面客户公司运维工程师临时安装 Linux 络指令iftop,终于能够分析ES所有节点网络IO流量的流进流出方向,但是观察分析相对来说比较痛苦,而且并不能绘制一个网络图谱,所以观察起来还是不够全面。

iftop仅仅在单机节点执行,所以只能看到单个节点的网络流量进与出,不过这已经非常好了,只是时间长一点。

基于网络IO流量进出初步分析观察,很快做出了决策,先是重启了现有Master节点,让ES集群重新选举新的Master,结果发现依然新选举的Master节点流量依然巨大;接着也临时申请增加2个新的ES节点,以为可以分散客户端访问的压力,事实上并未从根本上排查解决。此时的判断是认为某些业务索引查询,造成流量比较大,刚好在Master节点中转汇总,这里其实还没有分析到客户端的流量,所以做了一些无用的紧急服务端调整,但也证明了问题不在ES服务端。

图示:iftop网络流量示意图,能分析到单节点的流量进出,也是不错了

3)packetbeat监控分析

由于一些原因,并未部署Elastic Stack 监控分析产品,特别推荐Packetbeat网络流量监控分析产品,官方默认提供的网络流量图,非常有用,可以更快速定位流量走向,相比ittop,更加全面一些,可以总结为iftop的网络版本。Packetbeat内置官方一些可视化报表,如果觉得不够,可以基于ES提供的分析能力自行设计。

图示: packetbeta网络IO流量监控分析图,输入与输出

2、客户端排查

其次是从客户端应用排查。但实际上,客户公司运维人员第一时间发现问题,联系到我,我的第一个问题就是客户端应用在做什么业务,业务类型是什么。运维人员也如实告知正在做的业务类型以及应用情况,其实只能概要说明一下,详细的业务操作流程与影响的是不知道的,对于我来说,反正就知道了有个业务操作在影响着集群,具体怎么影响,还得继续排查。必须说明一下,这个可能是国内IT运维界的通病,看起来运维属于末端支持,本质更应该参与到业务系统的前沿阵地,从业务开始了解背后的运维问题。

然后通过ES服务端排查,掌握了一些信息,但远远不足以定位到问题,客户端排查就任重道远,必须要找出问题所在。ES集群响应慢有很多,但服务端只能发现问题,并不能从根本上解决,于是通过在服务端运行 thread_pool 与 task 命令,发现了集群的管理线程池特别多,任务一直爆满,这不正常,这就更加肯定一定是客户端的某些应用在恶意操作这些命令。

1)thread_pool 监控分析

集群响应慢,集群线程池是一个必须考虑的点,其中会查看到累计的任务量,经过打开指令观察,其中的 management 线程池特别高,由此判断客户端正在做一些任务,且属于management 线程池的,目前具体的依然不能确定,因为没有看到客户端应用具体的代码调用方式。

图示:thread_pool示意图,可以监控ES节点任务数

2)task监控分析

借助thread_pool分析,可以定位到 management 线程池正在大规模做任务,同理借助task 查看分析 具体做的任务是什么。(ps:实际客户环境中,发现是大量的state、stats任务,其中state任务数明显要高,且非常不正常,这里不方便透漏,后面会详细列出如何压测出来此结果)

图示:task示意图,可以查看到具体的任务类型

3)stats监控分析

借助 task 监控分析,发现有大量的stats指令,接着在服务端执行一次,看看情况如何,实际发现响应也比较慢,但按理是正常的,产生的队列次数并不高,只是执行慢,基于以上判断应该是别的任务阻塞导致。

stats 负责集群监控检查响应,常规的集群yellow,red,green就来自与此;另外也收集索引的统计信息,节点的统计信息,返回的数据量不多,常规下可以忽略不记,不过注意后面transport-client 访问需要。

图示:stats命令执行示意图

4)state监控分析

借助 task 监控分析,发现有大量的state指令,接着在服务端执行一次,发现了问题点,执行一次时间很长,且响应返回的内容超过70mb,这是问题,可以回答为什么之前的master节点流量如此之高。终于问题越来越清晰了,也越来越确定了。

图示:state命令执行示意图

5)transport-client应用访问

借助前面服务端现象分析,越来越确定问题出在客户端应用代码,大概率是操作不正确。

首先与客户交流,询问客户端应用代码访问ES的方式与程序版本,客户端开发团队很好配合,很快了解到应用客户端基于spring data elasticsearch框架,采用transport-client机制访问操作ES,但此时是不能定位是客户端造成的,也不能匆忙要求客户端提供源码。接下来很快在本地编写相应的测试程序源码。

Transport-client客户端绑定代码

代码语言:javascript
复制
/*模拟spring data elasticsearch配置transport-client,示例来自官方文档*/
@Configuration
public class TransportClientConfig extends ElasticsearchConfigurationSupport {

    @Bean
    public Client elasticsearchClient() throws UnknownHostException {
        Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build();        
        TransportClient client = new PreBuiltTransportClient(settings);
        client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); 
        return client;
    }

    @Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
    public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException {

                ElasticsearchTemplate template = new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter);
                template.setRefreshPolicy(refreshPolicy());                                                 

                return template;
    }
}

6)jmeter压测模拟

因为某些原因,我们只能提供间接性的服务方式,所以为了彻底解决上面的问题,需要在本地模拟测试,必须要模拟出生产的问题,才能要求客户端给出到源码。基于前面服务端与客户端应用了解到的信息,接下来就是要做压测,特别选择了jmeter工具,很快配置好本地集群,附加客户端应用访问代码。压测进行时,同步观察ES服务端threadpool与task,能够复现生产的问题,很快就得到了想要的结果。

图示:jmeter压测示例,开启更多线程数,超过ES测试服务器线程数

图示:jmeter压测示例,一次state命令执行需要消耗非常多的资源

既然在本地环境,借助jmeter压测出来生产上同样的问题,那么最后一步就是与客户应用开发团队一起review代码,这一步非常快,很快就定位到 客户应用端代码在具体业务操作前,会调用Cluster State API,进行业务逻辑判断,判断索引是否存在,判断索引字段是否存在等。

最终问题定位到了,产生原因也知道了,事情也算告一段落。

代码语言:javascript
复制
/* 
    客户应用端的某个访问代码
    借助state来判断索引的mapping信息 
    */
    public Map<String, Object> getMappingByField(String indexName, String type) throws IOException {
        ImmutableOpenMap<String, MappingMetaData> mappings = getClient().admin().cluster().prepareState().execute()
                .actionGet().getState().getMetaData().getIndices().get(indexName).getMappings();
        if(mappings.isEmpty()) {
            return null;
        }
        Map<String, Object> mapping = mappings.get(type).getSourceAsMap();
        Map<String, Object> properties = (Map<String, Object>) mapping.get(ES_PROPERTIES);
        return properties;
    }

3、问题分析过程回顾

  • 服务端分析,寻找突破口
  • 客户端分析,缩小排查范围
  • 模拟测试,复现生产问题,确定问题产生原因
  • 应用端代码分析,找出问题代码
  • 修改观察,确认问题已经解决
  • 经验总结,复查所有其它相关代码

图示:问题排查过程与流程

三、ES技术架构原理

本次ES问题排查涉及到的知识点以及技术性原理特别多,这里也来特别说明一下与此关联的架构原理,供大家日后参考与分析。

1、ES架构原理

ES对外虽然宣称是个分布式架构,可以横向扩展,但那说的是数据节点或者其它非管理节点。实际上ES是典型主从架构模式,一个集群只有一个Active Master节点,Master节点负责管理集群所有元数据信息,其中包括节点信息、索引信息、分片信息、节点与索引路由信息、节点与分片路由信息等,集群执行一次管理型的命令都需要先在Master执行,然后分发到其它节点等。

了解这一点非常重要,可以很好的解释,为什么在本次ES故障中,Master的网络流量刚好等于其余节点网络流量之和。客户端应用发起一次State统计,首先都是Master节点接受指令,接着分发给其它节点执行State统计,最终汇总到Master节点,造成了Master节点网络流量奇高,超过网卡极限,ES集群响应慢,但实际CPU与内存消耗又不高。

联想到这里,集群架构是不是可以想象型的大胆设计一下,现在的ES集群只有一个大当家,随着集群规模越来越大,大当家压力越来越大,大当家如果出现故障,就重新选大当家,这听起来好像不符合社会组织学。那能否下次修改一下,设计一个有大当家与二当家的模式,大当家与二大家职责再分离一下,大当家太忙,二当家分担一些。

图示:ES主从架构分布式示意图,一个集群只有一个Active Master节点

2、ES线程池

ES是个数据库产品,内部设计了多个任务线程池,不同的线程池任务与职责不一;线程池也分为多种类型,不同的线程池类型应对任务场景不一样;ES不同版本,线程池数量与类型会有大的差异,尤其是5.x与7.x版本,这种跨大版本,差异相当大。

由于在某机构做ES老师,时常与Java学员交流,发现了一些严重的认知误区:1.很多学员认为Java不能开发数据库产品,因为GC机制;2.多数人学习掌握的Java线程池都只用来做简单的多线程业务场景,从未想象过,类似ES这样集成了几十个线程池的玩法。所以必须承认,ES是一个非常好的Java开发高手学习参考的产品,特别是做后端开发,想要更加深入进阶的同学。

了解ES线程池内部设计非常重要,可以很好的解释,为什么本次ES故障中,只有Active Master节点的 management 线程池特别繁忙,其余线程池总体很闲,其余节点的线程池也非常闲。客户端应用发起一次State统计,首先接收任务的是Master节点的management线程池,此线程池类型为 scaling,且最大的线程数是一个固定值,不超过实际CPU合数,也不能超过固定值5,而不是根据节点CPU核数来自动弹性设计的。

当客户端应用采用多线程执行模式,且每次执行业务操作前,都先执行一次State 统计做业务逻辑判断,这就无形中增加了集群的负载,增加了Master节点的任务数量,客户端应用实例数量越多,并发线程数也越多,集群Master节点就几乎无法消化,毕竟Master节点就一个;也正是由于负责执行State任务的线程池是个固定值,Master节点CPU也不会打满,这就阻塞了所有的业务操作。

有时候官方文档并没有列出来,需要通过ES源码去查看,这点我至今也没有想明白,不光是过去历史版本,包括当前最新版本也是一样的,如当前 7.15.x,有的线程池就没有在官方文档列出来,但能在集群监控中查看到。

图示:ES内部线程池划分,(版本7.13.x,来自某机构ESVIP课程)

代码语言:javascript
复制
/*ES线程池初始化源码:org.elasticsearch.threadpool.ThreadPool */
public class ThreadPool implements ReportingService<ThreadPoolInfo>, Scheduler {
    public ThreadPool(final Settings settings, final ExecutorBuilder<?>... customBuilders) {
             /* ... 此处省略其它代码*/
        
           /* management 线程池初始化示例源码*/
            builders.put(Names.MANAGEMENT,
                    new ScalingExecutorBuilder(Names.MANAGEMENT, 1, boundedBy(allocatedProcessors, 1, 5), TimeValue.timeValueMinutes(5)));、
          /* ... 此处省略其它代码 */
    }
}

3、ES集群统计执行过程

Cluster State Api 执行一次指令,指令会传输给Active Master节点,Active Master分发指令给各个节点,各个节点收集好信息之后回传给Master,然后由 Master响应给客户端。

在开始排查问题中,初次判断失误,以为是客户端在做大量的业务数据查询任务,所有节点数据量都很高,其中大量依赖Master节点对外输出,实际不是。

图示:Cluster State Api执行过程示意图

4、ES动态创建索引

ES是典型的Free Schema数据产品,索引是可以动态创建的,索引的字段也是可以动态创建的,无需优先声明,更无需在应用代码中逻辑判断是否存在,是否需要创建,这是ES非常好的动态扩展能力之一,基于此给应用开发带来了相当多的便利,比如著名的“大宽表模式”查询场景,解决了海量数据关联实时查询的问题。

ES可以提前创建索引,也可以不创建,写入第一条数据,即可动态创建索引,并同时创建索引自动的mapping索引内部结构。若第二条数据写入,增加了很多新的数据内容结构,则会自动更新当前索引的mapping,自动刷新mapping。若新增加的数据字段缺失,也不会出错。ES默认内部会根据字段进行推测其字段对应的类型。

代码语言:javascript
复制
#动态创建空索引
PUT my-index-000001
{
}
#新增数据,动态创建索引,自动推测字段类型
PUT my-index-000001/_doc/1
{
  "create_date": "2015/09/02"
}
#更新数据,动态增加新字段类型
PUT my-index-000001/_doc/1
{
  "my_float":   "1.0", 
  "my_integer": "1" ,
  "create_date": "2015/09/02"
}

5、transport-client

Transport client 是官方早期推出的应用接入访问机制,自Rest Api 推出之后,官方就竭力要求切换过来,其中缘由部分并没有特别说明。Transport是一种直连方式,直接连接到ES集群中,连接之后保持长连接,并且需要定时执行内部的一些stats命令,来检查集群监控状况,这个其实非常多余,在极端情况下也非常消耗集群资源。

ES集群内部通信或者执行其它指令,都是通过transport机制,即使是rest api执行,内部也是转换为transport机制来执行。

Rest 接入相比 Transport 更加解耦,也可以尽力避免恶意干扰 transport 通信,更多的此处不展开,后续会有另外的文章来写。

图示:transport-client与rest api连接示意图

四、专家建议

此次从问题发现、问题定位、问题解决,花费了几天时间,有一些经验建议有必要特别说明一下。

1、全面的监控体系

监控体系是运维的眼睛,集群的各种运行信息,都需要借助强大的监控体系来保障,提供实时的分析等。本次案例中,客户公司监控体系相对来说比较传统落后,虽然有zabbix,但在一些新的分析问题方式上表现并不行,而且非常欠缺,如分析集群各个节点网络流量进出走向,包括服务端与客户端的关系,都是没有的。

选择一款全面性且非常有独立视角的监控产品非常重要,Elastic Stack 是新时代的产物,新事物可以帮助我们提升优化问题排查思维。

2、权限安全隔离

绝大多数数据库产品都提供了一些基本的安全策略与防护,可以通过设置一些安全用户群组与角色权限来限制避免此种问题。

比如传统的关系型数据库MySQL,可以通过给客户端应用分配更小的权限来限制。早期ES版本由于ES并未提供安全防护机制,很多应用团队都是直接使用,也没有做到基于用户群组的权限隔离。最新版本的ES提供了开源免费的基本安全策略,可以通过用户群组权限来避免客户端应用的无意识操作。

3、全面的知识体系

本次案例中,先从集群后端运行分析,到应用程序端源码分析,再到本地环境模拟测试,涉及到的技术点非常的宽泛,并不能从单一维度发现解决,也不能仅仅从ES知识层面排查解决;任何人都不能只从ES集群运行表象定位问题,很多问题爆发是属于末端,但引起的其实在另外一端,这就需要跨界的能力与思维,当然最重要的是构建自己的知识体系,有自己独立的解决问题排查思路,也得掌握必要的各种工具软件,不能仅仅局限在ES范围之内。

为了用好ES,我们需要掌握开发技能,熟练使用ES提供的开发特性,了解ES各种特性的边界,防止滥用。我们需要掌握ES集群架构基本的原理,基本的运行机制,避免认知上的误区,如ES是典型的主从架构分布式,并不是无中心的分布式。我们需要掌握常规的运维技能,从操作系统基础环境,到ES运行各种指标信息等。

4、全栈工程师理念

随着业务需求与社会发展,我们更多的是需要全栈式的工程师,当然必须声明,不是要求一个工程师同时兼任多种工作,不是按照国内某些企业“压榨式的全栈”。而是特别强调工程师的专业水平,工程师可以依据工作岗位比较容易切换角色,不仅仅局限某一个固定职位。很多优秀的IT产品都不是本职工作的人做出来的,而是一些跨界的人士创作出来的。

本次案例中,看似集群的问题是服务端,但实际上是由于开发知识面局限造成的,最后解决也是在应用端修改代码解决,这是一个典型的跨界问题,由服务端逐步往前发现是应用端的问题。为了模拟生产环境问题,需要用压测工具,这貌似又属于测试职责,但为了解决问题,从后往前。为了确定线程池设置问题,下载ES对应版本源码,去查看阅读对应线程池的设置,因为官方文档没有。为了确定客户端应用代码问题,基于客户开发人员提供的信息,编写模拟客户端应用代码行为。

目前国内的大多数公司工程师都是单一职责职位与技能,尤其是注重前后端分离之后,前端与后端技能分裂的更严重了,前端很多薪资很高,涨幅快,看不起后端,后端也逐步远离前端应用,更加不关心前端运行方式;后端应用与大数据开发也是,将大数据开发与应用开发分离,导致工程师的专业素养下降很快,不少大数据工程师都不具备很好的编程能力,这是值得大家思考的问题。

参考资料

  • State 参考文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html
  • threadpool参考文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html
  • elasticsearch transport client 参考文档 https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html
  • spring data elasticsearch 参考文档 https://docs.spring.io/spring-data/elasticsearch/docs/4.2.6/reference/html/#reference
  • jmeter http request 参考文档 http://jmeter.apache.org/usermanual/component_reference.html#HTTP_Request
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 杨建荣的学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档