第一章 为什么要分库分表

1.1 互联网大并发场景业务下对于OLTP的挑战

面向拥有超高并发,大规模数据存储的互联网OLTP(Online Transaction Processing,在线事务处理)类业务。同时因计算与数据量不断爆发增长,传统的企业级应用急需更强计算能力的在线事务型数据库。

  • 在线业务超高并发,扛不住!
  • 海量业务数据,存不下!
  • 复杂分析查询,性能慢!
  • 单表数据量过大,效率差!
  • 跨实例联机查询,搞不定!

1.2 分库分表才是OLTP的最终形态

  • 通过分库分表,将业务数据及访问压力分摊到多台单机数据库实例上,解决在线业务的超高并发难题。
  • 通过水平拆分可线性扩展数据存储空间,提供 PB 级存储能力。高效解决单机数据库存储瓶颈。
  • 针对在线业务提供 Parallel Query 以及 MPP 并行加速能力,可大幅提升在线业务海量数据下复杂分析查询的执行效率。
  • 数据库单表数量过大后,将导致数据库吞吐能力下降,整体性能迟缓。通过分库分表将单表数据水平拆分至各个MySQL中,有效解决单表数据量膨胀问题。
  • 业务通过使用分库分表即可在不同 RDS 实例多个数据库间进行联合查询及事务操作。可有效避免业务端繁琐复杂的硬代码处理方式,大幅提升业务开发效率。

第二章 为什么会引入分布式事务

2.1 分布式事务的典型场景

这里举一个分布式事务的典型例子——用户下单过程。

当我们的系统采用了微服务架构后,一个电商系统往往被拆分成如下几个子系统:商品系统、订单系统、支付系统、积分系统等。整个下单的过程如下:

  1. 用户通过商品系统浏览商品,他看中了某一项商品,便点击下单。
  2. 此时订单系统会生成一条订单。
  3. 订单创建成功后,支付系统提供支付功能。
  4. 当支付完成后,由积分系统为该用户增加积分。

上述步骤2、3、4需要在一个事务中完成。对于传统单体应用而言,实现事务非常简单,只需将这三个步骤放在一个方法A中,再用Spring的@Transactional注解标识该方法即可。Spring通过数据库的事务支持,保证这些步骤要么全都执行完成,要么全都不执行。但在这个微服务架构中,这三个步骤涉及三个系统,涉及三个数据库,此时我们必须在数据库和应用系统之间,通过某项黑科技,实现分布式事务的支持。

2.2 分布式事务的根源:微服务

2.2.1 什么是微服务

简而言之,微服务架构是一种将单应用程序作为一套小型服务开发的方法,每种应用程序都在其自己的进程中运行,并与轻量级机制(通常是HTTP资源的API)进行通信。这些服务是围绕业务功能构建的,可以通过全自动部署机制进行独立部署。这些服务的集中化管理已经是最少的,它们可以用不同的编程语言编写,并使用不同的数据存储技术。

2.2.2 微服务的优势

  1. 将复杂的业务拆分成多个小的业务,每个业务拆分成一个服务,将复杂的问题简单化。利于分工,降低新人的学习成本。
  2. 微服务系统是分布式系统,业务与业务之间完全解耦,随着业务的增加可以根据业务再拆分,具有极强的横向扩展能力。面对搞并发的场景可以将服务集群化部署,加强系统负载能力。
  3. 服务间采用 HTTP 协议通信,服务与服务之间完全独立。每个服务可以根据业务场景选取合适的编程语言和数据库。
  4. 微服务每个服务都是独立部署的,每个服务的修改和部署对其他服务没有影响。

2.2.3 微服务落地存在的问题

虽然微服务现在如火如荼,但对其实践其实仍处于探索阶段。很多中小型互联网公司,鉴于经验、技术实力等问题,微服务落地比较困难。如著名架构师Chris Richardson所言,目前存在的主要困难有如下几方面:

  1. 单体应用拆分为分布式系统后,进程间的通讯机制和故障处理措施变的更加复杂。
  2. 系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。
  3. 微服务数量众多,其测试、部署、监控等都变的更加困难。

随着RPC框架的成熟,第一个问题已经逐渐得到解决。例如dubbo可以支持多种通讯协议,springcloud可以非常好的支持restful调用。对于第三个问题,随着docker、devops技术的发展以及各公有云paas平台自动化运维工具的推出,微服务的测试、部署与运维会变得越来越容易。

而对于第二个问题,现在还没有通用方案很好的解决微服务产生的事务问题。分布式事务已经成为微服务落地最大的阻碍,也是最具挑战性的一个技术难题。 为此,本文将深入和大家探讨微服务架构下,分布式事务的各种解决方案。

第三章 一些基本概念和基本原理

3.1 读写分离

这个相对比较好理解一些,就是将数据库分为主从库,一个主库(Master)用于写数据,多个从库(Slaver)进行轮询读取数据的过程,主从库之间通过某种通讯机制进行数据的同步,是一种常见的数据库架构。下面这张图就展示了 “一主二从” 的结构:

img

当PolarDB-X存储资源MySQL主实例的读请求较多、读压力比较大时,您可以通过读写分离功能对读流量进行分流,减轻存储层的读压力。

PolarDB-X读写分离功能采用了对应用透明的设计。在不修改应用程序任何代码的情况下,只需在控制台中调整读权重,即可实现将读流量按自定义的权重比例在存储资源MySQL主实例与多个存储资源只读实例之间进行分流,而写流量则不做分流全部到指向主实例。

设置读写分离后,从存储资源MySQL主实例读取属于强读(即实时强一致读);而只读实例上的数据是从主实例上异步复制而来存在毫秒级的延迟,因此从只读实例读取属于弱读(即非强一致性读)。您可以通过Hint指定那些需要保证实时性和强一致性的读SQL到主实例上执行,详情请参见读写分离Hint

通常可以通过事务读取,这样可以将读请求打到主库上执行,保证实施强一致读。

读写分离仅对显式事务(即需要显式提交或回滚的事务)以外的读请求(即查询请求)有效,写请求和显式事务中的读请求(包括只读事务)均在主实例中执行,不会被分流到只读实例。

读写分离带来的一系列问题

大多数互联网数据操作往往都是读多写少,随着数据的增长,数据库的 “读” 会首先成为瓶颈。如果我们希望能线性地提升数据库的读性能和写性能,就需要让读写尽可能的不相互影响,各自为政。在使用读写分离之前我们应该考虑使用缓存能不能解决问题。然后再考虑对数据库按照 “读” 和 “写” 进行分组。读写分离意味着将一体的结构的进行分散,在数据量大、高并发的情景中要考虑以下这些问题:

  1. 如何保证 Master 的高可用,故障转移,熔断限流等。
  2. 读写操作的区分规则,代码层面如何处理好读命令和写命令,尽量无感知无业务入侵。
  3. 数据一致性的容忍度。虽然是数据同步,但是由于网络的不确定性这仍然是一个不可忽视的问题。
  4. 分库

3.2 库级垂直拆分

数据库垂直拆分指的是按照业务对数据库中的表进行分组,同组的放到一个新的数据库(逻辑上,并非实例)中。需要从实际业务出发将大业务分割成小业务。比如商城的整个业务中的用户相关表,订单相关表,物流相关表各自独立分类形成用户系统数据库,订单系统数据库,物流系统数据库,如下图:

img

  • 带来的好处

    • 业务清晰,职责单一 。
    • 易维护,易扩展 。
    • 数据服务化 。
  • 不好的方面

    • 提高了整个应用的复杂度,而且会形成跨库事务。
    • 引发 “木桶效应”,任何一个短板有可能影响整个系统。
    • 部分表关系不能 join只能通过服务相互调用来维系。
    • 甚至由于网络问题引发数据不一致。

在需要进行分库的情况下,通常可优先考虑垂直拆分。

3.3 库级水平拆分

在数据库垂直拆分后遇到单机数据库性能瓶颈之后,就可以考虑数据库水平拆分了。 之所以先垂直拆分才水平拆分,是因为垂直拆分后数据业务清晰而且单一,更加方便指定水平的标准。比如我们对商城业务垂直拆分后的 用户系统 进行水平拆分就比对整个商城业务进行水平拆分好找维度,我们可以根据用户注册时间的区间、用户的区域或者用户 ID 的范围、 hash 等条件,然后关联相关表的记录将数据进行拆分,如果放在整个商城业务上你是以用户为准还是以订单为准都不太好考虑。

我们按照每 100 万为区间对用户系统水平拆分如下:

img

  • 这种拆分的好处在于

    • 单个库的容量可控。
    • 单挑记录保证了数据完整性。
    • 数据关系可以通过 join 维持。
    • 避免了跨库事务 。
  • 缺点:

    • 拆分规则对编码有一定的影响。
    • 不同业务的分区交互需要统筹设计。

3.4 表级垂直拆分

表级垂直拆分可以简单来描述,即大宽表拆成多个小表。数据表垂直拆分就是纵向地把表中的列分成多个表,把表从 “宽” 变“窄”。一般遵循以下几个点进行拆分:

  • 冷热分离,把常用的列放在一个表,不常用的放在一个表。
  • 大字段列独立存放。
  • 关联关系的列紧密的放在一起。

我们把用户表中常用的和不常用的而且大字段分离成两张表:

img

3.5 表级水平拆分

表级别的水平拆分原理和库级别水平拆分原理类似,通常是对于一个库来说的,可以根据主键ID进行hash拆分,或者根据某个指定字段进行拆分。比如下图中user表根据name字段作为拆分键进行拆分:

img

表的水平拆分感觉跟库的水平拆分思想上都是一样的,只不过粒度不同。表结构维持不变。也就是说拆分后数据集的并集等于拆分前的数据集。

3.6 分布式事务

众所周知,数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行,要么全都不执行。这里特别强调了本地事务,也就是目前的数据库只能支持同一个数据库中的事务。但现在的系统往往采用微服务架构,业务系统拥有独立的数据库,因此就出现了跨多个数据库的事务需求,这种事务即为“分布式事务”。那么在目前数据库不支持跨库事务的情况下,我们应该如何实现分布式事务呢?

3.6.1 什么是事务?

事务由一组操作构成,我们希望这组操作能够全部正确执行,如果这一组操作中的任意一个步骤发生错误,那么就需要回滚之前已经完成的操作。也就是同一个事务中的所有操作,要么全都正确执行,要么全都不要执行。

事务的四大特性即 ACID

说到事务,就不得不提一下事务著名的四大特性。

  • 原子性(atomicity :原子性要求,事务是一个不可分割的执行单元,事务中的所有操作要么全都执行,要么全都不执行。
  • 一致性(consistency :一致性要求,事务在开始前和结束后,数据库的完整性约束没有被破坏。
  • 隔离性(isolation :事务的执行是相互独立的,它们不会相互干扰,一个事务不会看到另一个正在运行过程中的事务的数据。
  • 持久性(durability :持久性要求,一个事务完成之后,事务的执行结果必须是持久化保存的。即使数据库发生崩溃,在数据库恢复后事务提交的结果仍然不会丢失。

注意:事务只能保证数据库的高可靠性,即数据库本身发生问题后,事务提交后的数据仍然能恢复;而如果不是数据库本身的故障,如硬盘损坏了,那么事务提交的数据可能就丢失了。这属于『高可用性』的范畴。因此,事务只能保证数据库的『高可靠性』,而『高可用性』需要整个系统共同配合实现。

3.6.2 事务的隔离级别

这里扩展一下,对事务的隔离性做一个详细的解释。

在事务的四大特性ACID中,要求的隔离性是一种严格意义上的隔离,也就是多个事务是串行执行的,彼此之间不会受到任何干扰。这确实能够完全保证数据的安全性,但在实际业务系统中,这种方式性能不高。因此,数据库定义了四种隔离级别,隔离级别和数据库的性能是呈反比的,隔离级别越低,数据库性能越高,而隔离级别越高,数据库性能越差。

事务并发执行会出现的问题

我们先来看一下在不同的隔离级别下,数据库可能会出现的问题:

  • 更新丢失

当有两个并发执行的事务,更新同一行数据,那么有可能一个事务会把另一个事务的更新覆盖掉。

当数据库没有加任何锁操作的情况下会发生。

  • 脏读

一个事务读到另一个尚未提交的事务中的数据。

该数据可能会被回滚从而失效。

如果第一个事务拿着失效的数据去处理那就发生错误了。

  • 不可重复读

不可重复度的含义:一个事务对同一行数据读了两次,却得到了不同的结果。它具体分为如下两种情况:

    • 虚读:在事务1两次读取同一记录的过程中,事务2对该记录进行了修改,从而事务1第二次读到了不一样的记录。
    • 幻读:事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果发生了变化。

不可重复读与脏读的区别?

脏读读到的是尚未提交的数据,而不可重复读读到的是已经提交的数据,只不过在两次读的过程中数据被另一个事务改过了。

3.6.3 数据库的四种隔离级别

数据库一共有如下四种隔离级别:

  • Read uncommitted 读未提交

在该级别下,一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。

因此本级别下,不会出现更新丢失,但会出现脏读、不可重复读。

  • Read committed 读提交

在该级别下,未提交的写事务不允许其他事务访问该行,因此不会出现脏读;但是读取数据的事务允许其他事务的访问该行数据,因此会出现不可重复读的情况。

  • Repeatable read 重复读

在该级别下,读事务禁止写事务,但允许读事务,因此不会出现同一事务两次读到不同的数据的情况(不可重复读),且写事务禁止其他一切事务。

  • Serializable 序列化

该级别要求所有事务都必须串行执行,因此能避免一切因并发引起的问题,但效率很低。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

3.6.4 什么是分布式事务?

到此为止,所介绍的事务都是基于单数据库的本地事务,目前的数据库仅支持单库事务,并不支持跨库事务。而随着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。在微服务系统中,这些业务场景是普遍存在的。此时,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。

3.6.5 CAP理论

  • C (一致性) Consistency:

对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

  • A (可用性) Availability:

非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的。

  • P (分区容错性) Partition tolerance:

当出现网络分区后,系统能够继续工作。打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正工作。

CAP 三者是不能共有的,只能同时满足其中两点。基于 AP,我们又有了 BASE 理论。

  • **基本可用(Basically Available)**:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
  • **软状态(Soft state)**:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
  • **最终一致(Eventually consistent)**:最终一致是指经过一段时间后,所有节点数据都将会达到一致。

3.6.6 XA协议

首先我们来简要看下分布式事务处理的XA规范:

img

可知XA规范中分布式事务有AP,RM,TM组成:

  • 其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
  • 资源管理器(Resource Manager,简称RM):Rm管理计算机共享的资源,许多软件都可以去访问这些资源,资源包含比如数据库、文件系统、打印机服务器等。
  • 事务管理器(Transaction Manager ,简称TM):负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。

Xa主要规定了RM与TM之间的交互,下面来看下XA规范中定义的RM 和 TM交互的接口:

img

本图来着 参考文章XA规范25页

  • xa_start负责开启或者恢复一个事务分支,并且管理XID到调用线程
  • xa_end 负责取消当前线程与事务分支的关联
  • xa_prepare负责询问RM 是否准备好了提交事务分支
  • xa_commit通知RM提交事务分支
  • xa_rollback 通知RM回滚事务分支

XA协议是使用了二阶段协议的,其中:

  • 第一阶段TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO。RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。
  • 第二阶段TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare回执NO的话,则TM通知所有RM回滚自己的事务分支。

也就是TM与RM之间是通过两阶段提交协议进行交互的。

第四章 如何合理的进行分库分表

4.1 当前较流行的分库分表中间件

比较常见的包括:

  • *cobar *
  • *TDDL *
  • *atlas *
  • *sharding-jdbc *
  • mycat
  • DRDS
  • DBProxy

cobar****

阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 cobar 集群,cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。

Cobar 是提供关系型数据库(MySQL)分布式服务的中间件,它可以让传统的数据库得到良好的线性扩展,并看上去还是一个数据库,对应用保持透明。

Cobar以Proxy的形式位于前台应用和实际数据库之间,对前台的开放的接口是MySQL通信协议,将前台SQL语句变更并按照数据分布规则发到合适的后台数据分库,再合并返回结果,模拟单库下的数据库行为。

Cobar属于中间层方案,在应用程序和MySQL之间搭建一层Proxy。中间层介于应用程序与数据库间,需要做一次转发,而基于JDBC协议并无额外转发,直接由应用程序连接数据库,

性能上有些许优势。这里并非说明中间层一定不如客户端直连,除了性能,需要考虑的因素还有很多,中间层更便于实现监控、数据迁移、连接管理等功能。

Cobar属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的schema,集群日处理在线SQL请求50亿次以上。

由于Cobar发起人的离职,Cobar停止维护。后续的类似中间件,比如MyCAT建立于Cobar之上,包括现在阿里服役的RDRS其中也复用了Cobar-Proxy的相关代码。

TDDL

淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。

TDDL是Tabao根据自己的业务特点开发了(Tabao Distributed Data Layer, 外号:头都大了)。主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,

它是一个基于集中式配置的jdbc datasourcce实现,具有主备,读写分离,动态数据库配置等功能。

TDDL并非独立的中间件,只能算作中间层,处于业务层和JDBC层中间,是以Jar包方式提供给应用调用,属于JDBC Shard的思想。

TDDL源码:https://github.com/alibaba/tb_tddl

TDDL复杂度相对较高。当前公布的文档较少,只开源动态数据源,分表分库部分还未开源,还需要依赖diamond,不推荐使用

atlas

360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。

Atlas是一个位于应用程序与MySQL之间的基于MySQL协议的数据中间层项目,它是在mysql-proxy 0.8.2版本上对其进行优化,360团队基于mysql proxy 把lua用C改写,

它实现了MySQL的客户端和服务端协议,作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。它对应用程序屏蔽了DB的细节。

Altas不能实现分布式分表,所有的字表必须在同一台DB的同一个DataBase里且所有的字表必须实现建好,Altas没有自动建表的功能。

原有版本是不支持分库分表, 目前已经放出了分库分表版本。在网上看到一些朋友经常说在高并 发下会经常挂掉,如果大家要使用需要提前做好测试。

sharding-jdbc

当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也可以选择的方案。

sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问。

Sharding-JDBC是继dubbox和elastic-job之后,ddframe系列开源的第3个项目。

Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零:

  • 可适用于任何基于Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
  • 可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等。
  • 理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle、SQLServer等数据库的计划。

Sharding-JDBC定位为轻量Java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式。

Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。

SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持Binding Table以及笛卡尔积表查询。

mycat

基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding jdbc 来说,年轻一些,经历的锤炼少一些。

MyCAT是社区爱好者在阿里cobar基础上进行二次开发,解决了cobar当时存 在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活 跃度很高,

目前已经有一些公司在使用MyCAT。总体来说支持度比 较高,也会一直维护下去,发展到目前的版本,已经不是一个单纯的MySQL代理了,

它的后端可以支持MySQL, SQL Server, Oracle, DB2, PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。

MyCAT是一个强大的数据库中间件,不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让你的架构具备很强的适应性和灵活性,

借助于即将发布的MyCAT只能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表隐射到不同存储引擎上,而整个应用的代码一行也不用改变。

MyCAT是在Cobar基础上发展的版本,两个显著提高:后端由BIO改为NIO,并发量有大幅提高; 增加了对Order By, Group By, Limit等聚合功能

(虽然Cobar也可以支持Order By, Group By, Limit语法,但是结果没有进行聚合,只是简单返回给前端,聚合功能还是需要业务系统自己完成)

DRDS

DRDS是阿里巴巴自主研发的分布式数据库服务(此项目不开源),DRDS脱胎于阿里巴巴开源的Cobar分布式数据库引擎,吸收了Cobar核心的Cobar-Proxy源码,

实现了一套独立的类似MySQL-Proxy协议的解析端,能够对传入的SQL进行解析和处理,对应用程序屏蔽各种复杂的底层DB拓扑结构,获得单机数据库一样的使用体验,

同时借鉴了淘宝TDDL丰富的分布式数据库实践经验,实现了对分布式Join支持,SUM/MAX/COUNT/AVG等聚合函数支持以及排序等函数支持,

通过异构索引、小表广播等解决分布式数据库使用场景下衍生出的一系列问题,最终形成了完整的分布式数据库方案。

现在是品牌升级之后叫PolarDB-X。

DBProxy

DBProxy是美团点评DBA团队针对公司内部需求,在奇虎360公司开源的Atlas做了很多改进工作,形成了新的高可靠、高可用企业级数据库中间件

其特性主要有:读写分离、负载均衡、支持分表、IP过滤、sql语句黑名单、DBA平滑下线DB、从库流量配置、动态加载配置项

项目的Github地址是https://github.com/Meituan-Dianping/DBProxy

4.2 其他知名度较低的分库分表中间件

Heisenberg

Baidu.

其优点:分库分表与应用脱离,分库表如同使用单库表一样,减少db连接数压力,热重启配置,可水平扩容,遵守MySQL原生协议,读写分离,无语言限制,

mysqlclient, c, java都可以使用Heisenberg服务器通过管理命令可以查看,如连接数,线程池,结点等,并可以调整采用velocity的分库分表脚本进行自定义分库表,相当的灵活。

https://github.com/brucexx/heisenberg(开源版已停止维护)

CDS

JD. Completed Database Sharding.

CDS是一款基于客户端开发的分库分表中间件产品,实现了JDBC标准API,支持分库分表,读写分离和数据运维等诸多共,提供高性能,高并发和高可靠的海量数据路由存取服务,业务系统可近乎零成本进行介入,目前支持MySQL, Oracle和SQL Server.

(架构上和Cobar,MyCAT相似,直接采用jdbc对接,没有实现类似MySQL协议,没有NIO,AIO,SQL Parser模块采用JSqlParser, Sql解析器有:druid>JSqlParser>fdbparser.)

DDB

网易. Distributed DataBase.

DDB经历了三次服务模式的重大更迭:Driver模式->Proxy模式->云模式。

Driver模式:基于JDBC驱动访问,提供一个db.jar, 和TDDL类似, 位于应用层和JDBC之间. Proxy模式:在DDB中搭建了一组代理服务器来提供标准的MySQL服务,在代理服务器内部实现分库分表的逻辑。应用通过标准数据库驱动访问DDB Proxy, Proxy内部通过MySQL解码器将请求还原为SQL, 并由DDB Driver执行得到结果。

私有云模式:基于网易私有云开发的一套平台化管理工具Cloudadmin, 将DDB原先Master的功能打散,一部分分库相关功能集成到proxy中,如分库管理、表管理、用户管理等,一部分中心化功能集成到Cloudadmin中,如报警监控,此外,Cloudadmin中提供了一键部署、自动和手动备份,版本管理等平台化功能。

OneProxy

数据库界大牛,前支付宝数据库团队领导楼方鑫开发,基于mysql官方 的proxy思想利用c进行开发的,OneProxy是一款商业收费的中间件, 楼总舍去了一些功能点,专注在性能和稳定性上。测试过说在高并发下很稳定。

Oceanus

58同城数据库中间件

Oceanus致力于打造一个功能简单、可依赖、易于上手、易于扩展、易于集成的解决方案,甚至是平台化系统。拥抱开源,提供各类插件机制集成其他开源项目,新手可以在几分钟内上手编程,分库分表逻辑不再与业务紧密耦合,扩容有标准模式,减少意外错误的发生。

Vitess

这个中间件是Youtube生产在使用的,但是架构很复杂。 与以往中间件不同,使用Vitess应用改动比较大要 使用他提供语言的API接口,我们可以借鉴他其中的一些设计思想。

Kingshard

Kingshard是前360Atlas中间件开发团队的陈菲利用业务时间 用go语言开发的,目前参与开发的人员有3个左右, 目前来看还不是成熟可以使用的产品,需要在不断完善。

MaxScale与MySQL Route

这两个中间件都算是官方的吧,MaxScale是mariadb (MySQL原作者维护的一个版本)研发的,目前版本不支持分库分表。

MySQL Route是现在MySQL 官方Oracle公司发布出来的一个中间件。

4.3 如何选择拆分键

本文将介绍如何在PolarDB-X中选择合适的拆分键。

背景信息

拆分键即分库或分表字段,是水平拆分过程中用于生成拆分规则的数据表字段。PolarDB-X将拆分键值通过拆分函数计算得到一个计算结果,然后根据这个结果将数据分拆到私有定制RDS实例上。

数据表拆分的首要原则是尽可能找到数据所归属的业务逻辑实体,并确定大部分(或核心的)SQL操作或者具备一定并发的SQL都是围绕这个实体进行,然后可使用该实体对应的字段作为拆分键。

示例

业务逻辑实体通常与应用场景相关,下面的一些典型应用场景都有明确的业务逻辑实体(以此类推,其它应用场景也能找到合适的业务逻辑实体),其标识型字段可用来做拆分键。

  • 面向用户的互联网应用,围绕用户维度来做各种操作,那么业务逻辑实体就是用户,可使用用户ID作为拆分键。
  • 侧重于卖家的电商应用,围绕卖家维度来做各种操作,那么业务逻辑实体就是卖家,可使用卖家ID作为拆分键。
  • 游戏类在线应用,围绕玩家维度来做各种操作,那么业务逻辑实体就是玩家,可使用玩家ID作为拆分键。
  • 车联网在线应用,围绕车辆维度来做各种操作,那么业务逻辑实体就是车辆,可使用车辆ID作为拆分键。
  • 税务类在线应用,围绕纳税人来进行前台业务操作,那么业务逻辑实体就是纳税人,可使用纳税人ID作为拆分键。

例如某面向卖家的电商应用,需要对如下单表进行水平拆分:

1
CREATE TABLE sample_order (   id INT(11) NOT NULL,   sellerId INT(11) NOT NULL,   trade_id INT(11) NOT NULL,   buyer_id INT(11) NOT NULL,   buyer_nick VARCHAR(64) DEFAULT NULL,   PRIMARY KEY (id) )

确定业务逻辑实体为卖家,那么选择字段sellerId作为拆分键,则您可以使用如下分布式DDL语句建表:

1
CREATE TABLE sample_order (   id INT(11) NOT NULL,   sellerId INT(11) NOT NULL,   trade_id INT(11) NOT NULL,   buyer_id INT(11) NOT NULL,   buyer_nick VARCHAR(64) DEFAULT NULL,   PRIMARY KEY (id) ) DBPARTITION BY HASH(sellerId)

如果确实找不到合适的业务逻辑实体作为拆分键,特别是传统企业级应用,那么可以考虑以下方法来选择拆分键。

  • 根据数据分布和访问的均衡度来考虑拆分键,尽量将数据表中的数据相对均匀地分布在不同分表中,PolarDB-X推出了全局强一致二级索引和Parallel Query能够提高在此场景下SQL并发度并缩短响应时间。
  • 按照数字(字符串)类型与时间类型字段相结合作为拆分键,进行分库和分表,适用于日志检索类的应用场景。

例如某日志系统记录了用户的所有操作,现需要对如下日志单表进行水平拆分:

1
CREATE TABLE user_log (   userId INT(11) NOT NULL,   name VARCHAR(64) NOT NULL,   operation VARCHAR(128) DEFAULT NULL,   actionDate DATE DEFAULT NULL )

此时可以选择用户标识与时间字段相结合作为拆分键,并按照一周七天进行分表,则您可以使用如下分布式DDL语句建表:

1
CREATE TABLE user_log (   userId INT(11) NOT NULL,   name VARCHAR(64) NOT NULL,   operation VARCHAR(128) DEFAULT NULL,   actionDate DATE DEFAULT NULL ) DBPARTITION BY HASH(userId) TBPARTITION BY WEEK(actionDate) TBPARTITIONS 7

更多拆分键的选择和分表形式,请参见CREATE TABLE拆分函数概述

4.4 如何选择分片数

DRDS 中的水平拆分有两个层次:分库和分表。每个 RDS 实例上默认会创建8个物理分库,每个物理分库上可以创建一个或多个物理分表。分表数通常也被称为分片数。

一般情况下,建议单个物理分表的容量不超过500万行数据。通常可以预估1到2年的数据增长量,用估算出的总数据量除以总的物理分库数,再除以建议的最大数据量500万,即可得出每个物理分库上需要创建的物理分表数:

  1. 物理分库上的物理分表数 = 向上取整(估算的总数据量 / (RDS 实例数 * 8) / 5,000,000)

因此,当计算出的物理分表数等于1时,分库即可,无需再进一步分表,即每个物理分库上一个物理分表;若计算结果大于1,则建议既分库又分表,即每个物理分库上多个物理分表。

例如,某用户预估一张表在2年后的总数据量大概是1亿行,购买了4个 RDS 实例,那么按照上述公式计算:

  1. 物理分库上的物理分表数 = CEILING(100,000,000 / ( 4 * 8 ) / 5,000,000) = CEILING(0.625) = 1

结果为1,那么只分库即可,即每个物理分库上1个物理分表。

若上述例子中仅购买了1个 RDS 实例,那么按照上述公式计算:

  1. 物理分库上的物理分表数 = CEILING(100,000,000 / ( 1 * 8 ) / 5,000,000) = CEILING(2.5) = 3

结果为3,那么建议既分库又分表,即每个物理分库上3个物理分表。

4.5 单个表的容量限制最佳实践

分表的大小是有限制的,建议单个分表的数据记录数不宜超过500万。(阿里巴巴最佳实践)

4.6 DRDS 的分库分表,能否更换分库分表的拆分键

对于已经被建好的分库分表,DRDS 不支持变更它们的拆分键。如果确实有需要变更表的拆分键,可以采用以下的临时办法:

  • 选择新的分库键并重新建表;
  • 然后将原表的数据进行导入。

4.7 PolarDB-X实例中每一个RDS的分库数,每个分库里的分表数是否有限制

单个RDS实例的默认分库数目是8个,不可更改。

每个分库里的分表数目理论上是没有限制的,受限于PolarDB-X服务器本身的硬件资源。分表数目的选择需要依据对业务数据量的评估,详情请参见如何选择分片数

4.8 PolarDB-X是否支持分布式JOIN?它是如何支持复杂SQL?

PolarDB-X支持大部分的JOIN语法,但对于比较复杂的情况,PolarDB-X做了一些限制。例如大表之间的JOIN,由于执行代价过高,速度过慢容易导致性能或者系统不可用等情况,因此请尽量避免,详情请参见Join与子查询的优化和执行

4.9 平滑扩容

什么是平滑扩容

PolarDB-X 平滑扩容是指通过增加 RDS 的数量以提升整体性能。当 RDS 的 IOPS、CPU、磁盘容量等指标到达瓶颈,并且 SQL 优化、RDS 升配已无法解决瓶颈(例如磁盘已升至顶配)时,可通过 PolarDB-X 水平扩容增加 RDS 数量,提升 PolarDB-X 数据库的容量。

PolarDB-X 平滑扩容通过迁移分库到新 RDS 来降低原 RDS 的压力。例如,扩容前8个库的压力集中在一个 RDS 实例上,扩容后8个库分别部署在两个 RDS 实例上,单个 RDS 实例的压力就明显降低。如下图所示:

img

说明:平滑扩容多次后,如果出现 RDS 数量和分库数量相等的情况,需要创建另外一个 PolarDB-X 和预期容量 RDS 的数据库,再进行数据迁移以达到更大规模数据容量扩展的目标。此过程较复杂,推荐创建 PolarDB-X 数据库时要考虑未来2-3年数据的增长预期,做好 RDS 数量规划。

第五章 一定要分库分表吗?有没有过渡的方案

前面说到分库分表是OLTP的最终形态,但是对于大多数公司来说,分库分表引入的复杂度会大大提升开发成本。所以针对此场景我们设计了一定的过渡方案。

  • 读写分离:查询走只读实例,缓解主实例压力。
  • 查询分析走分析库:业务库只承载事务相关操作,业务库通过同步技术将数据实时同步至分析库(如阿里的ADB),查询分析场景走分析库。
  • 升级为更高性能的OLTP数据库,如PolarDB,单库最大64TB,单集群最大100TB,最大写TPS支持十万,完全兼容Mysql。

第六章 分布式事务解决方案

6.1 二阶段提交(2PC)

二阶段提交协议(Two-phase Commit Protocol,简称 2PC)是分布式事务的核心协议。在此协议中,一个事务管理器(Transaction Manager,简称 TM)协调 1 个或多个资源管理器(Resource Manager,简称 RM)的活动,所有资源管理器向事务管理器汇报自身活动状态,由事务管理器根据各资源管理器汇报的状态(完成准备或准备失败)来决定各资源管理器是“提交”事务还是进行“回滚”操作。

二阶段提交的具体流程如下:

  1. 应用程序向事务管理器提交请求,发起分布式事务;
  2. 在第一阶段,事务管理器联络所有资源管理器,通知它们准备提交事务;
  3. 各资源管理器返回完成准备(或准备失败)的消息给事务管理器(响应超时算作失败);
  4. 在第二阶段:
    • 如果所有资源管理器均完成准备(如图 1),则事务管理器会通知所有资源管理器执行事务提交;
    • 如果任一资源管理器准备失败(如图 2 中的资源管理器 B),则事务管理器会通知所有资源管理器进行事务回滚。

所有资源管理器完成准备,事务管理器协调各资源管理器提交事务

img

任一资源管理器准备失败,事务管理器协调各资源管理器回滚事务

img

6.2 TCC补偿方案

Try-Confirm-Cancel(TCC)是初步操作(Try)、确认操作(Confirm)和取消操作(Cancel)三种操作的缩写,这三种操作的业务含义如下:

  • Try 阶段:对业务系统做检测及资源预留;
  • Confirm 阶段:对业务系统做确认提交。默认 Confirm 阶段是不会出错的,只要 Try 成功,Confirm 一定成功;
  • Cancel 阶段:当业务执行出现错误,需要回滚的状态下,执行业务取消,释放预留资源。

TCC 是二阶段提交协议(Two-phase Commit Protocol,简称 2PC)的扩展,Try 操作对应 2PC 中一阶段的准备提交事务(Prepare),Confirm 对应 2PC 中二阶段事务提交(Commit),Cancel 对应 2PC 中二阶段事务回滚(Rollback)。

与 2PC 不同的是,TCC 是一种编程模型,是应用层的 2PC;TCC 的 3 个操作均由编码实现,通过编码实现了 2PC 资源管理器的功能。

TCC 自编码的特性决定 TCC 资源管理器可以跨数据库、跨应用实现资源管理,将对不同的数据库访问、不同的业务操作通过编码方式转换一个原子操作,解决了复杂业务场景下的事务问题。同时 TCC 的每一个操作对于数据库来讲都是一个本地数据库事务,操作结束则本地数据库事务结束,数据库的资源也就被释放;这就规避了数据库层面的 2PC 对资源占用导致的性能低下问题。

基本原理如下图所示:

img

事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。

TCC方案让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能,比如华为分布式事务中间件DTM性能极高,普通配置服务器可以支持全局事务1万+ TPS,分支事务计算方式为3万+ TPS (阿里分布式事务中间件也是采用后者计算方式)。 当然TCC方案也有不足之处,集中表现在以下两个方面:

对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。

实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。

上述原因导致TCC方案大多被研发实力较强、有迫切需求的大公司所采用。微服务倡导服务的轻量化,而TCC方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大。

6.3 基于消息的最终一致性

消息一致性方案是通过消息中间件保证上、下游应用数据操作的[一致性。基本思路是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作。

img

消息方案从本质上讲是将分布式事务转换为两个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本非常高。

入侵代码的方案是基于现有情形“迫不得已”才推出的解决方案,实际上它们实现起来非常不优雅,比如TCC,一个事务的调用通常伴随而来的是对该事务接口增加一系列的反向操作,提交逻辑必然伴随着回滚的逻辑,这样的代码会使得项目非常臃肿,维护成本高。

针对上面所说的分布式事务解决方案的痛点,那很显然,我们理想的分布式事务解决方案肯定是性能要好而且要对业务无入侵,业务层上无需关心分布式事务机制的约束,也就是本文所重点推荐的非侵入事务(全局事务),真正做到事务与业务分离。

下面举例基于阿里云RocketMQ事务消息来保证分布式事务的数据一致性来进行说明。

例如,针对一家互联网电商企业,其业务涉及广泛,如注册、订单、库存、物流等;同时,也会涉及许多业务峰值时刻,如秒杀活动、周年庆、定期特惠等。这些活动都对分布性系统中的各项微服务应用的处理性能带来很大的挑战。

消息队列 RocketMQ 版作为分布式系统中的重要组件,可用于应对这些挑战,例如解决应用的分布式事务的数据一致性问题。

注册系统注册的流程中,用户入口在网页注册系统,通知系统在邮件系统,两个系统之间的数据需要保持最终一致。

普通消息处理

如上所述,注册系统和邮件通知系统之间通过消息队列进行异步处理。注册系统将注册信息写入注册系统之后,发送一条注册成功的消息到消息队列 RocketMQ 版,邮件通知系统订阅消息队列 RocketMQ 版的注册消息,做相应的业务处理,发送注册成功或者失败的邮件。

img

流程说明如下:

  1. 注册系统发起注册。
  2. 注册系统向消息队列 RocketMQ 版发送注册消息成功与否的消息。2.1 消息发送成功,进入 3。 2.2 消息发送失败,导致邮件通知系统未收到消息队列 RocketMQ 版发送的注册成功与否的消息,而无法发送邮件,最终邮件通知系统和注册系统之间的状态数据不一致。
  3. 邮件通知系统收到消息队列 RocketMQ 版的注册成功消息。
  4. 邮件通知系统发送注册成功邮件给用户。

在这样的情况下,虽然实现了系统间的解藕,上游系统不需要关心下游系统的业务处理结果;但是数据一致性不好处理,如何保证邮件通知系统状态与注册系统状态的最终一致。

事务消息处理

此时,需要有利用消息队列 RocketMQ 版所提供的事务消息来实现系统间的状态数据一致性。

img

流程说明如下:

  1. 注册系统向消息队列 RocketMQ 版发送半事务消息。1.1 半事务消息发送成功,进入 2。 1.2 半事务消息发送失败,注册系统不进行注册,流程结束。(最终注册系统与邮件通知系统数据一致)
  2. 注册系统开始注册。2.1 注册成功,进入 3.1。 2.2 注册失败,进行 3.2。
  3. 注册系统向消息队列 RocketMQ 版发送半消息状态。3.1 提交半事务消息,产生注册成功消息,进入 4。 3.2 回滚半事务消息,未产生注册成功消息,流程结束。(最终注册系统与邮件通知系统数据一致)
  4. 邮件通知系统接收消息队列 RocketMQ 版的注册成功消息。
  5. 邮件通知系统发送注册成功邮件。(最终注册系统与邮件通知系统数据一致)

半事务消息(Half-transaction message)是消息队列中的一个特性,它常用于实现分布式系统中的最终一致性。消息队列 RocketMQ 版支持半事务消息,这项功能特别适合处理那些既需要执行本地事务,又需要发送消息到消息队列进行后续处理的场景。

在分布式系统中进行事务操作时,可能会涉及到一个系统操作(例如,更新数据库记录)和向其他系统或服务发送消息(例如,进行异步处理或通知)。在这种情况下,半事务消息非常有用,因为它们能够确保本地事务和消息发布之间的一致性。

半事务消息的流程大致分为以下几个步骤:

  1. 发送半事务消息:首先,发送一个半事务消息到消息队列。此时消息不会立即被消费者消费,因为它处于“未确定”的状态。
  2. 执行本地事务:在发送半事务消息后,执行相关的本地事务逻辑,例如修改数据库的某些记录。
  3. 本地事务结果:本地事务的执行结果会有两种:成功或失败。
    • 如果本地事务执行成功,发送确认消息,此时 RocketMQ 会将半事务消息更改为可供消费者消费的状态。
    • 如果本地事务执行失败,发送回滚消息,RocketMQ 会删除该半事务消息或执行回滚操作,确保消息不会被消费。
  1. 消息队列回查事务状态:为了处理长时间未确定状态的事务消息,RocketMQ 会定期向消息发送者的系统发起回查,查询本地事务的当前状态,然后基于查询结果确认或回滚半事务消息。

这个机制的好处是,它提供了一种机制来保证跨服务的操作都是成功的,或者在遇到问题时都被回滚,这样可以在不丢失消息的前提下保证各服务间处理的一致性。在分布式系统常见的设计模式,如Saga模式中,半事务消息广泛用于实现服务间复杂事务的一致性。

总而言之,半事务消息是一种特殊类型的消息,在消息和关联的本地事务之间建立了半成品状态,从而保证只有在本地事务成功完成时,消息才会被其他系统消费。这为分布式系统中事务一致性提供了一种相对安全的解决方案。

6.4 全局事务

6.4.1 为什么需要全局事务服务

一个完整的业务往往需要调用多个子业务或服务,随着业务的不断增多,涉及的服务及数据也越来越多,越来越复杂。传统的系统难以支撑,出现了应用和数据库等的分布式系统。分布式系统又带来了数据一致性的问题,从而产生了分布式事务。

6.4.2 阿里云全局事务服务GTS

全局事务服务GTS(Global Transaction Service)用于实现分布式环境下,特别是微服务架构下的高性能事务一致性。可以与RDS、MySQL、PostgreSQL等数据源,Spring Cloud、Dubbo、HSF及其他RPC框架,MQ消息队列等中间件产品配合使用,轻松实现分布式数据库事务、多库事务、消息事务、服务链路级事务及各种组合。

6.4.3 开源分布式事务解决方案Seata

Simple Extensible Autonomous Transaction Architecture(Seata)是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

2019 年,基于 GTS 的技术积累,阿里巴巴发起了开源项目 Seata

2020 年 2 月,基于 Seata 项目 GA 后的版本,GTS 实现与 Seata 的协议兼容,支持使用 Seata 的应用无缝迁移到云上,基于 GTS 提供的服务高效运行。

img

GTS 已经全面兼容和支持开源分布式事务 Seata,实现与 Seata 的协议兼容,支持使用 Seata 的应用无缝迁移到云上,基于 GTS 提供的服务高效运行。

6.4.4 分布式事务框架和事务模式

GTS 定义了一套事务框架以便描述分布式事务,在框架下支持不同事务模式运行。

核心组件定义

img

分布式事务包含以下 3 个核心组件:

  • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

一个典型的事务过程包括:

  1. TM 向 TC 申请开启(Begin)一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  2. XID 在微服务调用链路的上下文中传播。
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  4. TM 向 TC 发起针对 XID 的全局提交(Commit)或回滚(Rollback)决议。
  5. TC 调度 XID 下管辖的全部分支事务完成提交(Commit)或回滚(Rollback)请求。

事务框架

基于架构上定义的 3 个核心组件,分布式事务被抽象成如下事务框架。

img

3个核心组件的功能如下:

  • TM定义全局事务的边界。
  • RM负责定义分支事务的边界和行为。
  • TC、TM和RM交互,做全局的协调。交互包括开启(Begin)、提交(Commit)、回滚(Rollback)全局事务;分支注册(Register Branch)、状态上报(Branch Status Report)和分支提交(Branch Commit)、分支回滚(Branch Rollback)。

事务模式

事务模式是这个框架下 RM 驱动的分支事务的不同行为模式,即事务(分支)模式。事务模式包括 AT 模式、TCC 模式、Saga 模式和 XA 模式。

  • AT 模式

img

AT 模式 RM 驱动分支事务的行为分为以下两个阶段:

  • 执行阶段:
    1. 代理 JDBC 数据源,解析业务 SQL,生成更新前后的镜像数据,形成 UNDO LOG。
    2. 向 TC 注册分支。
    3. 分支注册成功后,把业务数据的更新和 UNDO LOG 放在同一个本地事务中提交。
  • 完成阶段:

    • 全局提交,收到 TC 的分支提交请求,异步删除相应分支的 UNDO LOG。
    • 全局回滚,收到 TC 的分支回滚请求,查询分支对应的 UNDO LOG 记录,生成补偿回滚的 SQL 语句,执行分支回滚并返回结果给 TC。
  • TCC模式

img

TCC 模式 RM 驱动分支事务的行为分为以下两个阶段:

  • 执行阶段:
    1. 向 TC 注册分支。
    2. 执行业务定义的 Try 方法。
    3. 向 TC 上报 Try 方法执行情况:成功或失败。
  • 完成阶段:

    • 全局提交,收到 TC 的分支提交请求,执行业务定义的 Confirm 方法。
    • 全局回滚,收到 TC 的分支回滚请求,执行业务定义的 Cancel 方法。
  • Saga 模式

img

Saga 模式 RM 驱动分支事务的行为包含以下两个阶段:

  • 执行阶段:
    1. 向 TC 注册分支。
    2. 执行业务方法。
    3. 向 TC 上报业务方法执行情况:成功或失败。
  • 完成阶段:

    • 全局提交,RM 不需要处理。
    • 全局回滚,收到 TC 的分支回滚请求,执行业务定义的补偿回滚方法。
  • XA模式

img

XA 模式 RM 驱动分支事务的行为包含以下两个阶段:

  • 执行阶段:
    1. 向 TC 注册分支。
    2. XA Start,执行业务 SQL,XA End。
    3. XA prepare,并向 TC 上报 XA 分支的执行情况:成功或失败。
  • 完成阶段:

    • 收到 TC 的分支提交请求,XA Commit。
    • 收到 TC 的分支回滚请求,XA Rollback。


本站总访问量