ClickHouse 部署与构建

ClickHouse 部署与构建
MeteorCat传统 MySQL/PostgreSQL 在单表动不动超过 100G 之后就会考虑用分库分表硬抗, 但是本身就是治标不治本的问题.
哪怕分表出来, 后台也是需要分页查询展示数据, 单表 100G 以上多表合并分页分页查询要CPU和内存占满都要几十秒
基于这种大规律数据日志的情况, 传统的 MySQL/PostgreSQL 已经很难负载这部分功能, 需要寻求其他数据库支持.
也是在这种情况下, 就需要了解到 列存数据库(Column-Oriented Storage) 和 行存数据库(Row-Oriented Storage) 的概念.
在此过程之中就了解到列数据库 - ClickHouse:
| 特性 | 行存数据库(MySQL/PostgreSQL) | 列存数据库(ClickHouse) |
|---|---|---|
| 存储方式 | 按行存储,一行的所有列数据连续存放 | 按列存储,一列的所有行数据连续存放 |
| 查询效率 | 适合整行读取(如事务、单条记录查询),但查询少量列时需加载整行数据,IO开销大 | 适合列投影查询(如统计、聚合、筛选少数列),仅加载需要的列,IO效率极高 |
| 压缩比 | 同列数据类型分散,压缩率低(通常1:3~1:5) | 同列数据类型一致,压缩率高(通常1:10~1:30),大幅减少存储占用 |
| 写入特性 | 单行/小批量写入高效(事务支持好) | 批量写入高效,单行写入性能差(部分列存不支持事务) |
| 索引适配 | B+树索引为主,适合点查 | 稀疏索引、位图索引为主,适合范围查询和聚合 |
这里就是比较明显的特性区分, 主要是要知道两者保存数据库的原理/优缺点和应用方向:
1 | #################################################################################################################### |
至此就区分出两种数据库类别:
-
OLTP(事务型操作): 数据需要完整可靠事务处理, 数据更新频繁,需要强一致性 -
OLAP(分析型操作): 海量数据分析和分页处理, 庞大的数据需要批量数据的查询、统计、分组
注意: 因为列数据库这种方式, 也就要求数据最好带有不可变性(删除或者修改会打乱内部的数据分块, 数据会被优化固定连续数据块)
如果修改/删除列数据库的数据, 会导致需要查询那部分数据重建关联的数据块(大规模修改成本极大), 而删除则是标记删除而非真正删除
所以基于这些特性, 可以划分两种数据库的区别:
| 数据库类型 | 适用场景 | 数据特性 | 核心诉求 |
|---|---|---|---|
| 行存(OLTP) | 事务型操作 | 可变:高频增删改 | 强一致性、低延迟 |
| 列存(OLAP) | 分析型操作 | 不可变:批量写入后极少修改 | 高吞吐、低IO、快速聚合 |
需要牢记这些应用场景, 否则乱用这类数据库会导致性能反而爆降.
ClickHouse 集群很难处理部署, 但是胜在单机性能已经足以升任大部分情况, 如果规模扩大之后就需要考虑购买云服务器厂商提供的方案
常规项目量级上去的比较合理的方案如下:
-
Kafka(解决 "入口削峰"): 做数据队列接收 API 用户日志上报 -
ClickHouse(解决 "海量日志分析"): 打点上报日志相关数据异步落地 -
MySQL|PostgreSQL(解决 "核心业务事务"): 核心后续可变业务异步落地
以上三者不是互斥关系, 而且需要按照数据类型和业务融入其中, 从而将海量数据汇总成为后端数据中心节点
部署搭建
如果没有专门的运维, 最好购买云服务厂商的 ClickHouse 服务(一般云服务器商都有带这类数据库服务)
这里需要最好查看官方文档部署手册并在本地启动服务.
官方安装手册: clickhouse
我这边采用官方 Debian/Ubuntu 发行版的来做部署, 具体流程按照官网输入命令:
1 | 参考地址: https://clickhouse.com/docs/zh/install/debian_ubuntu |
这就默认部署好简单的单机 clickhouse 服务, clickhouse 单机性能已经足够强, 基本上单台 8核16G 服务器就可以处理上亿的数据量级.
日常命令
虽然 clickhouse 尽量贴合 SQL 语法, 但是实际应用上还是和 SQL 差距很大, 所以需要了解下日常会用到的命令:
1 | -- 这里的命令都是需要在客户端内执行, 也就是运行 clickhouse-client --password 之后进入命令行模式 |
这里需要参考之前 Kafka 做消息队列的用户上报表, 将其转化成 clickhouse 数据表做参考:
1 | # 应用事件上报表 |
需要清楚 clickhouse 支持的类型, MySQL/PostgreSQL 和 clickhouse 的类型差别还是很大的, 目前支持的类型如下:
| ClickHouse类型 | 作用说明 |
|---|---|
Int8/UInt8 |
8位整数,范围-128~127/0~255,适合存储性别、状态等小数值(如role_gender) |
Int32/UInt32 |
32位整数,范围-2^31~2^31-1/0~2^32-1,适合事件ID、角色等级等(如action) |
Int64/UInt64 |
64位整数,范围-2^63~2^63-1/0~2^64-1,适合应用ID、用户UID等大数值(如appid/uid) |
UUID |
原生UUID类型,占用16字节(MySQL用36字节字符串),支持合法性校验(如id) |
LowCardinality(String) |
低基数字符串优化,适合取值有限的字段(如sid/sname),压缩率提升10~100倍, 可以视作 enum 枚举压缩优化 |
JSON |
原生JSON类型,支持直接解析查询(如extra),性能优于MySQL的JSON解析 |
DateTime64(3) |
毫秒级时间类型(MySQL需用bigint存毫秒戳),适合时间范围查询 |
Float32/Float64 |
32位单精度/64位双精度浮点数,范围与标准IEEE 754一致,存在精度丢失风险; 可用于非精确计算(如游戏战力估算、数据采样率) |
Decimal(P, S) |
定点数,P表示总位数,S表示小数位数(如Decimal(20,2)),无精度丢失; 可用于精确数值计算(如游戏币余额、充值金额) |
Decimal32(S)/Decimal64(S)/Decimal128(S) |
定点数的细分类型,分别对应32/64/128位存储,S为小数位数; 可用于超高精度计算(如金融级交易金额、游戏道具定价) |
String |
无长度限制的字符串类型,按需存储实际字符数,替代MySQL的varchar/text; 可用于任意长度字符串(如角色名、工会名称) |
FixedString(N) |
固定长度N的字符串,不足补0,超出报错,比String更节省存储; 可用于固定长度标识(如32位角色ID、16位设备ID) |
Enum8/Enum16 |
枚举类型,Enum8用1字节存储(对应Int8),Enum16用2字节(对应Int16),仅能存储预定义枚举值; 可用于固定枚举场景(如角色职业、装备品质:Enum8('normal'=1, 'rare'=2, 'epic'=3)) |
Date |
日期类型,存储年-月-日,范围1970-01-01至2105-12-31,占用2字节; 可用于按日期分区/查询(如上报日期) |
Date32 |
扩展日期类型,范围1900-01-01至2299-12-31,占用4字节; 可用于历史数据存储(如老玩家注册日期) |
DateTime |
秒级时间类型,存储年-月-日 时:分:秒,范围1970-01-01 00:00:00至2105-12-31 23:59:59,占用4字节; 可用于秒级时间范围查询(如登录时间) |
Interval |
时间间隔类型(如IntervalDay/IntervalHour),用于时间运算; 可用于时间偏移计算(如create_time + Interval 1 Day) |
IPv4/IPv6 |
原生IP类型,分别存储IPv4/IPv6地址,占用4/16字节,支持IP范围/子网查询; 可用于存储上报IP、服务器IP(如create_ip IPv4) |
Nullable(T) |
可为空类型,T为基础类型(如Nullable(UInt32)),占用1字节额外存储标识NULL; 可用于允许字段为空的场景(如玩家可选填的工会ID) |
Binary |
二进制类型,存储原始字节数据,替代String存储非字符数据; 可用于存储加密数据、图片哈希值 |
GeoPoint/GeoPolygon |
地理坐标类型,支持经纬度存储和空间查询(如距离计算、区域包含); 可用于游戏地理场景(如玩家位置、地图区域) |
Array(T) |
数组类型,T为元素类型(如Array(UInt32)/Array(String)),支持动态增删; 可用于多值存储(如玩家拥有的道具ID、角色技能列表) |
Map(K, V) |
键值对类型,K为键类型(必须可哈希),V为值类型(如Map(String, String)); 可用于灵活属性存储(如玩家自定义标签、设备参数) |
Tuple(T1, T2, ...) |
元组类型,存储不同类型的元素(如Tuple(UInt64, String, UUID)),不可变; 可用于组合字段存储(如坐标Tuple(Float64, Float64)) |
AggregateFunction |
聚合函数类型,存储预计算的聚合结果(如AggregateFunction(count, UInt64)); 可用于预聚合表(如按小时统计的活跃用户数) |
SimpleAggregateFunction |
简化聚合函数类型,仅支持简单聚合(sum/min/max),性能更优; 可用于轻量预聚合(如角色最高等级、累计充值金额) |
LowCardinality(T) |
低基数优化类型,不仅支持String,还支持Int/Date等类型(如LowCardinality(UInt32))); 可用于低基数数值/日期字段(如服务器ID、游戏大区ID) |
Expression |
表达式类型,基于其他字段计算生成(虚拟列),不占用实际存储; 可用于派生字段(如report_date Expression Date = toDate(create_time/1000)) |
注意: ClickHouse 之中字段默认是 NOT NULL; 除非显式声明 Nullable({插入的类型类型}), 否则插入 null 值报错(喜欢这设计)
而类型选择需要遵循以下规则:
-
数值优先:能用数值类型(如
UInt64)的场景,绝不使用字符串(如毫秒时间戳、角色等级); -
低基数优化:取值有限的字段,优先用
LowCardinality(String)或Enum(如服务器ID、职业); -
复合类型替代JSON:简单多值/键值对用
Array/Map,比JSON解析更高效; -
特殊类型适配业务:IP用
IPv4/IPv6,坐标用GeoPoint,减少手动解析的开销(但是不强求,只是作为规范建议); -
避免Nullable:非必要场景不要用
Nullable,会增加存储和查询开销(可通过默认值替代NULL)。
我个人还是比较喜欢采用 Uint64 方面做单纯 UTC 毫秒时间戳而时区转换是外部处理,让内部的数据字段功能更加纯粹(仅范围查询)
这里就是最后转换成 clickhouse 内部表内容, 如下所示来创建和处理:
1 | -- 再次切换到创建 logs 库 |
之后就准备生成测试数据并且开始常规的查询工作, 后续部分其实更加贴近 MySQL/PostgreSQL 的日常使用
测试与查询
这里生成一些日常的上报数据, 用于添加到 clickhouse 之中备用:
1 | -- clickhouse 插入功能和 MySQL/PostgreSQL 一样, 甚至多了一些特性 |
查询没什么好说的, 基本上和其他数据库的查询语法差不多, 只是导入大数据的时候能够明显感觉到快
正式环境日志数据导入之后, 真的能明显感觉到 clickhouse 的 ‘快’ 是什么感觉
至此就是完整的 clickhouse 搭建和使用流程, 其实正式上线的项目并不推荐个人搭建而是直接买云服务商方案, 毕竟能用钱来为数据安全兜底比什么都重要.
其他对应的语言 PHP/Java/Python 操作流程可能后续会额外说明, 基本上也是直接引入第三方库使用即可, 没什么太大难度.




