关于后端:ClickHouse在酷家乐日志监控系统中的实践

41次阅读

共计 7784 个字符,预计需要花费 20 分钟才能阅读完成。

日志零碎的背景

  • 日志是线上定位问题排障的重要工具之一,对于可观测畛域而言是不可或缺的。在日志零碎中,稳定性、老本、易用性以及可扩展性都极为重要。
  • 同时 ELK 体系是业界最罕用的日志技术栈之一,它采纳 JSON 格局作为传输方式,易于多种语言实现和解析,并反对动静结构化字段。ElasticSearch 作为存储引擎反对全文检索,能够从大量的日志信息中疾速搜寻到关键字。Kibana 则提供好看易用的数据展现。
  • 基于以上两点,应用 ELK 搭建日志零碎是十分惯例的技术选型。酷家乐自 2015 年搭建基于 ES 的 ELK 日志零碎,曾经应用了 7 年多,集群规模为几十台物理机(规格 50 核 256GB),每天记录超过数百亿条日志。
  • 随着业务零碎的高速倒退,咱们的日志零碎规模也在疾速扩大。然而,咱们也遇到了一些问题。为了解决这些问题,咱们不得不向下一代日志零碎迈进。

2020 解决 ES 写入不均等问题

遇到的痛点

  • 索引治理压力:晚期,咱们依据利用 cmdb 生成 索引名称,每个利用独占一个索引。大概 500 个索引对于 ES master 节点有较大的治理压力,在机器关机故障或机器搬迁后数据恢复都极为漫长。
  • 数据写入不平衡:因为新建索引并不知每个索引最终会多大,因而无奈确定 shard 数量。索引 shard 压力调配不均衡,导致写入 QPS 不能最大化,并且导致磁盘占用不平衡。

规模:监控日志 ES 集群采纳 k8s 宿主机独占模式部署。规模在 20 台宿主机左右,每台宿主机共 2 个节点,1 个 hot、1 个 warm。其中 hot 节点承当数据写入和热数据查问,装备 SSD 磁盘。warm 节点存储读写不频繁的冷数据,装备高容量 HDD 磁盘。

解决办法

合并索引:ES 官网举荐,一个 shard 大小在 30GB 左右,依据 ES 集群节点数,能够算出一个索引的最佳容量 600GB。于是将多个利用日志写入一个索引,最终索引数量缩小到约 13 个;

定时创立:每晚 8 点,取得前一天各个服务对应的 doc 数量,并计算出 doc 均匀大小,从而推断出第二日须要的索引个数,并在 hot 节点上立刻创立:

别名映射:采纳背包算法,通过读 / 写别名的形式尽可能将服务按前一日大小平均调配到新创建的索引上;

 

# 创立读写别名
def create_aliases(aliases, yesterday, tomorrow):
    for item in aliases.items():
        actions = []
        for alias in item[1]:
            actions.append({
                "add": {"index": item[0],
                    "alias": alias.replace(yesterday, tomorrow),
                    "filter": {
                        "term": {"aliasName": alias.replace(yesterday, tomorrow)
                        }
                    }
                }
            })
        payload = {"actions": actions}
        print(payload)
        r = requests.post('http://%s/_aliases' % ES_ADDR, json=payload)
        print('%d alias are created, return code %d' % (len(actions), r.status_code))

定时归档:每晚 9 点,将前一日在 hot 节点的索引迁徙到 warm 节点,减小 hot 节点空间占用,并删除 warm 节点指定工夫之前的数据,以革除不再读取的历史数据;

# 抉择 warm 节点进行数据归档
"index.routing.allocation.require.temperature": "warm",
"index.routing.allocation.total_shards_per_node": 4

解决后成果

通过别名治理多个利用到一个索引的读写关系:

下午高峰期单节点根本稳固在 QPS=36K 的写入:

随着业务倒退遇到的问题

  • 写入提早:日志的提早会对排障产生极大负面影响。作为一种利用产生的实时数据,随着业务利用规模倒退而紧跟着扩充,日志零碎必须在具备高吞吐量的同时,也要具备较高的实时性要求。Elasticsearch 因为分词等个性,写入受限时,会产生断路器 429 谬误,通过多轮调优后,面对这样的异样,除了扩容没有无效伎俩能解决。
  • 存储老本:因为 ES 压缩率不高,长时间存储日志,也会导致磁盘压力。因为这些因素,咱们不得不缩小日志的存储时长,这些因素也限度了排障的场景。

基于以上问题,同时思考到老本因素,观测团队着手调研新的存储计划。ClickHouse 从 18 年后在国内失去广泛应用,其强悍的写入性能失去统一好评,而日志的应用场景恰好是读多写少,因而 ClickHouse 是咱们的重点调研对象。

clickhouse 

ClickHouse 是一个用于 OLAP 的列式数据库管理系统,要害特色:

  • 大多数是读申请、数据总是以相当大的批 (> 1000 rows) 进行写入;
  • 不批改已增加的数据、每次查问都从数据库中读取大量的行,然而同时又仅须要大量的列;
  • 宽表,即每个表蕴含着大量的列;
  • 较少的查问(通常每台服务器每秒数百个查问或更少);
  • 对于简略查问,容许提早大概 50 毫秒、列中的数据绝对较小:数字和短字符串(例如,每个 URL 60 个字节)、解决单个查问时须要高吞吐量(每个服务器每秒高达数十亿行);
  • 事务不是必须的、对数据一致性要求低、每一个查问除了一个大表外都很小、查问后果显著小于源数据(数据被过滤或聚合后可能被盛放在单台服务器的内存中)。

ClickHouse 劣势

  • 适宜在线查问:意味着在没有对数据做任何预处理的状况下以极低的提早解决查问并将后果加载到用户的页面中。
  • 反对近似计算:ClickHouse 提供各种各样在容许就义数据精度的状况下对查问进行减速的办法:

    1. 用于近似计算的各类聚合函数,如:distinct、quantiles;
    2. 基于数据的局部样本进行近似查问时,仅会从磁盘检索少部分比例的数据;
    3. 不应用全副的聚合条件,通过随机抉择无限个数据聚合条件进行聚合。这在数据聚合条件满足某些散布条件下,在提供相当精确的聚合后果的同时升高了计算资源的应用。
  • 反对数据复制和数据完整性:ClickHouse 应用异步的多主复制技术。当数据被写入任何一个可用正本后,零碎会在后盾将数据分发给其余正本,以保证系统在不同正本上放弃雷同的数据。在大多数状况下 ClickHouse 能在故障后主动复原,在一些多数的简单状况下须要手动复原。

ClickHouse 毛病

  • 没有残缺的事务反对。
  • 短少高频率,低提早的批改或删除已存在数据的能力,仅能用于批量删除或批改数据。
  • 基于后面两点,clickhouse 利用于业务后盾存储是较为艰难的。但在监控或大数据场景就十分适合。

Druid

Druid 具备欠缺的生态,提供了很多十分不便的数据摄入性能,治理 UI 也比拟全,在监控零碎中,咱们保护了数千核规模的 Druid 集群;

  • 但它的组件形成非常复杂:这也是咱们保护的痛点,节点类型有 6 种(Overload, Coordinator, Middle Manager, Indexer, Broker 和 Historical);
  • 除了本身的节点,Druid 还依赖于 MySQL 存储元数据信息、Zookeeper 选举 Coordinator 和 Overlord、COS 备份历史数据。ClickHouse 的架构采纳了对等节点的设计,节点只有一种类型,没有主从节点。如果应用了正本性能,则依赖于 Zookeeper 保留数据段的同步进度;
  • 占用资源高:目前 hunter 数据存储 50% 采样,仍旧占用了 4000 多 GB 内存;
  • 因为是聚合存储,特地适宜存储点击流的数据。

ElasticSearch

ElasticSearch 是一个实时的分布式搜寻剖析引擎,它的底层构建在 Lucene 之上的。简略来说是通过扩大 Lucene 的搜寻能力,最大的劣势在于全文索引。

  • 横向扩展性:只须要减少一台服务器,做一点配置,启动一下 ES 过程就能够并入集群;
  • 分片机制提供更好的散布性:同一个索引分成多个分片(sharding),分而治之的形式来提供解决效率;
  • 高可用:提供复制(replica),一个分片能够设置多个复制分片,使得某台服务器宕机的状况下,集群仍旧能够照常运行;
  • 速度快,负载能力强,在面对海量数据时候,搜寻速度极快;
  • 各节点数据的一致性问题:其默认的机制是通过多播机制,同步元数据信息,然而在比拟忙碌的集群中,可能会因为网络的阻塞,或者节点解决能力达到饱和,导致各数据节点数据不统一——也就是所谓的脑裂问题,这样会使得集群处于不统一状态。目前并没有一个彻底的计划来解决这个问题,然而能够通过参数配置和节点角色配置来缓解这种状况。没有粗疏的权限治理,也就是说,没有像 mysql 那样的分各种用户,每个用户又有不同的权限。所以在操作上的限度须要本人开发一个系统化来实现;
  • ES 对机器性能一致性要求很高,容易呈现局部节点性能不佳导致的集群性能长尾问题。因为历史起因,监控组用的机器有很多是 3 年前的机器,CPU 内存 规格都不能保障完全一致,在组建 42(hot+warm)的集群时经常会因为个别节点性能稍低导致集群整体写入能力降落。特地是在流量高峰期时,当呈现节点节点负载过高时即会被熔断器剔除集群,进一步导致写入压力增大。

Clickhouse 在监控零碎中的日志存储计划

日志存储 ClickHouse 的灰度架构

采纳可灰度的形式,近半年内只对量极大的个别服务写 ClickHouse(写这篇文章时已全量写 ClickHouse)

  1. datahub 是一个可配置的元数据管理核心。提供一个接口,返回须要存 ClickHouse 的服务列表。该服务列表通过 toad 配置;
  2. flink 流计算依据服务列表,判断日志写入 ClickHouse,还是 ES;
  3. adhoc-apm 提供日志查问,依据 datahub 提供的服务列表,判断日志在 ClickHouse 还是 ES,查问对应的存储并返回后果。

CK 集群部署

clickhouse 部署架构

  1. chproxy 是 ck 查问代理,只有被对立查问层拜访;
  2. ck 是 clickhouse 实例节点,每个实例独享一台机器;
  3. ip 尾号单数为主本,复数为正本(理论没有主副辨别),图中 ch01 和 ch02 互为正本;
  4. 一对主副尽量用相邻的两个 ip;

左下角原图展现了 ch02 节点上的 三种表:

  1. 本地表下划线全小写;
  2. 合并表名称后缀为 \_merge, 用于代理一组 合乎指定表名前缀的本地表,次要不便运维,在必须创立新表时能同时查问旧表和新表的数据;
  3. 在 merge 表上套一层分布式表,分布式表名称后缀为 _all,查问申请会随机路由到 ck 任意节点,再由该节点发动到其它节点的申请;
  4. 分布式表和本地表的区别能够看 举荐:入门必读

所有实例基于 k8s 部署,划分独立的 namespace,每个 clickhouse 实例独享一台宿主机。

  • 数据存储:应用宿主机磁盘,有更高效的读写性能;
  • 节点标识:因为数据存储在宿主机磁盘,因而 pod 应用 deployment 无状态模式部署。同时 macros.xml 须要在宿主机创立,实例启动时挂载到 pod 配置文件夹中以标识以后节点身份。

局部 configmap 

在 k8s 上运行的实例(部分)

通过 tabix 查看表

数据摄入

Flink 惯例 ETL 解决,因为写分布式表或通过 chproxy 都会有微小的网络资源耗费,因而间接写入 CK 本地表;

CK 官网举荐的 sink:https://github.com/ivi-ru/flink-clickhouse-sink

基于此 Sink 做了几点改良:

  1. 写 ck 节点时如果产生网络异样以及节点响应超时,则会将该 ck 节点标记为不可写,在肯定工夫内不再向该节点发送申请,超过设定工夫后主动勾销标记;
  2. 应用 apache httpClient 代替 netty Async httpClient,防止堆外内存沉积的问题;同时为了避免 Sink 线程解体后卡死;
  3. INSERT 数据时,从 VALUES 改为 FORMAT 模式,解决字符串本义问题和防止结构简单的 map 字段(VALUE 模式:insert into table values (),(),()  ;  FORMAT 模式:反对应用压缩后的 JSON 批量提交数)
  4. 再可容忍 cpu 开销的条件下,应用 gzip 压缩后传输,缩小机器的网卡带宽占用;

建表语句

本地表:

-- show create table qunhe_log; 
CREATE TABLE monitor.qunhe_log
(`timestamp` DateTime64(3, 'Asia/Shanghai'),
    `hostGroup` String,
    `ip` String,
    `podname` String,
    `level` String,
    `cls` String,
    `behavior` String,
    `message` String,
    `id` String DEFAULT '',
    INDEX message_index message TYPE tokenbf_v1(30720, 3, 0) GRANULARITY 1
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/qunhelog', '{replica}')
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (level, hostGroup, podname, ip, timestamp)
SETTINGS storage_policy = 'hdd0_hdd1_only', index_granularity = 8192
  • 按天分区,开发独立定时工作删除过期分区,能够更明确的掌控数据从磁盘革除的工夫;
  • 排序键次要依照基数排序,hostGroup 在查问时是肯定会作为条件的。尽管 timestamp 也会作为条件,然而排在后面会显著影响数据筛选效率;
  • 调配 HDD 磁盘,SDD 磁盘留给指标类查问比拟多的表;
  • 增加 tokenbf_v1 分词索引,依据测试在过滤明确关键字是,能进步数据筛选效率 80% 以上;

merge 表:

CREATE TABLE monitor.qunhe_log_merge ON CLUSTER clicks_cluster AS monitor.qunhe_log ENGINE=Merge("monitor", '^qunhe_log($|(.*\d$))')
  • merge 表用于代理本地一组表,特地适宜创立新表,并对旧表重命名后,须要新旧表一起查问;
  • 这里 ‘^qunhe_log($|(.*\\d$))’ 匹配的表名比方: qunhe_log、qunhe_log_20230101、qunhe_log_20230102;

分布式表:

CREATE TABLE IF NOT EXISTS monitor.qunhe_log_all
    ON CLUSTER clicks_cluster
AS monitor.qunhe_log_merge
    ENGINE = Distributed(clicks_cluster, monitor, qunhe_log_merge)
  • 分布式表次要用于查问数据。家喻户晓大批量写入时必定不会抉择写分布式表,而是间接写各个节点本地表。

日志查问

对立查问层对查问封装后,基于此能够不便地在其它产品也做二次开发,包含日志查问的产品页面:
产品页次要分为三个模块:

  • 查问区;
  • 原文展示区;
  • 统计区;

检索性能:

统计性能:

数量统计:

多类型分组统计:

性能和监控

日志查问相干监控看板

最近一周日志量统计以及最近 1 小时各个服务日志量 TopN 排名:

查问效率监控

ClickHouse 监控

基于 clickhous_exporter 和宿主机 node_exporter 对 ck 的性能和资源监控

资源使用量

表动静监控

集群查问监控

日志存储革新实现后,日志存储的机器规模缩小一半以上。在写入方面,未呈现因为性能导致的写入提早。在查问方面,99% 的查问能在 2s 内返回,90% 的查问能在 1s 内返回,惯例查问显著快于 ES。当然了,某些服务日志量的确很大,比方网关这类日志,切实无奈通过索引提前筛选掉大多数数据就只能硬查了(加载到内存耗时较大)。如果增加过滤条件,命中分词索引也有极大的晋升;

总的来说,相比于 Elasticsearch 来说,ClickHouse 的运维更加简略和明确,尽管很多操作只能通过一步步命令操作实现,然而对于开发人员而言更易能掌控细节。次要有以下几个方面:

  1. 日志的接入和查问优化:接入很容易,适配表构造即可写入。配合 EXPLAIN 也很不便对查问优化;
  2. 日志生命周期治理:咱们独立开发了 ClickLive 实现对 日志分区的删除。相比 TTL 删除,定时删除具备工夫确定性。
  3. 接入监控体系:应用 clickhouse_exporter、node_exporter 裸露指标,通过 prometheus 接入 thanos 指标体系, 残缺实现了全拜访指标监控以及警报配置;

常见问题

zk 元信息失落,导致表进入 ReadOnly 模式


-- 查看表构造

SHOW CREATE TABLE monitor.qunhe_log


-- 重命名
RENAME TABLE monitor.qunhe_log TO monitor.qunhe_log_old ON CLUSTER clicks_cluster


-- 创立新表, 换一个 zk 门路
CREATE TABLE monitor.qunhe_log ON CLUSTER clicks_cluster . . .


-- 此时数据失常写入,上面开始数据恢复


-- 导入旧表分区到新表

-- 查问旧表分区,取得分区名称
SELECT table, partition  FROM system.parts  WHERE database = 'monitor' and table='qunhe_log'  GROUP BY (table,partition) ORDER BY (table,partition) ASC

-- 导入分区数据,分区一一导入。非组合分区格局:'20230112'
ALTER TABLE monitor.qunhe_log ON CLUSTER clicks_cluster ATTACH PARTITION (20230112,'2') FROM monitor.qunhe_log_old

-- 到此应该能解决问题

重启实例后,表进入 ReadOnly 模式

如果只是批改配置导致实例进入 ReadOnly 模式,大概率不须要做任何解决,会主动复原;

写入数据时报 Too many parts 异样

  1. 这个问题根本就是分区不合理导致的,个别查看下是不是应用了工夫戳分区和是不是有用其它字段做分区。咱们遇到的过将日志等级做分区,然而 flink 将日志等级解析异样,解析出了数千个值。
  2. 也有可能 写入批次中的数据条数太少,咱们日志写入场景中设置的 50k 条 / 批;

呈现大量损坏数据,进入 detach 目录

咱们最开始发现某一个节点呈现磁盘使用率异样,才发现大量数据进入 detach 目录。在双正本模式下,个别查问数据还是数据残缺的。

有可能是磁盘有损坏,个别换完磁盘能够解决。能够用 smartctl 工具检测一下:

  1. 磁盘短时间测试:sudo smartctl -t short  /dev/sdc1
  2. 查看测试进度:sudo smartctl -c /dev/sdc1
  3. 查看测试后果:sudo smartctl -l selftest /dev/sdc1

换新磁盘后,zk 会主动下发工作开始拉数据,此时个别带宽或磁盘 io 会跑慢,最好在业务流量低峰期做;

正文完
 0