6000字搞不懂大型网站架构技术细节:后端架构数据库分布式事务?

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 上节中讨论的数据库事务是解决“单个数据库数据不一致”的问题,而在一些具有规模的网站系统当中,数据库往往不止一个,一旦出现多个数据库,则会出现多数据库的数据不一致问题。多个数据库的数据不一致问题一般有两种场景,如图4.76所示。

分布式事务

上节中讨论的数据库事务是解决“单个数据库数据不一致”的问题,而在一些具有规模的网站系统当中,数据库往往不止一个,一旦出现多个数据库,则会出现多数据库的数据不一致问题。

多个数据库的数据不一致问题一般有两种场景,如图4.76所示。

网络异常,图片无法展示
|

图4.76 多数据库的数据不一致场景

场景一,由于“应用拆分”和“数据库分离”造成的多数据库数据不一致问题,例如,用户余额存放在“资金”数据库中,优惠券存放在“优惠券”数据库,当发生交易时,会出现扣减余额成功,但扣减优惠券失败的情况;场景二,当数据库中的数据个数超过千万级时,一般都需要进行分库,这也会造成多数据库的数据不一致问题,例如,用户1的余额存在数据库A中,而用户2的余额存在数据库B中,当用户1向用户2转账时,会出现用户1扣除金额成功,而用户2增加金额失败的情况。

解决“多数据库的数据不一致问题”的方式被称为分布式事务,确切地说,分布式事务不是某种具体的方式,而是解决“多数据库的数据不一致问题”的方式的统称,分布式事务的实现方式本身是开放的。

说明:关于分布式事务的相关理论有CAP原则和BASE理论;关于分布式事务的相关算法有2PC、3PC、TCC及本地消息表等。

分布式事务是行业内一大难题,在选取分布式事务具体方案之前,需要分析清楚是否真的需要分布式事务。很多时候,造成场景一的原因,是由于过度设计造成的,即在系统内部划分出很多没必要的独立模块。例如,图4.76中所举的例子,优惠券本身可以算是资金的一部分,如果把资金和优惠券合并成一个模块,则不需要分布式事务。

注意:以下介绍的分布式事务的解决方法都不是唯一方法,具体实施需要根据实际情况而定。

1.XA事务

XA(eXtended Architecture)是由X/Open组织提出的分布式事务的规范,XA事务能较好地解决“多数据库的数据不一致”问题。目前,比较流行的数据库(如Oracle、MySQL等)都支持XA事务。

注意:Oracle执行XA事务的性能要优于MySQL,另外,MySQL最好选用5.7或之后的版本,因为MySQL 5.7之前的版本对XA事务的支持都是有缺陷的。

XA事务可以简单地理解为多个数据库同时执行数据库事务,但与执行普通数据库事务不同的是,最后的提交阶段需要先检查所有事务的状态(是否允许提交)后才能提交。第三方软件使用数据库XA事务的流程如图4.77所示。以Java为例,通过JDBC使用XA事务的代码如代码4.55所示。

网络异常,图片无法展示
|

图4.77 第三方软件使用数据库XA事务的流程

代码4.55 通过JDBC使用XA事务

//连接数据库1,并获取资源管理器对象rm_1

Connection conn_1 = DriverManager.getConnection(

"jdbc:mysql://ip:port/xxx",

"userName",

"password");

XAConnection xaConn_1 = new MysqlXAConnection((com.mysql.jdbc.Connection)

conn_1, false);

XAResource rm_1 = xaConn_1.getXAResource();//连接数据库2,并获取资源管理器对象rm_2

Connection conn_2 = DriverManager.getConnection(

"jdbc:mysql://ip:port/xxx",

"userName",

"password");

XAConnection xaConn_2 = new MysqlXAConnection((com.mysql.jdbc.Connection)

conn_2, false);

XAResource rm_2 = xaConn_2.getXAResource();

//设置全局事务ID

//xxx可以用UUID.randomUUID().toString()生成

byte[] gtrid = "xxx".getBytes();

try{

//操作数据库1

//生成数据库1的事务ID,gtrid为全局事务ID,bqual_1为分支限定符

byte[] bqual_1 = "db_1".getBytes();

Xid xid_1 = new MysqlXid(gtrid, bqual_1, 1);

//启动数据库1的事务

rm_1.start(xid_1, XAResource.TMNOFLAGS);

//使用conn_1执行SQL语句(数据库1执行SQL语句)

//SQL语句执行结束,迁移事务状态

rm_1.end(xid_1, XAResource.TMSUCCESS);

//操作数据库2

//生成数据库2的事务ID,bqual_2需要与数据库1的值有所区别

byte[] bqual_2 = "db_2".getBytes();

Xid xid_2 = new MysqlXid(gtrid, bqual_2, 1);

//启动数据库2的事务

rm_2.start(xid_2, XAResource.TMNOFLAGS);

//使用conn_2执行SQL语句(数据库2执行SQL语句)

//SQL语句执行结束,迁移事务状态

rm_2.end(xid_2, XAResource.TMSUCCESS);

//两段提交

//准备阶段,获取两个数据库的事务状态

int prepare_1 = rm_1.prepare(xid_1);

int prepare_2 = rm_2.prepare(xid_2);

//提交阶段,根据两个事务的状态决定提交还是回滚

if (prepare_1 == XAResource.XA_OK && prepare_2 == XAResource.XA_OK) {

rm_1.commit(xid_1, false);

rm_2.commit(xid_2, false);

} else { //一个数据库事务存在问题,则回滚

rm_1.rollback(xid_1);

rm_2.rollback(xid_2);

}

} catch(XAException e) {

//发生错误,回滚事务

rm_1.rollback(xid_1);rm_2.rollback(xid_2);

}

XA事务在使用上是简单的,但是由于XA事务是同时对多个数据库执行数据库事务,因此会同时浪费多个数据库的性能。在大型网站系统当中,XA事务一般是不被提倡的,因为大型网站系统需要应对高并发场景,在高并发压力下,XA事务往往会大量阻塞任务,从而引发超时等异常。不过,在一个大型网站系统中,并发压力的分布往往是不均等的,也就是说,存在并发压力不大的模块,而在这些模块中使用XA事务也是可以的。因此,XA事务的好处是使用简单,但不适合用于并发压力大的功能模块。

2.JTA

JTA(Java Transaction API)是Java的事务管理框架。通过使用JTA,开发者可以更简单地实现XA事务。值得一提的是,JTA只是简化了编码,并没有改变XA事务性能差的状况。下面以Spring Boot为例,介绍使用JTA实现XA事务的过程。

说明:JTA实际上只是提供了事务管理的统一接口,它本身不负责具体的实现,具体的实现交由Atomikos或JOTM等事务管理器完成。

(1)引入JTA依赖包。需要在工程配置文件(build.gradle)中添加JTA的依赖包,如代码4.56所示,其中,选用Atomikos作用具体的事务管理器,数据库操作框架选用JDBC Template。

代码4.56 在build.gradle中添加JTA依赖包

dependencies {

//在dependencies中添加JTA的依赖包,选用Atomikos作为具体的事务管理器

implementation 'org.springframework.boot:spring-boot-starter-jta

atomikos'

//添加数据库操作框架依赖包,这里选用JDBC Template

implementation 'org.springframework.boot:spring-boot-starter-jdbc'

implementation 'com.alibaba:druid:1.0.26' //数据库连接池依赖

runtimeOnly '
mysql:mysql-connector-java' //MySQL驱动依赖

}

添加完依赖包后,需要同步工程配置。JTA的依赖包在同步工程配置后才会被下载和引入。在IntelliJ IDEA中,只需要单击“同步”按钮即可同步工程配置,如图4.78所示。

网络异常,图片无法展示
|

图4.78 在IntelliJ IDEA中同步build.gradle配置

(2)配置数据库信息。配置数据库连接信息需要在后端应用程序的配置文件(默认是application.properties)中设置。与平常配置数据库不同的是,这里需要配置两个数据库连接信息,如代码4.57所示,其中,数据库1通过Spring Boot提供的默认字段配置,数据库2通过自定义字段配置。

说明:默认情况下,Spring Boot只提供一个数据库的连接配置,如果需要连接多个数据库,则需要通过额外代码完成多个数据库的连接。代码4.57 在配置文件中添加数据库连接信息

#配置说明可参考4.4.4小节中的代码4.50

#数据库1的连接信息,通过Spring Boot的默认字段配置,也可通过自定义字段配置

spring.datasource.jdbc-url=jdbc:mysql://ip:port/xxx

spring.datasource.username=root

spring.datasource.password=password

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.max-active=20

spring.datasource.max-idle=8

spring.datasource.initial-size=10

#数据库2的连接信息,通过自定义字段配置,dao.extradb为自定义字段

dao.extradb.jdbc-url=jdbc:mysql://ip:port/xxx

dao.extradb.username=root

dao.extradb.password=password

dao.extradb.driver-class-name=com.mysql.cj.jdbc.Driver

dao.extradb.type=com.alibaba.druid.pool.DruidDataSourcedao.extradb.max-active=20

dao.extradb.max-idle=8

dao.extradb.initial-size=10

配置文件虽然配置了多个数据库连接信息,但默认情况下Spring Boot只接受一个数据库的设置,因此需要添加额外代码(新建一个Java文件)关联这些数据库连接信息,如代码4.58所示。

说明:代码4.58是全局设置,不需要被其他文件引用,存放位置最好在Dao层内,以方便开发者查阅。

代码4.58 在配置文件中添加数据库连接信息

package com.example.demo.dao.config;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.boot.jdbc.DataSourceBuilder;

import org.springframework.context.annotation.Primary;

import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration

public class DataBaseConfig {

//关联数据库1的连接信息,并设置为默认连接

//设置标识,关联函数DataBaseTemplate1()中的参数

@Bean(name="dataBaseConfig_1")

@Primary //默认数据库标识

//spring.datasource为数据库1的连接信息字段

@ConfigurationProperties(prefix="spring.datasource")

DataSource DataBaseConfig1(){

return DataSourceBuilder.create().build();

}

@Bean(name="databaseTemplate_1")

@Primary //默认数据库标识

public JdbcTemplate DataBaseTemplate1(@Qualifier("dataBaseConfig_1")

DataSource data){

return new JdbcTemplate(data);

}

//关联数据库2的连接信息

//设置标识,关联函数DataBaseTemplate2()中的参数

@Bean(name="dataBaseConfig_2")//dao.extradb为数据库2的自定义连接信息字段

@ConfigurationProperties(prefix = "dao.extradb")

DataSource DataBaseConfig2(){

return DataSourceBuilder.create().build();

}

@Bean(name="databaseTemplate_2")

public JdbcTemplate DataBaseTemplate2(@Qualifier("dataBaseConfig_2")

DataSource data){

return new JdbcTemplate(data);

}

}

(3)在Dao层中操作多个数据库,如代码4.59所示。在实际编码中,对不同数据库的操作最好分为不同的文件。

代码4.59 在Dao层中操作多个数据库

package com.example.demo.dao;

//引用JDBCTemplate的类

import org.springframework.jdbc.core.JdbcTemplate;

import … //省略其他引用的类

@Repository("TestDao")

public class TestDao {

//获取数据库1(默认数据库)的JdbcTemplate对象,此对象会被自动注入

@Autowired

private JdbcTemplate jdbcTemplate_1;

//获取数据库2的JdbcTemplate对象,databaseTemplate_2为代码4.58中的标识,

此对象会被自动注入

@Autowired

@Qualifier("databaseTemplate_2")

private JdbcTemplate jdbcTemplate_2;

//在数据库1增加一条记录

public String Create_1(String value1, String value2){

try{

//SQL语句,?为占位符,后续通过参数替换

String sqlString = "INSERT INTO formName VALUES (?, ?)";

//执行SQL语句

int result = jdbcTemplate.update(sqlString, value1, value2);

if(result > 0){

return "success";

}else {

return "fail";

}

}catch(Exception e){return "fail";

}

}

//在数据库2增加一条记录

public String Create_2(String value1, String value2){

try{

String sqlString = "INSERT INTO formName VALUES (?, ?)";

int result = jdbcTemplate.update(sqlString, value1, value2);

if(result > 0){

return "success";

}else {

return "fail";

}

}catch(Exception e){

return "fail";

}

}

(4)在业务层(Service层)中标记需要使用数据库事务的方法,如代码4.60所示,其中,@Transactional为XA事务标记。

说明:代码4.60中的@Transactional标识与数据库事务的标识相同,但其本质上是XA事务。

代码4.60 在Service层中添加事务标记

@Service("XXX")

public class XXXService {

@Resource(name = "TestDao")

private TestDao _testDao;

//标记数据库事务,当异常发生时,会自动撤销所有数据库操作。函数中不能用catch捕获

异常

@Transactional

public JSONObject ServiceFunction(JSONObject requestParam, JSONObject

returnParam){

//调用代码4.59中的函数1(操作数据库1)

_testDao.Create_1(…);

//调用代码4.59中的函数2(操作数据库2)

_testDao.Create_1 (…);

return returnParam;

}

}

3.本地消息表

当多个数据库操作分别处在不同的程序中却又需要保持数据一致性时(图4.76中的场景一),一般采用“本地消息表”实现分布式事务。“本地消息表”这个方案的核心是将分布式事务拆分成多个数据库事务进行处理,并通过额外的检查机制确保多个数据库事务都正常完成,以达到数据最终一致的效果。“本地消息表”方案的工作原理如图4.79所示。其中,检查程序可以是程序1本身,本地消息表的实体可以是本地文件、数据库中的表等。

网络异常,图片无法展示
|

图4.79 “本地消息表”方案的工作原理

“本地消息表”方案是解决分布式事务的一种思路,而其具体的实现是开放的,针对不同的应用场景或软件形态会有不同的实现方式。针对后端应用程序而言,可参照图4.80所示的工作流程,其中,检查程序最好是独立的一个程序,本地消息表一般是数据库中的表(可以为数据库1或数据库2中的表,也可以是独立数据库中的表)。

网络异常,图片无法展示
|

图4.80 后端应用程序采用“本地消息表”实现分布式事务的工作流程

说明:图4.80中的检查程序也可以是后端应用程序1中的定时任务,但是后端应用程序1可能被部署在多个服务器上,如果是这样的话,则需要使用Quartz等分布式定时器框架,以避免发生定时任务被多次执行的情况。

“本地消息表”方案的实现在实际编码中比较麻烦,而且会增加后端结构的复杂性。而性能方面,一般来说“本地消息表”方案更胜一筹。但是,因为“本地消息表”方案的具体实现是五花八门的,而且其内部可能会用到XA事务,所以很难评定与单纯使用XA事务的性能对比。

4.其他

除了“XA事务”和“本地消息表”这两种分布式事务解决方案以外,还有很多其他解决思路或方案,如2PC(两段式提交,XA事务是其中一种实现)、3PC(三段式提交)、基于消息队列的解决方案及TCC(事务补偿)方案等。但是,无论采用哪种方案,分布式事务都会增加系统复杂度和限制数据库性能,所以架构设计应该尽量避开分布式事务。如果不能完全避开分布式事务,则需要对系统内的分布式事务提供统一的规范,以防止“五花八门”的分布式事务解决方案出现在网站系统当中(限制系统混乱度)。

本文给大家讲解的内容是大型网站架构的技术细节:后端架构数据库--分布式事务

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
存储 NoSQL 关系型数据库
现代数据库技术的发展与应用
本文将探讨现代数据库技术的发展趋势和广泛应用领域。我们将从传统关系型数据库开始,介绍NoSQL数据库、分布式数据库以及最新的图数据库等技术,探讨它们的特点和优势。此外,我们还将讨论数据库在大数据、云计算和物联网等领域中的应用案例,并展望未来数据库技术的发展方向。
|
4天前
|
监控 Oracle 关系型数据库
Flink CDC(Change Data Capture)是一种用于捕获数据库变更的技术
Flink CDC(Change Data Capture)是一种用于捕获数据库变更的技术
20 8
|
2天前
|
存储 分布式数据库 数据处理
未来数据库技术发展趋势及挑战
【2月更文挑战第2天】 传统的数据库技术面临着越来越多的挑战,如数据规模的爆炸性增长、实时性和可扩展性要求的提升等。本文将探讨未来数据库技术的发展趋势,包括分布式数据库、无服务器数据库、区块链技术在数据库领域的应用等,并分析这些新技术带来的机遇和挑战。
6 0
|
2天前
|
人工智能 安全 大数据
未来数据库技术发展趋势与挑战
【2月更文挑战第2天】 随着信息时代的快速发展,数据库技术也在不断演进。本文将探讨未来数据库技术的发展趋势与挑战,涉及新型数据库技术的应用、人工智能与大数据对数据库的影响以及数据安全等方面。通过深入分析未来数据库技术的走向,可以更好地把握技术发展的脉搏,应对挑战并抓住机遇。
|
22小时前
|
存储 监控 持续交付
探讨后端微服务架构的演进与优化
【2月更文挑战第4天】随着互联网应用的快速发展,后端微服务架构作为一种灵活、可扩展的架构模式,逐渐成为各大企业和组织的首选。本文将从微服务架构的定义和特点入手,探讨其在实际应用中的演进过程以及优化策略,帮助读者更好地理解并应用后端微服务架构。
8 2
|
1天前
|
存储 关系型数据库 大数据
现代数据库技术的发展与挑战
【2月更文挑战第3天】 随着信息时代的到来,数据库技术作为信息系统的重要组成部分,在不断面临着新的发展和挑战。本文将探讨现代数据库技术的发展趋势和挑战,并分析其在大数据、云计算和人工智能等领域中的应用。
|
1天前
|
存储 安全 区块链
未来数据库技术发展趋势探析
【2月更文挑战第3天】 随着信息时代的快速发展,数据库技术作为信息存储和管理的核心工具,在不断演进与创新。本文将从人工智能、区块链、云计算等角度切入,探讨未来数据库技术的发展趋势,展望数据库技术的前景。
|
2天前
|
存储 NoSQL 物联网
现代数据库技术发展趋势与应用探讨
【2月更文挑战第2天】数据库作为计算机领域中重要的基础设施之一,在当今信息化时代扮演着至关重要的角色。本文将从现代数据库技术的发展趋势出发,探讨其在各个领域中的应用及未来的发展方向,旨在帮助读者深入了解数据库技术的最新动向。
|
2天前
|
运维 前端开发 Java
探索新一代数据库技术——无服务器数据库
【2月更文挑战第2天】本文将介绍一种创新的数据库技术——无服务器数据库。通过分析其原理、特点和应用场景,我们将揭示无服务器数据库在解决传统数据库面临的挑战方面的优势。同时,我们还将探讨无服务器数据库对于前端、后端以及Java等技术领域的影响,并展望其未来的发展趋势。
13 0
|
2天前
|
存储 NoSQL 数据管理
现代数据库技术发展趋势与应用前景探析
【2月更文挑战第2天】 随着信息时代的快速发展,数据库技术作为信息存储和管理的核心工具也在不断演进。本文将就当前现代数据库技术的发展趋势及其在实际应用中的前景进行探讨,旨在帮助读者更好地了解数据库技术的最新动向。

热门文章

最新文章