CAL PB级的日志存储迁移到Ceph的实践

2019 年 7 月 29 日

CAL PB级的日志存储迁移到Ceph的实践

一、背景


CAL(Central Application Logging) 系统主要负责收集和处理 eBay 内部各个应用池的日志,日处理超过 3PB 的数据,提供包含原始日志、聚合报告、正则表达式搜索等在内的多种形式的结果供 SRE(Site Reliability Engineer)和 PD 们(Product Developer,产品开发设计师)日常监控使用。从 21 世纪初上线至今,已经有 10 多年的历史了。


早期后台存储日志使用的是 NetApp 公司提供的 Filer,作为 NFS(Network File System,网络文件系统)的解决方案,所有 CAL 相关的模块都需要从 Filer 上读写数据。


CAL 使用的 Filer,是由图 1 所示的多个小的物理卷组成的,它有 2 个主要的问题


  1. 一旦需要增加一个新的卷,或者移除一个老的卷,都需要所有CAL组件做一次代码发布,效率很低

  2. 需要在写日志的时候选择最空闲的卷,来保证每个小卷都尽可能地使用均匀,但没有哪个算法能保证绝对均匀,空间使用效率不是很好



图 1


另一方面,随着日志数据量的逐年增长,CAL Filer 的容量(Volume)也增大到每个数据中心大约 300 多 T,但日志的有效时间却从原先的几天下降到现在的 20 个小时。同时,鉴于 NetApp Filer 提供的支持和监控都很有限,已经越来越不能满足 eBay CAL 团队对于存储的要求。再者,CAL 团队也在寻求更快更稳定更便宜的 NFS 存储方案。


鉴于以上几点原因,加上 eBay 内部也有团队深耕新式存储 Ceph 多年,很自然地,CAL 团队着手和 Ceph 团队一起开始了 CAL-on-Ceph 这样一个跨团队的合作项目


二、系统设计的修改


对于这样一个大规模的 CAL 系统,考虑到对 Filer 的使用经验和现状以及实时迁移,我们对现有的监控系统设计做了以下几个方面的修改


1. 目录结构简化


在 CAL 系统设计之初,出于易用性考虑,加之当时日志的容量并不是很大,我们设计了一种特殊的文件存放的目录结构,如图 2 所示,深度 15+。这个目录结构包含了原始日志文件的元数据信息(环境,日期,应用池,客户端 IP 等)。因此几类使用广泛的 API,例如 getPools(指定日期和环境,获取所有符合条件的应用池)和 getMachines (指定日期,环境,应用池,获取所有符合条件的机器(machine))都能通过只遍历目录来得到结果,不需要任何其他的依赖。



图 2(点击可观看大图)


可惜随着日志容量的与日俱增,这种把文件系统当数据库来使用的用例,已经让文件系统苦不堪言。随之带来的影响就是,文件系统越来越频繁地出现读写文件十分缓慢的现象,极大地影响了用户体验。即使把 Filer 换成 Ceph,这个问题也亟待解决。


考虑到 Ceph 后端 MDS(Metadata Server,元数据服务器)集群的可扩展性,我们将目录结构简化成下面的格式,如图 3 所示,深度为 5(其中第三层为 0-1023 的数字,主要是为了方便分散文件到 MDS 集群的不同服务器上去);将大量原本占据目录名存放的信息,转移到了文件名上



图 3(点击可观看大图)


2. 基于数据库的日志元数据存储和查询


目录结构简化只是解决了文件系统的问题,上面提到的很多 API 还是最适宜由数据库来服务,一条 select 语句就能搞定。因此,我们重新梳理了所有使用到文件目录的 API,将最重要的四个元组(日期,环境,应用池,客户端 IP)提取出来,作为创建数据库表的基本元素;并设计了一个包含 24 张表(每小时一张),且每张表包含四个列的数据库。考虑到 eBay DBA 的可用支持,最终选择了 MySQL


鉴于 eBay 拥有超过百万台的服务器,每个服务器又通过多个连接同时将日志写进 CAL 系统,可以预见这张表规模不会小。如果日志进入 CAL 系统后,上述 API 能在很短的 SLA 内被用户所消费,数据库的最大 TPS 可能会达到 50k(主要是写的 TPS)。如果不采取优化,只是一条一条写的话,性能上会有问题。


我们调研了几种方案,最终选用了“LOAD DATA LOCAL INFILE”的方式来做批量写。实验表明,这种将客户端多个插入请求合并到本地文件,再传输到 MySQL server 端去做批量插入的方法,最大能支持 322k TPS,足以满足当前以及未来很长一段时间内的需求。


3. 文件 IO 操作异步化


在迁移的过程中,为了验证 Ceph 的性能,需要先做双写。在不影响当前数据流到 Filer 的前提下,克隆数据,导入 Ceph。在预发布(Staging)环境做测试的时候,就已经发现 Ceph 相比于 Filer,一个劣势就是会出现一定频率的“卡死”现象,即某些 CAL 服务器会突然连不上 Ceph,所有对于 Ceph 的文件操作都直接没有响应了。同时还需要考虑到 Ceph 可能会有宕机等其他不可用的情况,在这些情况下,都不能影响到 Filer 的数据流。


CAL 原先是同步处理一条一条的日志,一旦碰到这种现象将会直接影响本该正常写到 Filer 的日志。现在我们将所有文件 IO 类的操作都从主处理流程上解耦,放到后台线程池中进行处理,辅之以线程超时以避免“卡死”的情况


同时,考虑到快速失败机制(fast fail),我们在后台有个额外线程,每隔一段时间主动检查 Ceph 的可用性。如果不可用,涉及到 Ceph 的文件 IO 操作会直接跳过,进一步减少不必要的内存堆积。


三、项目实施中碰到的问题


尽管事先已经考虑了诸多可能会出现的问题,并在预发布(Staging)环境进行了一定规模的测试,但是在生产(Production)环境实施的时候,我们仍然碰到了几个问题。下面分享几个主要的有代表性的问题


1. MySQL 查询性能急剧下降


首次在生产环境上线大约半小时后,我们就观测到几个主要 API 访问 MySQL 时出现大规模的读取困难,原先的查询秒级返回,变成现在的需要超过 1 分钟才能返回。查看 MySQL 的监控后,发现负载急剧增长,于是不得不先回滚。如图 4 所示:



图 4


通过排查日志,并且和 DBA 一起研究过后,我们将可能的原因缩小到以下几方面,并做了相应的优化


01 MySQL 里产生了大量碎片


现有的 DB 数据保留(retention)方法是采用 “DELETE * from TABLE xx where date < ${date}”,定期删除某个时间点之前的所有条目。但这并不是 MySQL 友好的方式,在目前单表几百万数据的情况下,随着时间的推移,会产生大量的数据碎片,进而造成 MySQL 主、从节点之间同步困难,最终影响数据库的性能。


咨询了 DBA 后,考虑到记录有效使用时间,我们将数据保留方法调整为若干个小时(<24 小时)后,直接“truncate”掉整张表,既快又不会产生碎片


02 MySQL 表中有大量重复数据


现有的写数据库操作逻辑是:当客户端同 CAL 服务器建立连接后,会将一条记录放在 CAL 服务器的本地缓存里,然后周期性地将缓存里的所有记录一次性通过上面提到的”LOAD DATA LOCAL INFILE”的方式写到 MySQL 里。但每个客户端会建立若干个(一般为 5 个)连接到 CAL 服务器。因此,可能来自同一个客户端的多个连接会落到同一个 CAL 服务器里


考虑到这种情况,在略微降低 SLA 的前提下,将周期性刷新缓存到 MySQL 的时间间隔从原来的一分钟,增大到了五分钟。同时在刷新前,再对缓存做一次去重。最终将单表的条目数量从原先的最多 900 多万条,减少到现在的 500 万条左右。


03 MySQL 表的设计没有索引


由于设计之初并没有考虑做表的索引,通过上面发生的问题,我们开始思考从索引的角度来进一步优化表的查询。考虑到现在的 4 列 (日期,应用池,环境,客户端 IP),其中日期的基数(cardinality)不高,因为一张表里存放的都是某一个小时的记录;环境变量也只有 prod,sandbox 等几个,显然大部分记录都集中在 prod 里,因此也不适合索引;客户端 IP 就更别说了,基数最高,索引没什么意义。


因此只有应用池这一列了,其索引基数大致几千,上百万条记录分布在这几千个应用池里,比较适合做索引。在对应用池这一列做了索引后,50 个线程同时使用 getMachines API,其平均响应时间从原先的 24 秒下降到现在的 1 秒左右


以上 3 个方面的优化都完成后,在生产环境实施该项目时就再也没出过 MySQL 方面的问题了


2.频繁 full GC


在生产环境做双写的时候,我们发现了一个现象:某些 CAL 进程运行了大概 30 分钟后,整个进程就没有任何响应了,也不写日志了。翻阅 GC(Garbage Collection, 垃圾回收)日志,发现它在运行 20 分钟后,就开始频繁做 full GC,不久之后每次 GC 都释放不掉任何内存了,总是 10G->10G


经过数次失败后,终于在进程开始 full GC 时,成功地完成了一次 heapdump。通过 MAT(Memory Analyzer Tool)工具分析,发现里面有超过 24K ScheduledFutureTask 对象(如图 5 所示),每个持有了大概 400K 堆内存,总计保留了将近 10G 的堆内存。


这个 ScheduledFutureTask 里包含了一个 rotateFuture(保留了一些堆外内存的引用),主要是负责定期地将缓存里保留的数据,刷到后端的 Filer 或者 Ceph 里。在 CAL 认为 Ceph 不可用的情况下,这部分 rotateFuture 没有能够进入正常的释放流程,导致越堆越多,最终用尽了 10G 堆内存的限制



图 5


发现了问题后解决方案并不复杂,添加在 Ceph 不可用情况下的 rotateFuture 释放即可,之后的模拟测试和最终上线后便再也没碰到过这个问题了


3.存储端同时读写同一文件时性能急剧下降


当只往 Ceph 写,但是不从 Ceph 里读数据的时候,Ceph 端显示的 Write IOPS(每秒进行读写操作的次数)只有大约 40K 左右。但是,当从 Ceph 端开始大规模读数据的时候,其 Write IOPS 突然飙升到了 170K 左右,然后整个集群的延迟开始显著增加。


对于 CAL 端来说,我们观测到的现象是:刷数据到 Ceph 的速度明显下降,经常出现写的速度跟不上从客户端读数据的速度,导致本地缓冲数据的队列迅速堆满,进而开始丢数据


和 Ceph 团队一起研究过后,发现 Ceph 为了性能考虑,在真正刷数据到持久化层之前,做了一层缓存。Ceph 会根据一定逻辑将收到的若干条数据做批量写。但是,这有个前提,即缓存里的数据没有别人需要消费。当 CAL 从 Ceph 端读取数据之后,每个文件都被多个客户端同时打开,同时进行读和写的操作,此时 Ceph 所做的缓存就失效了,IOPS 也就回归到了原本的真实水平。


针对这一现状,CAL 团队的应对方法是自己实现批量写,来减少刷数据的次数。原来是将一个个包含多条日志的数据块直接压缩,然后写到后端存储去。现在是将多个数据块压缩好后,合并成一个大的数据块,再写到后端存储去,相比之前,现在的方法进一步减少了刷数据的次数,降低了 Write IOPS。Ceph 端的监控显示:Write IOPS 从 170k 降到了 30k 左右,整体集群工作状态良好。


四、总结


虽然初衷只是替换掉日处理超过 3PB 数据的 CAL 系统所使用的后端存储,但是我们没有满足于此。通过一系列的设计优化,不仅成功地将数据迁移到了 Ceph 平台上,更借此机会优化了 CAL 系统的日志存储结构,获得了更好的读写性能,还降低了成本


截止 2019 年 5 月底,eBay 已将一整个数据中心的 CAL 日志搬迁到了 Ceph 上,相比以前的 Filer,共节省了大约 50 万美金,未来随着更多数据中心的日志迁移,相信还会节省更多。


本文转载自公众号 eBay 技术荟(ID:eBayTechRecruiting)


原文链接


https://mp.weixin.qq.com/s/6WuNy1GQY-e7qsXAjI5C7A


2019 年 7 月 29 日 08:00 5989

评论

发布
暂无评论
发现更多内容

Java反射说得透彻一些

秦怀杂货店

Java 反射 java反射

Spring Boot 监听 Redis Key 失效事件实现定时任务

Bruce Duan

Redis监听 监听过期键

Spring / Spring boot 异步任务编程 WebAsyncTask

Bruce Duan

异步任务编程 WebAsyncTask

【涂鸦物联网足迹】涂鸦云平台标准指令集说明

IoT云工坊

人工智能 cpu 物联网 智能家居 指令集

在Android中使用DataBinding(Kotlin)

simon

android Android进阶 JetPack DataBinding 数据绑定

完美!阿里P8仅用242页笔记,就由浅入深讲解了SQL概念

马士兵老师

Java sql 阿里巴巴 SQL语法 sql查询

JDBC【3】-- SPI技术以及在数据库连接中的使用

秦怀杂货店

数据库 spi

线程池运用不当的一次线上事故

AI乔治

Java 架构 高并发 线程池

transient关键字的作用以及几个疑问的解决

秦怀杂货店

序列化 反序列化 transient

HTTP2服务器推送的第一次尝试

新世界杂货铺

golang HTTP2.0

普渡科技荣登甲子光年“2020中国最具商业潜力的20家机器人Cool Vendor”

DT极客

背后技术:双11还能创造什么?

阿里云情报局

人工智能 数据挖掘 大数据 科技

快速理解二十三种设计模式(速记)

simon

设计模式 23种设计模式 Java设计模式

Mybatis【2.1】-- 从读取流到创建SqlSession发生了什么?

秦怀杂货店

数据库 mybatis SQLSession

甲方日常 51

大橘子

工作 随笔杂谈 日常

你以为只是简单的排序?(二)

书旅

go 数据结构与算法

权威报告发布:京东智联云首次参评即跻身机器学习卓越表现者阵营

京东智联云开发者

人工智能 云计算 供应链

Mybatis【2】-- 多个mapper文件以及namespace作用

秦怀杂货店

mybatis Mapper namespace

一次“诡异”的JVM缓存加载问题排查

AI乔治

Java 缓存 架构 JVM

记一次 Java 服务性能优化

AI乔治

Java 架构 性能优化 高性能

serialVersionUID作用是什么以及如何生成的?

秦怀杂货店

Java 序列化 serialVersionUID 反序列化

下笔如有神:这是一个基于营销行业的 AI 技术实践

京东智联云开发者

人工智能 自然语言处理 nlp

踩了一个java命令行参数顺序的坑

AI乔治

Java 架构 stream

Mybatis【1】-- 第一个Mybatis程序

秦怀杂货店

mybatis 入门 教程

五面进军饿了么!复盘总结11月上半月大厂面试真题,押题命中率高达95%以上

Java架构追梦

Java 阿里巴巴 架构 面试经历 面试题总结

影响王兴的一本书

池建强

读书笔记 无限游戏 王兴

JVM系列-java内存模型(JMM)

诸葛小猿

JMM Java内存模型 共享变量读写

排名前 16 的 Java 工具类

Bruce Duan

java工具类

python+requests对app和微信小程序进行接口测试

测试人生路

Python 接口测试

Scala语法特性(二):控制语句及函数方法

大规模数据处理学习者

Scala函数 Scala控制语句

你还在使用迭代器删除集合数据,out了,Java 中函数removeIf 不香么

Geek_6f0746

Java JAVA集合 Java迭代器

CAL PB级的日志存储迁移到Ceph的实践-InfoQ