分布式数据库,是近些年来颇受关注的领域。一方面随着数据规模不断增大,数据使用场景更为多样,对底层数据库的要求越来越高;另一方面对数据库的可用性、扩展能力等也都提出更高的要求。分布式数据库的出现,恰好满足了上述两方面的诉求。但当用户选择使用分布式的第一个问题,就是如何将之前基于单机或集中式数据库设计的数据结构迁移到分布式环境中,核心点就在于数据分片的设计。这其中的核心要点有两个:一是选择什么字段或字段组合作为分片键;二是使用什么分片算法来分片。本文尝试说明第一个问题。
1. 是否需要设计分片
是否需要设计分片?相信是大家首要提出的问题,作为一种新架构的出现,分布式数据库确实可以解决一些场景问题,但数据分片是在设计之初就需考虑。是否有一种更为透明的方式解决分片问题呢,这就引出一个概念—“数据分布独立性”。它是指用户或用户程序使用分布式数据库如同使用集中式数据库那样,不必关系全局数据的分布情况。也就是说全局数据的逻辑分片、片段的物理位置等情况对用户和用户应用程序式透明的。因此分布式数据库中分布独立性也成为分布透明性。一个分布式数据库系统可能提供的分布透明性层次越高,用户编写应用程序越容易。比较理想的情况是,软件工程师完全不必处理应用级别的分片,因为这并不是他们真正的工作。然而,理想很丰满,现实很骨感。完全透明的数据分片,在部分场景下仍然会面临问题,如低延迟、复杂业务、多中心等,这些场景仍然希望能够精准控制数据分片规则甚至物理位置。因此,如何设计分片策略也就成为新环境下DBA不得不面对的问题,起码是在相当长的时间是如此。正如同数据库初学者需学习的范式理论一样,未来数据分片的设计也是考验架构、研发及DBA的基本要求之一。
这里稍微展开说明下,分布式数据库的实现有两种主要方式,一是分库分表;一是NewSQL。两者的对比可参考下图。具体在分片上,前者通常可提供更为灵活精准的分片控制,后者则在分片易用性上更具优势。
2. 如何选择分片字段
数据分片的设计上需考虑两点:一是分片字段的选择;二是对应的分片算法。后续将重点谈及分片字段选择上,下面先简单说明下分片算法问题。
1).分片算法
分片算法,常规的有LIST、RANGE、HASH或自定义算法。根据各拆分算法特点,可进行选择。若范围均匀可采用HASH,冷热数据明显可采用RANGE等。同时可配合一些特性化设计,如采用二级映射方式解决扩缩容问题、特征编码字段满足多特征拆分等。针对最为常见的两个算法描述如下:
- RANGE
通过数据的范围进行分库分表,是最朴实的一种分库方案,它也可以和其他分库分表方案灵活结合使用。当需要使用分片字段进行范围查找时,RANGE分片策略可快速定位数据进行高效查询。大多数情况下有效避免跨分片查询的问题。在后期扩容时,也比较方便,只需要添加节点即可,无需对其他分片的数据进行迁移。但这种分布方式容易存在数据热点问题。
- HASH
虽然分库分表的方案众多,但是Hash分库分表是最大众最普遍的方案。随机分片其实并不是随机,也遵循一定规则。通常采用HASH取模的方式进行分片拆分,所以有时候也称为离散分片。随机分片的数据相对均匀,不容易出现热点和并发访问的瓶颈。但涉及后面数据迁移的话,不太方便。可使用一致性HASH算法在很大程度上避免此问题。此外,离散分片也容易面临跨分片查询的复杂问题。
2).分片字段
分片字段的选择,需涉及的因素很多,列个脑图分类下。下面将针对各因素详细说明:
- 数据结构:主键或唯一键
主键及唯一键,是数据库作为常见的约束,其是为了保证非空且唯一性。在分布式环境下,通常建议将主键或唯一键字段作为分片键或分片键的一部分,否则无法完成约束校验;当然也有产品支持单独约束校验。这里有个引申问题,就是主键设计问题,在分布式数据库架构下,尽量不要用自增作为表的主键,自增性能很差、安全性不高、不适用于分布式架构。通常可使用如UUID或全局发号器(雪花算法)。总之,用有序的全局唯一替代自增,是分布式数据库主键的推荐做法。
- 数据结构:索引
通过分片键可以把 SQL 查询路由到指定的分片,但是在现实的生产环境中,业务还要通过其他的索引访问表。针对原有系统的索引需要有单独策略。通常的策略是通过索引表的方式,即将索引转化为另一张分片表,对于查询来说通过二次查询解决,但显然这种方式不够优雅。因此,最优的设计不是创建一个索引表,而是将索引数据融入到分片键的信息中,这样通过查询的列就能直接知道所在的分片信息。效率更高,查询可以提前知道数据对应的分片信息,只需 一次查询就能获取想要的结果。总结下,索引对分片字段的选择上,没有直接影响。对于高频索引查询,可以考虑通过分片键的设计上进行增强。也可以通过全局二级索引(有些分布式数据库支持)来实现或针对分片内做普通索引。
- 数据结构:字段类型
作为分片键的字段,通常选择较为简单的数据类型字段,可以提高效率,如常见的数字、日期、文本等,对复杂字段如LOB、JSON等不推荐使用。此外,在设计时需考虑分片字段的类型稳定,尽量不要发生DDL变更。
- 数据特征:表规模
表规模是是否使用分片的关键因素之一。一旦表做了分片后,势必会造成一定的“功能退化”,如能采取其他方式缩小表的大小,尽量优先其他方式。可通过表的全生命周期规划,如常规的数据归档、压缩、转储、清理策略,减少数据量;或者利用数据库内置的如表分区、垂直分表等策略有效减小表的大小。
- 数据特征:离散度
这里说的离散度是指按某个字段或字段组合后,应用分片算法后,数据是否足够分散。数据分片的初衷就是减少表的规模,尽量做到数据打散是其根本原则之一。这里需要统计数据拆分后离散程度,尽量选择能充分打散的字段作为分片键。这里需注意,如果选择字段是带有业务特征,还要关注未来业务变化对它的影响。
- 访问特征:可变化性
选择固定、不再变化的字段作为分片键。虽然有些分布式数据库也支持分片键的修改,但毕竟修改后会涉及数据移动,成本代价很高;还是优选不变的字段为好。
- 访问特征:事务隔离
尽量选择按字段拆分后的数据,对数据的变化处理可集中在分片内解决。这样大量的业务变化是可以通过本地事务完成,开销比全局的要小很多,效率也高。
- 访问特征:数据过滤与关联
如此字段经常作为数据筛选字段被频繁使用,且选择率很好,可优先作为分片字段。另一种情况则是作为与其他关联表联合使用,优先选择那些参与到关联操作的字段为佳。尽量是数据在关联后,能在本地完成join动作,减少数据shuffle或上移汇聚类的操作。可通过对系统中执行的SQL进行统计分析,选择出需要分片那个表中最频繁被使用到或最为重要的字段类分片。这其中可能包含一些来自OLAP类的查询,可将此部分SQL排除在外。
- 分片字段顺序
如涉及多个字段作为分片键的话,顺序因素一般没有什么影响。主要是针对分片算法,可利用字段做分片即可。但对于复合分片的情况,是要考虑分片字段的主次关系的。