Logo
对象设计

对象设计 #

分片 #

核心概念 #

数据分片,是指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。数据分片的拆分方式又分为垂直分片和水平分片。

垂直分片 #

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案。

垂直分片原理图

垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。

水平分片 #

水平分片又称为横向拆分。相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。

水平分片原理图

水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是数据分片的标准解决方案。

名词解释 #

表是透明化数据分片的关键概念。SphereEx-DBPlusEngine 通过提供多样化的表类型,适配不同场景下的数据分片需求。

  • 逻辑表

相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0 到 t_order_9 ,他们的逻辑表名为 t_order 。

  • 真实表

在水平拆分的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9 。

  • 表组

指分片规则一致的主表和子表。使用表组进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。例如:t_order 表和 t_order_item 表,均按照 order_id 分片,并且使用 order_id 进行关联,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。

举例说明,如果 SQL 为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系,并且使用 order_id 进行关联后,路由的 SQL 应该为 2 条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中 t_order 在 FROM 的最左侧,ShardingSphere 将会以它作为整个绑定表的主表。所有路由计算将会只使用主表的策略,那么 t_order_item 表的分片计算将会使用 t_order 的条件。因此,绑定表间的分区键需要完全相同。

  • 广播表

指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

  • 单表

指所有的分片数据源中仅唯一存在的表。适用于数据量不大且无需分片的表。 如单表增长到一定规模,SphereEx-DBPlusEngine 也支持将其转为分片表。

  • 数据节点

数据分片的最小单元,由数据源名称和真实表组成。例:ds_0.t_order_0 。 逻辑表与真实表的映射关系,可分为均匀分布和自定义分布两种形式。

  • 均匀分布

指数据表在每个数据源内呈现均匀分布的态势,例如:

db0
├── t_order0
└── t_order1
db1
├── t_order0
└── t_order1

数据节点的配置如下:

db0.t_order0, db0.t_order1, db1.t_order0, db1.t_order1

  • 自定义分布 指数据表呈现有特定规则的分布,例如:
db0
├── t_order0
└── t_order1
db1
├── t_order2
├── t_order3
└── t_order4

数据节点的配置如下:

db0.t_order0, db0.t_order1, db1.t_order2, db1.t_order3, db1.t_order4

分片设计 #

  • 分片键

用于将数据库(表)水平拆分的数据库字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。SQL 中如果无分片字段,将执行全路由,性能较差。除了对单分片字段的支持,SphereEx-DBPlusEngine 也支持根据多个字段进行分片。分片字段的选择,涉及下列因素。

  • 数据结构:主键或唯一键

    主键及唯一键,是数据库作为常见的约束,其是为了保证非空且唯一性。在分布式环境下,通常建议将主键或唯一键字段作为分片键或分片键的一部分,否则无法完成约束校验。这里有个引申问题,就是主键设计问题,在分布式数据库架构下,尽量不要用自增作为表的主键,自增性能很差、安全性不高、不适用于分布式架构。通常可使用如 UUID 或全局发号器(雪花算法)。总之,用有序的全局唯一替代自增,是分布式数据库主键的推荐做法。

  • 数据结构:索引

    通过分片键可以把 SQL 查询路由到指定的分片,但是在现实的生产环境中,业务还要通过其他的索引访问表。针对原有系统的索引需要有单独策略。通常的策略是通过索引表的方式,即将索引转化为另一张分片表,对于查询来说通过二次查询解决,但显然这种方式不够优雅。因此,最优的设计不是创建一个索引表,而是将索引数据融入到分片键的信息中,这样通过查询的列就能直接知道所在的分片信息。效率更高,查询可以提前知道数据对应的分片信息,只需 一次查询就能获取想要的结果。总结下,索引对分片字段的选择上,没有直接影响。对于高频索引查询,可以考虑通过分片键的设计上进行增强。也可以通过全局二级索引(有些分布式数据库支持)来实现或针对分片内做普通索引。

  • 数据结构:字段类型

    作为分片键的字段,通常选择较为简单的数据类型字段,可以提高效率,如常见的数字、日期、文本等,对复杂字段如 LOB、JSON 等不推荐使用。此外,在设计时需考虑分片字段的类型稳定,尽量不要发生DDL变更。

  • 数据特征:表规模

    表规模是是否使用分片的关键因素之一。一旦表做了分片后,势必会造成一定的“功能退化”,如能采取其他方式缩小表的大小,尽量优先其他方式。可通过表的全生命周期规划,如常规的数据归档、压缩、转储、清理策略,减少数据量;或者利用数据库内置的如表分区、垂直分表等策略有效减小表的大小。

  • 数据特征:离散度

    这里说的离散度是指按某个字段或字段组合后,应用分片算法后,数据是否足够分散。数据分片的初衷就是减少表的规模,尽量做到数据打散是其根本原则之一。这里需要统计数据拆分后离散程度,尽量选择能充分打散的字段作为分片键。这里需注意,如果选择字段是带有业务特征,还要关注未来业务变化对它的影响。

  • 访问特征:可变化性

    选择固定、不再变化的字段作为分片键。虽然有些分布式数据库也支持分片键的修改,但毕竟修改后会涉及数据移动,成本代价很高;还是优选不变的字段为好。

  • 访问特征:事务隔离

    尽量选择按字段拆分后的数据,对数据的变化处理可集中在分片内解决。这样大量的业务变化是可以通过本地事务完成,开销比全局的要小很多,效率也高。

  • 访问特征:数据过滤与关联

    如此字段经常作为数据筛选字段被频繁使用,且选择率很好,可优先作为分片字段。另一种情况则是作为与其他关联表联合使用,优先选择那些参与到关联操作的字段为佳。尽量是数据在关联后,能在本地完成join动作,减少数据 shuffle 或上移汇聚类的操作。可通过对系统中执行的 SQL 进行统计分析,选择出需要分片那个表中最频繁被使用到或最为重要的字段类分片。这其中可能包含一些来自 OLAP 类的查询,可将此部分 SQL 排除在外。

  • 分片字段顺序

    如涉及多个字段作为分片键的话,顺序因素一般没有什么影响。主要是针对分片算法,可利用字段做分片即可。但对于复合分片的情况,是要考虑分片字段的主次关系的。

  • 分片算法

    分片算法,常规的有 LIST、RANGE、HASH 或自定义算法。根据各拆分算法特点,可进行选择。若范围均匀可采用 HASH,冷热数据明显可采用 RANGE 等。同时可配合一些特性化设计,如采用二级映射方式解决扩缩容问题、特征编码字段满足多特征拆分等。针对最为常见的两个算法描述如下:

    • RANGE

    通过数据的范围进行分库分表,是最朴实的一种分库方案,它也可以和其他分库分表方案灵活结合使用。当需要使用分片字段进行范围查找时,RANGE 分片策略可快速定位数据进行高效查询。大多数情况下有效避免跨分片查询的问题。在后期扩容时,也比较方便,只需要添加节点即可,无需对其他分片的数据进行迁移。但这种分布方式容易存在数据热点问题。

    • HASH

    虽然分库分表的方案众多,但是 Hash 分库分表是最大众最普遍的方案。随机分片其实并不是随机,也遵循一定规则。通常采用 HASH 取模的方式进行分片拆分,所以有时候也称为离散分片。随机分片的数据相对均匀,不容易出现热点和并发访问的瓶颈。但涉及后面数据迁移的话,不太方便。可使用一致性 HASH 算法在很大程度上避免此问题。此外,离散分片也容易面临跨分片查询的复杂问题。

  • 自定义分片算法

提供接口让应用开发者自行实现与业务实现紧密相关的分片算法,并允许使用者自行管理真实表的物理分布。自定义分片算法又分为:

  • 标准分片算法

用于处理使用单一键作为分片键的 = 、IN 、BETWEEN AND 、> 、< 、>= 、<= 进行分片的场景。

  • 复合分片算法

用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。

  • Hint 分片算法

用于处理使用 Hint 行分片的场景。

  • 自动分片算法

用于处理基于范围的自动分片场景,可实现自动创建及管理分片。

  • 强制分片

对于分片字段并非由 SQL 而是其他外置条件决定的场景,可使用 SQL Hint 注入分片值。例:按照员工登录主键分库,而数据库中并无此字段。SQL Hint 支持通过 SQL 注释方式使用。详情请参见强制分片路由。

使用规范 #

虽然 SphereEx-DBPlusEngine 希望能够完全兼容所有的 SQL 以及单机数据库,但分布式为数据库带来了更加复杂的场景。

  • SQL 支持程度

兼容全部常用的路由至单数据节点的 SQL;路由至多数据节点的 SQL 由于场景复杂,分为稳定支持、实验性支持和不支持这三种情况。

  • 稳定支持

稳定支持的 SQL

全面支持 DML、DDL、DCL、TCL 和常用 DAL。支持分页、去重、排序、分组、聚合、表关联等复杂查询。

  • 实验性支持

实验性支持的 SQL

实验性支持特指使用 Federation 执行引擎提供支持。该引擎处于快速开发中,用户虽基本可用,但仍需大量优化,是实验性产品。

  • 不支持

不支持的 SQL

数据加密 #

背景说明 #

作为数据防护是否能够成功实施的关键,企业需要在关键数据的安全性、保持应用系统的功能可用性,和系统可维护性方面综合考虑,来确定适合企业需要的加密保护的技术方案。下表罗列常用加密技术及应对的安全风险。

安全风险
磁盘加密文件加密数据库加密(TDE)数据库加密(三方加固)应用层加密
防止磁盘丢失引起数据泄露YYYYY
防止系统root账户和管理员账户访问NYYYY
控制数据库管理员访问数据NNNYY
抵抗定向威胁供给APT造成数据泄露NYYYY
提供细粒度访问记录、遵从法规NYNYY
确保备份数据和数据快照存储NYYYY
非结构化数据和文件的保护YYNNY
防止硬件和数据库厂商“偷窥”NYNYY
  • 磁盘加密

磁盘采用的块级别加密技术,例如AWS的EBS,阿里云的ECS等都支持磁盘加密。这种加密最大的好处在于,它对操作系统是透明的。性能在加密后较加密前有所降低,根据上层应用的不同性能下降幅度各异。

  • 文件加密

通过堆叠在其它文件系统之上(如 Ext2, Ext3, ReiserFS, JFS 等),为应用程序提供透明、动态、高效和安全的加密功能。典型的是用于加密指定的目录。需要关注的是这种加密方式可能会产生较大的性能损失。

  • 数据库加密-TDE

透明数据加密 TDE,是数据库提供的一种加密技术,即对数据文件执行实时 I/O 加密和解密。数据在写入磁盘之前进行加密,从磁盘读入内存时进行解密。TDE 不会增加数据文件的大小,开发人员无需更改任何应用程序。其对应密钥管理也是由数据库提供的API或组件实现,应用透明。在某些场景下磁盘或系统无法对用户开放(如云环境)的条件下,这种方式就比较适合。

  • 数据库加密-三方加固

数据库加密还有种方式是采用对数据库进行三方加固的方式,即将第三方专业数据库加密厂商的产品内置在数据库之中,提供透明数据加密能力。所谓透明是指,用户应用系统不需要做改造即可使用,且具有权限的用户看到的是明文数据,完全无感。此外,还可以增强原有数据库的安全能力,如提供三权分立、脱敏展示等。

  • 应用层加密

应用层加密,可以说是一种终极方案,其可保证在数据到达数据库之前,就已经做了数据加密,可实时保护用户敏感数据。这里关键需要提供应用透明性,保证应用无需改造或仅需少量改造。这种方式完全由用户自己控制,无需信任任何三方厂商提供的数据安全保障,得到充分的自由度和灵活性。例如可以跨多数据库提供统一安全加密策略等。

名词解释 #

  • 逻辑列

用于计算加解密列的逻辑名称,是 SQL 中列的逻辑标识。 逻辑列包含密文列(必须)、查询辅助列(可选)和明文列(可选)。

  • 逻辑列类型(dataType)

用于定义逻辑列的类型,例如 INT NOT NULL,VARCHAR(200) DEFAULT NULL 等,具体可以参见官方文档中各种方言字段的定义。例如 MySQL create statement 中 column_definition 的定义(https://dev.mysql.com/doc/refman/8.0/en/create‑table.html)。

  • 密文列(cipherColumn)

加密后的数据列。

  • 密文列类型(cipherDataType)

用于定义密文列的类型,同逻辑列类型。

  • 查询辅助列(assistedQueryColumn)

用于查询的辅助列。对于一些安全级别更高的非幂等加密算法,提供不可逆的幂等列用于查询。

  • 查询辅助列类型(assistedQueryDataType)

用于定义查询辅助列类型,同逻辑列类型。

  • 明文列(plainColumn)

存储明文的列,用于在加密数据迁移过程中仍旧提供服务。

在洗数结束后可以删除。

  • 明文列类型(plainDataType)

用于定义明文列类型,同逻辑列类型。

  • 加密洗数(encrypting)

批量对数据库中未加密数据进行加密。

  • 解密洗数(decrypting)

批量对数据库中加密数据进行解密。

实现原理 #

  • 数据加密原理

假如数据库里有一张表叫做 t_user,这张表里实际有两个字段 pwd_plain,用于存放明文数据、pwd_cipher,用于存放密文数据、pwd_assisted_query,用于存放辅助查询数据,同时定义 logicColumn 为 pwd。 那么,用户在编写 SQL 时应该面向 logicColumn 进行编写,即 INSERT INTO t_user SET pwd = '123'。SphereEx-DBPlusEngine 接收到该 SQL,通过用户提供的加密配置,发现 pwd 是 logicColumn,于是便对逻辑列及其对应的明文数据进行加密处理。 SphereEx-DBPlusEngine 将面向用户的逻辑列与面向底层数据库的明文列和密文列进行了列名以及数据的加密映射转换。 如下图所示:

即依据用户提供的加密规则,将用户 SQL 与底层数据表结构割裂开来,使得用户的 SQL 编写不再依赖于真实的数据库表结构。 而用户与底层数据库之间的衔接、映射、转换交由 SphereEx-DBPlusEngine 进行处理。下方展示了使用加密模块进行增删改查时,其中的处理流程和转换逻辑。

  • 云端密钥管理

将密钥管理在云端,例如利用 AWS 的 secretKey 功能保存密钥,从而提升整个加密的安全性和便捷性。程序在初始化加密算法时,会同 AWS 建立连接,从而获取存储在 AWS 中的相关密钥,然后将密钥存储在算法中。在整个数据加密的过程中不涉及与云端的网络交互。

  • 加密洗数

加密洗数通过 DistSQL 来触发洗数任务,程序收到洗数任务的请求之后,会根据当前的洗数规则以及加密规则创建洗数任务。洗数任务主要由两部分构成,一部分是查询任务,一部分是更新任务,查询任务负责查询用户的表数据并获取需要加密的明文字段,然后推送到通道中;更新任务则从通道中获取数据,并加密更新。整个任务的创建以及执行过程都会与治理中心进行交互,因此用户可以通过相关 DistSQL 来查询任务进度以及清理任务。

使用规范 #

  • 支持项
    • 对数据库表中某个或多个列进行加解密;
    • 兼容所有常用 SQL。
  • 不支持项
    • 需自行处理数据库中原始的存量数据;
    • 加密字段无法支持比较操作,如:大于、小于、ORDER BY、BETWEEN、LIKE 等;
    • 加密字段无法支持计算操作,如:AVG、SUM 以及计算表达式。
  • 其他
    • 加密规则中配置的加密列、辅助查询列、LIKE 查询列等需要和数据库中的列保持大小写一致。