zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

ES不香吗,为啥还要ClickHouse?

2023-03-09 22:04:36 时间

 Elasticsearch 是一个实时的分布式搜索分析引擎,它的底层是构建在 Lucene 之上的。简单来说是通过扩展 Lucene 的搜索能力,使其具有分布式的功能。

图片来自包图网

ES 通常会和其它两个开源组件 Logstash(日志采集)和 Kibana(仪表盘)一起提供端到端的日志/搜索分析的功能,常常被简称为 ELK。

Clickhouse 是俄罗斯搜索巨头 Yandex 开发的面向列式存储的关系型数据库。ClickHouse 是过去两年中 OLAP 领域中最热门的,并于 2016 年开源。

ES 是最为流行的大数据日志和搜索解决方案,但是近几年来,它的江湖地位受到了一些挑战,许多公司已经开始把自己的日志解决方案从 ES 迁移到了 Clickhouse,这里就包括:携程,快手等公司。

架构和设计的对比

ES 的底层是 Lucenc,主要是要解决搜索的问题。搜索是大数据领域要解决的一个常见的问题,就是在海量的数据量要如何按照条件找到需要的数据。搜索的核心技术是倒排索引和布隆过滤器。

ES 通过分布式技术,利用分片与副本机制,直接解决了集群下搜索性能与高可用的问题。

ElasticSearch 是为分布式设计的,有很好的扩展性,在一个典型的分布式配置中,每一个节点(node)可以配制成不同的角色。

如上图所示:

  • Client Node,负责 API 和数据的访问的节点,不存储/处理数据。
  • Data Node,负责数据的存储和索引。
  • Master Node,管理节点,负责 Cluster 中的节点的协调,不存储数据。

ClickHouse 是基于 MPP 架构的分布式 ROLAP(关系 OLAP)分析引擎。每个节点都有同等的责任,并负责部分数据处理(不共享任何内容)。

ClickHouse 是一个真正的列式数据库管理系统(DBMS)。在 ClickHouse 中,数据始终是按列存储的,包括矢量(向量或列块)执行的过程。

让查询变得更快,最简单且有效的方法是减少数据扫描范围和数据传输时的大小,而列式存储和数据压缩就可以帮助实现上述两点。

Clickhouse 同时使用了日志合并树,稀疏索引和 CPU 功能(如 SIMD 单指令多数据)充分发挥了硬件优势,可实现高效的计算。

Clickhouse 使用 Zookeeper 进行分布式节点之间的协调。

为了支持搜索,Clickhouse 同样支持布隆过滤器。

查询对比实战

为了对比 ES 和 Clickhouse 的基本查询能力的差异,我写了一些代码来验证:

  1. https://github.com/gangtao/esvsch 

这个测试的架构如下:

架构主要有四个部分组成:

①ES stack

ES stack 有一个单节点的 Elastic 的容器和一个 Kibana 容器组成,Elastic 是被测目标之一,Kibana 作为验证和辅助工具。

部署代码如下:

  1. version: '3.7' 
  2.  
  3. services: 
  4.   elasticsearch: 
  5.     image: docker.elastic.co/elasticsearch/elasticsearch:7.4.0 
  6.     container_name: elasticsearch 
  7.     environment: 
  8.       - xpack.security.enabled=false 
  9.       - discovery.type=single-node 
  10.     ulimits: 
  11.       memlock: 
  12.         soft: -1 
  13.         hard: -1 
  14.       nofile: 
  15.         soft: 65536 
  16.         hard: 65536 
  17.     cap_add: 
  18.       - IPC_LOCK 
  19.     volumes: 
  20.       - elasticsearch-data:/usr/share/elasticsearch/data 
  21.     ports: 
  22.       - 9200:9200 
  23.       - 9300:9300 
  24.     deploy: 
  25.       resources: 
  26.         limits: 
  27.           cpus: '4' 
  28.           memory: 4096M 
  29.         reservations: 
  30.           memory: 4096M 
  31.  
  32.   kibana: 
  33.     container_name: kibana 
  34.     image: docker.elastic.co/kibana/kibana:7.4.0 
  35.     environment: 
  36.       - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 
  37.     ports: 
  38.       - 5601:5601 
  39.     depends_on: 
  40.       - elasticsearch 
  41.  
  42. volumes: 
  43.   elasticsearch-data: 
  44.     driver: local 

②Clickhouse stack

Clickhouse stack 有一个单节点的 Clickhouse 服务容器和一个 TabixUI 作为 Clickhouse 的客户端。

部署代码如下:

  1. version: "3.7" 
  2. services: 
  3.   clickhouse: 
  4.     container_name: clickhouse 
  5.     image: yandex/clickhouse-server 
  6.     volumes: 
  7.       - ./data/config:/var/lib/clickhouse 
  8.     ports: 
  9.       - "8123:8123" 
  10.       - "9000:9000" 
  11.       - "9009:9009" 
  12.       - "9004:9004" 
  13.     ulimits: 
  14.       nproc: 65535 
  15.       nofile: 
  16.         soft: 262144 
  17.         hard: 262144 
  18.     healthcheck: 
  19.       test: ["CMD""wget""--spider""-q""localhost:8123/ping"
  20.       interval: 30s 
  21.       timeout: 5s 
  22.       retries: 3 
  23.     deploy: 
  24.       resources: 
  25.         limits: 
  26.           cpus: '4' 
  27.           memory: 4096M 
  28.         reservations: 
  29.           memory: 4096M 
  30.  
  31.   tabixui: 
  32.     container_name: tabixui 
  33.     image: spoonest/clickhouse-tabix-web-client 
  34.     environment: 
  35.       - CH_NAME=dev 
  36.       - CH_HOST=127.0.0.1:8123 
  37.       - CH_LOGIN=default 
  38.     ports: 
  39.       - "18080:80" 
  40.     depends_on: 
  41.       - clickhouse 
  42.     deploy: 
  43.       resources: 
  44.         limits: 
  45.           cpus: '0.1' 
  46.           memory: 128M 
  47.         reservations: 
  48.           memory: 128M 

③数据导入 stack

数据导入部分使用了 Vector.dev 开发的 vector,该工具和 fluentd 类似,都可以实现数据管道式的灵活的数据导入。

④测试控制 stack

测试控制我使用了 Jupyter,使用了 ES 和 Clickhouse 的 Python SDK 来进行查询的测试。

用 Docker compose 启动 ES 和 Clickhouse 的 stack 后,我们需要导入数据,我们利用 Vector 的 generator 功能,生成 syslog,并同时导入 ES 和 Clickhouse。

在这之前,我们需要在 Clickhouse 上创建表。ES 的索引没有固定模式,所以不需要事先创建索引。

创建表的代码如下:

  1. CREATE TABLE default.syslog( 
  2.     application String, 
  3.     hostname String, 
  4.     message String, 
  5.     mid String, 
  6.     pid String, 
  7.     priority Int16, 
  8.     raw String, 
  9.     timestamp DateTime('UTC'), 
  10.     version Int16 
  11. ) ENGINE = MergeTree() 
  12.     PARTITION BY toYYYYMMDD(timestamp
  13.     ORDER BY timestamp 
  14.     TTL timestamp + toIntervalMonth(1); 

创建好表之后,我们就可以启动 vector,向两个 stack 写入数据了。vector 的数据流水线的定义如下:

  1. [sources.in
  2.   type = "generator" 
  3.   format = "syslog" 
  4.   interval = 0.01 
  5.   count = 100000 
  6.  
  7. [transforms.clone_message] 
  8.   type = "add_fields" 
  9.   inputs = ["in"
  10.   fields.raw = "{{ message }}" 
  11.  
  12. [transforms.parser] 
  13.   # General 
  14.   type = "regex_parser" 
  15.   inputs = ["clone_message"
  16.   field = "message" # optional, default 
  17.   patterns = ['^<(?P<priority>\d*)>(?P<version>\d) (?P<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z) (?P<hostname>\w+\.\w+) (?P<application>\w+) (?P<pid>\d+) (?P<mid>ID\d+) - (?P<message>.*)$'
  18.  
  19. [transforms.coercer] 
  20.   type = "coercer" 
  21.   inputs = ["parser"
  22.   types.timestamp = "timestamp" 
  23.   types.version = "int" 
  24.   types.priority = "int" 
  25.  
  26. [sinks.out_console] 
  27.   # General 
  28.   type = "console" 
  29.   inputs = ["coercer"]  
  30.   target = "stdout"  
  31.  
  32.   # Encoding 
  33.   encoding.codec = "json"  
  34.  
  35.  
  36. [sinks.out_clickhouse] 
  37.   host = "http://host.docker.internal:8123" 
  38.   inputs = ["coercer"
  39.   table = "syslog" 
  40.   type = "clickhouse" 
  41.  
  42.   encoding.only_fields = ["application""hostname""message""mid""pid""priority""raw""timestamp""version"
  43.   encoding.timestamp_format = "unix" 
  44.  
  45. [sinks.out_es] 
  46.   # General 
  47.   type = "elasticsearch" 
  48.   inputs = ["coercer"
  49.   compression = "none"  
  50.   endpoint = "http://host.docker.internal:9200"  
  51.   index = "syslog-%F" 
  52.  
  53.   # Encoding 
  54.  
  55.   # Healthcheck 
  56.   healthcheck.enabled = true 

这里简单介绍一下这个流水线:

  • source.in:生成 syslog 的模拟数据,生成 10w 条,生成间隔和 0.01 秒。
  • transforms.clone_message:把原始消息复制一份,这样抽取的信息同时可以保留原始消息。
  • transforms.parser:使用正则表达式,按照 syslog 的定义,抽取出 application,hostname,message,mid,pid,priority,timestamp,version 这几个字段。
  • transforms.coercer:数据类型转化。
  • sinks.out_console:把生成的数据打印到控制台,供开发调试。
  • sinks.out_clickhouse:把生成的数据发送到Clickhouse。
  • sinks.out_es:把生成的数据发送到 ES。

运行 Docker 命令,执行该流水线:

  1. docker run \ 
  2.         -v $(mkfile_path)/vector.toml:/etc/vector/vector.toml:ro \ 
  3.         -p 18383:8383 \ 
  4.         timberio/vector:nightly-alpine 

数据导入后,我们针对一下的查询来做一个对比。ES 使用自己的查询语言来进行查询,Clickhouse 支持 SQL,我简单测试了一些常见的查询,并对它们的功能和性能做一些比较。

返回所有的记录:

  1. # ES 
  2.   "query":{ 
  3.     "match_all":{} 
  4.   } 
  5.  
  6. # Clickhouse  
  7. "SELECT * FROM syslog" 

匹配单个字段:

  1. # ES 
  2.   "query":{ 
  3.     "match":{ 
  4.       "hostname":"for.org" 
  5.     } 
  6.   } 
  7.  
  8. # Clickhouse  
  9. "SELECT * FROM syslog WHERE hostname='for.org'" 

匹配多个字段:

  1. # ES 
  2.   "query":{ 
  3.     "multi_match":{ 
  4.       "query":"up.com ahmadajmi"
  5.         "fields":[ 
  6.           "hostname"
  7.           "application" 
  8.         ] 
  9.     } 
  10.   } 
  11.  
  12. # Clickhouse、 
  13. "SELECT * FROM syslog WHERE hostname='for.org' OR application='ahmadajmi'" 

单词查找,查找包含特定单词的字段:

  1. # ES 
  2.   "query":{ 
  3.     "term":{ 
  4.       "message":"pretty" 
  5.     } 
  6.   } 
  7.  
  8. # Clickhouse 
  9. "SELECT * FROM syslog WHERE lowerUTF8(raw) LIKE '%pretty%'" 

范围查询,查找版本大于 2 的记录:

  1. # ES 
  2.   "query":{ 
  3.     "range":{ 
  4.       "version":{ 
  5.         "gte":2 
  6.       } 
  7.     } 
  8.   } 
  9.  
  10. # Clickhouse 
  11. "SELECT * FROM syslog WHERE version >= 2" 

查找到存在某字段的记录:

  1. # ES 
  2.   "query":{ 
  3.     "exists":{ 
  4.       "field":"application" 
  5.     } 
  6.   } 
  7.  
  8. # Clickhouse 
  9. "SELECT * FROM syslog WHERE application is not NULL" 

ES 是文档类型的数据库,每一个文档的模式不固定,所以会存在某字段不存在的情况;而 Clickhouse 对应为字段为空值。

正则表达式查询,查询匹配某个正则表达式的数据:

  1. # ES 
  2.   "query":{ 
  3.     "regexp":{ 
  4.       "hostname":{ 
  5.         "value":"up.*"
  6.           "flags":"ALL"
  7.             "max_determinized_states":10000, 
  8.               "rewrite":"constant_score" 
  9.       } 
  10.     } 
  11.   } 
  12.  
  13. # Clickhouse 
  14. "SELECT * FROM syslog WHERE match(hostname, 'up.*')" 

聚合计数,统计某个字段出现的次数:

  1. # ES 
  2.   "aggs":{ 
  3.     "version_count":{ 
  4.       "value_count":{ 
  5.         "field":"version" 
  6.       } 
  7.     } 
  8.   } 
  9.  
  10. # Clickhouse 
  11. "SELECT count(version) FROM syslog" 

聚合不重复的值,查找所有不重复的字段的个数:

  1. # ES 
  2.   "aggs":{ 
  3.     "my-agg-name":{ 
  4.       "cardinality":{ 
  5.         "field":"priority" 
  6.       } 
  7.     } 
  8.   } 
  9.  
  10. # Clickhouse 
  11. "SELECT count(distinct(priority)) FROM syslog " 

我用 Python 的 SDK,对上述的查询在两个 Stack 上各跑 10 次,然后统计查询的性能结果。

我们画出出所有的查询的响应时间的分布:

总查询时间的对比如下:

通过测试数据我们可以看出 Clickhouse 在大部分的查询的性能上都明显要优于 Elastic。

在正则查询(Regex query)和单词查询(Term query)等搜索常见的场景下,也并不逊色。

在聚合场景下,Clickhouse 表现异常优秀,充分发挥了列村引擎的优势。

注意,我的测试并没有任何优化,对于 Clickhouse 也没有打开布隆过滤器。可见 Clickhouse 确实是一款非常优秀的数据库,可以用于某些搜索的场景。

当然 ES 还支持非常丰富的查询功能,这里只有一些非常基本的查询,有些查询可能存在无法用 SQL 表达的情况。

总结

本文通过对于一些基本查询的测试,对比了 Clickhouse 和 Elasticsearch 的功能和性能。

测试结果表明,Clickhouse 在这些基本场景表现非常优秀,性能优于 ES,这也解释了为什么用很多的公司应从 ES 切换到 Clickhouse 之上。

作者:Gang Tao

编辑:陶家龙

出处:zhuanlan.zhihu.com/p/353296392