环境 Logback + Filebeat + Kafka + ClicksHoue + Metabase
apiVersion: v1
kind: ConfigMap
metadata:
name: 名称
# namespace: kube-system
labels:
k8s-app: 名称
data:
OUTPUT_KAFKA_HOSTS: kafka地址
filebeat.yml: |-
filebeat.config.inputs:
enabled: true
reload.enabled: true
reload.period: 10s
filebeat.inputs:
- type: log
paths:
- /文件地址
output.kafka:
enabled: true
# initial brokers for reading cluster metadata
hosts: '${OUTPUT_KAFKA_HOSTS}'
topics:
- topic: "topic1"
when.contains:
message: "\"标识字段\":\"标识字段内容1\""
- topic: "topic2"
when.contains:
message: "\"标识字段\":\"标识字段内容2\""
partition.round_robin:
reachable_only: false
version: '2.6.0'
required_acks: 1
compression: gzip
# max_message_bytes: 1000000
codec.format:
string: '%{[message]}'
建表sql 就是官网扒下来的也就不贴了。
select * from system.errors
Cannot parse input: expected '{' before: '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
\0x00是不可见字符,cat命令是看不到的。
用的vi看到的。
grep -Pa ‘\x00\x00\x00\x00\x00’ /日志目录 -nr 发现没有,初步定位问题为 Filebeat 导致的。
但是自己重现不了。
考虑到 filebeat的input 目前是按文本字符串来处理日志的,改为 json形式获取
修改后
apiVersion: v1
kind: ConfigMap
metadata:
name: 名字
# namespace: kube-system
labels:
k8s-app: 名字
data:
OUTPUT_KAFKA_HOSTS: kafka地址
filebeat.yml: |-
filebeat.config.inputs:
enabled: true
reload.enabled: true
reload.period: 10s
filebeat.inputs:
- type: log
paths:
- /日志目录
json.keys_under_root: true
json.overwrite_keys: true
# json.message_key: message
json.add_error_key: true
json.ignore_decoding_error: true
output.kafka:
enabled: true
# initial brokers for reading cluster metadata
hosts: '${OUTPUT_KAFKA_HOSTS}'
topics:
- topic: "topic1"
when.equals:
标识字段: "标识字段内容1"
- topic: "topic2"
when.equals:
标识字段: "标识字段内容2"
partition.round_robin:
reachable_only: false
version: '2.6.0'
required_acks: 1
compression: gzip
解决方案:改sql ,放弃 select * ,改为自定义
CREATE TABLE 库.XXX_queue ON CLUSTER `集群名`
(
`eventStartTime` UInt64,
其他字段
)
ENGINE = Kafka 其他kafka配置。
CREATE MATERIALIZED VIEW 库.XXX_mv ON CLUSTER `集群名` TO 库.XXX AS
select
`ts` as `eventStartTimeTs`,
`dtts` as `eventStartTime`,
其他字段
from (
SELECT *,eventStartTime as ts,fromUnixTimestamp64Milli(toInt64(`eventStartTime`)) as dtts
FROM 库.XXX_queue
) tmp_source;
clickhouse 涉及 命令,(最终发现所有clickhouse的时区都是 Asia/Shanghai)
查看 时区相关设置
select * from system.settings where name like '%zone%'
查看时区
SELECT timezone()
查看配置文件中的时区,默认是注释掉的
cat /etc/clickhouse-server/config.xml
查看服务器时区
date +%z
修改metabse使用的链接参数
use_client_time_zone=1 #使用客户端时区, 未生效
se_time_zone=UTC+8&use_server_time_zone=UTC+8 #使用服务端时区, 同样未生效
Metabase 涉及命令 (同样Metabase的时区也都是 Asia/Shanghai)
查看服务器时区
date +%z
查看 env是否成功配置
env
metabase sql 执行窗口 来验证时区是否生效。
select now(),toString(now()),timezone()
以及直接打开表来验证 datetime 类型的是否显示正确。
当使用 0.7.5 版本时可以正常显示
当使用 0.8.1 版本时不可以正常显示
# clickhouse docker-compose.yml
version: "3"
services:
server:
image: yandex/clickhouse-server
ports:
- "8123:8123"
- "9000:9000"
- "9009:9009"
volumes:
- D:\docker\clickhouse\tmp:/tmp
- D:\docker\clickhouse\data/:/var/lib/clickhouse/data
environment:
TZ: Asia/Shanghai
ulimits:
nproc: 65535
nofile:
soft: 262144
hard: 262144
client:
image: yandex/clickhouse-client
command: ['--host', 'server']
# metabase docker-compose.yml
version: "3"
services:
metabase:
# image: metabase/metabase:v0.43.0-rc1
# image: metabase/metabase:v0.42.4
image: metabase/metabase:v0.41.3.1
entrypoint: [ "bash", "-c", "apk add --no-cache tzdata && wget -P /plugins https://github.com/enqueue/metabase-clickhouse-driver/releases/download/0.7.5/clickhouse.metabase-driver.jar && /app/run_metabase.sh"]
volumes:
# - D:\docker\metabase\plugins:/plugins
- C:\Windows\System32\drivers\etc\hosts:/etc/hosts
ports:
- "3000:3000"
environment:
TZ: Asia/Shanghai
JAVA_TIMEZONE: Asia/Shanghai
MB_DB_TYPE: mysql
MB_DB_DBNAME: metabase
MB_DB_PORT: 3306
MB_DB_USER: root
MB_DB_PASS: 123456
MB_DB_HOST: host.docker.internal
最终解决方案:报错始终没解决,直接把数据备份到一个新表,把表删除了重新建,然后再把数据 insert 回来。 参考 : https://clickhouse.com/docs/en/sql-reference/statements/insert-into/#inserting-the-results-of-select。
下面是排查走过的路。
Clickhouse Cannot execute replicated DDL query, maximum retires exceeded
直译 : 不能执行分布式DDL语句 , 超出最大限度的retires 。
参考: clickhouse 常见问题处理(持续更新中) 、ClickHouse 在商业中台的实践和应用
直接现象,不能加字段了报错了,
所用SQL
ALTER TABLE 库.表 ON CLUSTER `集群名称` ADD COLUMN `字段名1` 类型, ADD COLUMN `字段名2` 类型;
返回:Cannot execute replicated DDL query, maximum retires exceeded
排查方案,根据 clickhouse 常见问题处理(持续更新中 中提示找到ZK的路径,先找clickhouse 针对 ZK的配置 。
命令行:/$ cd /etc/clickhouse-server
命令行:/etc/clickhouse-server$ ls
conf.d config.d config.xml users.d users.xml
命令行:/etc/clickhouse-server$ cd config.d/
命令行:/etc/clickhouse-server/config.d$ ls
01-clickhouse-01-listen.xml 01-clickhouse-02-logger.xml 01-clickhouse-03-query_log.xml 01-clickhouse-04-part_log.xml chop-generated-remote_servers.xml
命令行:/etc/clickhouse-server/config.d$ cd ../conf.d
命令行:/etc/clickhouse-server/conf.d$ ls
chop-generated-macros.xml chop-generated-zookeeper.xml
命令行:/etc/clickhouse-server/conf.d$ cat chop-generated-zookeeper.xml
zookeeper-0.zookeepers.zoons
2181
zookeeper-1.zookeepers.zoons
2181
zookeeper-2.zookeepers.zoons
2181
30000
10000
/clickhouse/XXX/task_queue/ddl
然后 执行sql看了下ZK 中存的啥, 果然是一堆没执行成功的SQL。
select * from system.zookeeper where path ='/clickhouse/XXX/task_queue/ddl' order by name asc
也可以看 历史的DDL 都执行了啥 参考 DDL task queue is stuck
select * from system.distributed_ddl_queue order by entry asc
尝试自动清理 参考配置ClickHouse分布式DDL记录自动清理。有自动清理但是是七天的过期时间。不知道这个是只存了错误的还是都存了。
尝试清理zk下的这些node。
找到报错的源码,原来是在集群中多次执行没有成功。
https://clickhouse.com/codebrowser/ClickHouse/src/Interpreters/DDLWorker.cpp.html#829
那就该去看看每台机器都报啥错了。 一直都是报这个 tmp 的文件夹已存在
2022.07.21 03:26:56.169474 [ 149 ] {} 库名.表名: auto DB::StorageReplicatedMergeTree::processQueueEntry(ReplicatedMergeTreeQueue::SelectedEntryPtr)::(anonymous class)::operator()(DB::StorageReplicatedMergeTree::LogE
ntryPtr &) const: Code: 84, e.displayText() = DB::Exception: Part in /var/lib/clickhouse/data/库名/表名/tmp_clone_202204_0_2_1_4 already exists
看了下是空的文件夹,自己去手动 rm -r 了也不行,还是会自动再新增,继续报这个错。
想自己到6台机器上执行下增加列跳过这把报错,结果也不行,但是给了我们提示,说副本中的metadata 和zk中的不一样
Code: 517. DB::Exception: Received from localhost:9000. DB::Exception: Metadata on replica is not up to date with common metadata in Zookeeper. Cannot alter.
项目里都是以json来打日志的,filebeat input 中也配置了 json.keys_under_root: true 等json.* 发现丢数据了,有些日志没收集到。
filebeat 处理网络存储有问题,本地不会,而我们用的是阿里的nas。
https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-network-volumes.html
- topic: "xxx-error"
when.has_fields: ['error']
查看 xxx-error 内容,发现是filebeat扫描到了日志但是解析错了
报错:
{\"message\":\"Error decoding JSON: invalid character '\\\\x00' looking for beginning of value\"
导致报错原因:filebeat 读取网络存储的时候读取了很多 NUL,示例日志:
在日志开头莫名其妙的加了很多个 \u0000 也就是 ASCII 的 null。
\\u0000*n {\\\"timestamp\\\":\\\"123456\\\""}}
这里说下这个NUL 在 unicode 中是 \u0000。 在 ASICC 0, 操作系统中是 \0。不同编程语言又不一样。不同编辑器展示的又不一样。vim 展示的是 ^@ 、 sublime 展示的是<0x00>
这个其实是Clickhouse报过这个错,但是当时linux命令排查了下发现日志文件里没有NUL,以为是filebeat传输给多加了NUL,当时直接改了filebeat改为读json了,但是没处理error的。(相当于做了个有bug就把bug提示干掉),这次终于定位准了,是filebeat 和网络存储配合不好导致的。
当时用到查NUL的命令:
grep -Pa '\x00\x00\x00\x00\x00' 目录 -nr
logging.level: debug
filebeat.config.inputs:
enabled: true
reload.enabled: true
reload.period: 10s
filebeat.inputs:
- type: log
paths:
- D:\logs\log-demo\XXX.log
processors:
# 去掉开头的NUL 并将内容放到 key1 中
- dissect:
tokenizer: "%{key1}"
# field: "message"
target_prefix: ""
trim_values: "all"
trim_chars: "\x00"
overwrite_keys: true
# 去掉开头的空格 key1 中
- dissect:
tokenizer: "%{key1}"
field: "key1"
target_prefix: ""
trim_values: "all"
trim_chars: " "
overwrite_keys: true
# 删掉message ,上边的已经处理后的 message 放到了 key1中
- drop_fields:
fields: ["message"]
# 将key1 由 string 转换成json 并将其中的元素放到 json的root下,也就是一级json。
# 其实不确定是这一步 string转的json还是 上一步去掉了空格自己就转了。
- decode_json_fields:
fields: ["key1"]
process_array: false
max_depth: 1
target: ""
overwrite_keys: true
add_error_key: true
# 删掉key1 减少
- drop_fields:
fields: ["key1"]
output.file:
path: "D:\\logs\\tmp"
filename: filebeat-target.log
参考:https://github.com/ClickHouse/ClickHouse/issues/1589
导致该原因操作 : 经过上一步问题五的修改filebeat出现了嵌套解析的问题。String中存json应该是个挺常见的场景,但是确实是没有考虑到,之道上线才报了解析异常。
错误配置 : max_depth 上一步设置为了2 , 应该设置为1的。
- decode_json_fields:
fields: ["key1"]
process_array: false
# 这里错了。
max_depth: 2
target: ""
overwrite_keys: true
add_error_key: true
原文件示例
{"timestamp":"123","AAA":"{\"alert\":[\"aaaaa\"]}"}
max_depth: 2 结果。 这时AAA为json对象
{"timestamp":"123","AAA":{"alert":["aaaaa"]}}
max_depth: 1 结果。 这时AAA为String 与原内容相同。
{"timestamp":"123","AAA":"{\"alert\":[\"aaaaa\"]}"}
CH中kafka engine 与 MV( MATERIALIZED VIEW )是 n <-> 1,kafka engine 并发写入MV 导致数据丢失。
kafka engine 将kafka_num_consumers 设为1 。
附一个clickhouse kafka 流程https://clickhouse.com/docs/en/integrations/kafka/kafka-table-engine/
将kafka中的数据写到文件用到命令,
--from-beginning : 是新建了一个消费者组,但是consumer 没权限建组,报错了,只能用下边的从每个分区取。
mycount=0; while (( $mycount < 3 )); do ./bin/kafka-console-consumer.sh --bootstrap-server kafka地址 --topic topic --offset 预估开始时间的offset --partition $mycount --max-messages 1000 >> /tmp/XXX.log;((mycount=$mycount+1)); done;
mycount=3; while (( $mycount < 36 )); do ./bin/kafka-console-consumer.sh --bootstrap-server kafka地址 --topic topic --offset 预估开始时间的offset --partition $mycount --max-messages 1000 >> /tmp/XXX.log;((mycount=$mycount+1)); done;
grep '关键字' /XXX.log | grep -E 条件1|条件2|条件n
select * from system.settings where name = 'insert_deduplicate';
3.2 换了思路先查了下 xxx_mv 里的数据,发现也少了。又查到一个MATERIALIZED VIEW always lost some data,继续改参数 parallel_view_processing=1 用新group消费,还是丢且随机丢。参考/知识扩展 Metabase数据库列值缓存的分析与改进
实际解决步骤:
场景 示例:执行下边sql后正常返回没报错,但是字段值没update。
ALTER TABLE `库名`.`表名` ON CLUSTER `default`
update `时间字段` = Nullable字段名
where `条件` and 条件;
原因:Nullable 的字段不能直接给 非Nullable字段设置值。
排查过程:
1、直接google sql查update为什么不生效。
找到:https://github.com/ClickHouse/ClickHouse/issues/17806 。查系统表的 MergeTree 后台异变表(不知道为啥叫异变表,可能是地翻译问题,理解为记录影响数据变化信息的表就好)。
select table, parts_to_do, is_done from system.mutations;
实际直接 select * 可以看到更多信息。
2、看到报错
Code: 349, e.displayText() = DB::Exception: Cannot convert NULL value to non-Nullable type: while executing 'FUNCTION CAST(Nullable字段名: 79, 'DateTime64(3, \'Asia/Shanghai\')' : 85) -> CAST(Nullable字段名, 'DateTime64(3, \'Asia/Shanghai\')') DateTime64(3, 'Asia/Shanghai') : 84' (version 21.8.2.1)
3、 直接kill掉 后台报错的的 statements 。
参考:官网 KILL Statements .
KILL MUTATION WHERE database = '库名' AND table = '表名' AND mutation_id = '0000000067'
4、直接拿一条看下字段的返回类型 以及各函数返回类型。
select `Nullable字段名`,coalesce(Nullable字段名),assumeNotNull(Nullable字段名),toDateTime64(toString(`Nullable字段名`),3,'Asia/Shanghai') ,toString(`Nullable字段名`) from `库`.`表` where `条件` and 条件;
5、最终 assumeNotNull 是生效的。
如何知道的assumeNotNull 函数
先google查CH 中Nullable怎么转回正常类型。 然后找到了官网 Nullable处理函数。翻了翻看到了 assumeNotNull
将可为空类型的值转换为非Nullable类型的值。