作者:民工哥

来源:https://segmentfault.com/a/1190000041419728

影响数据库选择的因素

数据量:是否海量数据,单表数据量太大会考验数据库的性能

数据结构:结构化 (每条记录的结构都一样) 还是非结构化的 (不同记录的结构可以不一样)

是否宽表:一条记录是 10 个域,还是成百上千个域

数据属性:是基本数据 (比如用户信息)、业务数据 (比如用户行为)、辅助数据 (比如日志)、缓存数据

是否要求事务性:一个事务由多个操作组成,必须全部成功或全部回滚,不允许部分成功

实时性:对写延迟,或读延迟有没有要求,比如有的业务允许写延迟高但要求读延迟低

查询量:比如有的业务要求查询大量记录的少数列,有的要求查询少数记录的所有列

排序要求:比如有的业务是针对时间序列操作的

可靠性要求:对数据丢失的容忍度

一致性要求:是否要求读到的一定是最新写入的数据

对增删查改的要求:有的业务要能快速的对单条数据做增删查改 (比如用户信息),有的要求批量导入,有的不需要修改删除单条记录 (比如日志、用户行为),有的要求检索少量数据 (比如日志),有的要求快速读取大量数据 (比如展示报表),有的要求大量读取并计算数据 (比如分析用户行为)

是否需要支持多表操作

不同的业务对数据库有不同的要求

SQL 数据库 & NoSQL 数据库

SQL 数据库就是传统的关系型数据库

行列式表存储

结构化数据

需要预定义数据类型

数据量和查询量都不大,如果数据量大要做分表

对数据一致性、完整性约束、事务性、可靠性要求比较高

支持多表 Join 操作

支持多表间的完整性,要删除 A 表的某条数据,可能需要先删除 B 表的某些数据

SQL 的增删改查功能强

较为通用,技术比较成熟

大数据量性能不足

高并发性能不足

无法应用于非结构化数据

扩展困难

常用的 SQL 数据库比如 Oracle、MySQL、PostgreSQL、SQLite

NoSQL 泛指非关系型数据库

表结构较灵活,比如列存储,键值对存储,文档存储,图形存储

支持非结构化数据

有的不需要预定义数据类型,有的甚至不需要预定义表

支持大数据量

多数都支持分布式

扩展性好

基本查询能力,高并发能力比较强 (因为采用非结构化、分布式,并牺牲一致性、完整性、事务性等功能)

对数据一致性要求比较低

通常不支持事务性,或是有限支持

通常不支持完整性,复杂业务场景支持较差

通常不支持多表 Join,或是有限支持

非 SQL 查询语言,或类 SQL 查询语言,但功能都比较弱,有的甚至不支持修改删除数据

不是很通用,技术多样,市场变化比较大

常用的 NoSQL 数据库比如

列式:HBase、Cassandra、ClickHouse

键值:Redis、Memcached

文档:MongoDB

时序:InfluxDB、Prometheus

搜索:Elasticsearch

SQL 和 NoSQL 是一个互补的关系,应用在不同的场景中。

OLTP & OLAP

OLTP (On-Line Transaction Processing)

主要做实时事务处理

比如处理用户基本信息、处理订单合同、处理银行转账业务、企业的 ERP 系统和 OA 系统,等等

频繁地,对少量数据,甚至是单条数据,做实时的增删改查

数据库经常更新

通常对规范化、实时性、稳定性、事务性、一致性、完整性等有要求

操作较为固定,比如订单业务,可能永远就那几个固定的操作

数据库主要模型是 3NF 或 BCNF 模型

OLAP (On-Line Analytical Processing)

数据仓库,主要做历史数据分析,为商业决策提供支持

比如对大量的用户行为做分析,对设备的状态、使用率、性能做分析

频率较低地,对大量数据,做读取、聚合、计算、分析,实时性要求不高,对吞吐能力要求较高

通常列的数量比较多,但每次分析的时候只取少部分列的数据

通常是批量导入数据

通常数据导入后不会修改,主要是读取操作,写少读多

通常对规范化、事务性、一致性、完整性等要求较低,甚至一个查询操作失败了也不会有什么影响

操作较为灵活,比如一个海量用户行为数据表,可以想出许多不同的方法,从不同的角度对用户做分析

数据库主要是星型、雪花模型

不使用高性能的 OLAP 之前,更传统的做法是通过离线业务构建 T+1 的离线数据,比较滞后

OLTP 通常用传统的关系数据库,如果数据量大要分表,对事务性、一致性、完整性等要求不高的话也可以用 NoSQL

OLAP 通常用 NoSQL,数据量不大的话也可以用传统的关系数据库

关系型数据库 Oracle、SQL Server、MySQL、PostgreSQL、SQLite

Oracle:甲骨文开发的商业数据库,不开源,支持所有主流平台,性能好,功能强,稳定性好,安全性好,支持大数据量,比较复杂,收费昂贵。

SQL Server:微软开发的商业数据库,只能在 Windows 运行。

MySQL:甲骨文拥有的开源数据库,支持多种操作系统,体积小,功能弱些,简单的操作性能好,复杂的操作性能差些。** PostgreSQL**:使用 BSD 协议的完全开源免费的项目,支持多种操作系统,功能更强大,可以和多种开源工具配合。

SQLite:开源、轻型、无服务器、零配置,一个数据库就只是一个文件,在应用程序内执行操作,占用资源小,可用于嵌入式或小型应用。

应用场景

Oracle 多用于银行等高要求的领域,要求不高的比如互联网行业多用 MySQL 和 PostgreSQL,而 SQLite 用于嵌入式或作为应用程序内的数据库使用,SQL Server 用于 Window 服务器。

HBase (宽表、列式存储、键值对存储、NoSQL、OLTP)

基于 Hadoop 的 HDFS 分布式文件系统

分布式数据库,需要 ZooKeeper 作为节点间的协调器

支持宽表,支持非结构化数据,不需要预定义列和数据类型

列式存储,每个 HFile 文件只存储一个列族的数据,一个列族可以有多个 HFile,而 HFile 内部按 Key-Value 格式存储,其中 Key 是 rowkey, column family, column, timestamp 的组合并且按 rowkey 在 HFile 中按序存储,而 value 就是 Column Cell 的值

支持海量数据 (千亿级数据表)

数据先写入内存,达到阀值再写入磁盘,性能好,占用内存大

不支持 SQL,不支持 Join,有自己专用的语句,支持增删改查

自动分区、负载均衡、可线性扩展

自动故障迁移

强一致性 (每个分区 Region 只由一个 Region Server 负责,容易实现强一致性)

CP 模型 (不保证可用性,每个 Region 只由一个 Region Server 负责,Server 挂了得做迁移导致暂时不可用)

不支持事务、二级索引

应用场景

组件比较多,比较重,适用于已有的 Hadoop 平台,适用于海量宽表数据、需要增删改查、OLTP 的场景

Phoenix (基于 HBase 的数据库引擎、关系型、OLTP)

嵌入到 HBase 的 Region Server 的数据库引擎

支持 SQL

支持 Join

支持事务 (需要在定义表的时候配置)

支持二级索引

支持撒盐

支持 JDBC

应用场景

用于强化 HBase,主要作为 OLTP,查询性能要求不高的话也可作为 OLAP,多用于 HDP (HDP 有集成 Phoenix)

Cassandra (宽表、键值对存储、NoSQL、OLTP)

无单点故障:Cassandra 节点按环形排列,没有中心节点,每个节点独立互联地扮演相同角色,每个节点都可以接受读写请求,数据可以有多个副本存储在多个节点,节点之间通过 Gossip (P2P) 协议交换状态信息,集群中有若干节点配为种子节点,用于新加入的节点获取集群拓扑结构并启动 Gossip 协议

提供类 SQL 语言 CQL

适合结构化、非结构化数据

Table 需要定义 Partition Key、Clustering Key、以及普通列,其中 Partition Key 用于分区和排序,即按照 Partition Key 的 Hash Token 决定了数据被分配到哪个节点,并且在节点内也是按该 Hash Token 按序存储的,有相同 Partition Key 的数据会存在一起,并且按照 Clustering Key 排序存储,有点类似于 HBase 的 RowKey、ColumnFamily、Column,不过 HBase 是相同 CF 存一起,内部再按 RowKey 排序存储,再取 Column 值 (Column 值不排序),而 Cassandra 是先按 Partition Key 的 Token 排序存储,内部再按 Clustering 排序存储,再取普通 Column 的值 (Column 值不排序)

高度可扩展,允许添加硬件、节点以提高数据容量,同时保持快速的响应时间

通过 Consistency 命令可以配置一致性级别,主要是通知客户端操作前,必须确保的 replica 的成功数量

Cassandra 采用的是最终一致性,是 CAP 理论里的 AP

Cassandra 不支持 Join 和子查询

应用场景

主要用于 OLTP,要求不高的话也可以作为 OLAP 使用,和 HBase 比需要的组件比较少,维护比较容易

Redis (基于内存的 Key-Value 的 NoSQL 数据库,OLTP)

由 C 语言编写

支持多种数据类型如 strings,hashes,lists,sets,sorted sets,bitmaps,hyperloglogs,geospatial 等

操作原子性,保证了两个客户端同时访问服务器将获得更新后的值

数据存储在内存中

可以配置持久化,周期性的把更新数据写入磁盘,或周期性地把修改操作写入追加记录文件,也可以关闭持久化功能,将 Redis 作为一个高效的网络缓存数据功能使用

支持主从同步,数据可以从主服务器向任意数量的从服务器同步,从服务器可以是关联其他从服务器的主服务器,这使得 Redis 可执行单层树复制,存盘可以有意无意的对数据进行写操作,由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录,同步对读取操作的可扩展性和数据冗余很有帮助

支持消息的发布/订阅(Pub/Sub)模式

单线程模式,即网络 IO、数据读写,都由一个线程完成,正因为如此保证了原子性、稳定性、代码容易维护,之所以单线程不影响性能,是因为数据都在内存,操作本来就高效,当然这里的单线程指网络 IO、数据读写这个主功能,实际上还有其他线程,比如周期性写入硬盘的线程

高版本在网络 IO 这块使用了多线程 (因为在高并发操作时,网络 IO 成为了瓶颈),但读写操作还是单线程 (操作内存数据性能还是非常高的,能应付高并发场景)

应用场景

通常作为高性能内存数据库、缓存、消息中间件等使用

memcached (基于内存的 Key-Value 的 NoSQL 数据库,OLTP)

开源、高性能、分布式的基于内存的 Key-Value 数据存储,作用类似于 Redis

存储 String/RawData,不定义数据结构 (Redis 有 hash、list、set 等多种结构)

数据通常由 key,flags,expire time,bytes,value 组成

服务端基本上只能简单的读写数据,服务端能支持的操作比较少

包含 Server 组件和 Client 组件,可以有多个 server 但 server 之间是独立的,没有同步广播等机制,需要选择哪个 server 由 client 的 API 决定的

数据只在内存,不会落到硬盘

没有安全机制

协议简单性能高效

应用场景

memcached 比较简单,作为纯粹的 Key-Value 缓存性能会比 Redis 好些,但功能没有 Redis 强大

MongoDB (文档数据库,NoSQL,OLTP)

之所以说是文档数据库,是因为它的数据是以 JSON 文档的形式存储

MongoDB 的概念和很多数据库不一样,它的 collection 相当于表,document 相当于行,field 相当于列,比如:

db.user.insert( { "name": "Lin", "age": 30 "address": { "street": "Zhongshan Road", "city": "Guangzhou", "zip": 510000 }, "hobbies": ["surfing", "coding"] })

这是一条插入语句,这里的 db 是指当前数据库,user 就是 collection 相当于表,insert 语句里面的 JSON 就是 document 相当于其他数据库的行,name,age,street 这些就是 field 相当于列

相同的文档可以插入多次而不会被覆盖,实际上 mongodb 会自动创建 \_id 字段作为 primary key,并分配不同的数值,所以不会重复,也可以 insert 的时候指定 \_id,但如果 \_id 已经存在则会报错

可以看到,mongodb 是非结构化数据,不需要预定义 collection,也不需要预定义数据结构

提供丰富的查询表达式

支持二级索引,自动负载平衡,读效率比写高

支持分布式、支持故障恢复、数据冗余、分片、水平扩展

可以配置存储引擎,WiredTiger Storage Engine (默认) 会做内存到文件的映射以提高性能,但内存消耗大,In-Memory Storage Engine (企业版支持) 只存在内存,不会落盘

高版本支持 Join,支持事务

支持安全认证功能

提供扩展,比如实现可视化的工具,实现 BI 集成的工具

应用场景

mongodb 更适用于高度非结构化,或者源数据就是 JSON,每条数据比较大,以 OLTP 为主的场景,不适合于事务要求比较高,或比较复杂的大数据量的查询的场景,另外由于 mongodb 的语法和其他数据库差异比较大,需要一定的学习成本

Hive (基于 HDFS 的数据库引擎、关系型、OLAP)

Hive 是基于 Hadoop 的一个数据仓库工具

数据存储在 HDFS,创建表的时候要通过 STORED AS 命令指定存储格式比如 TEXTFILE、ORCFILE、PARQUET,也可以通过 STORED BY 命令指定为 HBase,可以创建新表也可以创建已有 HBase 表的映射

查询通过 MapReduce、Spark 等作业完成

提供了类 SQL 查询语言 HQL (HiveQL),支持用户定义函数 (UDF)

高版本支持事务 (需要创建表时指定)

支持海量数据

结构化数据

支持增删改查

应用场景

不适合于 OLTP,主要作为 OLAP 用于大数据批量查询使用,需要有 Hadoop 平台

Impala (基于 HDFS、HBase、Kudu 存储,并行计算,关系型,OLAP)

Cloudera 开发的基于内存的分布式并行计算的数据库查询引擎

主要由 C++ 实现,和 Hadoop 的交互使用 JNI

Impala 使用和 Hive 一样的 metadata、SQL、ODBC driver、UI,这样在提高了 HDFS 的 SQL 查询性能的同时,又提供了相似的用户使用体验

和 Hive 一样可以通过 STORED AS 指定 HDFS 的存储格式比如 TEXTFILE、ORCFILE、PARQUET

通过 Hive 操作的表,需要手动同步到 Impala

Impala 不仅 SQL 和 Hive 一样,实际上元数据也存在 Hive 中

表数据除了 HDFS,也可以存储到 HBase,但需要在 HBase 建表,然后在 Hive 通过 STORED BY 建立映射表,由于 Impala 和 Hive 使用一样的 metadata,在 Hive 建好表后,只要在 Impala 执行刷新命令 INVALIDATE METADATA,就可以看到对应的 HBase 表

支持 Join、Aggregate 等功能

支持 JDBC、ODBC

和 Hive 不同,Impala 不依赖于 MapReduce,而是在每个 HDFS DataNode 上运行自己的引擎实现并行处理

Impala 的并行处理引擎主要由 state store、catalog service、多个 impala daemon 组成

每个 impala daemon 都可以接收 client 的请求,impala daemon 由 query planner、query coordinator、query executor 组成,planner 接收 client 的 SQL 查询,然后分解为多个子查询,由 coordinator 将子查询分发到各个 daemon 的 executor 执行,daemon 获取 HDFS、HBase 数据、计算、然后返回给 coordinator,然后由 coordinator 聚合后将最终结果返回给 client

Impala 是无中心结构,每个 daemon 都可以接受连接查询,可以通过 HA Proxy 实现多个 daemon 的负载均衡

state store 用于收集监控各个 daemon 的状态

catalog service 将 SQL 做出的元数据变化通知给集群中所有的 impala daemon

Impala 的计算都在内存进行,对内存要求比较高

Impala 在 2.8 以后才支持 update 操作,但是只限于 Kudu 存储,需要安装 Kudu,并通过 STORED AS 指定 Kudu 作为数据库的存储,Kudu 是 Cloudera 开发的列式存储管理器,目的是做 OLAP,并且平衡 HDFS 和 HBase 的性能,Kude 的随机读写性能比 HDFS(比如 Parquet)好,但是比 HBase 差,而大数据量查询性能比 HDFS(比如 Parquet)差,但比 HBase 好,Kude 和 Impala 高度集成,也可以和 MapReduce/Spark 集成,用 Kudu 替换 HDFS/HBase 这样 Impala 就可以做 update,兼顾 OLAP 和改数据的需求,适合于以 OLAP 为主又有一定的 Update 需求的场景,Kudu 可以配置一致性,采用结构化表数据模型,需要定义主键,不使用 HDFS 而是有自己的组件存储和管理数据, 采用 c++ 没有 full gc 风险

应用场景

Impala 不适合于 OLTP,主要作为 OLAP 用于大数据批量查询使用

需要有 Hadoop 平台和 Hive

性能比 Hive 好很多

作为 OLAP 的性能比 Phoenix 之类的好

主要是 CDH 在推,CDH 有集成 Impala

Presto (基于多种数据源,并行计算,关系型,OLAP)

Facebook 推出的基于内存的分布式并行计算的数据库查询引擎

由 coordinator server、discovery server (通常集成在 coordinator 里,也可以独立)、多个 worker server 组成

coordinator 负责与 client 交互,负责管理 worker,负责解析 statement、规划 query、创建一系列的 stage、再转换成一系列的 task 分发到不同 worker 并发执行

worker 负责执行 task 和处理数据,会通过 connector 获取数据,和其他 worker 交互中间数据,最终结果会由 coordinator 返回给 client

connector 是适配器,使得 Presto 可以访问不同的数据库

内建的 connector 主要是 Hive,此外有很多三方开发的 connector 比如 cassandra、es、kafka、kudu、redis、mysql、postgresql 等等

需要在配置文件配置 catalog,这里 catalog 维护 schema 并通过 connector 指向一个数据源,定位 presto 表都是从 catalog 开始的,比如 hive.test\_data.test 指的是 hive catalog 下的 test\_data schema 下面的 test 表,而 schema 的概念则依赖于具体的 connector,比如对于 mysql 而言,presto 的 schema 就是 mysql 的 schema,而对于 cassandra 而言,presto 的 schema 就是 cassandra 的 keyspace,可以建立多个 catalog 关联同一个 connector 比如环境里有多个 kafka 集群那可以有 kafka1 和 kafka2 两个 catalog

statement 可以认为就是 presto 收到的 sql 语句,然后会解析成 query plan,然后 query 又被分为多个 stages,这些 stages 组成一个树的结构,每个 stage 会聚合计算它下面的其他 stages 的结果,每个 stage 又分为一个或多个 tasks,这些 task 会分发到不同的 worker 并行执行,每个 task 处理不同的数据分片,每个 task 又有一个或多个 driver 并发处理数据

Presto 支持 JDBC 接口,JDBC 的 URL 格式为 jdbc:presto://host:port/catalog/schema 或 jdbc:presto://host:port/catalog 或 jdbc:presto://host:port

支持 Join 查询,并且支持多数据源的 join 查询 (多张大表的 join 可能会影响性能),跨数据源查询的时候需要指定完整的表名即 [catalog].[schema].[table],并且使用 presto://host:port 连接 JDBC,不指定 catalog 和 schema

有限支持子查询

不支持 update 操作

支持安全机制

支持标准的 ANSI SQL

扩展性好

可以和 Tableau 集成

支持 Spark

应用场景

适合有多种数据源的大数据量的 OLAP 查询

性能和 Impala 可能差不多,但支持多种数据源,不依赖 Hadoop

Greenplum (基于多个 PostgreSQL,并行计算,关系型,OLAP)

基于多个 PostgreSQL 的分布式并行计算的数据库查询引擎

内部的 PostgreSQL 有做改动以适应并行分布式计算

主要由一个 master、多个 segments、一个 interconnect 组成

master 维护元数据,接收并认证 client 的链接,接收 SQL 请求,解析 SQL,生成 query plan,并将任务分发到 segments,协调聚合 segments 的返回结果,并将最终结果返回给 client,可以设置 master 为主从配置

每个 segment 有个独立的 PostgreSQL 数据库,每个 segment 负责存储部分数据,并执行相应的查询计算,segment 也可以配置备份机制

Interconnect 是 Greenplum 的网络层,负责 master 和 segment 的链接,以及各个 segment 之间的链接

链接和 SQL 语法都和 PostgreSQL 兼容,支持 JDBC、ODBC

创建表时可以指定是用列存储、行存储、外部表 (数据在其他系统比如 HDFS 而 GP 只存储元数据)

操作外部数据,需要安装 PXF (Platform Extension Framework),有了 PXF 可以支持 Hive、HBase、Parquet、S3、MySQL、ORACLE 等等

支持安全、权限配置

支持分布式事务,支持 ACID,保证数据的强一致性,不是使用锁,而是使用 MVCC (Multi-Version Concurrency Control) 来保证数据一致性

shared-nothing 架构

应用场景

和 Impala、Presto 类似都是并行内存计算,但 Greenplum 性能可能稍差一点点,并且 Greenplum 还分开源版和商业版,有的功能只有商业版才支持

Kylin (基于 Hive、HBase,并行计算,关系型,多维度、预计算 OLAP)

传统 OLAP 根据数据存储方式的不同分为 ROLAP(Relational OLAP)以及 MOLAP(Multi-Dimension OLAP),ROLAP 以关系模型的方式存储数据,优点在于体积小,查询方式灵活,缺点是每次查询都需要对数据进行聚合计算,而 Kylin 属于 MOLAP

Kylin 将数据按维度的不同组合,提前计算好结果,形成 Cube (立方体) 结构,这样查询速度快,缺点是数据量不容易控制,N 个维度可以有 2**N 种组合,可能会出现维度爆炸的问题,而且数据有改动的话需要重新计算

比如有 Phone 和 Country 两张维度表,以及 Sale 事实表 (明细表),取手机品牌、国家、日期作为三个维度,有 (null)、(品牌)、(国家)、(日期)、(品牌、国家)、(品牌、日期)、(国家、日期)、(品牌、国家、日期) 共 8 种组合,可以提前计算好这 8 种 group by 组合的 sale 的各种汇总信息 (sum、count 等),一个维度组合的一个汇总信息称为一个 cuboid,所有的 cuboid 合起来就被称为一个 Cube

Kylin 的数据源可以是 Hive 或 Kafka (Json 格式消息,key 就是列名)

Kylin 的预计算结果存到 HBase,RowKey 就是各种维度的组合,相应的明细汇总存在 Column 中,这样 SQL 就变成对 RowKey 的扫描,并进一步的对 Column 计算 (如果需要的话),这样查询性能自然就提升了,可以支持亚秒级查询

Kylin 支持 ODBC,JDBC,RESTful API 等接口

Kylin 可以和 Tableau、PowerBI 等 BI 工具集成

使用步骤如下

创建 Project

同步 Hive 表或 Kafka 表

创建 Data Model

创建并命名 Model

选择 Fact Table (事实表) 和 Lookup Table (查找表,主要是维度信息),以及 Join 字段

从 Fact Table 和 Lookup Table 中挑选维度列 (可以被 Cube 做 group by)

从 Fact Table 选择指标列 (可以被 Cube 做 aggregation)

从 Fact Table 选择用于日期分区的列,不需要就留空

添加 Filter (可以被 Cube 用来做 Where 操作)

创建 Cube

创建并命名 Cube,并选择要关联的 Data Model

添加维度列 (必须从 Data Model 配置的维度列中选择)

添加指标列 (必须从 Data Model 配置的指标列中选择)

共有 8 种 aggregation 操作可以配置给指标列:SUM, MAX, MIN, COUNT, COUNT\_DISTINCT, TOP\_N, EXTENDED\_COLUMN and PERCENTILE (如果要查 avg 实际上就是用 sum 除以 count 得出,所以这里不需要配置 avg 等可以通过预计算结果进一步计算出的操作)

build Cube,实际是通过 MapReduce/Spark 计算,任务完成后结果会写入 HBase

build 成功后即可直接用 SQL 查询了,实际是根据维度查 RowKey,然后把 Column 存的聚合结果取出,如果必要的话再做进一步计算

如果数据源有改动,需要重新 build Cube

可以看到 Kylin 是一个纯粹的 OLAP 工具,通过预计算提升查询性能,但无法及时反应出数据源的改变,预计算可能很耗时并且可能会占用大量空间,且需要和 Hadoop 集成

基于预计算的 OLAP 数据查询引擎还有 Druid

ClickHouse (列存储,向量化计算,并行计算,OLAP)

俄罗斯企业 Yandex 开发的 OLAP 数据库

列存储对于 OLAP 的好处

由于 OLAP 经常是在大量数据列中检索少量列,如果采用行存储,意味着要逐行扫描,并且每行都很大,而采用列存储只需要扫描要检索的列,能减少 IO

假设有的记录并没有存储要检索的列,行存储依然要扫描该记录才知道,而对于列存储则不存在这样的问题,因为没存储,自热而然就不会扫描到

因为同一列的数据类型、大小比较一致,列存储更容易压缩,效率更高,进一步减少 IO

IO 的减少还意味着有更多数据可以被缓存到内存

向量化计算

SIMD (Single Instruction,Multiple Data,单指令流多数据流),现在的 CPU 支持这样的功能,通过一条指令即可利用多核对一组数据 (也就是向量) 进行 CPU 层面的并发计算,适用于纯基础计算的场景,如果有判断、跳转、分支的场景则不合适

ClickHouse 有一个向量计算引擎,尽可能地使用 SMID 指令,批量并行地处理数据,大大提升了处理能力

主要由 C++ 实现

无中心化结构,由一个集群的 server 组成,并且每个 server 都可以接受客户端的链接查询,server 收到请求后会和其他 server 协调做并行计算,每个 server 都是多线程的,server 之间通过 ZooKeeper 协调同步

支持分片(shard),数据可以跨节点存储在不同分片中,一个分片就是一个节点,或者多个节点组成一个有副本备份的分片,由配置文件配置

支持分区,通过 Partition By 命令创建表

分片和分区有时候不好区分,分片更多指的是表的数据分布在不同节点,而且一个节点可以存储多个数据库、多个表的数据,而分区更多指的是按某列数据将一个大表分成多个小表,比如按日期列分区,每天一个分区表,既可以查分区表,也可以查大表

支持副本备份、支持数据完整性

表引擎(Table Engine)

在某个 server 创建的表只是该 server 的本地表,不是分布式的,如果要创建分布式表,需要在每个 server 创建相同名字的表,再在其中一台 server 上创建分布式表(会自动在所有 server 上都创建),这个分布式表是个逻辑表,不真正存储数据,而是映射到各个 server 的本地表,会自动做并行计算

ENGINE = Distributed(cluster\_name, database, table, [sharding\_key])

cluster\_name 是在配置文件里配置的

ENGINE = Memory 数据存在内存

ENGINE = ODBC(connection\_settings, external\_database, external\_table)

ENGINE = JDBC(dbms\_uri, external\_database, external\_table)

ENGINE = MySQL("host:port", "database", "table", "user", "password")

ENGINE = PostgreSQL("host:port", "database", "table", "user", "password")

ENGINE = MongoDB(host:port, database, collection, user, password)

ENGINE = HDFS(URI, format)

ENGINE = Kafka() SETTINGS kafka\_broker\_list = "host:port", kafka\_topic\_list = "topic1"

ENGINE = Log;

ENGINE = TinyLog;

ENGINE = MergeTree()

ENGINE = AggregatingMergeTree()

创建表的时候要通过 Engine 命令指定要用的表引擎,决定如何存储数据

最常用的是 MergeTree 系列引擎,比如

比较轻量级的 Log 系列引擎

允许从其他数据源查询,比如

特殊类型,比如

分布式

通常使用 MergeTree 存储,数据可以快速地按序 append 到一颗 MergeTree 的后面,后台会做合并和压缩操作,这样提升了数据插入的性能

主索引,数据按 Primary Key 排序

也可以在创建表时通过 Order By 指定排序的字段

支持二级索引,也叫跳数索引 data skipping index,比如 minmax 索引,会统计每一段数据内某列数据(或是某个表达式)的最大值和最小值,检索的时候可以依据 minmax 决定是否跳过这段数据(感觉比较怪,性能应该比重建一张索引表的做法要差吧)

支持 TTL,包括列级别、行级别、分区级别的 TTL

支持 HTTP、TCP 接口

支持 JDBC、ODBC

有三方工具支持将其他数据库如 PG 的数据导入 ClickHouse

有三方工具支持和一些可视化工具如 Grafana、DBeaver、Tabix 集成

有三方工具支持 Kafka、Flink、Spark 等

支持 SQL,支持 group by、order by、join、部分子查询等功能

支持 array、json、tuple、set 等复杂数据类型

支持近似计算,比如计算平均值,可以取部分数据计算,这样提升了性能,但降低了准度

自适应 Join 算法,比如优先使用 Hash-Join,如果有多张大表则自动改用 Merge-Join

安全机制、基于 Role 的权限控制

支持错误恢复、扩展性好

不足的地方

对高并发的支持不足

没有成熟的事务功能

修改、删除数据的性能比较差,并且仅提供有限支持

Primary Key 是采用稀疏索引,即索引只能指向一段数据,具体的数据还得一条条查,所以如果是查少量数据,或者查询单条数据,性能会比较差

不依赖 Hadoop、列存储、向量化、并行、多线程、多存储引擎

单表查询性能极好,比 Impala、Presto 之类的要好很多

多表查询性能差些,比 Impala、Presto 之类的要差

Elasticsearch (倒索引、分词、搜索引擎)

Elastic Stack 是一组组件包括 Elasticsearch、Logstash、Filebeat、Kibana 等

Elasticsearch 是基于 Apache Lucene 开发的搜索引擎,主要由 Java 开发

Elasticsearch 集群主要由 master、data、ingest、coordinating 节点组成

每个节点可以同时配置多种角色,比如即是 master 又是 data,但在大型集群中,通常每个节点只负担一种功能

coordinating 是每个节点都会有的功能,不需要配置,即无论 master 还是 data 都会有 coordinating 功能,用于接收 client 的读写请求,然后将请求转发给相应节点处理,然后将处理结果合并后返回给 client,在大型集群中为了不对 master/data 等节点造成太大压力,可以配置多个专门的 coordinating,通过将 role 配置为空或是将 master、data、ingest 设置为 false (取决于不同版本) 即可,这样这些 coordinating 节点就只负责接收响应 client 请求不做其他工作,就像是一个反向代理的负载均衡一样,能提高并发性能

master 负责管理整个集群,负责对 index 的创建删除等管理操作,决定数据要分片到哪个节点,管理其他节点的状态等等,可以配置多个 master 做 HA,需要单数个,至少要 3 个,系统实际上自动选举其中一个做 master,如果该 master 挂了,会从其他配置为 master 的节点中重新选举一个,master 的配置可以低一些

data 负责存储、计算、处理数据,对资源要求比较高,data 还可以进一步配置,指定节点用于存储 hot data、warm data、cold data 等等

Ingest 是可选节点,专门用于针对某些数据和操作,做流水线预处理

Elasticsearch 的数据存储主要由 index,type,document 组成

index 就类似于 SQL 中的一个数据库,可以直接创建 index,也可以通过 index template 作为模板创建 index,模板通常用于具有相同配置的多个 index,比如每天都建立一个 index 用于存储当天的日志

type 就类似于 SQL 中的表 (这种说法不完全对,官方有澄清过,因为不同的 type 并不是完全独立无关的),早期版本中,一个 index 下可以有多个 type,从 6.0 开始只能有一个 type,从 7.0 开不建议使用 type 这个概念,据说从 8.0 开始将完全不支持 type

document 就是像是 SQL 中的一行记录,document 使用的是非结构化的数据,由 JSON 格式表示,JSON 的每个 field 就相当于一个列

每个 document 会一个唯一的 \_id,如果不指定则由系统自动生成

每个 document 有严格递增的序号 \_seq\_no 代表文档写入/更新的先后顺序

每个 document 有 \_version 字段,每次更改这个字段就加 1

可以先建立 index,也可以不提前建立 index,写入数据时自动创建

不需要提前设置 document 的 field,写入数据时自动创建,每个 document 的 field 可以不一样,也可以提前设置 field,使得每个 document 的 field 必须一样

Elasticsearch 会自动对所有 field 建立索引,并且会自动做分词处理,即把一个句子比如 "hello world" 自动分成 "hello" 和 "world" 两个词做索引,没有分词的 "hello world" 是 keyword,大小限制是 256,经过分词的比如 "hello" 是一个 text

Elasticsearch 采用倒索引 (inverted index),主要由三部分组成:Term Index (单词索引)、Term Dictionary (单词字典)、Posting List (索引项列表)

Term Index 存在内存中,不保存所有单词,而是保存单词的前缀,比如保存 he、wor、ad、sar 等等,指出以这些前缀作为开头的单词在 Term Dictionary 中的起始位置,这样 Term Index 的大小就比较小,可以存在内存中,并且可以帮助快速定位要读取的内容在 Term Dictionary 中的位置,可以大大减少磁盘 IO 的次数

Term Dictionary 存在磁盘中,通常单词量会非常大,记录着 index 的每个单词到 Posting List 的关联关系,通过 B+ 树或 Hash 表方式以满足高性能的插入与查询

Posting List 记录着:出现该单词的所有 document 的 id,该单词在该 document 中出现的次数,该单词在该 document 中的位置

搜索引擎通过这样的倒排序索引找到对应的 document,再做进一步处理

由于会对所有 field 做索引,数据量会非常大

数据先写入内存,然后定期将数据落盘形成一个个 segment,当 segment 多了之后会进行 merge 组成更大的 segment

为了防止内存中还没落盘的数据丢失,会写入 translog,类似于 HBase 的 WAL,其实这也需要磁盘 IO,会影响性能,但比落盘数据要简单

segment 由多个文件组成,记录着元数据、field 信息、正排序的索引信息 (即从 document id 找到相应的数据)、field 数据 (相当于按列存储)、倒排序索引数据、等等

支持 REST API 接口操作,通过在 Body 的 JSON 格式数据提高丰富的语法,可以执行很多操作

支持 Event Query Language (EQL):for event-based time series data, such as logs, metrics, and traces, 通过 REST API 的 Body 指定

支持 JDBC、ODBC,这里 table 指定的是 index,而 column 指定的是 field,这里的 SQL 不支持 JOIN

不支持事务

跨表查询不完全支持,而且要定义父子文档,要定义 join 类型的 field,比较复杂

读写有一定延时性,即写入的数据无法立刻索引到,至少要等 1 秒钟

和传统的数据库有一定差异,需要一定的学习成本

部分 REST API 操作的例子

curl localhost:9200 # 查看集群基本信息curl localhost:9200/_cluster/health?pretty # 查看集群健康 (pretty 是 JSON 格式化输出)curl localhost:9200/_cluster/state?pretty # 查看集群状态curl localhost:9200/_cluster/stats?pretty # 查看统计信息curl localhost:9200/_nodes?pretty # 查看节点信息curl localhost:9200/_cat # 列出可以查看的各种信息 (cat 命令列出的信息比较简化)curl localhost:9200/_cat/healthcurl localhost:9200/_cat/nodescurl localhost:9200/_cat/indicescurl -X PUT "http://localhost:9200/my_index/my_doc/123" -H "Content-Type: application/json" -d "{ "name": "Lin", "title": "senior designer", "age": 30}" ## 指定 document id 为 123,会自动创建 my_index,my_doc 以及各个 fieldscurl -X POST "http://localhost:9200/my_index/my_doc" -H "Content-Type: application/json" -d "{ "name": "Wang", "title": "senior designer", "age": 35}" ## 由系统自动创建 document idcurl -X POST "http://localhost:9200/my_index/my_doc_2" -H "Content-Type: application/json" -d "{ "name": "n_1", "type": "t_1", "value": 1}" ## 报错,不允许 index 下有两个 typecurl -X POST "http://localhost:9200/my_index/_doc" -H "Content-Type: application/json" -d "{ "name": "n_1", "type": "t_1", "value": 1}" ## 允许,_doc 就是 my_doc(可以一开始就只用 _doc 而不需要 type 名)curl -X POST "http://localhost:9200/my_index/_doc" -H "Content-Type: application/json" -d "{ "name": "Li", "address": {"city": "guangzhou", "district": "tianhe"}}" ## 允许新的 fields,允许复杂类型,貌似不支持列表 "address": ["xxx"]curl localhost:9200/my_index/my_doc/123?pretty ## 查看 id 为 123 的记录curl localhost:9200/my_index/my_doc/_search?pretty ## 查看所有记录curl localhost:9200/my_index/_search?prettycurl localhost:9200/_all?pretty ## 列出所有 index 的设置和 mapping (就是 field 的信息)curl localhost:9200/my_index?pretty ## 查看 my_index 的设置和 mappingcurl localhost:9200/my_index/_mapping?pretty ## 查看 my_index 的 mappingcurl -X GET -H "Content-Type: application/json" localhost:9200/my_index/_search?pretty -d "{ "query": { "term":{ "name":"lin" } }}" ## 简单的查询,还有更多的语法和功能curl -X GET -H "Content-Type: application/json" localhost:9200/my_index/_search?pretty -d "{ "query": { "term":{ "title.keyword":"senior designer" } }}" ## 默认查询的是分词,如果要查没分词的,应该加上 keywordcurl -X GET -H "Content-Type: application/json" localhost:9200/my_index/_search?pretty -d "{ "query": { "term":{ "address.city":"guangzhou" } }}" ## 查询嵌套的字段curl localhost:9200/_search?pretty ## 在所有 index 中查找curl -H "Content-Type: application/json" localhost:9200/my_index/_analyze?pretty -d "{ "analyzer" : "standard", "text" : "This is the apple"}" ## 如何分析一段文字curl -X PUT "http://localhost:9200/my_index_3" ## 创建 indexcurl -X PUT -H "Content-Type: application/json" "http://localhost:9200/my_index_6" -d "{ "settings": { "number_of_shards": 1, "number_of_replicas": 1 }, "mappings": { "properties": { "name": { "type": "text" }, "title": { "type": "text" }, "value": { "type": "long" } } }}" ## 创建 index 同时指定 field,在版本 7 以后不需要指定 type (不需要指定 my_doc 之类的)

应用场景

适合于以搜索为主的业务场景,最开始 ELK (Elasticsearch + Logstash + Kibana) 就是用于日志收集和搜索

Spark/Flink

数据库虽然强大,但如果遇到复杂的逻辑计算也是无能为力,这种情况,就需要有专门的计算工具

Spark 和 Flink 都是高性能的并行计算引擎,Flink 更偏向实时流业务,Spark 更偏向批处理业务,都可以用来高效地处理数据

BI

数据通常要可视化,比较常用的 BI 工具有 Tableau (收费的) 和 PowerBI

整体系统架构

K8S (容器部署) + SpringCloud (微服务) + Keycloak (认证) + Kafka (数据流) + Spark/Flink (数据处理) + ELK (日志收集) + PG/MySQL (基本数据) + NoSQL-OLTP (大数据量业务数据) + OLAP (分析业务) + BI (数据可视化)

推荐内容