背景
传统的将数据集中存储至单一节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足海量数据的场景。
从性能方面来说,由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降; 同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。
从可用性的方面来讲,服务化的无状态性,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。 而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。
从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于 DBA 的运维压力就会增大。 数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在 1TB 之内,是比较合理的范围。
在传统的关系型数据库无法满足互联网场景需要的情况下,将数据存储至原生支持分布式的 NoSQL 的尝试越来越多。 但 NoSQL 对 SQL 的不兼容性以及生态圈的不完善,使得它们在与关系型数据库的博弈中始终无法完成致命一击,而关系型数据库的地位却依然不可撼动。
数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。
通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。 数据分片的拆分方式又分为垂直分片和水平分片。
核心概念
1、逻辑表
水平拆分的数据库(表)的相同逻辑和数据结构表的总称。例:订单数据根据主键尾数拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名为t_order。
2、真实表
在分片的数据库中真实存在的物理表。即上个示例中的t_order_0到t_order_9。
3、数据节点
数据分片的最小单元。由数据源名称和数据表组成,例:ds_0.t_order_0。
4、绑定表
指分片规则一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。举例说明,如果SQL为:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
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条,它们呈现为笛卡尔积:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
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);
在配置绑定表关系后,路由的SQL应该为2条:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
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的条件。故绑定表之间的分区键要完全相同。
5、广播表
指所有的分片数据源中都存在的表,表结构和表中的数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。
6、分片键
用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。
7、分片算法
通过分片算法将数据分片,支持通过=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。
目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。
-
精确分片算法
对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。
-
范围分片算法
对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合StandardShardingStrategy使用。
-
复合分片算法
对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。
-
Hint分片算法
对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
8、分片策略
包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。
-
标准分片策略
对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。
-
复合分片策略
对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。
-
行表达式分片策略
对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如:
t_user_$->{u_id % 8}表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。 -
Hint分片策略
对应HintShardingStrategy。通过Hint指定分片值而非从SQL中提取分片值的方式进行分片的策略。
-
不分片策略
对应NoneShardingStrategy。不分片的策略。
分布式事务如何实现的?
在 Sharding JDBC 中,如果涉及到多个库的多张表进行更新,而且这些库使用了不同的数据库连接,事务的生效会受到一些限制。 Sharding JDBC 提供了两种事务类型,分别是本地事务(LOCAL)和分布式事务(XA)。具体情况取决于您的事务配置。
-
本地事务(LOCAL): 如果您配置了本地事务,那么每个分片库都会使用独立的本地事务。这意味着在一个事务中无法跨越多个分片库。当您在一个事务中涉及到多个库的更新操作时,如果它们分别属于不同的数据源(不同的数据源代表不同的数据库连接),则每个数据源将会开启自己的本地事务,而无法保证全局事务的一致性。这种情况下,您需要谨慎处理跨库的事务问题。
yamlCopy code spring: shardingsphere: transaction: type: LOCAL -
分布式事务(XA): 如果您配置了分布式事务(XA),Sharding JDBC 将尝试使用 XA 事务协议来协调多个分片库的事务。这样,跨越多个分片库的事务将能够保持全局一致性。但需要注意的是,XA 事务会引入一些性能和配置上的开销,同时要求数据库引擎本身支持 XA 事务。
yamlCopy code spring: shardingsphere: transaction: type: XA使用XA 实现分布式事务, 需要注意一下几点: 然而,需要注意以下几点:
-
数据库引擎支持: XA 事务要求数据库引擎本身支持 XA 协议。确保您使用的数据库引擎能够正确地实现 XA 事务。
-
性能开销: XA 事务会引入一些性能开销,因为它需要协调多个分布式资源管理器来保持事务的一致性。这可能会导致性能略微下降。
-
异常处理: 在使用 XA 事务时,需要处理可能发生的异常情况,例如网络故障、数据库故障等。合理的异常处理机制是确保系统鲁棒性的关键。
XA 实现分布式事务的步骤
- 事务协调器(Transaction Coordinator): Sharding JDBC 使用事务协调器来协调多个参与者的事务。协调器负责协调事务的开始、提交和回滚等操作。Sharding JDBC 中的协调器会根据 XA 协议的规范执行相应的协调操作。
- 参与者(Transaction Participant): 参与者是分布式事务中的各个数据库。在 Sharding JDBC 中,每个分片库都是一个参与者。这些参与者负责执行分片库上的本地事务,并在事务协调器的指导下协调全局事务。
- 全局事务标识(Global Transaction Identifier): 在 XA 协议中,全局事务标识是用于唯一标识一个全局事务的。Sharding JDBC 使用全局事务标识来关联多个分片库上的事务,确保它们属于同一个全局事务。
- 事务状态记录: Sharding JDBC 需要记录和管理每个分片库上的事务状态。这通常涉及将事务状态信息存储在可靠的持久性存储介质中,以便在系统故障或恢复时能够正确地协调全局事务。
- 事务提交和回滚: 当全局事务需要提交时,事务协调器会向各个参与者发出提交请求。如果任何一个参与者在提交过程中发生错误,整个全局事务将回滚。反之,如果所有参与者成功提交,全局事务将被提交。
Mysql的主从同步延迟如何处理
mysql5.7之后使用GTID使用并发组提交,实现主从复制的低延迟。如果是不能接受延迟数据的话,最好还是不要使用主从复制。
分库解决什么问题?
- 并发连接数
分表解决什么问题?
- 提高查询性能: 随着数据量的增长,单一表中的数据可能会变得非常庞大,导致查询性能下降。通过将表水平拆分成多个小表,可以提高查询性能,因为每个小表的数据量较小,查询速度更快。
- 优化存储空间: 大表需要更多的磁盘空间和内存来存储和检索数据,而且索引的维护也会变得更加复杂。通过分表,可以将数据均匀分布到多个表中,每个表的数据量减小,从而节省存储空间和降低索引维护的开销。
- 提高并发处理能力: 单一表上的高并发写入操作可能导致锁竞争和性能瓶颈。通过分表,可以将写入操作分散到多个小表上,从而提高并发处理能力,减少锁的竞争。
- 简化数据维护: 在某些场景下,数据需要定期归档或清理,例如删除过期日志。如果数据分布在多个小表中,对于每个小表的数据维护变得更为简单,不需要处理整个大表。
- 降低锁冲突的可能性: 当多个并发事务同时访问同一个表时,可能会发生锁冲突。通过分表,可以将数据分散到多个表中,减小了多个事务同时访问同一表的概率,降低了锁冲突的可能性。
- 更好地支持水平扩展: 在某些情况下,当系统需要水平扩展时,通过将数据分散到多个表中,可以更容易地在多个节点上进行水平扩展。