电商场景下 ES 搜索引擎的稳定性治理实践
继上文在完成了第一阶段 ES 搜索引擎的搭建后,已经能够实现对千万级别的商品索引的读写请求的支持。目前,单机房读流量在 500~1000 QPS 之间,写流量在 500 QPS 左右。
但随着业务的发展,问题也逐渐开始暴露,起源是在某次活动下线的时候,ES 集群某个机房 CPU 迅速被打满,读延迟上升,而其他机房却是正常的,之后仍然出现了多次 CPU 暴涨,多个机房的其中一个机房被打满或者同时打满的情形,然而读写流量波动却不大或者根本不及日常峰值。我们意识到此时出现的就是 ES 集群的性能问题,在第一阶段当系统依赖组件不可用时,为此系统拥有一定的容灾能力,暂时没有考虑业务使用姿势带来的风险,而这种风险是更可怕的,源于它随机,毫无规律,不可控制。
在此情况下,也许大家会考虑通过扩容来解决问题,但当前情况已经是在我们扩容后发生的问题了,所以很明显此时扩容已经解决不了问题了。程序员经常说的几句表达风险等级的话:
-
阶段一:不知道自己不知道(Unconscious incompetence)
-
阶段二:知道自己不知道(Conscious incompetence)
-
阶段三:知道自己知道(Conscious competence)
-
阶段四:不知道自己知道(Unconscious competence)
用在此时就是阶段一和阶段二,不知道自己不知道,所以也就无法在业务风险控制这里发力,当问题出现后,知道了自己不知道,但不知道发生了什么,不知道为什么会发生,不知道还会发生什么。看似充满随机性,但实际上却是一个很严肃的问题,招商平台对大促活动非常重要,由此也引发了我们新的思考,我们做 ES 稳定性的全局视角是什么,该怎么定义和归类?这些思考也为后续的治理提供了更好的解决思路和发现问题的角度。
治理思路
ES集群读写链路图
在治理思路中我们仍然从系统读写两个入口入手,分别细化读和写链路应该考虑的问题和风险以及需要达到的业务目标,下文将从具体的实施步骤进行介绍。
治理的目标是什么
治理目标有两个,分别是系统的可用性、稳定性和数据质量,系统可用性指的是稳定提供读写能力,数据质量即保证 ES 的数据和源数据完全一致,并且延迟符合业务预期,达到不仅有数据而且是有质量的数据标准。
如何量化目标
在量化目标中,系统可用性沿用了 ES 集群 SLA 进行衡量可用性。数据质量可以理解为数据最终一致性和数据延迟,目前我们核心的数据包含准实时数据流,报名记录 DB->ES,商品比价通过文档数据库->ES,并需要定时更新指标。DB->ES 设定的目标是 30s 内的一致率在 99.9% 以上,通过准实时对账进行监控报警监测。
随着商品控价越来越重要,比价的数据筛选和查询也尤为重要,文档数据库->ES 设定的是不存在超时小时级别的同步延迟,且将定时更新指标定为 T+1。
如何达成目标
原则:自上而下,逐层拆解,彼此独立,互为补充。
优化措施
此时回顾一下,上节我们提到的 ES CPU 暴涨问题最后是如何解决的?实际上,我们并没有走捷径,而是将 ES 读链路全部梳理了一遍,分析每次 CPU 暴涨的流量差异点。之前的分析仅仅是从 ES 集群监控上分析不同索引的流量趋势,由于差异点太小,无法进行有效分析。因此,我们仍然需要先完善监控报警机制,将 ES 上层的云引擎服务的接口流量监控全部聚合在一个监控看板上,并加入了 API /中间 RPC 层--> 数据中心 RPC 服务--> ES,从而找到了问题的突破口。我们发现,CPU 上涨的点 Scroll 流量偏高,因为 Scroll 流量比 Search 流量更耗 CPU,因此 Scroll 流量会被打满。在明确原因之后,我们也就开启了 ES 性能的优化之路。
为什么 ES Scroll 流量比 Search 流量更耗 CPU?
-
Search 查询有数据缓存而 Scroll 没有:在Search API 中,ES 会执行查询并返回匹配的结果集。这些结果通常是直接从索引中检索的,并且在查询时可能会使用缓存来提高性能。一旦查询完成,ES 会将结果缓存在内存中,以便稍后进行排序、分页等操作。这样,在后续的请求中,如果只需要访问缓存中的数据,可以避免重新计算和访问磁盘,从而减少了 CPU 的消耗。相比之下,Scroll API 在处理流量时不会使用缓存。它的工作方式是创建一个游标(Cursor),并在服务器端维护一个快照,以便在后续的请求中能够继续从上一个请求的位置继续返回结果。这意味着每次请求都需要重新计算和访问磁盘上的数据,并且不能利用缓存。这会导致更多的 CPU 计算和磁盘访问,从而增加了 CPU 的消耗。
-
Search 是无状态查询,Scroll 需要上下文维护:Scroll API 需要维护上下文信息,以便在后续的请求中能够正确地返回结果。这个上下文信息可能包括游标位置、排序信息、过滤条件等。为了保持这些上下文的一致性和完整性,ES 需要在服务器端维护和更新相关的状态。
-
这不意味着 Scroll API 一定比 Search API 更耗 CPU。实际的 CPU 消耗还受到多个因素的影响,包括查询的复杂性、数据量的大小、硬件配置等,需要结合实际情况观测。
ES 查询链路治理
将 ES 不合规的 Scroll 流量全部迁走
在某次大促活动之前,招商平台提供的某个活动下报名记录的全量获取接口走的全是 ES Scroll 流量,基本维持在 100+ QPS 水平,大多场景用于离线对账和首次数据拉取,我们通过跟业务沟通改离线对账或者走 DB 查询等方式,把不合理的 Scroll 查询迁移走。迁移了 Scroll 请求约 30+ 业务方, QPS 从 100+ 降到个位数,基本解决了 Scroll 场景的性能隐患。
ES 慢查询治理
慢查询是一个相对的概念,不是一个绝对的概念,不是说某种查询一定是慢查询,或者某种查询一定不是慢查询,他和数据规模等因素相关性很大。大多都是因为实现方式的原因,他的慢会随着数据规模增长而逐渐明显,所以支持亿级数据量和万级、百万级完全不是一回事,不到一定数据量,同样的实现可能也并不会产生问题。
在大规模数据场景下,慢查询的慢会越发明显,往往慢查询几十的 QPS 就能占用正常查询上千 QPS 所需要的资源。在其它流量突然增加的情况下,一般慢查询的耗时会成倍增加,也意味着它占用的资源一直得不到释放,给系统带来巨大的性能隐患。下面举例说明一些观察到的 ES 慢查询:
-
Terms 查询在每次查询的数量过大时都会导致慢查询,系统当时存在每次 Terms 查询 万个商品的场景,耗时在 1s+,商品写流量进来后,查询耗时翻好几倍,CPU 被打满。
-
对 Double 类型字段做 Term 查询,因为检索方式和数据结构不匹配,同样还是因为数据量过大,导致慢查询。
-
高区分度字段 Terms 聚合。
慢查询的规避手段也已经相对比较成熟。可以完善慢查询的监控报警机制,在 CPU 使用率是偏高时制定合理的报警阈值。借此我们也梳理了 ES 查询可能存在的慢查询 Case,排查其他业务隐患,由此慢查询带来的 CPU 上涨问题也已经被排查解决。
Range 查询优化
缓存是提升 ES 查询性能的重要手段,如果查询缓存命中率低,则可以定向优化。ES Filter 查询的时候会缓存查询频次较高的请求结果,然而 Range 查询的特殊点在于,如果每次查询的时间区间不一样,会导致一直缓存,然而命中率极低,引发系统频繁 GC,从而造成稳定性问题。
优化方法:
-
Range 查询走普通查询,不通过 Filter 过滤器缓存。
-
优化 Range 查询,比如指定时间区间查询,提高分片维度请求缓存命中率,并降低缓存频繁构建和垃圾回收频率。仅查询需要的字段
在我们的系统中就曾出现过获取活动列表活动的配置非常大,流量变高时迅速把 CPU 打满的问题。主要因为 ES 查询默认是 query_then_fetch 模式,如果业务的索引文档比较大,每次查询都返回整个索引文档的话,那么 Fetch 的耗时就会变高,造成慢查询,或者内存被打爆的情况,所以仅查询需要的字段可以节省带宽,和磁盘访问耗时,从而提升查询效率。
ES 写入链路治理
仅写入需要索引的字段
ES 的定位是搜索和统计,所以我们在后面的治理中也是非常谨慎对待需要写入 ES 的字段,仅写入索引和统计字段,其它数据则可以回表查询,该方式可以避免索引膨胀速度过快,影响查询和索引重建效率,也是更多资源的浪费。
Nested 索引优化
索引通常会面临父子文档关联文档这样的查询场景,有的还要求子文档能够独立搜索,Nested 类型就是 ES 帮甲方解决此类问题的。Nested 其实是非常好的一个设计,性能也很优越,但它的前提是子文档不能太大,子文档深度不能太深,文档膨胀相对可控,查询方式友好,总之大数据规模使用 Nested,需要多加前提,能不用就不用,小数据规模就不用太有负担。
Nested 的查询和索引性能都稍逊于普通索引类型,通常是普通索引好几倍的资源消耗,我们为了解决商品索引 SPU->SKU Nested 慢查询问题,以及降低索引膨胀速度,通过将 ES 的 SKU Nested 索引设置为 Object 类型,并且把 SKU 维度的信息计算结果作为 SPU 字段共同提供简单查询,满足业务查询需要,这样我们既做到了业务无损,也降低了系统压力。通过以上优化我们的写入膨胀系数降低了 20 倍左右,文档数从 40 亿缩减到了 2 亿,并通过压测佐证写入性能提升了 20%+,也不会再高频出现该类慢查询的情况。
消息乱序问题
RocketMQ 乱序问题:
-
通过 Client SDK 发送数据时,如果发送失败则会快速重试发送到其它 Queue,此时同一个 Key 的消息在不同的 Queue 中造成消息乱序到达 ES;
-
RocketMQ 如果出现发生 Rebalance,可能会导致同一组消息同时给多个消费者消费,从而发生 ABA 覆盖写问题。
以上都会造成丢失更新的问题,所以需要利用 RocketMQ 来保证有序性,但也并不能达到 100% 的效果。我们在比价消费场景中就曾遇到问题,一个报名商品有 N 个 SKU,会分别进行站内外比价,以及自身比价结果计算,基本上都是并发进行的,这就导致多个比价结果在同一时刻到达,其中一个消息在写入时发生失败自动重试写入到其它 Queue,即发生了比价消息更新覆盖的问题。在具体的解决过程中我们设计了如下三个方案:
-
采用比价的 Version 乐观锁控制,采用 Script+Verison 写,但是由于 Script 的写入性能不高,而且比价目前的写入流量最高 2k+,未来随着商品量级增加会更多,所以未采用。
-
采用 ES 的 Version 版本号控制,写入时带上 Version 版本号,但是因为前面介绍过我们的一条报名记录会有多个写入入口,全局 Version 版本号的形式成本太高,也未采用。
-
✅ 采用批量聚合消费的方式。即 FaaS 单条消费改为批量消费,按照最大消息数或者聚合时间消费聚合,这样可以处理单条消息并发更新的 Case。采用该方式首先因为改动成本低,本身的 SDK 也能够支持,可以聚焦解决问题,少量 Case 依然使用对账 T+1 补偿的方式。
ES 资源隔离
目前我们的 ES 集群承载着所有招商需要的 ES 索引的流量,包括活动、企划、报名实体索引等,目前的稳定性保障预期总读流量可以支持 2000+ QPS,写流量 6000+ QPS。然而在压测时使用线上真实流量压测,我们在分别压读、压写,同时压读和写,通过控制变量的方式压测时发现读流量的资源倾斜非常严重,部分节点的 CPU 使用率很高,整体压不上去。通过分析后发现是因为不同索引的分片分布不一样导致的,所以读写流量分布不均,并且不同索引的重保等级是不一样的,介于此原因,我们认为资源隔离可以更好的规避风险,提高系统可用性,根据不同的分片分布特性,分配不同的 ES 集群规格,也有利于资源使用率最大化。
治理效果
-
ES 集群资源使用情况符合预期,不再出现 CPU 暴涨、CPU 被打满、持续慢查询情况,基本解决了非预期的 CPU 增长问题,系统性能保持稳定。
-
ES 索引文档数从 40 亿缩减到 2 亿+,ES 写性能提升 20%+,写入 QPS 最高可支持 1w+,性能上可以超出业务需求满足业务使用。
当然在每次活动之前,我们也都会结合稳定性的治理 House 来分析容量变化、流量变化、监控报警,并根据需求定向优化以上几个方面,保证每一次的系统变化都在预期范围内,把一切不确定因素变得确定。当然在未来的实践中仍需不断探索,挖掘 ES 在实践能力上的更多可能性。
相关文章:

电商场景下 ES 搜索引擎的稳定性治理实践
继上文在完成了第一阶段 ES 搜索引擎的搭建后,已经能够实现对千万级别的商品索引的读写请求的支持。目前,单机房读流量在 500~1000 QPS 之间,写流量在 500 QPS 左右。 但随着业务的发展,问题也逐渐开始暴露࿰…...

jdk8与jdk17的区别。springboot2.x与springboot3.x的区别
1. jdk8与jdk17的区别 Java JDK 8 和 JDK 17 之间存在许多区别,包括功能、性能、语言特性和工具等方面。以下是它们之间的一些主要区别: 功能和语言特性: JDK 8引入了许多重要的语言特性,包括Lambda表达式、方法引用、Stream API、…...

Pytest测试中的临时目录与文件管理!
在Pytest测试框架中,使用临时目录与文件是一种有效的测试管理方式,它能够确保测试的独立性和可重复性。在本文中,我们将深入探讨如何在Pytest中利用临时目录与文件进行测试,并通过案例演示实际应用。 为什么需要临时目录与文件&a…...

arduino 编程esp8266
概述: 1.板子外设资源的访问:Libraries - Arduino Reference 注意:开发板未nodeMCU1.0(esp-12e)(esp8266-01s上调试的。) 2.硬件接线 en,vcc接3.3v,gnd接地(也就是和串口共地),gpio1接地。tx接串口rx,rx接串…...

基于springboot实现数据资产管理系统项目【项目源码+论文说明】计算机毕业设计
基于springboot实现数据资产管理系统演示 摘要 固定资产管理系统主要是完成对系统用户管理、资产信息管理、资产变更管理、资产用途管理、资产类别管理和资产增减管理。因为利用本系统管理员可以直接录入信息,修改信息,删除信息,并且若在录入…...

在Java中如何将十进制转换为二进制,八进制,十六进制以及它们之间的互相转换
目录 一、算法实现进制之间的转换 (1)十进制转换为二进制 (2)二进制转换成十进制 二、Java中的API实现进制转换 (1)十进制转换为二进制 (2)十进制转换为八进制 (3…...

AK/SK加密认证
一、AK/SK概念 Access Key (AK):AK是一个全局唯一的字符串标识符,用于标识用户。它类似于用户名,但仅用于身份识别,并不包含任何秘密信息。 Secret Access Key (SK):SK则是一个高度保密的密钥,类似于密码&…...

前端实现websocket通信讲解(vue2框架)
websocket: WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的Websocket是一个持久化的协议 websocket提供的api&a…...

解决ffmpeg播放摄像头延时的问题(项目案例使用有效)
第一1.目前使用的对接的海康威视的摄像机,并且采用的流媒体服务器NodeMediaServer 进行收数据流并发流数据。但是延时达到了20秒,所以客户看到的效果不是很乐观,没有办法,只能开始优化播放延时的问题,至于对接摄像头的方案有好几种。我这种情况是时间没有延迟只有画面是有…...

Android 音频系统
导入 早期Linux版本采用的是OSS框架,它也是Unix及类Unix系统中广泛使用的一种音频体系。 ALSA是Linux社区为了取代OSS而提出的一种框架,是一个源代码完全开放的系统(遵循GNU GPL和GNU LGPL)。ALSA在Kernel 2.5版本中被正式引入后,OSS就逐步…...

Java必须掌握的二叉堆知识点(含面试大厂题含源码)
二叉堆是一种常用的优先队列数据结构,广泛应用于各种场景,比如任务调度、带权图的最短路径算法(如Dijkstra算法)等。在Java面试中,了解二叉堆的基本概念、实现方式和操作是非常重要的。下面是一些关于二叉堆的关键知识…...

[Java、Android面试]_03_java内存管理:虚拟内存、堆、垃圾回收
本人今年参加了很多面试,也有幸拿到了一些大厂的offer,整理了众多面试资料,后续还会分享众多面试资料,感兴趣的朋友可收藏关注, 现分享如下: 文章目录 1. Java虚拟机运行时数据区2. Java堆3. 垃圾回收3.1 如…...

PTA题解 --- 求整数段和(C语言)
今天是PTA题库解法讲解的第二天,接下来讲解求整数段和,题目如下: 为了解决这个问题,你可以遵循以下的思路: 1. 读取输入的两个整数A和B。 2. 使用一个for循环,从A遍历到B。 3. 在循环中,打印当…...

virsh管理虚拟机的命令行工具
virsh是一个管理虚拟机的命令行工具,提供了丰富的命令来查看、创建、管理虚拟机。以下是一些常用的virsh命令: 查看帮助和版本: virsh --help:查看virsh命令的帮助信息。virsh -version:查看virsh的版本信息。 查看虚…...

数据集成平台选型建议
一 数据集成介绍 数据集成平台是一种用于管理和协调数据流动的软件工具或服务。它的主要目标是将来自多个不同数据源的数据整合到一个统一的、易于访问和分析的数据存储库中。这些数据源可以包括数据库、云应用、传感器、日志文件、社交媒体等等。数据集成平台的关键任务是确保…...

Centos8安装Docker,使用阿里云源
一、前期准备 1.关闭防火墙,SELINUX systemctl stop firewalld.service systemctl disable firewalld.service setenforce 0 sed -i "s/SELINUXenforcing/SELINUXdisabled/g" /etc/selinux/config查看状态 systemctl status firewalld systemctl status…...

FFmpeg概念和简单使用
FFmpeg是一个开源的跨平台多媒体处理工具套件,包含了用于处理音频、视频和图像的各种工具、库和命令行程序。它由一个主要的命令行工具ffmpeg和一系列相关工具组成,可以执行各种各样的多媒体操作。以下是FFmpeg中一些重要的概念: 音频、视频和…...

OJ_最长公共子序列
题干 C实现 #include <iostream> #include <stdio.h> #include <algorithm> using namespace std;int dp[1002][1002];int main() {int n,m;char s1[1001];char s2[1001];scanf("%d%d",&n,&m);scanf("%s%s",s1,s2);//dp[i][j]是…...

SpringBoot拦截器获取token用户对象优雅地传递到Controller层
项目场景: SpringBoot拦截器获取token用户对象优雅地传递到Controller层 问题描述 后端有许多接口都需要请求中携带有正确的Token,这时采用拦截器来验证token,但是每个接口都还是需要解析一遍token,浪费资源,不免显得…...

从零开始学HCIA之SDN03
1、VXLAN相关概念 (1)NVE(Network Virtual Edge),网络虚拟化边界,是运行VXLAN的设备,其实体是一种虚拟逻辑接口,负责VXLAN数据的封装和解封装,其主要参数包括源VTEP以及…...

C语言深度理解之——结构体内存对齐
前言: 在C语言中,结构体(struct)是一种用户自定义的数据类型,可以包含不同类型的数据成员。在定义结构体时,编译器会根据平台的要求对结构体的内存进行对齐,以提高内存访问的效率。结构体内存对…...

LeetCode 热题 100 | 回溯(二)
目录 1 39. 组合总和 2 22. 括号生成 3 79. 单词搜索 菜鸟做题,语言是 C,感冒快好版 关于对回溯算法的理解请参照我的上一篇博客; 在之后的博客中,我将只分析回溯算法中的 for 循环。 1 39. 组合总和 题眼:c…...

混合内容错误https中加载了http
一、遇到问题 iframe嵌套时,混合内容错误https中加载了http,但是已经确认了ifreme中是https的,最后发现在/my/edit?applyid1改为/my/edit/?applyid1,加了一个斜杠,直接解决了 /my/edit是vue页面,其他页…...

游戏免费下载平台模板源码
功能介绍 此游戏网站模板源码是专门为游戏下载站而设计的,旨在为网站开发者提供一个高效、易于维护和扩展的解决方案。 特点: 响应式设计:我们的模板可以自适应不同设备屏幕大小,从而为不同平台的用户提供最佳的浏览体验。 …...

鸿蒙视频播放的实现
文章目录 前言播放效果视频播放的实现总结 一、前言 现在市面上很多应用都跟视频有关,那么在鸿蒙系统上怎么来播放视频呢,今天就讲解视频播放控件,让你也能快速地进行视频播放功能开发。 最后呢,我会提供一个鸿蒙中涉及的主要…...

QT----计算器
目录 1 搭建标准界面2、 逻辑编写2.1 初始化 github链接:基于qt的计算器 更多内容可以点击这里查看个人博客:个人博客 1 搭建标准界面 按照下图搭设界面 修改样式让这计算器看起来更像一点,同时对按钮分组进行样式编辑,添加字符…...

Linux:kubernetes(k8s)Deployment的操作(13)
创建deployment 命令 kubectl create deploy nginx-deploy --imagenginx:1.7.9 再去使用以下命令分别查询 ubectl get deploy kubectl get replicaset kubectl get pod 他是一个层层嵌套的一个关系 首先是创建了一个 deploy 里面包含着replicaset replicaset里面含有…...

20240619-James-快速鸟瞰并发编程, 呕心沥血整理的架构技术(第3篇)
接着第1, 2篇后,我们继续来跟进一下并发编程的其它内容,如下: 第9节 java.util.concurrent包 线程池 线程池的核心接口是ExecutorService。java.util.concurrent还提供了一个静态工厂类Executors,其中包含用于创建配置线程池的…...

C语言——详解字符函数和字符串函数(一)
Hi,铁子们好呀!今天博主来给大家更一篇C语言的字符函数和字符串函数~ 具体讲的内容如下: 文章目录 🎆1.字符分类函数💯💯⏩1.1 什么是字符分类函数的?💯💯⏩1.2 字符函数的类型有哪…...

三款内衣洗衣机的顶级较量:希亦、小吉、由利,谁才是性价比之王?
洗衣机在我们的生活中可谓是非常常见的了,几乎每家每户都具备着一台。即便是有洗衣机,也有不少人不会将自己我贴身衣物直接扔在洗衣机里清洗,而是会自己手工手洗。这跟我们传统上的观念有很大的关系,认为把内衣、内裤等贴身衣物放…...