业务快速发展过程中对于各种日志数据的查询分析需求会导致日志存储规模急剧增长,传统ELK和以ElasticSearch为核心存储的日志系统随之会面临成本、稳定性以及性能等诸多挑战,业界也看到越来越多的国内外公司将存储切换到ClickHouse,比如携程、快手、B站、Cloudflare和Uber等,从他们的分享中看到收益都很明显,我们的日志系统也开始尝试从ElasticSearch迁移到ClickHouse,并在过程中探索和积累了一套最大程度照顾原有用户使用习惯实现平滑切换的整体方案。
1 背景介绍
公司内流量最大的通用日志系统底层存储自从2020年由ElasticSearch逐步切换到Clickhouse之后在成本以及稳定性等方面均有显著提升。在今年国庆期间稳定支撑了超过5000亿条/天的日志量,而成本也仅仅是原先ElasticSearch方案的30%。
除了该日志系统外,公司内还存在很多日志系统,大多基于开源主流ELK方式。在规模越来越大后,成本跟稳定性方面的问题也就逐渐暴露了,所以计划将所有日志系统的底层存储全部切换成ClickHouse。
关于日志场景下Clickhouse的选型与用法业界已经有很多的公开分享,不作为本文重点,有兴趣可自行搜索下相关资料。
在存储切换完成之后最重要的是解决查询UI用户体验问题,也看到有公司在把日志存储切到Clickhouse后选择自研一套查询UI,但想要照顾用户原先所有使用习惯从原生Kibana无缝切换到新平台是不太实现的,需要所有业务同事熟悉一套新的语法跟UI交互,无形中给他们加了很大的成本。
所以,怎么能够让业务用户以零学习成本使用新平台,这是一个最棘手的问题。
2 方案介绍
我们的思路其实也比较寻常,我们选择在原生Kibana跟ElasticSearch之间新增一层Proxy,该Proxy来做ElasticSearch跟ClickHouse的语法转换,如图:
我们自研了一套Proxy(取名CKibana),该Proxy负责将图表请求转换成ClickHouse语法查询到ClickHouse结果后模拟成ElasticSearch响应返回给Kibana,这样就可以直接在原生Kibana上面展示ClickHouse中的数据,除了语法转换,我们还解决了很多实际使用过程中遇到的问题。
CKibana: 现已正式开源,https://github.com/TongchengOpenSource/ckibana
介于ClickHouse的查询并发能力限制,我们保留了ElasticSearch,该ElasticSearch可以用来做结果的缓存等高级特性以及保存Kibana相关的元数据,本身非常轻量。
3 CKibana使用
准备环境
- Kibana: 用来提供给业务做UI展示
- ElasticSearch: 用来做kibana元数据存储+查询缓存等高级特性
- ClickHouse: 真实存日志数据的存储
- CKibana: 提供Proxy等高级功能,实现让用户直接在原生Kibana上查询ClickHouse数据
开始使用
启动CKibana
配置ElasticSearch相关信息
启动,需要JDK17+
java -jar ckibana.jar
修改Kibana
修改Kibana配置,将ElasticSearch地址改为CKibana地址
此时,Kibana功能完全可用,可以将CKibana当成一个ElasticSearch Proxy
配置ClickHouse连接信息与索引白名单
设置ClickHouse连接信息:
curl --location --request POST 'localhost:8080/config/updateCk?url=ckUrl&user=default&pass=default&defaultCkDatabase=ops'
配置需要切换到ClickHouse的index
curl --location --request POST 'localhost:8080/config/updateWhiteIndexList?list=index1,index2'
ClickHouse字段跟ElasticSearch字段的关系
es类型 | ck类型 |
---|---|
keyword | String |
text | String |
ip | String(代理自动识别ipv4和ipv6) |
integer | Int32 |
long | Int64 |
float | Float32 |
double | Float64 |
创建index pattern
注意点:
- 首先确定输入的index pattern跟ClickHouse表是否一致,index pattern跟跟ClickHouse表名是精确匹配
- 如果不能晒选到对应的表格,可以基于CKibana日志中的sql排查,是否可查询到对应的表
- 注意时间字段,否则会筛选不了时间字段,筛选逻辑如下:
- 字段为Date类型,比如DateTime64类型,会被认为是时间类型
- 字段名中包含time,比如(@timestamp UInt64),会被认为是时间类型
这两种情况下满足任意一个,字段都会被认为是时间字段,如果选择不了时间字段,需要检查下ClickHouse表中字段是否符合匹配逻辑。
开始使用
配置完成index pattern后,就可以正常的使用Kibana的图表功能了
高级功能
采样
Kibana的图表大部分都是关注趋势,当命中结果集太大时候,消耗的ClickHouse的资源也较多。我们提供了采样功能,在数据集较大的时候,能够保障图表趋势跟实际的差不多,而且可以很好的控制ClickHouse的资源使用。
`- 注: 对应的ClickHouse的表需要按照ck的采样表创建 clickhouse sample
- 如果采样阈值设置的过小,会导致还原出来的值跟真实值差异较大,我们线上设置的采样值为500w`
开启采样需要两步:
- 配置需要采样的表
- 更新采样阈值,当命中结果集超过阈值时,会触发采样
采样逻辑 Math.max(0.01, Double.parseDouble(String.format("%.5f", sampleParam.getSampleCountMaxThreshold() * 1.00 / sampleParam.getSampleTotalCount())))
展开流量器的响应结果,可以看到采样值
时间round+缓存
当线上出现一个故障时候,有大量的sre同学跟业务同学都需要通过nginx日志来查询问题,而且查询的条件基本都相同,但是ClickHouse为了使得查询性能最好,而尽量的使用更多的cpu来参加计算,这样导致这些场景下ClickHouse的cpu会直接跑满。且在同学们的不断retry下,cpu不能恢复。
所以我们做了一个时间round+缓存功能。
时间round: 比如设置round为20s,则查询时间条件中的s的精度会%20
,相当于最多延迟了20s来查询数据。
有了时间round后,则大量的查询条件就一致了,这个时候只需要开启结果缓存,则就可以很好的缓解ClickHouse的压力。
设置时间round:
curl --location --request POST 'localhost:8080/config/updateRoundAbleMinPeriod?roundAbleMinPeriod=20000' 单位ms
设置打开缓存:
curl --location --request POST 'localhost:8080/config/updateUseCache?useCache=true'
是否命中缓存,在响应结构里面可以看到
查询监控 + 黑名单
Kibana的查询语法相对比较随意,一些查询对ClickHouse的资源消耗会比较大,所以我们将所有的查询以及耗时都做了监控,这样可以比较方便的查看都做了哪些查询,设置可以对查询做黑名单的控制,这样就可以限制不太友好的查询
开启监控:
curl --location --request POST 'localhost:8080/config/updateEnableMonitoring?enableMonitoring=true'
如上图,我们可以监控每个查询的详情,语法以及耗时。
如上图,可以基于kibana图表功能,做更直观的分析。
查询时间限制
很多时候,有人想要查看某个条件最近的趋势,直接查询最近7天等等,这样会导致资源消耗比较大,所以CKibana支持了最长时间查询范围,来限制使用。
curl --location --request POST 'localhost:8080/config/updateMaxTimeRange?maxTimeRange=864000000' 单位ms
keyword查询
为了更好的匹配ElasticSearch的使用习惯。 field.keyword
查询相当于对field的精确查询,否则为模糊搜索
比如 host.keyword:"www.baidu.com"
换成sql为: host="www.baidu.com"
Discover性能优化
ClickHouse的强项在AP场景,当查询时间跨度比较大时,传统的SQL:select x from table where x order by time desc limit 10
这种方式会导致查询性能非常低,而且消耗大量的ClickHouse资源。
针对这种带趋势图+明细的场景我们做了性能优化,充分发挥ClickHouse的AP能力。会将执行拆分为两步:
- 基于Ck 聚合能力,查询出每分钟满足条件的日志数量
- 基于每分钟的日志数量自动裁剪日志搜索的时间跨度,比如一分钟内的日志条数就满足了要求,则查询明细时自动缩减到1分钟的跨度。
基于查询时间的自动裁剪功能,会使得Discover版本查询性能有质的提升且对ClickHouse的cpu占用有很大的优化。
如上图,一个Discover查询拆成了3个sql:
- 计算是否需要采样
- 统计每分钟的日志数
- 自动裁剪查询时间范围
4 Nginx日志相关使用案例
Clickhouse表结构
CREATE
(
`@timestamp` UInt64,
`X-Request-Id` String,
`addr` String,
`ap_area` String,
`byte` Int64,
`bytes_recv` Int64,
`Bbtes_sent` Int64,
`content-type` String,
`content_length` Int64,
`crp` String,
`csi` String,
`cspanid` String,
`difftime` Int32,
`error_body` String,
`error_client` String,
`error_host` String,
`error_request` String,
`error_server` String,
`error_upstream` String,
`forwarded` String,
`host` String,
`hostname` String,
`idc` LowCardinality(String),
`index_name` LowCardinality(String),
`ip` String,
`logant_idc` LowCardinality(String),
`logant_type` LowCardinality(String),
`origin_ip` String,
`referer` String,
`remote_port` String,
`request_method` LowCardinality(String),
`request_time` Int64,
`request_uri` String,
`request_url` String,
`scheme` String,
`server_addr` String,
`server_name` String,
`server_port` String,
`server_protocol` String,
`source` String,
`sspanid` String,
`st` String,
`status` Int32,
`timeuse` Float64,
`traceid` String,
`type` String,
`ua` String,
`up_addr` String,
`up_status` Int32,
`upstream_name` String,
`upstream_response_time` Int32,
`worker_pid` String,
`ck_assembly_extension` String,
`bytes_sent` Int64,
INDEX timestamp_index `@timestamp` TYPE minmax GRANULARITY 8192
)
ENGINE = MergeTree
PARTITION BY (toYYYYMMDD(toDateTime(`@timestamp` / 1000, 'Asia/Shanghai')), toHour(toDateTime(`@timestamp` / 1000, 'Asia/Shanghai')))
ORDER BY (host, request_uri, intHash64(`@timestamp`))
SAMPLE BY intHash64(`@timestamp`)
SETTINGS in_memory_parts_enable_wal = 0, index_granularity = 8192
切记将host
放置在order by的第一位。因为查Nginx日志大多数情况下需要根据host
查询。
CKibana所有配置
json
{
"Proxy": {
"ck": {
"url": "ip:6321",
"user": "user",
"pass": "pass",
"defaultCkDatabase": "db"
},
"es": {
"host": "ip:31940"
},
"roundAbleMinPeriod": 120000,
"round": 20000,
"maxTimeRange": 86400000,
"blackIndexList": null,
"whiteIndexList": ["ops_bjtlblog_all", "other_index_all"],
"enableMonitoring": true
},
"query": {
"sampleIndexPatterns": ["ops_bjtlblog_all"],
"sampleCountMaxThreshold": 5000000,
"useCache": true,
"maxResultRow": 30000
},
"threadPool": {
"msearchProperty": {
"coreSize": 4,
"queueSize": 10000
},
"commonProperty": {
"coreSize": 4,
"queueSize": 10000
}
},
"defaultShard": 2
}
线上效果图
4 收益
截止到目前,借助CKibana的核心能力,我们已经完成了所有Nginx访问日志和业务自定义日志全量从ElasticSearch切换到Clickhouse,存储成本降为原来的30%以内。同时基于ClickHouse的分布式表能力可以做到即使在单中心故障时日志查询也不受任何影响,无论在成本还是稳定性方面相比ElasticSearch都有了较大的提升,并沿用了灵活强大的原生Kibana作为可视化工具,可以让用户继续使用自己习惯的Kibana面板便捷地进行日志查询和分析。
5 最后
在我们日志系统不断演进的过程中离不开众多优秀的开源项目,现在我们也将CKibana (ClickHouse Proxy for Kibana & Clickhouse visualization tool) 正式开源了,希望能够帮助到更多的人同时也期望能跟社区一起共建不断完善功能与特性,充分发挥日志场景下Kibana可视化+ClickHouse存储这对组合的威力,让大家用起来更加丝滑,欢迎Star和给我们提ISSUE。
项目Github地址: github.com/TongchengOp...