聊聊分布式 SQL 数据库Doris(六)

发布时间 2023-11-27 14:22:57作者: 又见阿郎

负载均衡

此处的负载均衡指的是FE层的负载均衡.

当部署多个 FE 节点时,用户可以在多个 FE 之上部署负载均衡层来实现 Doris 的高可用。官方文档描述: 负载均衡

实现方式

实现方式有多种,如下列举。

开发者在应用层自己进行重试与负载均衡。

JDBC Connector

发现一个连接挂掉,就自动在其他连接上进行重试。应用层代码重试需要应用自己配置多个 doris 前端节点地址。

通过 JDBC Connector实现自动重试与均衡负载:

jdbc:mysql:loadbalance://[host:port],[host:port].../[database][?propertyName1][=propertyValue1][&propertyName2][=propertyValue
Proxy SQL

架设一层代理,通过ProxySQL代理层实现负载。

Nginx 反向代理

通过网关反向代理实现负载。

数据倾斜

由于数据在分区或分桶或者是源数据端的数据存储就不均匀,因此在导入到Doris中分布不均匀,导致Doris的性能和稳定性不好。

原因

Doris出现数据倾斜的原因有多种,其中一些常见的原因包括:

  • 数据分布不均匀:某些列的取值范围过大或过小,导致数据在分区或分桶时分布不均匀。这可能是由于业务逻辑、数据源分布或其他因素导致的。
  • 集群负载不均衡:如果Doris集群中的节点性能存在差异,可能会导致数据倾斜。例如,某些节点的计算能力或存储容量比其他节点低,这可能会导致数据集中到这些节点上。
  • 数据导入不均匀:在数据导入过程中,如果没有均衡地分配数据到各个实例或分区,可能会导致数据倾斜。例如,某些实例或分区导入的数据量比其他实例或分区多,这可能会导致数据集中到这些实例或分区上。
  • 热点数据访问:如果某些数据被频繁地访问或更新,可能会导致这些数据集中到某些节点上,从而引起数据倾斜。

解决

为了解决Doris的数据倾斜问题,可以尝试以下方法:

  • 合理设计表结构:在创建表时,应该尽量避免使用取值范围过大的列作为分区键或分桶列。如果必须使用这类列,可以考虑使用复合分区或哈希分布来均匀地分布数据。
  • 调整数据倾斜列的取值范围:如果某些列的取值范围过大或过小,可以考虑将它们的数据分布调整到更合理的范围内。这可以通过数据清洗、数据变换或数据分箱等方式实现。
  • 使用动态分区:Doris支持动态分区功能,可以根据需要自动调整分区数量和分桶数量。通过合理设置动态分区的参数,可以使得数据更加均匀地分布在各个分区中。
  • 使用虚拟列:Doris支持虚拟列功能,可以根据需要自动计算并存储一些列的值。通过合理设置虚拟列的表达式和存储方式,可以使得数据更加均匀地分布在各个分区中。
  • 调整Doris参数设置:Doris的一些参数设置可能会影响数据倾斜问题的处理效果。例如,可以通过调整副本数量、并发写入数量等参数来优化Doris的性能和稳定性。
  • 避免单个节点负载过高:在部署Doris集群时,应该避免将大量数据集中到单个节点上。可以通过调整副本数量、分区策略等方式来均衡地分布数据到各个节点上。
  • 使用负载均衡技术:可以使用负载均衡技术来将数据访问请求分布到各个节点上,从而避免单个节点负载过高的问题。这可以通过使用负载均衡器、DNS轮询等技术来实现。

高并发点查

点查: 是指通过等值条件(例如 WHERE 子句中的等值条件)来查询单个行或单个数据点的查询操作。点查询通常用于检索具有特定键值的行或数据,其特点是通过提供唯一的主键值或唯一索引值来定位并返回一行数据/单个数据点。

在高并发服务场景中,如果用户希望从系统中获取整行数据,对于列存格式引擎,在表宽时,列存格式将大大放大随机读取IO,这就会导致读取性能降低;其次,FE层是对外提供的是访问服务,同时会分析、解析SQL,也可能会导致高并发查询时的高CPU开销。为了解决性能问题,引入了行存、短查询路径、PreparedStatement解决。官方文档描述: 高并发点查

行存

仅仅支持在建表时开启行存模式,但需要额外的空间来存储行存数据。实现逻辑是将行存编码后存在单独的一列中,用于简化行存的实现。在create的property中指定属性:

"store_row_column = "true"

行存(Row Storage)

  • 存储方式:行存以行为单位存储数据,即将每一行的数据存储在一起。
  • 特点:每一行的所有列数据都存储在相邻的位置,形成一个数据块。这种存储方式对于整行的读写操作是高效的,适合于 OLTP(在线事务处理)场景,其中通常需要快速地执行对单个行的操作。
  • 适用场景:适用于需要频繁进行整行读写的场景,如交易处理系统等。

列存(Column Storage)

  • 存储方式:列存以列为单位存储数据,即将同一列的数据存储在一起。
  • 特点:每一列的所有行数据都存储在相邻的位置,形成一个数据块。这种存储方式对于聚合操作和分析查询是高效的,因为查询通常只涉及到部分列的数据。列存适用于 OLAP(在线分析处理)场景,其中通常需要执行复杂的分析查询。
  • 适用场景:适用于需要进行大规模数据分析和聚合查询的场景,如数据仓库和数据分析平台等。

由于列存储是按列存储的,获取整行数据需要从不同列的数据块中进行随机读取,增加了磁盘I/0操作的次数;如果列宽度较大,那么需要读取的数据块数量就会增加,导致随机读取的开销放大;同时较大的列宽导致单个记录的大小较大,需要传输更多的数据量到查询引擎。这会增加网络传输的开销,尤其是在分布式系统中,如果数据分布在多个节点上,点查询可能需要从多个节点传输数据。

Unique 模型下的点查优化

Unique模型支持合并时写入(Merge-On-Write)策略,当开启该策略结合行存时,对于主键的点查会走短路径对SQL执行优化,仅需执行一次RPC查询即可完成。如下示例:

CREATE TABLE `tbl_point_query` (
    `key` int(11) NULL,
    `v1` decimal(27, 9) NULL,
    `v2` varchar(30) NULL,
    `v3` varchar(30) NULL,
    `v4` date NULL,
    `v5` datetime NULL,
    `v6` float NULL,
    `v7` datev2 NULL
) ENGINE=OLAP
UNIQUE KEY(`key`)
COMMENT 'OLAP'
DISTRIBUTED BY HASH(`key`) BUCKETS 1
PROPERTIES (
    "replication_allocation" = "tag.location.default: 1",
    "enable_unique_key_merge_on_write" = "true",
    "light_schema_change" = "true",
    "store_row_column" = "true"
);

注意:

  1. enable_unique_key_merge_on_write应该被开启, 存储引擎需要根据主键来快速点查
  2. 当条件只包含主键时,如select * from tbl_point_query where key = 123,类似的查询会走短路径来优化查询
  3. light_schema_change应该被开启, 因为主键点查的优化依赖了轻量级 Schema Change 中的column unique id来定位列
  4. 只支持单表key列等值查询不支持join、嵌套子查询, where条件里需要有且仅有key列的等值, 可以认为是一种key value查询

PreparedStatement

Doris在FE层提供了与MySQL协议兼容的PreparedStatement特性(目前只支持主键点查)。当PreparedStatement开启时,SQL与其表达式将被提前计算并缓存到Session级别的内存缓存中,后续的查询直接使用缓存对象即可。

示例:

  1. 设置JDBC url并在Server端开启prepared statement
url = jdbc:mysql://127.0.0.1:9030/ycsb?useServerPrepStmts=true
  1. 使用 PreparedStatement
// use `?` for placement holders, readStatement should be reused
PreparedStatement readStatement = conn.prepareStatement("select * from tbl_point_query where key = ?");
...
readStatement.setInt(1234);
ResultSet resultSet = readStatement.executeQuery();
...
readStatement.setInt(1235);
resultSet = readStatement.executeQuery();
...

PreparedStatement 支持使用占位符参数(如?)来表示 SQL 语句中的变量部分。在执行语句之前,可以通过设置参数的方式为占位符提供实际的数值。这有助于防止 SQL 注入攻击,并提高安全性。

开启行缓存

对于前面提到的行存,一行里包括了多列数据,Doris默认支持的列缓存可能被大查询给刷掉,为了增加行缓存命中率,单独引入了行存缓存,行缓存复用了 Doris 中的 LRU Cache 机制来保障内存的使用,通过指定下面的的BE配置来开启

  1. disable_storage_row_cache是否开启行缓存, 默认不开启。
  2. row_cache_mem_limit指定 Row cache 占用内存的百分比, 默认 20% 内存。