分库分表--ShardingSphere

一、背景

从性能方面来说,由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降; 同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

从可用性的方面来讲,服务化的无状态性,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。 而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。

从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于 DBA 的运维压力就会增大。 数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在 1TB 之内,是比较合理的范围。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。 数据分片的拆分方式又分为垂直分片和水平分片

二、分库分表

1、垂直分片(表按照业务分到不同库当中)

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。 垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。

下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案。

2、水平分片(字段拆分到不同表/库当中)

水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是数据分片的标准解决方案。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。

三、数据分片

1、表概念

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

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

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

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

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

(6)单表:指所有的分片数据源中仅唯一存在的表。 适用于数据量不大且无需分片的表。注意:符合以下条件的单表会被自动加载:

  • 数据加密、数据脱敏等规则中显示配置的单表

  • 用户通过 ShardingSphere 执行 DDL 语句创建的单表

其余不符合上述条件的单表,ShardingSphere 不会自动加载,用户可根据需要配置单表规则进行管理。

2、分片(注意分片算法和分布式主键)

(1)分片键:用于将数据库(表)水平拆分的数据库字段。 例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,Apache ShardingSphere 也支持根据多个字段进行分片。

(2)分片算法:用于将数据分片的算法,支持 =>=<=><BETWEEN 和 IN 进行分片。 分片算法可由开发者自行实现,也可使用 ShardingSphere 内置的分片算法语法糖,灵活度非常高。

(3)自动化分片算法:分片算法语法糖,用于便捷的托管所有数据节点,使用者无需关注真实表的物理分布。 包括取模、哈希、范围、时间等常用分片算法的实现。

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

1)标准分片算法:用于处理使用单一键作为分片键的 =INBETWEEN AND><>=<= 进行分片的场景。

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

3)Hint 分片算法:用于处理使用 Hint 行分片的场景。

(5)分片策略:包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。 真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。

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

(7)分布式主键

 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。现阶段可使用UUIDV7、SNOWFLAKE等不重复且部分有序的ID方案可以得到一定解决。

四、分布式事务

ShardingSphere 支持的事务包括:本地事务、XA事务和柔性事务三种类型

1、本地事务

适用于单一数据节点的事务控制,符合ACID特性(原子性、一致性、隔离性、持久性),由关系型数据库原生支持。 ‌但是不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交,且无法回滚。

2、XA事务

基于分布式事务规范(Java Transaction API),通过Atomikos等事务管理器实现,需配置Spring框架的PlatformTransactionManager。 ‌但是不支持以下三种情况:

  • 服务宕机后,在其它机器上恢复提交/回滚中的数据;
  • MySQL 事务块内,SQL 执行出现异常,执行 Commit,数据保持一致;
  • 配置 XA 事务后,存储单元名称最大长度不超过45个字符。
3、BASE 事务(柔性事务)

采用最终一致性模型,支持TCC和Saga模式,通过Seata实现反向操作自动生成。 ‌

五、读写分离(读/写操作分别路由至主/从库)

1、概念

对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。

通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。

与将数据根据分片键打散至各个数据节点的水平分片不同,读写分离则是根据 SQL 语义的分析,将读操作和写操作分别路由至主库与从库。读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统性能。

2、数据不一致

读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题。 这包括多个主库之间的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。 下图展现了将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。

读写分离的方案通常需要两个层面来配合:
(1)数据层

需要将Master数据实时同步到Slave,通常使用三方工具来实现。例如:Canal框架、Mysql自带的主从同步方案。

(2)应用层

是把读/写请求分发到不同库中,其本质上也是数据路由的功能,可以采用ShardingSphere实现。

1)主库:添加、更新以及删除数据操作所使用的数据库,目前仅支持单主库

2)从库:查询数据操作所使用的数据库,可支持多从库。

3)主从同步:将主库的数据异步的同步到从库的操作,由于主从同步的异步性,从库与主库的数据会短时间内不一致。

4)负载均衡策略:通过负载均衡策略将查询请求疏导至不同从库。分为:操作轮询、事务轮询、操作随机、事务随机、强制主库五种负载均衡策略

六、分库分表带来的问题

1、富查询
采用分库分表之后,如何满足跨越分库的查询?

使用ES的宽表借助分库网关+分库业务虽然能够实现多维度查询的能力,但整体上性能不佳且对正常的写入请求有一定的影响。业界应对多维度实时查询的最常见方式便是借助 Es;

2、深分页问题

按游标查询,或者叫每次查询都带上上一次查询经过排序后的最大 ID;

3、数据倾斜

主要是因为分表时候字段选择不合适造成的某个表数据量比其他表数据量大很多导致的。主要带来三个问题:性能瓶颈、资源利用率低、影响其他业务。主要解决方案就是:

(1)数据分库基础上再进行二次分表就行了。

(2)物理隔离,根据业务把数据隔离到单独库中,但是成本较高不推荐。

4、分布式事务

跨多库的修改及多个微服务间的写操作导致的分布式事务问题?

(1)强一致性

1)两阶段提交 (2PC - Two-Phase Commit) / XA

阶段一(准备阶段):事务协调者询问所有参与者(各个数据库或服务):“可以提交吗?” 参与者执行事务,但不提交,只是锁定资源并返回“Ready”或“Abort”。

阶段二(提交/回滚阶段):如果所有参与者都回复“Ready”,协调者发送“Commit”指令,所有参与者正式提交;如果任何一个参与者回复“Abort”或超时,协调者发送“Rollback”指令,所有参与者回滚。

优点:
强一致性,概念简单。

缺点:
性能差:同步阻塞,在整个过程中资源都被锁定,响应时间长。
协调者单点故障:协调者宕机会导致参与者一直处于不确定状态。
数据不一致风险:在第二阶段,如果部分参与者收到Commit并成功,但网络故障导致其他参与者没收到,就会产生数据不一致。

适用场景:对一致性要求极高,且并发量不大、业务逻辑简单的内部系统。在微服务间使用需谨慎。

2)Seata AT 模式

Seata 是阿里开源的分布式事务解决方案,其 AT (Auto Transaction) 模式是对2PC的优化。

一阶段:
Seata 拦截业务SQL,解析语义,生成更新前后的快照(undo_log) 并保存。然后执行业务SQL并提交。这里已经提交了本地事务,所以资源锁定时长很短。

二阶段:
如果全局事务成功,Seata TC (事务协调器) 通知各参与者,各参与者异步删除undo_log。
如果全局事务失败,TC通知各参与者,各参与者根据一阶段生成的undo_log进行补偿回滚。

优点:相比传统2PC,性能更好,对业务代码侵入性较低(通过注解@GlobalTransactional)。

缺点:需要引入Seata中间件,增加了架构复杂性;仍然有全局锁,高并发下有一定性能损耗。

适用场景:希望用较低侵入性实现强一致性的项目。
(2)最终一致性

核心思想:将一个大事务拆分成多个小的本地事务,通过异步协调和补偿机制来保证最终一致性。

1)Saga 模式

工作原理:将一个分布式事务拆分成一系列本地事务,每个本地事务都有一个对应的补偿事务。
正向操作:T1, T2, T3, ... Tn 依次执行。
补偿操作:C1, C2, C3, ... Cn,用于撤销对应正向操作的影响。
执行过程中,如果T3失败,则会按照逆序执行已成功步骤的补偿操作:C2 -> C1。

实现方式:
编排(Choreography):各个服务通过事件(Event)进行沟通,没有中央协调器。服务A完成T1后发布事件,服务B监听事件并执行T2,以此类推。松耦合,但流程复杂时难以跟踪。
协作者(Orchestration):引入一个中央协调器(Orchestrator),它负责按顺序调用各个服务的接口,并在失败时调用补偿接口。流程清晰,易于管理和监控,但协调器可能成为瓶颈。

优点:无全局锁,性能高,吞吐量大。

缺点:业务设计复杂,需要为每个正向操作设计补偿操作;存在“脏写”风险(其他事务可能在Saga未完成时读到中间状态)。

适用场景:业务流程长、参与者多、对性能要求高的场景。例如:电商下单、酒店机票预订。

2)TCC 模式

TCC (Try-Confirm-Cancel) 也是一种补偿型事务,但它要求业务提供三个接口。

工作原理:
Try 阶段:尝试执行,完成所有业务的检查和资源预留(例如,冻结库存、扣减优惠券、冻结资金)。此阶段不进行最终操作。
Confirm 阶段:如果所有参与者的Try都成功,则进入Confirm阶段,确认执行真正的业务操作(例如,扣减真实库存、使用优惠券、扣款)。此操作需满足幂等性。
Cancel 阶段:如果任何一个参与者的Try失败,则进入Cancel阶段,取消执行,释放Try阶段预留的资源(例如,解冻库存、返还优惠券、解冻资金)。此操作也需满足幂等性。

优点:性能较好,在Try阶段就锁定了资源,保证了强隔离性。

缺点:对业务侵入性极强,需要将每个业务操作改造为三个接口,代码量增加。

适用场景:对一致性要求高,且业务模型本身适合“资源预留”的场景,如金融、交易等。

3)本地消息表 + 消息队列(最大努力通知)

工作原理:
当应用服务A需要执行一个分布式事务时,它先在自己的数据库中执行本地业务操作,并在同一数据库事务中,插入一条消息到“本地消息表”。
有一个独立的“消息Relay”进程,轮询本地消息表,将新消息发送到消息队列(如RabbitMQ, Kafka, RocketMQ)。
服务B从消息队列消费消息,执行自己的本地业务操作。
如果服务B执行成功,一切正常。
如果服务B执行失败,消息会被重试。如果达到最大重试次数仍然失败,该消息会被投入死信队列,由人工或专门的补偿服务进行干预和处理。

优点:实现简单,依赖成熟的MQ,对业务侵入性相对较小,保证了最终一致性。

缺点:消息可能被重复消费,要求服务B的操作是幂等的。属于“最大努力通知”,不保证100%实时一致。

适用场景:绝大多数需要最终一致性的业务场景,如支付成功后通知订单系统、用户注册后发送欢迎邮件等。

方案一致性性能复杂度侵入性适用场景
2PC/XA强一致内部系统、传统银行,一致性要求极高的非高并发场景
Seata AT强一致Java技术栈,希望用框架解决强一致性问题
Saga最终一致长流程、高并发业务,如电商、旅行预订
TCC最终一致极高金融、交易等涉及资金、需要强隔离性的场景
本地消息表最终一致主流推荐,大多数业务场景,如状态同步、事件通知
5、如何将老数据进行迁移 

双写不中断迁移步骤:

(1)线上系统里所有写库的地方,增删改操作,除了对老库增删改,都加上对新库的增删改;

(2)系统部署以后,还需要跑程序读老库数据写新库,写的时候需要判断updateTime;

(3)循环执行,直至两个库的数据完全一致,最后重新部署分库分表的代码就行了;

6、如何生成自增的id主键

(1)使用redis

(2)并发不高可以单独起一个服务,生成自增id

(3)设置数据库step自增步长可以支撑水平伸缩

(4)UUID适合文件名、编号,但是不适合做主键,现阶段UUIDV7也可以作为主键

(5)snowflake雪花算法,综合了41时间(ms)、10机器、12序列号(ms内自增)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值