简介: 本文的主旨在于通过彻底剖析ClickHouse和Elasticsearch的内核架构,从原理上讲明白两者的优劣之处,同时会附上一份覆盖多场景的测试报告给读者作为参考。
作者:阿里云数据库OLAP产品部 仁劼
Clickhouse是俄罗斯搜索巨头Yandex开发的完全列式存储计算的分析型数据库。ClickHouse在这两年的OLAP领域中一直非常热门,国内互联网大厂都有大规模使用。Elasticsearch是一个近实时的分布式搜索分析引擎,它的底层存储完全构建在Lucene之上。简单来说是通过扩展Lucene的单机搜索能力,使其具有分布式的搜索和分析能力。Elasticsearch通常会和其它两个开源组件Logstash(日志采集)和Kibana(仪表盘)一起提供端到端的日志/搜索分析的功能,常常被简称为ELK。
今天很多用户在实际的业务场景中,常常面对ClickHouse和Elasticsearch技术选型的难题。用户对ClickHouse和Elasticsearch的内核知识了解不足,往往只能通过性能测试的手段来进行选型。本文的主旨在于通过彻底剖析ClickHouse和Elasticsearch的内核架构,从原理上讲明白两者的优劣之处,同时会附上一份覆盖多场景的测试报告给读者作为参考。
分布式架构
Elasticsearch和ClickHouse都是支持分布式多机的数据产品,这里作者首先要比对的就是两者的分布式架构差异,分布式结构设计对产品的易用性和可扩展性具有非常重要的影响。在分布式架构上,核心要解决的几个问题包括:节点发现、Meta同步、副本数据同步。Elasticsearch作为一个老牌的开源产品,在这块上做的相对比较成熟。原生的节点发现、Meta同步协议,给用户非常好的易用性体验。Elasticsearch的Meta同步协议需要解决的问题其实和开源的Raft协议非常相似,只不过在Elasticsearch诞生的时候还没有Raft出现,所以就只能自己动手搞一个了。经过这么多年的打磨,Elasticsearch的Meta同步协议也是相当成熟了。依托于此,Elasticsearch具有非常易用的多角色划分,auto schema inference等功能。值得一提的是Elasticsearch的多副本数据同步,并没有复用Meta同步协议,而是采用传统的主备同步机制,由主节点负责同步到备节点,这种方式会更加简单高效。
ClickHouse的分布式架构能力相对会简单一些,这也是因为ClickHouse还是一个比较年轻的开源产品,还处在分布式易用性不断迭代上升的阶段。ClickHouse引入了外置的ZooKeeper集群,来进行分布式DDL任务(节点Meta变更)、主备同步任务等操作的下发。多副本之间的数据同步(data shipping)任务下发也是依赖于ZooKeeper集群,但最终多副本之间的数据传输还是通过Http协议来进行点对点的数据拷贝,同时多副本都可写,数据同步是完全多向的。至于节点发现,ClickHouse目前都没有这方面的能力,都是需要通过手动配置集群节点地址来解决。ClickHouse目前这种脚手架式的分布式架构,导致它具有极强的灵活部署能力和运维介入能力,对用户的易用性略差,用户门槛相对较高,但是在能力上限方面,ClickHouse的分布式部署扩展性并没有短板,集群规模上限对比Elasticsearch没有差异。ClickHouse架构扁平,没有前端节点和后端节点之分,可部署任意规模集群。同时ClickHouse在多副本功能上有更细粒度的控制能力,可以做到表级别副本数配置,同一物理集群可划分多个逻辑集群,每个逻辑机器可任意配置分片数和副本数。
存储架构
写入链路设计
写入吞吐能力是大数据场景下的一项核心指标,用户对大数据产品的要求不光是要存的下,还要写得快。这里首先介绍Elasticsearch的实时写入链路设计:在Elasticsearch的每一个Shard中,写入流程分为两部分,先写入Lucene,再写入TransLog。写入请求到达Shard后,先写Lucene内存索引,此时数据还在内存里面,接着去写TransLog,写完TransLog后,刷新TransLog数据到磁盘上,写磁盘成功后,请求返回给用户。这里有几个关键点,一是把写Lucene放在了最前面,主要是防止用户的写入请求包含“非法”的数据。二是写Lucene索引后,并不是可被搜索的,需要通过refresh把内存的对象转成完整的Segment后,然后再次reopen后才能被搜索,这个refresh时间间隔是用户可设定的。可以看出Lucene索引并没有写入实时可见的能力,所以Elasticsearch是一个近实时(Near Real Time)的系统。最后是每隔一段比较长的时间,比如30分钟后,Lucene会把内存中生成的新Segment刷新到磁盘上,刷新后索引文件已经持久化了,历史的TransLog就没用了,才会清空掉旧的TransLog。
△Elasticsearch单Shard写入链路
△ClickHouse单Shard写入链路
对比Elasticsearch的写入链路,ClickHouse的写入方式更加“简单直接”、极致,上面已经讲过Elasticsearch是一个近实时系统,内存存储引擎中新写入的数据需要定时flush才可见。而ClickHouse则是干脆彻底放弃了内存存储引擎这一功能,所有的数据写入时直接落盘,同时也就省略了传统的写redo日志阶段。在极高写入吞吐要求的场景下,Elasticsearch和ClickHouse都需要为了提升吞吐而放弃部分写入实时可见性。只不过ClickHouse主推的做法是把数据延迟攒批写入交给客户端来实现。另外在多副本同步上,Elasticsearch要求的是实时同步,也就是写入请求必须写穿多个副本才会返回,而ClickHouse是依赖于ZooKeeper做异步的磁盘文件同步(data shipping)。在实战中ClickHouse的写入吞吐能力可以远远超过同规格的Elasticsearch。
Segment vs DataPart
Elasticsearch和ClickHouse的存储设计外表上看起来非常相似,但能力却又截然不同。Elasticsearch的磁盘文件由一个个Segment组成,Segment实际上是一份最小单位的Lucene索引,关于Segment内部的存储格式这里不展开讨论。而Segment又会在后台异步合并,这里合并主要解决两个问题:1)让二级索引更加有序;2)完成主键数据变更。二级索引是一种“全局”有序的索引,全部数据构建到一个索引里面比构建到多个索引里对查询的加速更明显。Elasticsearch是支持主键删除更新的,这都是依托于Lucene索引的删除功能来实现的,更新操作会被转换成删除操作加写入操作。当Lucene索引的Segment里存在多条删除记录时,系统就需要通过Segment合并来剔除这些记录。在多个Segment进行合并的时候,Lucene索引中的存储数据表现出的是append-only的合并,这种方式下二级索引的合并就不需要进行“重排序”。
对比Elasticsearch中的Segment,ClickHouse存储中的最小单位是DataPart,一次批量写入的数据会落盘成一个DataPart。DataPart内部的数据存储是完全有序的状态(按照表定义的order by排序),这种有序存储就是一种默认聚簇索引可以用来加速数据扫描。ClickHouse也会对DataPart进行异步合并,其合并也是用来解决两个问题:1)让数据存储更加有序;2)完成主键数据变更。DataPart在合并存储数据时表现出的是merge-sorted的方式,合并后产生的DataPart仍然处于完全有序状态。依赖于DataPart存储完全有序的设定,ClickHouse实现主键数据更新的方式和Elasticsearch截然不同。Elasticsearch在变更主键时,采用的是“先查原纪录-生成新记录-删除原纪录-写入新纪录”的方式,这种方式完全限制住了主键更新的效率,主键更新写入和append-only写入的效率差异非常大。而ClickHouse的主键更新是完全异步进行的,主键相同的多条记录在异步合并的时候会产生最新的记录结果。这种异步批量的主键更新方式比Elasticsearch更加高效。
最后总结一下Segment和Data