本文将从以下五部分切入,讲述日志系统的演进之路:携程日志的背景和现状、如何搭建一套日志系统、从 ElasticSearch 到 Clickhouse 存储演进、日志3.0重构及未来计划。
一、日志背景及现状
图1
2012年以前,携程的各个部门日志自行收集治理(如图1)。这样的方式缺乏统一标准,不便治理管控,也更加消耗人力和物力。
从2012年开始,携程技术中心推出基于 ElasticSearch 的日志系统,统一了日志的接入、ETL、存储和查询标准。随着业务量的增长,数据量膨胀到 4PB 级别,给原来的 ElasticSearch 存储方案带来不少挑战,如 OOM、数据延迟及负载不均等。此外,随着集群规模的扩大,成本问题日趋敏感,如何节省成本也成为一个新的难题。
2020年初,我们提出用 Clickhouse 作为主要存储引擎来替换 ElasticSearch 的方案,该方案极大地解决了 ElasticSearch 集群遇到的性能问题,并且将成本节省为原来的48%。2021年底,日志平台已经累积了20+PB 的数据量,集群也达到了数十个规模(如图2)。
2022年开始,我们提出日志统一战略,将公司的 CLOG 及 UBT 业务统一到这套日志系统,预期数据规模将达到 30+PB。同时,经过两年多的大规模应用,日志集群累积了各种各样的运维难题,如集群数量激增、数据迁移不便及表变更异常等。因此,日志3.0应运而生。该方案落地了类分库分表设计、Clickhouse on Kubernetes、统一查询治理层等,聚焦解决了架构和运维上的难题,并实现了携程 CLOG 与 ESLOG 日志平台统一。
图2
二、如何搭建日志系统
2.1 架构图
从架构图来看(如图3),整个日志系统可以分为:数据接入、数据 ETL、数据存储、数据查询展示、元数据管理系统和集群管理系统。
图3
2.2 数据接入
数据接入主要有两种方式:
第一种是使用公司框架 TripLog 接入到消息中间件 Kafka(Hermes协议)(如图4)。
图4
第二种是用户使用 Filebeat/Logagent/Logstash 或者写程序自行上报数据到 Kafka(如图5),再通过 GoHangout 写入到存储引擎中。
图5
2.3 数据传输ETL(GoHangout)
GoHangout 是仿照 Logstash 做的一个开源应用(Github链接),用于把数据从 Kafka 消费并进行 ETL,最终输出到不同的存储介质(Clickhouse、ElasticSearch)。其中数据处理 Filter 模块包含了常见的 Json 处理、Grok 正则匹配和时间转换等一系列的数据清理功能(如图6)。GoHangout 会将数据 Message 字段中的 num 数据用正则匹配的方式提取成单独字段。
图6
2.4 ElasticSearch 数据存储
早期2012年第一版,我们使用 ElasticSearch 作为存储引擎。ElasticSearch 存储主要由 Master Node、Coordinator Node、Data Node 组成(如图7)。Master 节点主要负责创建或删除索引,跟踪哪些节点是集群的一部分,并决定哪些分片分配给相关的节点;Coordinator 节点主要用于处理请求,负责路由请求到正确的节点,如创建索引的请求需要路由到 Master 节点;Data 节点主要用于存储大量的索引数据,并进行增删改查,一般对机器的配置要求比较高。
图7
2.5 数据展示
数据展示方面我们使用了 Elastic Stack 家族的 Kibana(如图8)。Kibana 是一款适合于 ElasticSearch 的数据可视化和管理工具,提供实时的直方图、线形图、饼状图和表格等,极大地方便日志数据的展示。
图8
2.6 表元数据管理平台
表元数据管理平台是用户接入日志系统的入口,我们将每个 Index/ Table 都定义为一个Scenario(如图9)。我们通过平台配置并管理 Scenario 的一些基础信息,如:TTL、归属、权限、ETL 规则和监控日志等。
图9
三、 从Elasticsearch到Clickhouse
我们将会从背景、Clickhouse 简介、ElasticSearch 对比和解决方案四方面介绍日志从 ElasticSearch 到 Clickhouse 的演进过程。2020年初,随着业务量的增长,给 ElasticSearch 集群带来了不少难题,主要体现在稳定性、性能和成本三个方面。
(1)稳定性上:
- ElasticSearch 集群负载高,导致较多的请求 Reject、写入延迟和慢查询。
- 每天 200TB 的数据从热节点搬迁到冷节点,也有不少的性能损耗。
- 节点间负载不均衡,部分节点单负载过高,影响集群稳定性。
- 大查询导致 ElasticSearch 节点 OOM。
(2)性能上:
- ElasticSearch的吞吐量也达到瓶颈。
- 查询速度受到整体集群的负载影响。
(3)成本上:
- 倒排索引导致数据压缩率不高。
- 大文本场景性价比低,无法保存长时间数据。
3.1 Clickhouse 简介与 Elasticsearch 对比
Clickhouse 是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。Yandex 在2016年开源,使用 C++ 语法开发,是一款PB级别的交互式分析数据库。包含了以下主要特效:列式存储、Vector、Code Generation、分布式、DBMS、实时OLAP、高压缩率、高吞吐、丰富的分析函数和 Shared Nothin g架构等。
图10
Clickhouse采用的是 SQL 的交互方式,非常方便上手。接下来,我们将简单介绍一下 Clickhouse 的类 LSM、排序键、分区键特效,了解 Clickhouse 的主要原理。
首先,用户每批写入的数据会根据其排序键进行排序,并写入一个新的文件夹(如201905_1_1_0),我们称为 Part C0(如图10)。随后,Clickhouse 会定期在后台将这些 Part 通过归并排序的方式进行合并排序,使得最终数据生成一个个数据顺序且空间占用较大的 Part。这样的方式从磁盘读写层面上看,能充分地把原先磁盘的随机读写巧妙地转化为顺序读写,大大提升系统的吞吐量和查询效率,同时列式存储+顺序数据的存储方式也为数据压缩率提供了便利。201905_1_1_0与201905_3_3_0合并为201905_1_3_1就是一个经典的例子。
另外,Clickhouse 会根据分区键(如按月分区)对数据进行按月分区。05、06月的数据被分为了不同的文件夹,方便快速索引和管理数据。
图11
我们看中了 Clickhouse 的列式存储、向量化、高压缩率和高吞吐等特效(如图11),很好地满足了我们当下日志集群对性能稳定性和成本的诉求。于是,我们决定用Clickhouse来替代原本 ElasticSearch 存储引擎的位置。
3.2 解决方案
有了存储引擎后,我们需要实现对用户无感知的存储迁移。这主要涉及了以下的工作内容(如图12):自动化建表、GoHangout 修改、Clickhouse 架构设计部署和 Kibana 改造。
图12
(1)库表设计
图13
我们对ck在日志场景落地做了很多细节的优化(如图13),主要体现在库表设计:
- 我们采用双 list 的方式来存储动态变化的 tags(当然最新的版本22.8,也可以用map和新特性的 json 方式)。
- 按天分区和时间排序,用于快速定位日志数据。
- Tokenbf_v1 布隆过滤用于优化 term 查询、模糊查询。
- _log_increment_id 全局唯一递增 id,用于滚动翻页和明细数据定位。
- ZSTD 的数据压缩方式,节省了40%以上的存储成本。
(2)Clickhouse 存储设计
Clickhouse 集群主要由查询集群、多个数据集群和 Zookeeper 集群组成(如图14)。查询集群由相互独立的节点组成,节点不存储数据是无状态的。数据集群则由Shard组成,每个 Shard 又涵盖了多个副本 Replica。副本之间是主主的关系(不同于常见的主从关系),两个副本都可以用于数据写入,互相同步数据。而副本之间的元数据一致性则有 Zookeeper 集群负责管理。
图14
(3)数据展示
为了实现用户无感知的存储切换,我们专门实现了 Kibana 对 Clickhouse 数据源的适配并开发了不同的数据 panel(如图15),包括:chhistogram、chhits、chpercentiles、chranges、chstats、chtable、chterms 和 chuniq。通过 Dashboard 脚本批量生产替代的方式,我们快速地实现了原先 ElasticSearch 的 Dashboard 的迁移,其自动化程度达到95%。同时,我们也支持了使用 Grafana 的方式直接配置 SQL 来生成日志看板。
图15
(4)集群管理平台
为了更好地管理 Clickhouse 集群,我们也做了一整套界面化的 Clickhouse 运维管理平台。该平台覆盖了日常的 shard 管理、节点生成、绑定/解绑、权重修改、DDL 管理和监控告警等治理工具(如图16)。
图16
3.3 成果
- 迁移过程自动化程度超过95%,基本实现对用户透明。
- 存储空间节约50+%(如图17),用原有ElasticSearch的服务器支撑了4倍业务量的增长。
- 查询速度比ElasticSearch快4~30倍,查询P90小于300ms,P99小于1.5s。
图17
四、 日志3.0构建
时间来到2022年,公司日志规模再进一步增加到 20+PB。同时,我们提出日志统一战略,将公司的 CLOG 及 UBT 业务统一到这套日志系统,预期数据规模将达到 30+PB。另外,经过两年多的大规模应用,日志系统也面临了各种各样的运维难题。
(1) 性能与功能痛点
- 单集群规模太大,Zookeeper 性能达到瓶颈,导致 DDL 超时异常。
- 当表数据规模较大时,删除字段,容易超时导致元数据不一致。
- 用户索引设置不佳导致查询慢时,重建排序键需要删除历史数据,重新建表。
- 查询层缺少限流、防呆和自动优化等功能,导致查询不稳定。
(2) 运维痛点
- 表与集群严格绑定,集群磁盘满后,只能通过双写迁移。
- 集群搭建依赖 Ansible,部署周期长(数小时)。
- Clickhouse 版本与社区版本脱节,目前集群的部署模式不便版本更新。
面对这样的难题,我们在2022年推出了日志3.0改造,落地了集群 Clickhouse on Kubernetes、类分库分表设计和统一查询治理层等方案,聚焦解决了架构和运维上的难题。最终,实现了统一携程 CLOG 与 ESLOG 两套日志系统。
4.1 ck on k8s
我们使用 Statefulset、反亲和、Configmap 等技术实现了 Clickhouse 和 Zookeeper 集群的 Kubernetes 化部署,使得单集群交付时间从2天优化到5分钟。同时,我们统一了部署架构,将海内外多环境部署流程标准化。这种方式显著地降低了运维成本并释放人力。更便利的部署方式有益于单个大集群的切割,我们将大集群划分为多个小集群,解决了单集群规模过大导致 Zookeeper 性能瓶颈的问题。
4.2 类分库分表设计
图18
(1)数据跨如何跨集群
假设我们有三个数据集群1、2、3和三个表A、B、C(如图18)。在改造之前,我们单张表(如A)只能坐落在一个数据集群1中。这样的设计方式,导致了当集群1磁盘满了之后,我们没有办法快速地将表A数据搬迁到磁盘相对空闲的集群2中。我们只能用双写的方式将表A同时写入到集群1和集群2中,等到集群2的数据经过了TTL时间(如7天)后,才能将表A从数据集群1中删除。这样,对我们的集群运维管理带来了极大的不方便和慢响应,非常耗费人力。
于是,我们设计一套类分库分表的架构,来实现表A在多个集群1、2、3之间来回穿梭。我们可以看到右边改造后,表A以时间节点作为分库分表的切换点(这个时间可以是精确到秒,为了好理解,我们这里以月来举例)。我们将6月份的数据写入到集群1、7月写到集群2、8月写到集群3。当查询语句命中6月份数据时,我们只查询集群1的数据;当查询语句命中7月和8月的数据,我们就同时查询集群2和集群3的数据。
我们通过建立不同分布式表的方式实现了这个能力(如:分布式表tableA_06/tableA_07/tableA_08/tableA_0708,分布式表上的逻辑集群则是是集群1、2、3的组合)。这样,我们便解决了表跨集群的问题,不同集群间的磁盘使用率也会趋于平衡。
(2)如何修改排序键不删除历史数据
非常巧妙的是,这种方式不仅能解决磁盘问题。Clickhouse 分布式表的设计只关心列的名称,并不关心本地数据表的排序键设置。基于这种特性,我们设计表A在集群2和集群3使用不一样的排序键。这样的方式也能够有效解决初期表A在集群2排序键设计不合理的问题。我们通过在集群3上重新建立正确的排序键,让其对新数据生效。同时,表A也保留了旧的7月份数据。旧数据会在时间的推移一下被TTL清除,最终数据都使用了正确的排序键。
(3)如何解决删除大表字段导致元数据不一致
更美妙的是,Clickhouse 的分布式表设计并不要求表A在7月和8月的元数据字段完全一致,只需要有公共部分就可以满足要求。比如表A有在7月有11个字段,8月份想要删除一个弃用的字段,那么只需在集群3上建10个字段的本地表A,而分布式表 tableA_0708 配置两个表共同拥有的10个字段即可(这样查分布式表只要不查被删除的字段就不会报错)。通过这种方式,我们也巧妙地解决了在数据规模特别大的情况下(单表百TB),删除字段导致常见的元数据不一致问题。
(4)集群升级
同时,这种多版本集群的方式,也能方便地实现集群升级迭代,如直接新建一个集群4来存储所有的09月的表数据。集群4可以是社区最新版本,通过这种迭代的方式逐步实现全部集群的升级。
4.3 元数据管理
为了实现上述的功能,我们需要维护好一套完整的元数据信息,来管理表的创建、写入和 DDL(如图19)。该元数据包含每个表的版本定义、每个版本数据的数据归属集群和时间范围等。
图19
4.4 统一查询治理层
(1)Antlr4 的 SQL 解析
在查询层,我们基于 Antlr4 技术,将用户的查询 SQL 解析成 AST 树。通过 AST 树,我们能够快速地获得 SQL 的表名、过滤条件、聚合维度等(如图20)。我们拿到这些信息后,能够非常方便地对 SQL 实时针对性的策略,如:数据统计、优化改写和治理限流等。
图20
(2)查询代理层
图21
我们对所有用户的SQL查询做了一层统一的查询网关代理(如图21)。该程序会根据元数据信息和策略对用户的 SQL 进行改写,实现了精准路由和性能优化等功能。同时,该程序会记录每次查询的明细上下文,用于对集群的查询做统一化治理,如:QPS 限制、大表扫描限制和时间限制等拒绝策略,来提高系统的稳定性。
五、未来计划
通过日志3.0的构建,我们重构了日志系统的整体架构,实现集群 Kubernetes 化管理,并成功地解决了历史遗留的 DDL 异常、数据跨集群读写、索引重构优、磁盘治理和集群升级等运维难题。2022年,日志系统成果地支撑了公司 CLOG 与 UBT 业务的数据接入,集群数据规模达到了30+PB。
当然,携程的日志系统演进也不会到此为止,我们的系统在功能、性能和治理多方面还有不少改善的空间。在未来,我们将进一步完善日志统一查询治理层,精细化地管理集群查询与负载;推出日志预聚合功能,对大数据量的查询场景做加速,并支持 AI智能告警;充分地运用云上能力,实现弹性混合云,低成本支撑节假日高峰;推到日志产品在携程系各个公司的使用覆盖等。让我们一起期待下一次的日志升级。