影响数据库选择的因素
- 数据量:是否海量数据,单表数据量太大会考验数据库的性能
- 数据结构:结构化 (每条记录的结构都一样) 还是非结构化的 (不同记录的结构可以不一样)
- 是否宽表:一条记录是 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/health
curl localhost:9200/_cat/nodes
curl localhost:9200/_cat/indices
curl -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 以及各个 fields
curl -X POST 'http://localhost:9200/my_index/my_doc' -H 'Content-Type: application/json' -d '{
"name": "Wang", "title": "senior designer", "age": 35
}' ## 由系统自动创建 document id
curl -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 下有两个 type
curl -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?pretty
curl localhost:9200/_all?pretty ## 列出所有 index 的设置和 mapping (就是 field 的信息)
curl localhost:9200/my_index?pretty ## 查看 my_index 的设置和 mapping
curl localhost:9200/my_index/_mapping?pretty ## 查看 my_index 的 mapping
curl -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"
}
}
}' ## 默认查询的是分词,如果要查没分词的,应该加上 keyword
curl -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' ## 创建 index
curl -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 (数据可视化)