前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【项目实战经验】一文搞懂云数据库PostgreSQL与MySQL实践案例

【项目实战经验】一文搞懂云数据库PostgreSQL与MySQL实践案例

原创
作者头像
疯狂的KK
发布2023-09-11 12:32:29
6270
发布2023-09-11 12:32:29
举报
文章被收录于专栏:Java项目实战Java项目实战

目录大纲

1.PostgreSQL简介

2.MySQL Tinyint(1)与Tinyint(4):数据存储的大小差异与项目报错和解决

3.MySQL事务管理Transaction rolled back because it has been marked as rollback only报错解决与解析

4.线上环境对roll back only 的处理

5.线上环境对嵌套事务的解决方案

6.11个demo分析事务失效的场景

7.分布式事务

8.事务也能异步

选文出发点

结合实际项目出发,从实际经验分享解决问题的真实经验,落地应用,理念解析,任何天上飞的理念一定有落地的实现,没有落地的应用都是空谈,真正的解决像是那个问题,积累项目实战经验,欢迎订阅专栏

Java项目实战
Java项目实战

PostgreSQL 简介

PostgreSQL = Post + gre +SQL (快速解决SQL数据库)

PostgreSQL 是全球强大的开源数据库,支持主流开发语言,包括 C,C++,Perl,Python,Java,Tcl 以及 PHP 等,能够对 SQL 规范的完整实现,以及丰富多样的数据类型支持,包括 JSON 数据、IP 数据和几何数据等,而这些能力大部分商业数据库都无法全面支持。在过去的若干年间,PostgreSQL 正在以飞快的速度发展,目前已经广泛用在包括地球空间、移动应用、数据分析等各个行业,已成为众多企业开发人员和创新公司的首选。

腾讯云云数据库 PostgreSQL 简介

云数据库 PostgreSQL 能够让您在云端轻松设置、操作和扩展目前功能最强大的开源数据库 PostgreSQL,腾讯云将负责绝大部分处理复杂而耗时的管理工作,如 PostgreSQL 软件安装、存储管理、高可用复制、以及为灾难恢复而进行的数据备份,让您更专注于业务程序开发。

目前,腾讯云已经提供 PostgreSQL 10、11、12、13、14 、15版本。

云数据库PostgreSQL的环境准备

1、打开云文档

云数据库 PostgreSQL_PostgreSQL数据库_开源关系型数据库_腾讯云 (tencent.com)

2、来到管理控制台只需6步拥有自己的数据库

3、创建实例

4、选择离自己最近的节点

5、配置用户名密码

6、连接实例

7、连接不上检查安全组

7、开启外网访问

2.MySQL Tinyint(1)与Tinyint(4):数据存储的大小差异与项目报错和解决

测试环境报错,历史业务从未发现本报错,由于业务场景需要,将某字段提升至129次,原字段Tinyint(4)

在数据库中报错,报错如下

在数据库开发中,选择正确的数据类型对于提高数据存储效率和查询性能至关重要。MySQL作为一种常用的关系型数据库,提供了多种数据类型供开发者选择。

1. Tinyint数据类型简介

Tinyint是MySQL中用于存储小整数的数据类型,它占用1个字节的存储空间,可表示的范围为-128到127。根据定义,Tinyint(1)和Tinyint(4)在存储空间上没有区别,都占用1个字节。然而,在实际使用中,它们存在一些差异。

2. Tinyint(1)与Tinyint(4)的区别

Tinyint(1)和Tinyint(4)之间的差异主要体现在数据存储和数据显示上。

2.1 数据存储差异

虽然Tinyint(1)和Tinyint(4)在存储空间上都占用1个字节,但它们的最大存储范围却不同。Tinyint(1)可以存储的最大值为127,而Tinyint(4)可以存储的最大值为9999。这意味着,如果使用Tinyint(1)存储大于127的值,将会出现溢出的情况,导致数据错误。

2.2 数据显示差异

Tinyint(1)和Tinyint(4)在数据显示上也存在差异。当使用Tinyint(1)存储数据时,MySQL会对存储的值进行截断,只保留最后一位。例如,存储值为123的Tinyint(1)字段,在显示时会被截断为3。而Tinyint(4)则会完整地显示存储的值。

3. Tinyint(1)与Tinyint(4)的应用场景

根据上述的差异,我们可以根据具体的需求来选择使用哪种数据类型。

3.1 Tinyint(1)的应用场景

Tinyint(1)适用于存储布尔类型的数据,通常用于表示真或假、开或关等状态。由于它只能存储1位数据,所以可以节省存储空间。例如,在用户表中,可以使用Tinyint(1)来表示用户的状态,如是否激活、是否禁用等。

示例代码:

代码语言:javascript
复制
CREATE TABLE user (
  id INT PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  active TINYINT(1) NOT NULL
);

3.2 Tinyint(4)的应用场景

Tinyint(4)适用于存储整数类型的数据,通常用于表示具体的数值大小。由于它可以存储4位数据,所以适用于存储较大的整数值。例如,在商品库存表中,可以使用Tinyint(4)来表示商品的数量。

示例代码:

代码语言:javascript
复制
CREATE TABLE product (
  id INT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  quantity TINYINT(4) NOT NULL
);

项目报错实际由于估测场景缺失,造成未评估字段长度报错,改为int后又再次上线解决未引发错误。

实战项目篇

1、线上环境对roll back only 的处理与产生

代码语言:javascript
复制
org.springframework.dao.CannotAcquireLockException: 
### Error updating database.  Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may involve xxxMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO xxx
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

产生原因:

事务嵌套,内层事务将异常捕获未抛出。

2、线上环境对嵌套事务的解决方案

优化点可以从以下几点进行考虑:

最为直接的方法便是去掉嵌套事务,在controller层统一决定异常处理

对于类似开发过程中,需考虑将相关方法长事务中查询方法剔除,将方法内事务缩短为最小事务

出现突发情况,应提供最为简单有效的方案,让业务正常操作,不受影响

开发应对当时的技术方案告知相关测试

在代码层面,后续代码需要前面操作事务释放锁

无需等待插入结果 直接插入后续数据

将查询放在事务外面尽量将大事务变为小事务

捕获异常 自动重试

但是短时间内我还没有时间进行整改,在不影响主流程的情况下未进行整改,但我后续才知道大错特错。

排查

代码语言:javascript
复制
@timestamp September 1st
# @version  1
t LOG_DATEFORMAT_PATTERN    yyyy-MM-dd HH:mm:ss.SSS
t LOG_LEVEL_PATTERN %5p
t _id   VMaG
t _index    applog-2021.09.01
# _score    1
t _type doc
t appindex  applog
t appname   app
t host  10.0.74.157
t level ERROR
# level_value   40,000
t logger_name   ExceptionLogCollector
t message   未知异常[500] => Transaction rolled back because it has been marked as rollback-only
# port  10,792
t stack_trace   org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.1.4.RELEASE.jar!/:5.1.4.RELEASE]
spring-tx-5.1.4.RELEASE.jar-
org.springframework.transaction.interceptor.TransactionInterceptor#事务拦截器
avatar

spring事务分为声明式事务和编程式事务,若目标方法存在事务,spring会对bean生成一个代理对象,从日志来看是cglib的

入口98行springaop事务增强 TransactionAspectSupport在事务中的调用,执行代理类的目标方法触发invoke

代码语言:javascript
复制
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            final InvocationCallback invocation) throws Throwable
方法为protected的,根据源代码注释解析

if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager))

如果事务属性为null 且事务类型是CallbackPreferringPlatformTransactionManager进入304行commitTransactionAfterReturning(txInfo);方法

意为事务成功后执行,有异常不执行,没有事务不执行,也就是为后面的事务方法异常时没执行进行了铺垫,533行

txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());事务进行commit时进行判断

如果不是进行全局事务提交 但是是RollbackOnly的话

走processRollback处理实际回滚

代码语言:javascript
复制
@Override
    public final void commit(TransactionStatus status) throws TransactionException {
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException(
                    "Transaction is already completed - do not call commit or rollback more than once per transaction");
        }

        DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
        if (defStatus.isLocalRollbackOnly()) {
            if (defStatus.isDebug()) {
                logger.debug("Transactional code has requested rollback");
            }
            processRollback(defStatus, false);
            return;
        }

        if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
            if (defStatus.isDebug()) {
                logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
            }
            日志追踪的710行-----记住此处传true
            processRollback(defStatus, true);
            return;
        }

        processCommit(defStatus);
    }


private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
        try {
            入参为true
            boolean unexpectedRollback = unexpected;

            try {
                triggerBeforeCompletion(status);

                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Rolling back transaction to savepoint");
                    }
                    status.rollbackToHeldSavepoint();
                }
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction rollback");
                    }
                    doRollback(status);
                }
                else {
                    // Participating in larger transaction
                    if (status.hasTransaction()) {
                        if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                            if (status.isDebug()) {
                                logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                            }
                            doSetRollbackOnly(status);
                        }
                        else {
                            if (status.isDebug()) {
                                logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
                            }
                        }
                    }
                    else {
                        logger.debug("Should roll back transaction but cannot - no transaction available");
                    }
                    // Unexpected rollback only matters here if we're asked to fail early
                    if (!isFailEarlyOnGlobalRollbackOnly()) {
                        unexpectedRollback = false;
                    }
                }
            }
            catch (RuntimeException | Error ex) {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                throw ex;
            }

            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            日志追踪的873行  抛出异常
            // Raise UnexpectedRollbackException if we had a global rollback-only marker
            if (unexpectedRollback) {
                throw new UnexpectedRollbackException(
                        "Transaction rolled back because it has been marked as rollback-only");
            }
        }
        finally {
            cleanupAfterCompletion(status);
        }
    }

事务这里场景和传播行为相关知识点太多了,这个后续接着分析

但就此场景将伪代码贴一下

代码语言:javascript
复制
try

    {
        methodA()
    }catch

    {
    }

    @Transactional(rollbackFor = Exception.class)
    public methodA() {
        methodB()
    }

    @Transactional(rollbackFor = Exception.class)
    public methodB() {
        try {
            methodC()
        } catch {
        }
    }

    methodC() {
        当C方法抛出异常时
    }

不知道大家对于rpc行为调用的接口是如何处理的,我们以前是将rpc调用的接口有Biz接收进来,进行参数处理,领域模型转换后,调取service进行内部数据处理的,但此时的接口在主流程上会伴随着另一个第三方接口的写操作,需进行事务处理,那么内层service接口为什么还要进行事务管理?在设计上理应不对rpc接口操作的service进行开放调用的,但业务上区分不同场景,不同供应商,不同酒店等对接口进行了反射调用,或者app调用,导致内层service也进行了事务操作,那么问题来了,嵌套事务时,如果内层事务注解取消不抛出

UnexpectedRollbackException,实际此方法内并没有完全执行完,

我希望是怎样的?我希望在保持事务原子性的前提,内层事务回滚则整个全局事务回滚,且不报此异常

第一种方法isGlobalRollbackOnParticipationFailure方法,让主事务来决定是否回滚,

改动成本大

而在Springaop中,被拦截的方法需要显式的抛出异常,并不能经过任何处理,这样aop才能进行回滚,默认aop是只catchruntimeException的异常 第二种方法可以在catch块里加上 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手动回滚 即便上层事务发生了异常,也想要最终提交整个事务呢?如果有这样的需求的话,可以给事务管理器配置一个参数 setGlobalRollbackOnParticipationFailure(false); # 改动成本大

解决方案:在内层方法中不进行方法的try catch,有异常操作时在外层事务进行处理,且可决定是否回滚,特定的异常也再次处理

回顾:事务的失效场景(事务不生效和事务不回滚)

3、11个demo分析事务失效的场景

代码语言:javascript
复制
@Slf4j
@Service
public class DemoService {

	@Autowired
	private Test1Mapper test1Mapper;
	
	@Autowired
	private TestMapper testMapper;
	
	@Autowired
	private InvalidTransactionService invalidTransactionService;
	
	@Autowired
	private ExecutorService executorService;
	
	@Autowired
	private DemoService _self;
	
	@Autowired
	private ValidTransactionService validTransactionService;
	
	@Autowired
	private RequireNewTransactionService requireNewTransactionService;
	
	/********************************************************
	 * 事务不生效场景1
	 * 相当于调用this调用,没有产生代理对象调用,解决方法,自己把自己注入以后调用
	 ********************************************************/
	public void demo1() {

		invalidTransaction();
		
		//TODO other logic code here
	}
	
	@Transactional
	public void invalidTransaction() {
		TestDO test = new TestDO();
		test.setName("11111");
		testMapper.insert(test);
		
		Test1DO test1 = new Test1DO();
		test1.setCust("2222");
		test1Mapper.insert(test1);
		throw new WMSException(ErrorCodeEnum.BD10001001.code(),"事务不生效场景1");
	}
	
	/********************************************************
	 * 事务不生效场景二
	 * 这个例子的目的是为了catch住内层事务的异常,让外层事务成功,但是实际上没有内外层事务都回滚了
	 *
	 * 这里A和B都受事务控制,并且是处于同一个事务的。
	 * A调用B,A中抓了B的异常,当B发生异常的时候,B的操作应该回滚,但是A吃了异常,A方法中没有产生异常,所以A的操作又应该提交,二者是相互矛盾的。
	 * spring的事务关联拦截器在抓到B的异常后就会标记rollback-only为true,当A执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。
	 *
	 * 报错提示:Transaction rolled back because it has been marked as rollback-only
	 *
	 * 如果想使外层事务生效可以把内层事务传播特性修改为:@Transactional(propagation = Propagation.REQUIRES_NEW)
	 * 
	 ********************************************************/
	@Transactional
	public void demo2() {
		TestDO test = new TestDO();
		test.setName("3333");
		testMapper.insert(test);
		try {
			invalidTransactionService.transaction();
		}catch (Exception e) {
			log.error("服务异常,异常被捕获", e);
		}
	}
	
	/********************************************************
	 * 事务不生效场景三
	 *
	 * 因为开了线程异步执行,等于事务完全在两个线程内,不在一个线程,所以即使抛错,也是一个生效一个不生效,
	 * 事务没有回滚
	 * 
	 ********************************************************/
	@Transactional
	public void demo3() {
		TestDO test = new TestDO();
		test.setName("5555");
		testMapper.insert(test);
		
		executorService.execute(() -> {
			Test1DO test1 = new Test1DO();
			test1.setCust("6666");
			test1Mapper.insert(test1);
		});
		
		throw new WMSException(ErrorCodeEnum.BD10001001.code(),"事务不生效场景3");
	}
	
	/********************************************************
	 * 事务不生效场景八
	 * Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked,如果遇到checked意外就不回滚。
	 * Exception包含RuntimeException体系和其他非RuntimeException的体系
	 * Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。
	 * spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作
	 *
	 *
	 *那么什么是检查型异常什么又是非检查型异常呢?
	 * 1.继承自runtimeexception或error的是非检查型异常,而继承自exception的则是检查型异常(当然,runtimeexception本身也是exception的子类)。
	 * 2.对非检查型类异常可以不用捕获,而检查型异常则必须用try语句块进行处理或者把异常交给上级方法处理总之就是必须写代码处理它。所以必须在service捕获异常,然后再次抛出,这样事务方才起效。
	 *
	 * @throws IOException 
	 * 
	 ********************************************************/
	@Transactional
	public void demo8() throws IOException {
		TestDO test = new TestDO();
		test.setName("11111");
		testMapper.insert(test);
		
		Test1DO test1 = new Test1DO();
		test1.setCust("2222");
		test1Mapper.insert(test1);
		throw new IOException("事务不生效场景8");
	}
	
	/********************************************************
	 * 事务不生效场景九
	 * @throws IOException 
	 * 
	 ********************************************************/
	
	public void demo9(){
		invalidTransaction2();
	}
	
	@Transactional
	private void invalidTransaction2() {
		TestDO test = new TestDO();
		test.setName("11111");
		testMapper.insert(test);
		
		Test1DO test1 = new Test1DO();
		test1.setCust("2222");
		test1Mapper.insert(test1);
		throw new WMSException("事务不生效场景9");
	}
	
	/********************************************************
	 * 事务生效场景1
	 * 
	 ********************************************************/
	public void demo4() {
		
		_self.invalidTransaction();
		
		//TODO other logic code here
	}
	
	/********************************************************
	 * 事务生效场景二
	 *
	 * 因为内层没有事务控制,所以内层报错,不会混回滚,同样外层catch住,所以外层业务成功
	 ********************************************************/
	@Transactional
	public void demo5() {
		TestDO test = new TestDO();
		test.setName("7777");
		testMapper.insert(test);
		
		try {
			validTransactionService.transaction();
		}catch (Exception e) {
			log.error("服务异常,异常被捕获", e);
		}
	}
	
	/********************************************************
	 * 事务生效场景三
	 *
	 *内层事务配置的是REQUIRES_NEW,表示自己用自己的,不和外层有牵连,内层如果报错,事务会回滚
	 * 外层如果catch住了,就可以正常执行,外层生效,内层回滚
	 ********************************************************/
	@Transactional
	public void demo6() {
		TestDO test = new TestDO();
		test.setName("9999");
		testMapper.insert(test);
		
		try {
			requireNewTransactionService.transactionWithException();
		}catch (Exception e) {
			log.error("服务异常,异常被捕获", e);
		}
	}
	
	/********************************************************
	 * 独立事务
	 * 内外层事务独立,内层操作未报错,事务正常执行,外层有错,事务回滚。
	 ********************************************************/
	@Transactional
	public void demo7() {
		TestDO test = new TestDO();
		test.setName("9999");
		testMapper.insert(test);
		
		requireNewTransactionService.transaction();
		throw new WMSException(ErrorCodeEnum.BD10001001.code(),"独立事务");
	}
	
}

4、分布式事务以及分布式事务嵌套

一次业务操作需要跨多个数据源或需要垮多个系统进行远程调用,就会产生分布式事务问题

全局事务一致性问题

全局事务id+三组件 tc+tm+rm

Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)

Seata 是 Simple Extensible Autonomous Transaction Architecture 的简写,由 feascar 改名而来。

AT模式 默认

TCC模式

XA模式

SAGA模式 长事务解决方案

XID 由ip 端口号 加全局事务id生成

关于分布式事务,工程领域主要讨论的是强一致性和最终一致性的解决方案。典型方案包括:

两阶段提交(2PC, Two-phase Commit)方案

eBay 事件队列方案

TCC 补偿模式

缓存数据最终一致性

一致性理论

分布式事务的目的是保障分库数据一致性,而跨库事务会遇到各种不可控制的问题,如个别节点永久性宕机,像单机事务一样的ACID是无法奢望的。另外,业界著名的CAP理论也告诉我们,对分布式系统,需要将数据一致性和系统可用性、分区容忍性放在天平上一起考虑。

两阶段提交协议(简称2PC)是实现分布式事务较为经典的方案,但2PC 的可扩展性很差,在分布式架构下应用代价较大,eBay 架构师Dan Pritchett 提出了BASE 理论,用于解决大规模分布式系统下的数据一致性问题。BASE 理论告诉我们:可以通过放弃系统在每个时刻的强一致性来换取系统的可扩展性。

CAP理论在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)3 个要素最多只能同时满足两个,不可兼得。

其中,分区容忍性又是不可或缺的。

avatar

一致性:分布式环境下多个节点的数据是否强一致。可用性:分布式服务能一直保证可用状态。当用户发出一个请求后,服务能在有限时间内返回结果。分区容忍性:特指对网络分区的容忍性。举例:Cassandra、Dynamo

等,默认优先选择AP,弱化C;HBase、MongoDB 等,默认优先选择CP,弱化A。

BASE理论核心思想:

基本可用(BasicallyAvailable):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用。

软状态(SoftState):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性。

最终一致性(EventualConsistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态。

  1. 一致性模型 数据的一致性模型可以分成以下 3 类: 强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。 弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。 分布式系统数据的强一致性、弱一致性和最终一致性可以通过Quorum NRW算法分析。
  2. 分布式事务解决方案 2PC方案——强一致性 2PC的核心原理是通过提交分阶段和记日志的方式,记录下事务提交所处的阶段状态,在组件宕机重启后,可通过日志恢复事务提交的阶段状态,并在这个状态节点重试,如Coordinator重启后,通过日志可以确定提交处于Prepare还是PrepareAll状态,若是前者,说明有节点可能没有Prepare成功,或所有节点Prepare成功但还没有下发Commit,状态恢复后给所有节点下发RollBack;若是PrepareAll状态,需要给所有节点下发Commit,数据库节点需要保证Commit幂等。 avatar 2PC方案的问题:同步阻塞。数据不一致。单点问题。升级的3PC方案旨在解决这些问题,主要有两个改进:增加超时机制。两阶段之间插入准备阶段。但三阶段提交也存在一些缺陷,要彻底从协议层面避免数据不一致,可以采用Paxos或者Raft算法。 eBay 事件队列方案——最终一致性 eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将需要分布式处理的任务通过消息或者日志的方式来异步执行,消息或日志可以存到本地文件、数据库或消息队列,再通过业务规则进行失败重试,它要求各服务的接口是幂等的。描述的场景为,有用户表user 和交易表transaction,用户表存储用户信息、总销售额和总购买额,交易表存储每一笔交易的流水号、买家信息、卖家信息和交易金额。如果产生了一笔交易,需要在交易表增加记录,同时还要修改用户表的金额。 avatar 论文中提出的解决方法是将更新交易表记录和用户表更新消息放在一个本地事务来完成,为了避免重复消费用户表更新消息带来的问题,增加一个操作记录表updates_applied来记录已经完成的交易相关的信息。

这个方案的核心在于第二阶段的重试和幂等执行。失败后重试,这是一种补偿机制,它是能保证系统最终一致的关键流程。

TCC (Try-Confirm-Cancel)补偿模式——最终一致性

某业务模型如图,由服务 A、服务B、服务C、服务D 共同组成的一个微服务架构系统。服务A 需要依次调用服务B、服务C 和服务D

共同完成一个操作。当服务A 调用服务D 失败时,若要保证整个系统数据的一致性,就要对服务B 和服务C 的invoke

操作进行回滚,执行反向的revert 操作。回滚成功后,整个微服务系统是数据一致的。

avatar

实现关键要素:服务调用链必须被记录下来。每个服务提供者都需要提供一组业务逻辑相反的操作,互为补偿,同时回滚操作要保证幂等。必须按失败原因执行不同的回滚策略。

缓存数据最终一致性

在我们的业务系统中,缓存(Redis 或者Memcached)通常被用在数据库前面,作为数据读取的缓冲,使得I/O

操作不至于直接落在数据库上。以商品详情页为例,假如卖家修改了商品信息,并写回到数据库,但是这时候用户从商品详情页看到的信息还是从缓存中拿到的过时数据,这就出现了缓存系统和数据库系统中的数据不一致的现象。

要解决该场景下缓存和数据库数据不一致的问题我们有以下两种解决方案:为缓存数据设置过期时间。当缓存中数据过期后,业务系统会从数据库中获取数据,并将新值放入缓存。这个过期时间就是系统可以达到最终一致的容忍时间。更新数据库数据后同时清除缓存数据。数据库数据更新后,同步删除缓存中数据,使得下次对商品详情的获取直接从数据库中获取,并同步到缓存。

常用组件: Seata,Sega,Atomikos

avatar

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

avatar

安装

关键注解全局@GlobalTranstional

1.更改事务组名称service

2.store更改mode 修改db

3.执行sql

4.修改注册进nacos

5.启动seata-server.bat

如何保证分布唯一全局id的生成

5、分布式事务异步方案

看下分布式事务的异步问题,根据事务的xid搭配future在切面里对注解进行处理,实现异步+分布式事务的并存

注意事项

这个依赖只是用来解决部分问题,不是解决全部问题

这个仅用于TM端,不要用来RM端(其实要实现RM端的话,可以仿照SeataAsyncAspect,写一个aspect,很简单的)

不要进行事务嵌套,不支持事务嵌套!!!

确保异步的多个操作之间是没有先后顺序的

这个是一个私人包装处理,仅供参考,还未应用到生产环境

我正在参与 腾讯云开发者社区数据库专题有奖征文

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录大纲
  • 选文出发点
    • PostgreSQL 简介
      • 腾讯云云数据库 PostgreSQL 简介
        • 云数据库PostgreSQL的环境准备
          • 1、打开云文档
          • 2、来到管理控制台只需6步拥有自己的数据库
          • 3、创建实例
        • 4、选择离自己最近的节点
          • 5、配置用户名密码
            • 6、连接实例
              • 7、连接不上检查安全组
                • 7、开启外网访问
                  • 2.MySQL Tinyint(1)与Tinyint(4):数据存储的大小差异与项目报错和解决
                  • 实战项目篇
                    • 1、线上环境对roll back only 的处理与产生
                      • 2、线上环境对嵌套事务的解决方案
                        • 3、11个demo分析事务失效的场景
                          • 4、分布式事务以及分布式事务嵌套
                            • 5、分布式事务异步方案
                            相关产品与服务
                            TDSQL MySQL 版
                            TDSQL MySQL 版(TDSQL for MySQL)是腾讯打造的一款分布式数据库产品,具备强一致高可用、全球部署架构、分布式水平扩展、高性能、企业级安全等特性,同时提供智能 DBA、自动化运营、监控告警等配套设施,为客户提供完整的分布式数据库解决方案。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                            http://www.vxiaotou.com