得物 Zookeeper SLA 也可以 99.99% | 得物技术

news2024/9/23 7:18:33

一、背景

ZooKeeper(ZK)是一个诞生于2007年的分布式应用程序协调服务。尽管出于一些特殊的历史原因,许多业务场景仍然不得不依赖它。比如,Kafka、任务调度等。特别是在 Flink 混合部署 ETCD 解耦 时,业务方曾要求绝对的稳定性,并强烈建议不要使用自建的 ZooKeeper。出于对稳定性的考量,采用了阿里的 MSE-ZK。自从 2022 年 9 月份开始使用至今,我们没有遇到任何稳定性问题,SLA 的可靠性确实达到了 99.99%。

在 2023 年,部分业务使用了自建的 ZooKeeper(ZK)集群,然后使用过程中 ZK 出现了几次波动,随后得物 SRE 开始接管部分自建集群,并进行了几轮稳定性加固的尝试。接管过程中我们发现ZooKeeper在运行一段时间后,内存占用率会不断增加,容易导致内存耗尽(OOM)的问题。我们对这一现象非常好奇,因此也参与了解决这个问题的探索过程。

二、探索分析

确定方向

在排查问题时,我们非常幸运地发现了一个测试环境的故障现场,该集群中的两个节点恰好处于OOM的边缘状态。

图片

有了故障现场,那么一般情况下距离成功终点只剩下50%。

内存偏高,按以往的经验来看,要么是非堆,要么是堆内有问题。从火焰图和jstat 都能证实:是堆内的问题。

图片

图片

如图所示:说明 JVM 堆内存在某种资源占用了大量的内存,并且FGC都无法释放。

内存分析

为了探究 JVM 堆中内存占用分布,我们立即做了一个JVM堆Dump。分析发现 JVM 内存被 childWatches 和 dataWatches 大量占用。

图片

图片

dataWatches:跟踪 znode 节点数据的变化。

childWatches:跟踪 znode 节点结构(tree)的变化。

childWatches和dataWatches同源于WatcherManager。

经过资料排查,我们发现 WatcherManager 主要负责管理 Watcher。ZooKeeper(ZK)客户端首先将 Watcher 注册到 ZooKeeper 服务器上,然后由 ZooKeeper 服务器使用 WatcherManager 来管理所有的 Watcher。当某个 Znode 的数据发生变更时,WatchManager 将触发相应的 Watcher,并通过与订阅该 Znode 的 ZooKeeper 客户端的 socket 进行通信。随后,客户端的 Watch 管理器将触发相关的 Watcher 回调,以执行相应的处理逻辑,从而完成整个数据发布/订阅流程。

图片

进一步分析WatchManager,成员变量 Watch2Path、WatchTables 内存占比高达 (18.88+9.47)/31.82 = 90%。

图片

而 WatchTables、Watch2Path 存储的是 ZNode 与 Watcher 正反映射关系,存储结构图所示:

图片

WatchTables【正向查询表】

HashMap<ZNode, HashSet<Watcher>>

场景:某个ZNode发生变化,订阅该ZNode的Watcher会收到通知。

逻辑:用该ZNode,通过 WatchTables 找到对应的所有 Watcher 列表,然后逐个发通知。

Watch2Paths【逆向查询表】

HashMap<Watcher, HashSet>

场景:统计某个 Watcher 到底订阅了哪些ZNode

逻辑:用该Watcher,通过 Watch2Paths 找到对应的所有 ZNode 列表

Watcher 本质是 NIOServerCnxn,可以理解成一个连接会话。

如果ZNode、和 Watcher 的数量都比较多,并且客户端订阅 ZNode 也比较多,甚至全量订阅。这两张Hash表记录的关系就会呈指数增长,最终会是一个天量!

当全订阅时,如图演示:

当 ZNode数量:3,Watcher 数量:2   WatchTables 和 Watch2Paths 会各有 6 条关系

图片

当 ZNode数量:4,Watcher 数量:3   WatchTables 和 Watch2Paths 会各有 12 条关系

图片

通过监控我们发现,异常的ZK-Node。ZNode数量大概有20W,Watcher数量是5000。而Watcher与ZNode的关系条数达到了1亿。

如果存储每条关系的需要1个 HashMap&Node(32Byte),由于是两个关系表,double一下。那么其它都不要计算,光是这个"壳",就需要 2*10000^2*32/1024^3 = 5.9GB 的无效内存开销。

分析到这里,大家应该明白了。为什么我们的ZK内存总是在走“钢丝”,经常OOM。

意外发现

既然已经确定了问题的原因,接下来我们应该考虑如何解决它。

通过上面的分析可以得知,我们需要避免客户端出现对所有 ZNode 进行全面订阅的情况。然而,实际情况是,许多业务代码确实存在这样的逻辑,从 ZTree 的根节点开始遍历所有 ZNode,并对它们进行全面订阅。

我们或许能够说服一部分业务方进行改进,但无法强制约束所有业务方的使用方式。因此,我们解决这个问题的思路在于监控和预防。然而,遗憾的是,ZK 本身并不支持这样的功能,这就需要对 ZK 源码进行修改。

通过对源码的跟踪和分析,我们发现问题的根源又指向了 WatchManager,并且我们仔细研究了这个类的逻辑细节。经过深入理解后,我们发现这段代码的质量似乎像是由应届毕业生编写的,存在大量线程和锁的不恰当使用问题。通过查看 Git 记录,我们发现这个问题可以追溯到 2007 年。然而,令人振奋的是,在这一段时间内,出现了 WatchManagerOptimized(2018),通过搜索 ZK 社区的资料,我们发现了 [ZOOKEEPER-1177],即在 2011 年,ZK 社区就已经意识到了大量 Watch 导致的内存占用问题,并最终在 2018 年提供了解决方案。正是这个WatchManagerOptimized 的功劳,看来ZK社区早就进行了优化。

图片

有趣的是,ZK默认情况下并未启用这个类,即使在最新的 3.9.X 版本中,默认仍然使用 WatchManager。也许是因为 ZK 年代久远,渐渐地人们对其关注度降低了。通过询问阿里的同事,我们确认了 MSE-ZK 也启用了 WatchManagerOptimized,这进一步证实了我们关注的方向是正确的。因此,我们认为有必要深入挖掘一下这个类的潜力。

优化探索

锁的优化

在默认版本中,使用的 HashSet 是线程不安全的。在这个版本中,相关操作方法如 addWatch、removeWatcher 和 triggerWatch 都是通过在方法上添加了 synchronized 重型锁来实现的。而在优化版中,我们采用了 ConcurrentHashMap 和 ReadWriteLock 的组合,以更精细化地使用锁机制。这样一来,在添加 Watch 和触发 Watch 的过程中能够实现更高效的操作。

图片

存储优化

这是我们关注的重点。从 WatchManager 的分析可以看出,使用 WatchTables 和 Watch2Paths 存储效率并不高。如果 ZNode 的订阅关系较多,将会额外消耗大量无效的内存。

令我们感到惊喜的是,WatchManagerOptimized 在这里使用了“黑科技” -> 位图。

利用位图将关系存储进行了大量的压缩,实现了降维优化。

Java BitSet 主要特点:

  • 空间高效:BitSet 使用位数组存储数据,比标准的布尔数组需要更少的空间。

  • 处理快速:进行位操作(如 AND、OR、XOR、翻转)通常比相应的布尔逻辑操作更快。

  • 动态扩展:BitSet 的大小可以根据需要动态增长,以容纳更多的位。

BitSet 使用一个 long[] words 来存储数据,long 类型占 8 字节,64位。数组中每个元素可以存储 64 个数据,数组中数据的存储顺序从左到右,从低位到高位

比如下图中的 BitSet 的 words 容量为 4,words[0]从低位到高位分别表示数据 0~63是否存在,words[1] 的低位到高位分别表示数据 64~127 是否存在,以此类推。其中 words[1] = 8,对应的二进制第 8 位为 1,说明此时 BitSet 中存储了一个数据 {67}。

图片

WatchManagerOptimized 使用 BitMap来存储所有的 Watcher。这样即便是存在1W的 Watcher。位图的内存消耗也只有8Byte*1W/64/1024=1.2KB。如果换成 HashSet ,则至少需要 32Byte*10000/1024=305KB,存储效率相差近300倍。

WatchManager.java:private final Map<String, Set<Watcher>> watchTable = new HashMap<>();private final Map<Watcher, Set<String>> watch2Paths = new HashMap<>();
WatchManagerOptimized.java:private final ConcurrentHashMap<String, BitHashSet> pathWatches = new ConcurrentHashMap<String, BitHashSet>();private final BitMap<Watcher> watcherBitIdMap = new BitMap<Watcher>();

ZNode到 Watcher 的映射存储,由 Map<string, set> 换成了 ConcurrentHashMap<string, BitHashSet>。也就是说不再存储 Set,而是用位图来存储位图索引值。

图片

我们用 1W的ZNode,1W的Watcher,极端点走全订阅(所有的Watcher订阅所有的ZNode),做存储效率PK:

图片

可以看到 11.7MB PK 5.9GB,内存的存储效率相差:516倍

逻辑优化

图片

  • 添加监视器:两个版本都能够在常数时间内完成操作,但是优化版通过使用ConcurrentHashMap提供了更好的并发性能。

图片

  • 删除监视器:默认版可能需要遍历整个监视器集合来找到并删除监视器,导致时间复杂度为O(n)。而优化版利用BitSetConcurrentHashMap,在大多数情况下能够快速定位和删除监视器,O(1)。

图片

  • 触发监视器:默认版的复杂度较高,因为它需要对每个路径上的每个监视器进行操作。优化版通过更高效的数据结构和减少锁的使用范围,优化了触发监视器的性能。

图片

三、性能压测

JMH 微基准测试

zookeeper 3.6.4 源码编译, JMH micor 压测 WatchBench。

图片

pathCount:表示测试中使用的ZNode路径数目。

watchManagerClass:表示测试中使用的 WatchManager 实现类。

watcherCount:表示测试中使用的观察者(Watcher)数目。

Mode:表示测试的模式,这里是 avgt,表示平均运行时间。

Cnt:表示测试运行的次数。

Score:表示测试的得分,即平均运行时间。

Error:表示得分的误差范围。

Units:表示得分的单位,这里是毫秒/操作(ms/op)。

  • ZNode与Watcher 100 万条订阅关系,默认版本使用 50MB,优化版只需要 0.2MB,而且不会线性增加。

  • 添加Watch,优化版(0.406 ms/op)比 默认版(2.669 ms/op)提升 6.5 倍。

  • 大量触发Watch ,优化版(17.833 ms/op)比 默认版(84.455 ms/op)提升 5 倍。

性能压测

接下来我们在一台机器(32C 60G) 搭建一套 3节点 zookeeper 3.6.4 使用优化版与默认版进行容量压测对比。

场景一:20W znode 短路径

Znode 短路径: /demo/znode1 

图片

场景二:20W znode 长路径

Znode 长路径: /sentinel-cluster/dev/xx-admin-interfaces/lock/_c_bb0832d5-67a5-48ab-8fe0-040b9ddea-lock/12

图片

  • Watch 内存占用跟 ZNode 的 Path 长度有关。

  • Watch 的数量在默认版是线性上涨,在优化版中表现非常好,这对内存占用优化来说改善非常明显。

灰度测试

基于前面的基准测试和容量测试,优化版在大量 Watch 场景内存优化明显,接下来我们开始对测试环境的 ZK 集群进行灰度升级测试观察。

第一套 zookeeper 集群 & 收益

图片

默认版

图片

优化版

图片

图片

效果收益:

  • election_time (选举耗时):降低 60%

  • fsync_time (事务同步耗时):降低 75%

  • 内存占用:降低 91%

第二套 zookeeper 集群 & 收益

图片

图片

图片

图片

效果收益:

  • 内存:变更前 JVM Attach 响应无法响应,采集数据失败。

  • election_time(选举耗时):降低 64%。

  • max_latency(读延迟):降低 53%。

  • proposal_latency(选举处理提案延迟):1400000 ms --> 43 ms。

  • propagation_latency(数据的传播延迟):1400000 ms --> 43 ms。

第三套 zookeeper 集群 & 收益

图片

默认版

图片

优化版

图片

图片

效果收益:

  • 内存:节省 89%

  • election_time(选举耗时):降低42%

  • max_latency(读延迟):降低 95%

  • proposal_latency(选举处理提案延迟):679999 ms --> 0.3 ms

  • propagation_latency(数据的传播延迟):928000  ms--> 5 ms

四、总结

通过之前的基准测试、性能压测以及灰度测试,我们发现了 Zookeeper 的 WatchManagerOptimized。这项优化不仅节省了内存,还通过锁的优化显著提高了节点之间的选举和数据同步等指标,从而增强了 Zookeeper 的一致性。我们还与阿里MSE的同学进行了深度交流,各自在极端场景模拟压测,并达成了一致的看法:WatchManagerOptimized 对 Zookeeper 的稳定性提升显著。总体而言,这项优化使得 Zookeeper 的 SLA 提升了一个数量级。

ZooKeeper有许多配置选项,但大部分情况下不需要调整。为提升系统稳定性,我们建议进行以下配置优化:

  • 将 dataDir(数据目录)和 dataLogDir(事务日志目录)分别挂载到不同的磁盘上,并使用高性能的块存储。

  • 对于 ZooKeeper 3.8 版本,建议使用 JDK 17 并启用 ZGC 垃圾回收器;而对于 3.5 和 3.6 版本,可使用 JDK 8 并启用 G1 垃圾回收器。针对这些版本,只需要简单配置 -Xms 和 -Xmx 即可。

  • 将 SnapshotCount 参数默认值 100,000 调整为 500,000,这样可以在高频率 ZNode 变动时显著降低磁盘压力。

  • 使用优化版的 Watch 管理器 WatchManagerOptimized。

Ref:

https://issues.apache.org/jira/browse/ZOOKEEPER-1177

https://github.com/apache/zookeeper/pull/590

 *文/Bruce

本文属得物技术原创,更多精彩文章请看:得物技术官网

未经得物技术许可严禁转载,否则依法追究法律责任!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1596476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络与系统攻防技术实验及实验报告

1.实验内容 正确使用msf编码器&#xff0c;veil-evasion&#xff0c;自己利用shellcode编程等免杀工具或技巧 正确使用msf编码器&#xff0c;使用msfvenom生成如jar之类的其他文件veil&#xff0c;加壳工具使用C shellcode编程 通过组合应用各种技术实现恶意代码免杀 如果成功…

多目标跟踪 | 基于anchor-free目标检测+ReID的实时一阶多类多目标跟踪算法实现

项目应用场景 面向多目标检测跟踪场景&#xff0c;项目采用 anchor-free 目标检测ReID 的实时一阶段多类多目标跟踪算法实现&#xff0c;效果嘎嘎好。 项目效果 项目细节 > 具体参见项目 README.md (1) 类别支持 1~10 object classes are what we need non-interest-…

智能热流体仿真软件AICFD 2024R1新版本功能介绍

AICFD是由天洑软件自主研发的一款通用的智能热流体仿真软件。软件引入AI技术&#xff0c;具备智能问答、智能加速、智能预测等特色功能&#xff0c;解决用户在传统CFD软件中遇到的“网格划分繁、求解设置难、仿真计算慢”等痛点&#xff0c;使设计师和工程师可以专注于业务本身…

AI大模型语言开源大语言模型完整列表

开源大语言模型完整列表 Large Language Model (LLM) 即大规模语言模型&#xff0c;是一种基于深度学习的自然语言处理模型&#xff0c;它能够学习到自然语言的语法和语义&#xff0c;从而可以生成人类可读的文本。 所谓"语言模型"&#xff0c;就是只用来处理语言文…

WP免费主题下载

免费wordpress模板下载 高端大气上档次的免费wordpress主题&#xff0c;首页大图全屏显示经典风格的wordpress主题。 https://www.wpniu.com/themes/289.html 免费WP主题 蓝色简洁实用的wordpress免费主题模板&#xff0c;免费主题资源分享给大家。 https://www.wpniu.com/…

如何查找overlayfs对应的POD如何根据pod找到containerd id

如何查找overlayfs对应的POD mount |grep overlayfs | grep 1738 ctr -n k8s.io c list | grep 11ac4083419be11174746b68d018a0a402d9ae43c6b52125810fe1ec7db63bc6 查找目录并统计大小 find / -name "jfsCache" -exec du -sh {} | sort -rh如何根据pod找到c…

配置IP地址并验证连通性

1.实验环境 主机 A和主机 B通过一根网线相连&#xff0c;如图6.13所示。 图6.13 实验案例一示意图 2.需求描述 为两台主机配置!P地址&#xff0c;验证P地址是否生效&#xff1b;验证同一网段的两台主机可以互通&#xff0c;不同网段的主机不能直接互通。 3.推荐步骤 为两台…

笔记软件功能多样的是哪款?做笔记的软件哪个好用

在快节奏的现代生活中&#xff0c;笔记软件已成为我们提高工作效率、记录生活点滴的重要工具。想象一下&#xff0c;在繁忙的工作中&#xff0c;你能够快速记录下关键信息&#xff0c;或在灵感迸发时及时捕捉&#xff0c;这是多么方便高效。 一款功能多样的笔记软件&#xff0…

JAVA-贪吃蛇(源代码)

游戏界面: 图片素材: 背景图片 蛇身 食物 蛇头 标题 源代码: 运行界面 package com.snake.game;public class snakeApp {public static void main(String[] args) {//添加界面new snakeJFrame();} }游戏界面类JFrame package com.snake.game;import javax.swing.*; import …

WEB前端-用户注册倒计时

<body><textarea name"" id"" cols"30" rows"10">用户注册协议欢迎注册成为京东用户&#xff01;在您注册过程中&#xff0c;您需要完成我们的注册流程并通过点击同意的形式在线签署以下协议&#xff0c;请您务必仔细阅读…

腾讯EdgeOne产品测评体验——多重攻击实战验证安全壁垒:DDoS攻击|CC压测|Web漏洞扫描|SQL注入

腾讯EdgeOne产品测评体验——实战验证安全壁垒&#xff1a;DDoS攻击|CC压测|Web漏洞扫描|SQL注入 写在最前面一、产品概述1.1 什么是边缘安全加速平台 EO&#xff1f;1.2 EdgeOne产品功能 二、准备工作2.1 选择&#xff1a;NS&#xff08;Name Server&#xff09;接入模式或 CN…

智慧用电安全管理系统

智慧用电安全管理系统 智慧用电安全管理系统是智能电网中客户侧关键的构成部分&#xff0c;是基本建设新型智慧城市的基本&#xff0c;将完成地区内各种各样用电设备的智能化系统监管&#xff0c;完成地区内日常生活与工作中安全性、舒服。 一、智慧用电安全管理系统介绍 …

FANUC机器人单轴零点标定的具体方法(全轴零点标定不方便时可采用)

FANUC机器人单轴零点标定的具体方法(全轴零点标定不方便时可采用) 前面和大家分享了FANUC机器人进行零点标定的原因和方法,具体可参考以下链接中的内容:: FANUC机器人进行零点标定的目的和具体方法步骤详解

Python学习从0开始——项目一day01爬虫(二)

Python学习从0开始——项目一day01爬虫&#xff08;二&#xff09; 一、解析response数据二、json转换三、文件保存四、存储json对象五、完整代码 上一篇 一、解析response数据 在已经知道我们获取图片的最终URL存在于请求响应response中&#xff0c;下一步的重点就放在解析re…

18.软件定时器

一、简介 软件定时器是指具有定时功能的软件&#xff0c;FreeRTOS 提供的软件定时器允许在创建前设置一个 软件定时器定时超时时间&#xff0c;在软件定时器成功创建并启动后&#xff0c;软件定时器开始定时&#xff0c;当软件定 时器的定时时间达到或超过先前设置好的软件定时…

LOCK、ACC、ON、START的含义及正确使用

背景 前段时间在开发一个远程锁车的需求时&#xff0c;讨论到了电源状态的场景。由于初次进入汽车电子行业&#xff0c;对很多基础概念不清晰。当时听主机厂商的同事介绍一遍后&#xff0c;并不是很理解。于是趁着空闲&#xff0c;给自己充充电&#xff0c;也希望能够帮到有需…

Office 365卡顿怎么办?SD-WAN可以解决

随着数字化浪潮的推进&#xff0c;Office 365等云办公应用已成为企业日常运营不可或缺的工具。然而&#xff0c;许多企业在使用Office 365时遭遇了网络卡顿的难题&#xff0c;给工作人员带来诸多不便。随着SD-WAN技术的成熟和普及&#xff0c;这一难题得到了有效的解决。 Offic…

HarmonyOS实战开发-状态管理、通过使用页面级的状态变量 和应用级的状态变量 来实现应用的状态管理。

介绍 本示例通过使用页面级的状态变量 和应用级的状态变量 来实现应用的状态管理。 效果预览 使用说明 1.点击首页中的基本类型进入对应页面&#xff0c;点击按钮可以更改圆形的颜色&#xff1b;点击查看源码可以展示基本类型功能效果的源码。 2.点击首页中的数组类型进入对…

密码知识汇总

文章目录 密码学知识&#xff23;&#xff29;&#xff21;三要素机密性&#xff08;Confidentiality&#xff09;完整性&#xff08;Integrity&#xff09;可用性&#xff08;Availability&#xff09; 非安全信道的风险以及应对措施风险应对措施使用加密技术&#xff08;防窃…

【第十五届】蓝桥杯省赛C++b组

今年的蓝桥杯省赛已经结束了&#xff0c;与以往不同&#xff0c;今年又回到了8道题&#xff0c;而22&#xff0c;23年出现了10道题 大家觉得难度怎么样&#xff0c;欢迎进来讨论&#xff0c;博主今年没参加哈&#xff0c;大家聊聊&#xff0c;我听听大家的意见和看法哈 试题A:…