- 微盟APM系统告警能力的设计与实现
一、背景 随着分布式系统架构的普及,系统越来越复杂,常常被切分为多个独立子系统并以集群方式部署在数十甚至成百上千的机器上。为掌握系统运行状态,确保系统健康,我们需要一些手段去监控系统,以了解系统行为,分析系统的性能,或在系统出现故障时,能有能力发现问题、记录问题、定位问题。也可以根据监控数据发现系统瓶颈,提前感知故障,预判系统负载能力等。 因此「微盟APM系统」(链接:https://developers.weixin.qq.com/community/develop/article/doc/000aaed6f98b28093f3a5265151c13)应运而生,在微盟各业务线投入时候后,微盟各个业务线的小程序的公众号的流量也从2020年初的日均百万级别提升了2倍多。之前APM系统的主要功能是进行日志、打点、报错等信息的采集和汇总,并以调用链的形式展现出来,方便开发、运营人员直观查询并根据报错、耗时等性能指标来排查异常或进行功能点优化。但随着流量的不断提升,另一个课题摆在了面前。在发现错误或者异常行为的情况下,如何能够在第一时间得知这样的情况并作出应对,故我们基于APM系统打造了告警能力。 二、架构设计 整体架构[图片] 以上是我们的整体设计流程图,在我们对APM进行优化后 (链接:https://developers.weixin.qq.com/community/develop/article/doc/00006209434c506bc31cc3c4454c13),我们引入了Ckafka,利用消息队列的高吞吐、低延迟的特点,达到更高的消息处理速度。基于Ckafka,我们将告警设计成为一种数据消费的过滤条件。 2. 创建告警 如下图所示,用户首先需要在APM系统中创建一条报警规则: [图片] 用户可以在规则中选择 所监听的服务产生的告警级别抑制时间(告警的间隔)发送告警的渠道(企业微信、邮件、短信等等)告警接收人 (单人/用户组)然后用户需要对该条规则进行补充,即如何才能触发此条规则完成告警,那么就需要添加告警策略了。 何为告警策略?顾名思义就是告警规则的判断依据,只有满足了所有或者任一策略,该条告警规则才会真正对接受人发起告警。 告警策略的主要由以下几部分组成: 间隔:时间间隔阀值:即时间间隔单位内满足策略的次数过滤器:具体过滤的条件,主要为采集数据内的一些属性操作符:对阀值的补充,是大于还是大于等于是否为平均值以上条件即约束了在固定时间间隔内满足过滤器内条件并超过阀值的数据,即会触发此条告警策略。 举个例子,让我们来新建一条策略: [图片] 通过 Redis 的 pub/sub 功能告知 kafka-consumer ,在consumer中会根据该报警策略生成新的过滤规则,在消费数据中进行过滤判断,若触发过滤条件则通过令牌桶算法(基于Redis)记录触发次数,从而保证在分布式的架构中也能完成阀值统计,一旦触发阀值后,则会进行对应的告警。 [图片] 此条策略约束了若5分钟内满足过滤器条件的数据出现的平均值超过10条,即满足此条策略。 由于过滤器中的值为用户上报数据中的部分字段,该字段的高度自由化,方便用户自定义监听维度。故可以定义出各种满足用户需求的告警维度,例如: 报错频次卡顿频次页面数据访问频次某些接口的调用频次而在整个告警规则中,基于一个或若干策略策略的触发,才会正真产生一条告警数据。根据该条产生的告警数据内配置的告警对象(人/用户组)以及告警方式(企业微信/邮件/短信等)进行精准告警。 3. 设计思路 上文提到微盟APM系统采用了Kafka作为消息队列用于承载日渐增多的数据量级。且由于初始数据发送至Kafka的时候有可能是进行过压缩等处理,故需要在消费者层面进行数据处理后才能存储至Elasticsearch。所以我们基于这样的架构,在消费者端增加了一个数据筛选流程用于处理告警规则的判断。 [图片] 不同于利用Job的设计,使用筛选条件的模式需要将过滤规则尽可能的扁平化,同时也更契合ES的数据格式,可谓一举两得。故我们会对用户自定义的告警格式进行展平,使之和数据上报至ES的格式保持一致,最后使用Hash的方式直接筛选数据,时间负责度为O(1),经测试对数据消费基本不会产生影响。 由于告警基本都是以频次来计算是否触发阀值,故我们采用了类似 rate-limiter 的令牌桶算法来计算触发告警策略的次数,当超过预定阀值之后即判断满足该条告警策略。而 rate-limiter 在分布式的应用中一般都会使用Redis在进行数量统计,故我们在告警策略/告警规则的变更中也一样使用了Redis来进行Pub/Sub,所有在APM上的告警操作能瞬间同步到所有的Consumer上并产生效用,不会存在延时,立即起效。 以上就是微盟APM告警系统的整体架构以及实现,感谢您的阅读,我们下次再见。
2021-05-19 - 微盟APM系统之数据流处理优化篇
从2020年微盟APM系统大规模投入使用以来(链接:https://developers.weixin.qq.com/community/develop/article/doc/000aaed6f98b28093f3a5265151c13),微盟各个业务线的小程序的公众号的流量也从2020年初的日均百万级别人次(UV)上涨了两倍多。同时随着人均驻留时长大幅度提升、业务复杂程度大幅度加大,人均产生的调用链、运行时日志也较初上线时翻了三倍多。这就意味着,原有的APM系统架构要面对提升的流量,就会产生高额的费用。为此,我们计划将微盟APM系统(代号MARS - Monitoring & Automatic Reporting System)的数据处理架构进行优化和升级,目标是应对更高的流量并且降低服务端成本,同时也希望借此拓展MARS的功能、效率、以及灵活度等等。 [图片] 上图为MARS的最初数据处理的流程图。从中我们可以发现,这个模式虽然存在直观、简单、开发成本低等优势,也存在以下问题: 1、Node.js应用直接请求进入ElasticSearch导致流量没有统一收口 2、统一化服务对机器要求高,且每次修改风险大 3、数据进入ElasticSearch未做队列、聚合等处理,失败率高 4、失败日志查阅不方便,难以定位问题 5、没有合理的环境隔离 鉴于以上问题,我们认为需要对数据校验和消费进行分离,才能够在每次修改时做简单的增量发布,从而实现对接各种内部系统或者不同数据库实现如周数据环比等新的业务需求。在微盟,我们有一套非常成熟的基于Kubernetes的OMS系统,我们在此简称OMS Kubernetes。我们认为OMS Kubernetes已有较为完善的DevOps和日志系统功能,还有比较良好的扩容能力(当然在消费者服务上为了避免新加入consumers引发kafka rebalance,因此设置为手动扩容),因此新的系统应该被搭建在OMS Kubernetes之上,实现资源最大化利用的同时竟可能的复用已有的内部系统。 [图片] 通过规划,我们认为新的系统可以按照以上描述的,从原有的直接从数据源发送数据并且写入ElasticSearch的模式修改为,由统一的网关服务生产、校验数据以后进入Kafka,然后在消费者内对数据进行小队列的聚合和处理后再发送至ElasticSearch内。 通过以上系统的改造,我们首先能够获得服务器承压能力和成功率的大幅度提升。以下为老APM服务的压测结果: [图片] 从上图可以看出,老的APM系统的承压能力基本上取决于ElasticSearch的承压能力,在面临大规模流量访问时,虽然一开始可以维持2000+的TPS,但是很快TPS大规模下降,并且响应速度非常不理想。最终在单台压力机,请求1分钟的情况下,虽然有多台黑石物理机为载体,就已经出现无法承压的表现,错误率1.57%,平均TPS 908,平均响应时间854毫秒。 [图片] 上图为最终优化版本的新版DEV环境下的MARS系统(0.5核2GB Pod),虽然在DEV下的Gateway服务的载体性能较多台黑石机弱了数百倍,但是依然可以实现几乎相同的1.65%的错误率的TPS 649,虽然平均耗时有较大的提升,到了2955毫秒。并且,新的服务没有出现之前1分钟不到就出现无法访问的情况,能够在高压下依然维持600上下的TPS。 我们将相同的服务上线(结果见下图),设置为20个1核2GB的Pods(固定配置,无自动扩容),得到了可喜的0错误,TPS 38073,平均耗时13毫秒的结果。实际上20个1核2GB的Pods在性能和费用上还远不上原先单台黑石物理机,更不要说是多台了。因此除了性能上新的架构较原有有了质的飞跃,成本上也有大幅度的削减。鉴于此,我们将线上的扩容能力设置为20-100(实际上我们认为初始为10台可能已经可以满足90%以上的业务场景)。 [图片] 当然,在多次压测过程中,我们发现了Node.js服务的一些缺陷。如Node.js可以在短时间内接收大量请求,并且响应速度极高,但是在收到一定的请求以后会进入“装死”模式,既对新的请求不作出任何响应,直到CPU和内存占用降低到一定的比率下。鉴于这个问题的存在我们做出了以下的性能优化改进: 有效 1、提升OLD内存使用 2、判断同时处理的异步请求达到阀值后切换为同步响应 3、允许更长的监控检测响应时间 无效 5、CPU占有率提高后切换为同步响应 6、内存占有率提高后切换为同步响应 7、连续网络请求达到阀值后切换为同步响应 8、CPU占有率或内存占有率提高后切换为同步响应 9、5+6+7的权重算法 我们认为在Node.js对系统性能的技术可能因为其Runtime特性和实际请求时的event loop处理逻辑是脱钩的,因此出现了5、6、7、8、9方案无效的情况。使用actix框架(Rust)编写的程序在和DEV相同配置的的Pod(0.5核2GB)内可以避免错误率的出现,并且响应时间有明显降低,但是鉴于现有的OMS Kubernetes并不支持Rust应用,且其生态相对于Node.js应用不是非常成熟,因此我们将服务端逻辑由Node.js往Rust的迁移定到了下一期的规划内。
2021-05-08