共计 9485 个字符,预计需要花费 24 分钟才能阅读完成。
企业数据越存越多,存储容量与查问性能、以及存储老本之间的矛盾对于技术团队来说是个广泛难题。这个难题在 Elasticsearch 与 ClickHouse 这两个场景中尤为突出,为了应答不同热度数据对查问性能的要求,这两个组件在架构设计上就有一些将数据进行分层的策略。
同时,在存储介质方面,随着云计算的倒退,对象存储以低廉的价格和弹性伸缩的空间取得了企业的青眼。越来越多的企业将温、冷数据迁徙至对象存储。 但如果将索引、剖析组件间接对接至对象存储时会产生查问性能、兼容性等问题。
这篇文章将为大家介绍这两个场景中冷热数据分层的基本原理,以及如何通过应用 JuiceFS 来应答在对象存储上存在的问题。
01- Elasticsearch 数据分层构造详解
在介绍 ES 如何实现冷热数据分层策略之前先来理解三个相干的概念:Data Stream,Index Lifecycle Management 和 Node Role。
Data Stream
Data Stream(数据流)是 ES 中一个重要概念,它有如下特色:
- 流式写入:它是一个流式写入的数据集,而不是一个固定大小的汇合;
- 仅追加写:它是用追加写的形式将数据更新进去,且不须要批改历史数据;
- 工夫戳:每一条新增的数据都会有一个工夫戳记录是什么时候产生的;
- 多个索引:在 ES 里有一个索引的概念,每一条数据最终会落到它对应的一个索引中,然而数据流是一个更下层、更大的概念,一个数据流背地可能会有很多索引,这些索引是依据不同的规定来生成的。一个数据流尽管由很多的索引来形成,然而只有最新的索引才是可写的,历史索引是只读的,一旦固化好之后就不能再批改。
日志数据就是合乎数据流特色的一类数据,它是只追加写,同时也得有工夫戳,用户会依据不同的维度,比方按天或者按其余的维度来生成新的索引。
下图是一个数据流建设索引的简略示例,在用数据流的过程中,ES 会间接写到最新的索引,而不是历史索引,历史索引不会被批改。随着后续更多新的数据生成,这个索引也会积淀成为一个老的索引。
下图,当用户往 ES 外面去写数据时,大抵分为两个阶段:
- 阶段 1:数据会先写到内存的 In-memory buffer 缓冲区;
- 阶段 2:缓冲区依据肯定的规定和工夫,再落到本地磁盘上,就是下图绿色的长久化的数据,在 ES 中叫做 Segment。
这个过程中可能会有一些时间差,在长久化的过程中,如果去触发查问,新创建的 Segment 不能被搜寻到。一旦这个 Segment 长久化实现之后,就能够立刻被下层的查问引擎搜寻。
Index Lifecycle Management
Index Lifecycle Management,简称 ILM,就是索引的生命周期治理。ILM 将索引的生命周期定义为 5 个阶段:
- 热数据(Hot):须要频繁更新或者查问的数据;
- 温数据(Warm):不再更新,但仍会被频繁查问的数据;
- 冷数据(Cold):不再更新,且查问频率较低的数据;
- 极冷数据(Frozen):不再更新,且简直不会被查问的数据。能够比拟释怀地把这类数据放在一个绝对最低速最便宜的存储介质中;
- 删除数据(Delete) : 不再须要用到,能够释怀删除的数据。
一个索引里的数据,不论是 index 还是 segment,都会经验这些阶段,这个分类的规定很好地帮忙用户去治理 ES 里的数据,用户能够本人定义不同阶段的规定。
Node Role
在 ES 中,每一个部署节点都会有一个 Node Role,也就是节点角色。每一个 ES 节点会调配不同的角色,比方 master、data、ingest 等。用户能够联合节点角色,以及上文提到的不同生命周期的阶段来组合进行数据管理。
数据节点会有不同的阶段,可能是一个存储热数据的节点,也可能是一个存储温数据、冷数据,甚至极冷数据的节点。须要依据节点的性能去给他调配不同的角色,同时会给不同的角色的节点配置不同的硬件。
比方,对于热数据节点须要配置高性能的 CPU 或者磁盘,对于温冷数据的节点,基本上认为这些数据被查问的频率较低,这个时候其实对于某些计算资源的硬件要求就没有那么高了。
节点角色是依据生命周期的不同阶段来定义的,须要留神的一点是,每一个 ES 节点,能够有多种角色,这些角色并不是一一对应的关系。上面有个示例,在 ES 的 YAML 文件外面配置的时候,node.roles 就是节点角色的配置,能够针对这个节点应该有的角色给它配置多种角色。
node.roles: ["data_hot", "data_content"]
生命周期策略
在理解完 Data Stream、Index Lifecycle Management、Node Role 这些概念当前,就能够为数据创立一些不同的生命周期策略(Lifecycle Policy)。
依据生命周期策略中定义的不同维度的索引特色,如索引的大小、索引里的文档的数量、索引创立的工夫,ES 能够主动地帮用户把某个生命周期阶段的数据滚动到另一个阶段,在 ES 中的术语是 rollover。
比方,用户能够制订基于索引大小维度的特色,把热数据滚动到温数据,或者依据一些其它规定,再把温数据滚动到冷数据。这样,索引在不同生命周期的阶段之间去滚动的时候,相应的它索引的数据也会去做迁徙和滚动。ES 能够主动实现这些工作,然而生命周期策略则须要用户本人来定义。
上面的截图,是 Kibana 的治理界面,用户能够通过图形化的形式去配置生命周期策略。能够看到有三个阶段,从上到下别离是热数据、温数据以及冷数据。
开展其中热数据阶段的高级设置,能够看到更具体,上文提到的基于不同维度特色的策略配置,如在下图左边看到的这三个选项。
- 索引的大小,示意图上的例子是 50GB,当索引的大小超过 50GB 的时候,就会把它从热数据阶段滚动到温数据阶段。
- 最大的文档数,ES 里索引的单元是文档,用户数据是以文档的模式写入 ES 中的,所以文档数也是一个能够掂量的指标。
- 最大索引创立工夫,这里的示例是 30 天,假如某个索引曾经创立了 30 天了,这个时候就会触发刚刚提到的从热数据阶段到温数据的滚动。
02- ClickHouse 数据分层架构详解
下图是一组从大到小的俄罗斯套娃,它十分形象地展示了 ClickHouse 的数据管理模式,MergeTree 引擎。
- Table: 在图片的最左边是一个最大的概念,用户最开始要创立或者可能间接接触到的就是 Table;
- Partition:是一个更小的维度或者更小的粒度。在 ClickHouse 里,数据分成 Partition 来存储,每个 Partition 会有一个标识;
- Part:在每个 Partition 中,又会再进一步地细分为多个 Part。如果查看 ClickHouse 磁盘上存储的数据格式,能够认为每一个子目录就是一个 Part;
- Column:在 Part 里会看到一些更小粒度的数据,即 Column。ClickHouse 的引擎应用的是列式存储,所有的数据都是依照列存的形式来组织。在 Part 目录里会看到很多列,比方 Table 可能有 100 列,就会有 100 个 Column 文件;
- Block:每个 Column 文件里是依照 Block 的粒度来组织。
上面这个示例中,在 table 目录下能够看到有 4 个子目录,每个子目录就是上文提到的 Part。
$ ls -l /var/lib/clickhouse/data/<database>/<table>
drwxr-xr-x 2 test test 64B Aug 8 13:46 202208_1_3_0
drwxr-xr-x 2 test test 64B Aug 8 13:46 202208_4_6_1
drwxr-xr-x 2 test test 64B Sep 8 13:46 202209_1_1_0
drwxr-xr-x 2 test test 64B Sep 8 13:46 202209_4_4_
图示的最左边这一列,每个子目录的名字后面可能是一个工夫,比方 202208 相似这样的前缀,202208 其实就是 Partition 名。Partition 名字是用户本人来定义的,然而依照约定俗成或者一些实际习惯,通常会应用工夫来命名。
比方,202208 这个 Partition,它会有两个子目录,子目录就是 Part,一个 Partition 通常会由多个 Part 来形成。用户在往 ClickHoue 写入数据时,会先写到内存里,再依据内存里的数据结构,长久化到磁盘上。同一个 Partition 外面的数据如果比拟大的话,在磁盘上就会变成很多 part。ClickHouse 官网倡议不要在一个 Table 下创立太多 Part,它会定期或者不定期地对 Part 进行合并,缩小总的 Part 数量。Merge 的概念就是合并 Part,这也是 MergeTree 这个引擎的名字起源之一。
再通过一个例子来理解 Part。Part 里会有很多小文件,有一些是元信息,比方索引信息,帮忙用户疾速查找数据。
$ ls -l /var/lib/clickhouse/data/<database>/<table>/202208_1_3_0
-rw-r--r-- 1 test test ?? Aug 8 14:06 ColumnA.bin
-rw-r--r-- 1 test test ?? Aug 8 14:06 ColumnA.mrk
-rw-r--r-- 1 test test ?? Aug 8 14:06 ColumnB.bin
-rw-r--r-- 1 test test ?? Aug 8 14:06 ColumnB.mrk
-rw-r--r-- 1 test test ?? Aug 8 14:06 checksums.txt
-rw-r--r-- 1 test test ?? Aug 8 14:06 columns.txt
-rw-r--r-- 1 test test ?? Aug 8 14:06 count.txt
-rw-r--r-- 1 test test ?? Aug 8 14:06 minmax_ColumnC.idx
-rw-r--r-- 1 test test ?? Aug 8 14:06 partition.dat
-rw-r--r-- 1 test test ?? Aug 8 14:06 primary.id
在示例的右侧,以 Column 作为前缀的这些文件是理论的数据文件,相比元信息通常会比拟大。这个示例中只有 A、B 两列,理论的表里可能有很多列。所有这些文件,包含元信息、索引信息,都会独特帮忙用户疾速地在不同文件之间去做跳转或者查找。
ClickHouse 存储策略
如果要在 ClickHouse 里做冷热数据分层,会用到相似于 ES 中提到的生命周期策略,在 ClickHouse 里称为存储策略(Storage Policy)。
与 ES 稍有不同,ClickHouse 官网并没有将数据划分不同的阶段,比方热数据、温数据、冷数据这些不同的阶段,ClickHouse 提供了一些规定和配置办法,须要用户本人来制订分层策略。
每个 ClickHouse 节点反对同时配置多块磁盘,存储介质能够是多种多样的。比方,个别用户为了性能会给 ClickHouse 节点配置 SSD 盘;对于一些温冷数据,用户能够把数据存储在老本更低的介质,如机械盘。ClickHouse 的用户对底层存储介质是无感知的。
与 ES 类似,ClickHouse 用户须要依据数据不同的维度特色去制订存储策略,比方每个 part 子目录的大小、整个磁盘的残余空间比例等,当满足某个维度特色设定的条件时就会触发存储策略的执行。这个策略会将某一个 part 从一块盘迁徙到另外一块盘。在 ClickHouse 中,一个节点配置的多块盘是有优先级的,默认状况下数据会优先落在最高优先级的盘上。这样实现了 Part 从一个存储介质转移到另外一个存储介质上。
通过 ClickHouse 的一些 SQL 命令,如 MOVE PARTITION/PART 命令能够手动触发数据迁徙,用户也能够通过这些命令做一些功能性的验证。其次有某些状况下,可能也心愿可能通过手动的形式,而不是主动转移的形式来显式把 part 从以后的存储介质上转移到另外一个存储介质上。
ClickHouse 还反对基于工夫的迁徙策略,这是一个独立于存储策略的概念。数据写入后,ClickHouse 会依照每个表的 TTL 属性设置的工夫来触发磁盘上数据的迁徙。比方设置 TTL 为 7 天,ClickHouse 就会把表中超过 7 天的数据从以后的磁盘(如默认的 SSD)再写到另外一个更低优先级的磁盘上(如 JuiceFS)。
03- 温冷数据存储:为什么应用对象存储 + JuiceFS?
企业把温、冷数据寄存到云上后,存储老本相较于传统的 SSD 架构大为降落。 企业还享受到了云上的弹性伸缩空间;不必为数据存储去做任何运维操作,比方扩缩容,或者一些数据清理类的工作 。温冷数据所需的存储容量比热数据大很多,尤其是随着时间推移,会产生大量须要长期保留的数据,如果这些数据都存储在本地,相应的运维工作将不堪重负。
但如果在对象存储上应用 Elasticsearch、ClickHouse 这类数据利用组件,会存在写入性能差、兼容性等问题。心愿兼顾查问性能的企业,开始在云上寻找解决方案。在这样的背景之下,JuiceFS 被越来越多地利用于数据分层的架构之中 。
通过上面 ClickHouse 写入性能测试能够直观理解到写入 SSD、JuiceFS 以及对象存储的性能差别。
JuiceFS 的写入吞吐量远大于间接对接对象存储,靠近 SSD。当用户把热数据转移到和煦数据这一层时,对于写入性能也有肯定要求。在迁徙的过程中,如果底层存储介质的写入性能差,整个迁徙的流程也会拖得很长,对于整个 pipeline 或数据管理也会带来一些挑战。
下图的 ClickHouse 查问性能测试应用实在业务中的数据,并选取几个典型的查问场景进行测试。其中 q1-q4 是扫描全表的查问,q5-q7 是命中主键索引的查问。测试后果如下图:
JuiceFS 与 SSD 盘的查问性能根本相当,均匀差别在 6% 左右,然而对象存储相比 SSD 盘有 1.4 至 30 倍的性能降落 。得益于 JuiceFS 高性能的元数据操作以及本地缓存个性,能够主动将查问申请须要的热数据缓存在 ClickHouse 节点本地,大幅晋升了 ClickHouse 的查问性能。须要留神的是以上测试中对象存储是通过 ClickHouse 的 S3 磁盘类型进行拜访,这种形式只有数据是存储在对象存储上,元数据还是在本地磁盘。如果通过相似 S3FS 的形式把对象存储挂载到本地,性能会有进一步的降落。
另外值得一提的是 JuiceFS 是一个齐全兼容 POSIX 的文件系统,它可能与下层利用(如 Elasticsearch、ClickHouse)有很好的兼容。用户对底层存储是分布式文件系统或者是本地磁盘是没有感知的。如果间接应用对象存储,不能很好地实现与下层利用的兼容。
04- 实操:ES + JuiceFS
Step 1:筹备多种类型节点,调配不同角色 。每一个 ES 节点能够调配不同的角色,比方存热数据、温数据、冷数据等,用户须要筹备不同机型的节点来匹配不同角色的需要。
Step 2:挂载 JuiceFS 文件系统 。个别用户将 JuiceFS 用于温、冷数据的存储,用户须要在 ES 温数据节点或冷数据的节点上把 JuiceFS 文件系统挂载到本地。用户能够通过符号链接或其它形式把挂载点配置到 ES 中去,让 ES 认为它的数据存储在本地目录里,但这个目录背地其实是一个 JuiceFS 文件系统。
Step 3:创立生命周期策略 。这个须要每个用户本人去定制,用户既能够通过 ES API 去创立,也能够通过 Kibana 去创立,Kibana 提供了一些绝对便捷的形式去创立和治理生命周期策略。
Step 4:为索引设置生命周期策略 。创立完生命周期策略之后,用户须要把这个策略利用到索引上,也就是要为索引去设置刚刚创立好的策略。用户能够通过索引模板的形式,能够在 Kibana 里创立索引模板,也能够通过 index.lifycycle.name,显式通过 API 配置。
这里有几个小提示:
Tip 1:Warm 或 Cold 节点的正本数(replica)能够设置为 1。所有数据实质上都是放在 JuiceFS 上,它的底层是对象存储,因此数据的可靠性曾经足够高了,所以在 ES 这边能够适当升高正本数,节俭存储空间。
Tip 2:开启 Force merge 可能会导致节点 CPU 继续占用,酌情敞开 。从热数据转移到温数据这个阶段时,ES 会将所有热数据索引对应的底层 segment 做合并。如果开启 Force merge 这个性能,ES 会先合并完这些 segment 当前,再把它存储到温数据的底层零碎。然而合并 segment 是一个十分耗费 CPU 的过程,如果温数据的数据节点同时也须要承载一些查问申请,能够酌情敞开这个性能能,也就是一成不变地把数据保留下来,间接写到底层存储中。
Tip 3:Warm 或 Cold 阶段的索引能够设置为只读 。在给温数据和冷数据阶段建设索引时,咱们基本上能够认为这些数据是只读的,这些阶段的索引不会被批改。设置为只读能够适当升高温冷数据节点上的资源,比方内存能够开释一些,从而节俭一些在温节点或者冷节点上的硬件资源。
05- 实操:ClickHouse + JuiceFS
Step 1:在所有 ClickHouse 节点上挂载 JuiceFS 文件系统。 这个门路能够是任意门路,因为 ClickHouse 会有一个配置文件去指向挂载点。
Step 2:批改 ClickHouse 配置,新增 JuiceFS 盘。 在 ClickHouse 中把刚刚挂载好的 JuiceFS 文件系统挂载点增加进来,让 ClickHouse 能够辨认这个新磁盘。
Step 3:新增存储策略,设定下沉数据规定。 这个存储策略会依据用户的规定去不定期的、主动地将数据从默认磁盘上下沉到指定的,比方 JuiceFS 中。
Step 4:为特定表设置存储策略及 TTL。 存储策略制订好之后,须要把这个策略利用到某一个表上。后期测试阶段和验证阶段,能够把用绝对大一点的表去做测试和验证,如果用户心愿基于工夫维度来实现数据下沉,就同时也须要在表上设置 TTL。整个下沉过程是一个主动的机制,能够通过 ClickHouse 的 system 表查看以后正在进行数据迁徙的 part 以及迁徙进度。
Step 5:手动挪动 part 进行验证。 能够通过手动执行 MOVE PARTITION
命令的形式去验证以后的配置或存储策略是否失效。
下图是一个具体示例,在 ClickHouse 中有一个叫做 storage_configuration
的配置项,其中蕴含 disks 配置,这里会把 JuiceFS 作为一个盘加进来,咱们将它命名为“jfs”,但其实能够用任意名字,挂载点是 /jfs
目录。
<storage_configuration> <disks> <jfs> <path>/jfs</path> </jfs> </disks> <policies> <hot_and_cold> <volumes> <hot> <disk>default</disk> <max_data_part_size_bytes>1073741824</max_data_part_size_bytes> </hot> <cold> <disk>jfs</disk> </cold> </volumes> <move_factor>0.1</move_factor> </hot_and_cold> </policies></storage_configuration>
再往下是 policies 配置项,这里定义了一个叫做 hot_and_cold
的存储策略,用户须要定义一些具体的规定,如 volumes 中依照先热后冷的优先级排列,数据首先会落到 volumes 里的第一个 hot 盘上,及默认的 ClickHouse 磁盘,个别是本地的 SSD。
volumes 中的 max_data_part_size_bytes
配置示意当某一个 part 的大小超过设定的大小之后,就会触发存储策略的执行,对应的 part 会下沉到下一个 volume,也就是 cold volume。在下面的示例中,cold volume 就是 JuiceFS。
最上面的 move_factor
配置代表 ClickHouse 会依据以后磁盘的残余空间比例来触发存储策略的执行。
CREATE TABLE test (d DateTime, ...) ENGINE = MergeTree...TTL d + INTERVAL 1 DAY TO DISK 'jfs'SETTINGS storage_policy = 'hot_and_cold';
如下面的代码所示,有了存储策略之后,在创立表或者批改这个表的 schema 时,能够在 SETTINGS 中设置 storage_policy 为后面定义的 hot_and_cold 存储策略。上述代码中倒数第二行的 TTL 即为上文提过的基于工夫的分层规定。在这个示例中,咱们指定的表中某一个叫做 d 的列,它的类型是 DateTime,联合 INTERVAL 1 DAY 就示意当新的数据写进来超过一天之后,这些数据就会转移到 JuiceFS 上。
06- 瞻望
第一,正本共享。 无论是 ES 还是 ClickHouse,他们都是由多副原本保证数据的可用性和可靠性。JuiceFS 实质上是一个共享文件系统,任何一份数据写入到 JuiceFS 之后,不再须要保护多个正本。比方,用户有两个 ClickHouse 节点,都有某一个表的或者某一个 part 的正本,这两个节点都下沉到了 JuiceFS,它可能会写两次一样的数据。 将来,咱们是否能够做到让下层引擎可能感知到上层应用的是一个共享存储,当数据下沉的时候去升高正本数,这样在不同节点之间是能够做正本共享的。 从应用层来说,用户查看这个表,part 数还是多正本,但理论在底层的存储上只保了一个正本,因为实质上数据是能够共享的。
第二点,故障复原。 当数据曾经下沉到一个远端的共享存储之后,如果 ES 或 ClickHousle 节点宕机故障之后,怎么疾速地做故障复原?除了热数据以外的大部分数据其实都曾经转移到了一个远端的共享存储上,这个时候如果要去复原或创立一个新的节点时,老本会比传统的基于本地盘的故障复原形式轻量很多,这在 ES 或者 ClickHouse 场景上是值得摸索的。
第三点,存算拆散。 不论 ES 也好,还是 ClickHouse,整个社区也都在尝试或者摸索在云原生的大环境下,怎么去让传统的这些基于本地盘的存储系统变成一个真正的存算拆散零碎。但存算拆散不是仅仅简略地把数据和计算拆散就好了,同时要满足下层各种简单的需要,比方对于查问性能的需要、对于写入性能的需要、对各种维度调优的需要,在存量拆散整个大的方向上还是有许多值得摸索的技术难点。
第四点,其余下层利用组件数据分层摸索。 除了 ES 和 ClickHouse 这两个场景,咱们最近也有在做一些尝试,把 Apache Pulsar 中的温冷数据下沉到 JuiceFS 中,用到的一些策略和计划与本文中提到的是相似的,只不过在 Apache Pulsar 中,它须要下沉的数据类型或者数据格式不太一样。有了进一步成功实践后,会分享进去。
相干浏览:
- JuiceFS 在携程海量冷数据场景下的实际
- Shopee x JuiceFS: ClickHouse 冷热数据拆散存储架构与实际