作者:扬大平仔

前携程、网易高级工程师,现为货拉拉高级工程师。热爱技术,敢于将新技术用于项目实践。

前言

为了解决线上问题定位慢,相应不及时等问题。所以我们决定开发一套智能问题定位系统。对于我们的一些核心系统,每个订单会对应推送多个司机(线上最多达到上千个司机,平均也有几百个司机)。如果要每个司机都记录一条埋点信息那么数据量将会非常庞大,目前埋点数据日均60+亿,一个月接近2000亿数据。这种以司机维度存储数据会存在大量的数据冗余,耗费大量存储等机器资源。并且传统的关系型数据库在动则几十亿到上千亿级别的表上查询,几乎做不到秒级响应。前期我们调研的时候考虑过使用hbase 、druid、kudu等,但这些要么不太适合我们的业务要求,要么就是公司还不支持。后来调研了clickhouse 后,如沐春风。使用clickhouse 能够绝佳的满足我们的需求。首先,clickhouse 不但查询速度快,而且能够像mysql 那样支持多维度查询;其次,clickhouse 的嵌套类型 能够很好的满足我们订单和司机一对多的存储要求。调研后决定存储复合结构数据,即订单维度存储,一个订单对应多个司机。

一、什么是clickhouse?

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。ClickHouse是Yandex(俄罗斯最大的搜索引擎)开源的一个用于实时数据分析的基于列存储的数据库,其处理数据的速度比传统方法快100-1000倍。ClickHouse的性能超过了目前市场上可比的面向列的DBMS,每秒钟每台服务器每秒处理数亿至十亿多行和数十千兆字节的数据。

详细可以移步官方文档:什么是ClickHouse? | ClickHouse文档

二、ClickHouse在具体业务的实践

1.系统基本设计

为了更好的收集机构化的数据,我们自己开发了一款埋点sdk用于收集各业务放的埋点日志,日志统一异步发送Kafka,然后我们在大数据平台使用flink消费Kafka数据,经过一定处理后写入click house。然后我们也开发了一个专门用于在线查询的系统对外提供查询服务。

2.具体实现

  • flink 摄入

因为写clickhouse底层都是使用httpclient的方式写入的,所以对于clickhouse来说单条频繁写入效率很低,适合批量写入。官网建议没批次写入100000+条(要视flink TM 内存大小调整,防止批量过大出息oom)。我们自定义了sink 用于摄入clickhouse,达到一定批次或者执行checkpoint时就写入一次。

  • clickhouse 部署

为了实现高可用,在具体部署上我们采用的是多副本,写本地表查分布式表。ck的表分为两种:

分布式表

一个逻辑上的表, 可以理解为数据库中的视图, 一般查询都查询分布式表. 分布式表引擎会将我们的查询请求路由本地表进行查询, 然后进行汇总最终返回给用户。

本地表:

实际存储数据的表

1. 不写分布式表的原因

分布式表接收到数据后会将数据拆分成多个parts, 并转发数据到其它服务器, 会引起服务器间网络流量增加、服务器merge的工作量增加, 导致写入速度变慢, 并且增加了Too many parts的可能性.

数据的一致性问题, 先在分布式表所在的机器进行落盘, 然后异步的发送到本地表所在机器进行存储,中间没有一致性的校验, 而且在分布式表所在机器时如果机器出现down机, 会存在数据丢失风险.

数据写入默认是异步的,短时间内可能造成不一致.

2. Replication & Sharding

ClickHouse依靠ReplicatedMergeTree引擎族与ZooKeeper实现了复制表机制, 成为其高可用的基础。ClickHouse像ElasticSearch一样具有数据分片(shard)的概念, 这也是分布式存储的特点之一, 即通过并行读写提高效率. ClickHouse依靠Distributed引擎实现了分布式表机制, 在所有分片(本地表)上建立视图进行分布式查询。

三、线上问题以及解决方案

3.1 线上问题

问题1: 司机明细相关业务查询极其缓慢,一个查询往往要耗时15s, 订单明细相关查询却很快。

问题2: 汇总相关指标查询特别慢,一个查询接近20S

 3.2尝试解决方案  

通过分析发现,查询缓慢的都是针对司机维度查询的。虽然我们针对司机ID 这个维度建了一个二级的索引 (INDEX driverId_idx drivers.driverId TYPE bloom_filter(0.01) GRANULARITY 5),也分析了查询sql 的执行计划,重执行日志上看是使用到了我们建的二级索引的。但查询还是十分的慢,这完全超过了我们的预期。

  • 尝试绕过嵌套类型作为查询条件

一开始分析发现,查询慢的都是因为在查询条件中使用了嵌套类型的司机ID作为查询条件,这导致了查询十分缓慢。顾我们尝试使用一个额外的字段来绕过嵌套类型查询。新增一个driverIds 字段,里面包含所有司机ID(多个司机ID用,拼接),在这个字段上建索引。查询司机场景时通过 driverIds like %%查询;使用driverIds作为条件like查询,2600万数据查询耗时3+s左右,性能能提升一倍,但还是不够理想,随着数据量上升,耗时将越来越大。

Select appId,arrayElement(driversDriverId, index) as driversDriverId
,pushId,orderId,uuid,orderPushType,orderVehicleId,eventTime
,arrayElement(driversAppStatus, index)as driversAppStatus,eventLogEvent, arrayElement(isWinner, index) as isWinner
from (Select arrayFirstIndex(id -> id = '9047485', drivers.driverId) as index,appId,
drivers.driverId as driversDriverId,pushId,orderId,uuid,orderPushType,orderVehicleId,eventTime
,drivers.appStatus as driversAppStatus,eventLogEvent,drivers.isWinner as isWinner
from ai_XX_log_db.ai_XXXX_all prewhere driverIds like '%9047485%'
AND toUnixTimestamp(eventTime) >= 1629907200 AND toUnixTimestamp(eventTime) <= 1629993600
AND appId = 'XXX' AND scene = 'XXXX' )
ORDER BY eventTime DESC LIMIT 1,20;
  • 通过sq执行计划进行分析

尝试绕过嵌套类型作为查询条件带来的性能提升并不够理想,于是我们决定深入了解clickhouse的一些底层机制。尝试通过SQL执行计划来确定一个sql 的查询瓶颈。目前查看sql 执行计划有两种方法,方法一(20.6之前版本)

clickhouse-client -u xxxx --password xxxxxx --send_logs_level=trace <<< 'your query sql' > /dev/null

方法二(20.6与20.6之后版本)

explain SQL

方法一是指定clickhouse 执行日志级别为trace,这样可以打印出来sql 各个阶段执行的日志,通过日志型来分析SQL执行情况,能够详细的了解到SQL执行情况。方法二有点像mysql那样,但这个只能打印部分SQL执行情况,不够详细。所以我们最终使用了方法一。

部分问题SQL的执行计划日志:

测试环境2600W 查询耗时大概7.3S+, 生产环境2+亿数据查询耗时15+s。

从上面的执行计划看,driverId_idx 是使用到了的,但主键索引和分区所以都没有用到。没有用到主键索引是导致查询慢的主要原因。至于为什么,这个要从clickhouse的底层存储结构说了,这里不详细说明,想了解的可以去看看这两个帖子:

1、云数据库ClickHouse二级索引-最佳实践

2、ClickHouse内核分析-MergeTree的存储结构和查询加速

  • 提出解决方案

通过上面的一系列分析,我们知道导致click house查询慢的根本原因是没有使用到clickhouse主键索引(一级索引)。click house 的存储结构决定对于大数据量查询时,使用主键索引能够精确的找到所需的数据块,减少不必要的数据块扫描,这样更够极大的提高查询效率。顾为了使用司机ID查询的时候也能够用到主键索引,所以我们尝试自己建立一个索引映射机制,类似于hbase 做二级索引。首先建立一张司机和订单的映射表,将查询司机的场景也映射到通过订单查询的场景。如果存储司机维度的数据,那么每天需要存储60亿数据,一个月就是1800亿,这是相当庞大的数据量。为此我们考虑到了分表策略,取司机ID最后一位作为表后缀,即分十张表存储数据,将数据控制在百亿级别。在数据这块我们是使用flink摄入数据的,使用分表了策略后我们又自定义了分表摄入的策略。

1、建立司机和订单映射表:

其中一张表:

CREATE TABLE ai_XX_log_db.ai_XXX_log_driver_local_0 ON CLUSTER ck_cluster (
orderId String,
driverId String,
scene String,
eventTime DateTime
) ENGINE ReplicatedMergeTree('/clickhouse_ai/tables/{shard}/ai_XXX_log_db/ai_XXX_log_driver_local_0', '{replica}')
PARTITION BY toYYYYMMDD(eventTime) TTL eventTime + INTERVAL 14 DAY
ORDER BY(driverId, eventTime, scene)
SETTINGS index_granularity = 8192;

在测试环境中单表数据在800+亿,通过司机ID、时间和scene 查询订单ID耗时在200ms 以内。然后通过订单ID到主表中查询埋点记录,耗时在300ms以内。

完整查询示例:

select orderId,orderTime,eventTime,drivers.driverId as driversDriverId ,
drivers.isWinner as isWinner,drivers.pkRule as pkRule,
eventLogLogicType,eventLogRefProp, eventLogEvent
from ai_XX_log_db.ai_XXXevent_log_all prewhere scene = 'pk_driver_result'
AND toUnixTimestamp(eventTime) >=1630857600 AND toUnixTimestamp(eventTime) <= 1630944000
AND appId = 'ai-pk-api'
AND has(drivers.driverId, '1899302024')
AND orderId IN (
select DISTINCT orderId from ai_order_log_db.ai_XXX_log_driver_all_4
WHERE driverId = '1899302024'
AND toUnixTimestamp(eventTime) >=1630857600 AND toUnixTimestamp(eventTime) <= 1630944000
AND scene = 'pk_driver_result'
)
ORDER BY eventTime DESC

通过这样优化后我们绝大部分司机维度查询场景耗时在500ms 以内,有些查询耗时在200ms以内,性能较原来的最高提高了近百倍。

clickhouse 在货拉拉的应用实践,千亿级别数据实现秒级查询相关推荐

  1. 新松机器人产业小镇_湘潭新松机器人产业园项目开工建设 点亮千亿级别产业...

    湘潭高新区开启"智造谷"建设新篇章 项目整体鸟瞰效果图 湘潭在线11月15日讯(湘潭日报社全媒体记者 李新辉  罗斌 通讯员 刘澜) 满载着湘潭产业转型升级希望,作为湖南省&quo ...

  2. 千亿级别数字的人民币读法

    </pre><pre name="code" class="java">package com.sungeo; /*** 人民币的读法是 ...

  3. 6亿数据秒级查询,ClickHouse太快了!

    " ClickHouse 在数据分析技术领域早已声名远扬,最近由于项目需求使用到了 ClickHouse 做分析数据库,于是用测试环境做了一个单表 6 亿数据量的性能测试. 图片来自 Pex ...

  4. ClickHouse留存分析工具十亿数据秒级查询方案

    作者:陈璐,腾讯 CSIG 高级数据分析师 本文实践了对于千万级别的用户,操作总数达万级别,每日几十亿操作流水的留存分析工具秒级别查询的数据构建方案.同时,除了留存分析,对于用户群分析,事件分析等也可 ...

  5. 千亿大数据处理能力是如何炼成的?

    (此文来自乐字节) 源起谷歌"三驾马车" 聊起大数据,就绕不开谷歌的"三驾马车".早在 2003 年,谷歌发表第一篇论文--谷歌文件系统(GFS):第二年,谷歌 ...

  6. 重磅!共掘千亿大数据市场 智领云2021年合作伙伴招募计划正式启动

    2021年3月26日,在春暖花开,万物复苏之际,智领云将在武汉光谷凯悦酒店举办"数据驱动 智领未来"合作伙伴沙龙,我们诚邀您来参加本次会议,了解智领云领先的产品.技术理念及合作伙伴 ...

  7. Elasticsearch+Mongo亿级别数据导入及查询实践

    数据方案: 在Elasticsearch中通过code及time字段查询对应doc的mongo_id字段获得mongodb中的主键_id 通过获得id再进入mongodb进行查询   1,数据情况: ...

  8. 千亿级数量下日志分析系统的技术架构选型

     
 随着数据已经逐步成为一个公司宝贵的财富,大数据团队在公司往往会承担更加重要的角色.大数据团队往往要承担数据平台维护.数据产品开发.从数据产品中挖掘业务价值等重要的职责.所以对于很多大数据工程师 ...

  9. 听过TB、PB级大数据,ZB级的大数据探索与应用实践是怎么样的?【附PPT】

    据报告显示到2025年,全球将产生180ZB的数据.这些海量的数据正是企业进行数字化转型的核心生产因素,然而真正被有效存储.使用和分析的数据不到百分之十.如何从ZB级的数据中寻找分析有价值的信息并回馈 ...

最新文章

  1. python字典去重
  2. Go 语言编程 — 数据类型转换
  3. python手机版编程-手机编python
  4. JavaScript中innerHTML与innerText,createTextNode的区别
  5. WebStorm 使用经验
  6. node 升级_Node.js 版本知多少?又该如何选择?
  7. 配置 mybatis的 log4j.properties
  8. c# 标准正太分布函数_数据标准化处理中的min-max和zscore
  9. 【备忘】mysql常用操作汇总
  10. Nhibernate学习的第一天
  11. Unity3D学习笔记(二十七):MVC框架下的背包系统(2)
  12. python数据处理源代码_python数据分析与应用源数据和代码
  13. 电子商务概论(农)之章节课后题
  14. Docsify 侧边栏 : 自动生成sidebar与子目录sidebar
  15. c/c++文本单词查询
  16. 免费图床-树洞外链-阿离图床
  17. Python模块之logging详解
  18. ssm基于jsp的在线点餐系统 毕业设计源码111016
  19. 怎样修复计算机系统声音,电脑没声音了如何恢复?电脑突然没声音修复方法汇总...
  20. [AHOI2014/JSOI2014] 骑士游戏

热门文章

  1. bmi计算 python_Python中的BMI计算器
  2. 写给小白:AWS,开启新世界大门的钥匙
  3. 在photoshop中,从1寸到24寸照片的大小是多少?
  4. jQuery雪花插件JQuery-Snowfall Plugin
  5. win10 全屏秒退
  6. 裴建瑞2021011195
  7. Kronecker(克罗内克积)积
  8. 关于git的commit提交回退的三种命令模式
  9. 内存取证常见例题思路方法-volatility (没有最全 只有更全)
  10. 用Html+js自己写了一个小游戏