分布式事务 TCC-Transaction 源码解析——事务存储器

本文主要基于 TCC-Transaction 1.2.3.3 正式版

1. 概述

2. 序列化

2.1 JDK 序列化实现

2.2 Kyro 序列化实现

2.3 JSON 序列化实现

3. 存储器

3.1 可缓存的事务存储器抽象类

3.2 JDBC 事务存储器

3.3 Redis 事务存储器

3.4 Zookeeper 事务存储器

3.5 File 事务存储器

666. 彩蛋

友情提示:欢迎关注公众号【芋道源码】。关注后,拉你进【源码圈】微信群和【芋艿】搞基嗨皮。

友情提示:欢迎关注公众号【芋道源码】。关注后,拉你进【源码圈】微信群和【芋艿】】搞基嗨皮。

友情提示:欢迎关注公众号【芋道源码】。关注后,拉你进【源码圈】微信群和【芋艿】】搞基嗨皮。

1. 概述

本文分享事务存储器。主要涉及如下 Maven 项目:

:tcc-transaction 底层实现。

在 TCC 的过程中,根据应用内存中的事务信息完成整个事务流程。But 实际业务场景中,将事务信息只放在应用内存中是远远不够可靠的。例如:

应用进程异常崩溃,未完成的事务信息将丢失。

应用进程集群,当提供远程服务调用时,事务信息需要集群内共享。

发起事务的应用需要重启部署新版本,因为各种原因,有未完成的事务。

因此,TCC-Transaction 将事务信息添加到内存中的同时,会使用外部存储进行持久化。目前提供四种外部存储:

JdbcTransactionRepository,JDBC 事务存储器

RedisTransactionRepository,Redis 事务存储器

ZooKeeperTransactionRepository,Zookeeper 事务存储器

FileSystemTransactionRepository,File 事务存储器

本文涉及到的类关系如下图( 打开大图 ):

你行好事会因为得到赞赏而愉悦

同理,开源项目贡献者会因为 Star 而更加有动力

为 TCC-Transaction 点赞!传送门

ps:笔者假设你已经阅读过《tcc-transaction 官方文档 —— 使用指南1.2.x》。

2. 序列化

在《TCC-Transaction 源码分析 —— TCC 实现》「4. 事务与参与者」,可以看到 Transaction 是一个比较复杂的对象,内嵌 Participant 数组,而 Participant 本身也是复杂的对象,内嵌了更多的其他对象,因此,存储器在持久化 Transaction 时,需要序列化后才能存储。

,对象序列化接口。实现代码如下:

目前提供JDK自带序列化Kyro序列化两种实现。

2.1 JDK 序列化实现

TCC-Transaction 使用的默认的序列化

2.2 Kyro 序列化实现2.3 JSON 序列化实现

JDK 和 Kyro 的序列化实现,肉眼无法直观具体存储事务的信息,你可以通过实现 ObjectSerializer 接口,实现自定义的 JSON 序列化。

3. 存储器

,事务存储器接口。实现代码如下:

不同的存储器通过实现该接口,提供事务的增删改查功能。

3.1 可缓存的事务存储器抽象类

可缓存的事务存储器抽象类,实现增删改查事务时,同时缓存事务信息。在上面类图,我们也可以看到 TCC-Transaction 自带的多种存储器都继承该抽象类。

CachableTransactionRepository 构造方法实现代码如下:

使用 Guava Cache 内存缓存事务信息,设置最大缓存个数为 1000 个,缓存过期时间为最后访问时间 120 秒。

实现代码如下:

调用 方法,新增事务。新增成功后,调用 方法,添加事务到缓存。

为抽象方法,子类实现该方法,提供新增事务功能。

实现代码如下:

调用 方法,更新事务。

若更新成功后,调用 方法,添加事务到缓存。

若更新失败后,抛出 OptimisticLockException 异常。有两种情况会导致更新失败:(1) 该事务已经被提交,被删除;(2) 乐观锁更新时,缓存的事务的版本号( )和存储器里的事务的版本号不同,更新失败。为什么?在《TCC-Transaction 源码分析 —— 事务恢复》详细解析。更新失败,意味着缓存已经不不一致,调用 方法,移除事务从缓存中。

为抽象方法,子类实现该方法,提供更新事务功能。

实现代码如下:

调用 方法,删除事务。

调用 方法,移除事务从缓存中。

为抽象方法,子类实现该方法,提供删除事务功能。

实现代码如下:

调用 方法,优先从缓存中获取事务。

调用 方法,缓存中事务不存在,从存储器中获取。获取到后,调用 方法,添加事务到缓存中。

为抽象方法,子类实现该方法,提供查询事务功能。

实现代码如下:

调用 方法,从存储器获取超过指定时间的事务集合。调用 方法,循环事务集合添加到缓存。

为抽象方法,子类实现该方法,提供获取超过指定时间的事务集合功能。

3.2 JDBC 事务存储器

,JDBC 事务存储器,通过 JDBC 驱动,将 Transaction 存储到 MySQL / Oracle / PostgreSQL / SQLServer 等关系数据库。实现代码如下:

,领域,或者也可以称为模块名,应用名,用于唯一标识一个资源。例如,Maven 模块 ,我们可以配置该属性为 。

,表后缀。默认存储表名为 ,配置表名后,为 。

,存储数据的数据源。

,序列化。当数据库里已经有数据的情况下,不要更换别的序列化,否则会导致反序列化报错。建议:TCC-Transaction 存储时,新增字段,记录序列化的方式。

表结构如下:

,仅仅数据库自增,无实际用途。

,Transaction 序列化。

3.3 Redis 事务存储器

,Redis 事务存储器,将 Transaction 存储到 Redis。实现代码如下:

,key 前缀。类似 JdbcTransactionRepository 的 属性。

一个事务存储到 Reids,使用 Redis 的数据结构为 HASHES。

key : 使用 + ,实现代码如下:

HASHES 的 key :使用 。

添加和更新 Transaction 时,使用 Redis HSETNX,不存在当前版本的值时,进行设置,重而实现类似乐观锁的更新。

读取 Transaction 时,使用 Redis HGETALL,将 Transaction 所有 对应的值读取到内存后,取 值最大的对应的值。

HASHES 的 value :调用 方法,序列化 Transaction。实现代码如下:

TODO 为什么序列化两次

在实现 方法,无法像数据库使用时间条件进行过滤,因此,加载所有事务后在内存中过滤。实现代码如下:

FROM 《TCC-Transaction 官方文档 —— 使用指南1.2.x》

使用 RedisTransactionRepository 需要配置 Redis 服务器如下:

appendonly yes

appendfsync always

3.4 Zookeeper 事务存储器

,Zookeeper 事务存储器,将 Transaction 存储到 Zookeeper。实现代码如下:

,存储 Zookeeper 根目录,类似 JdbcTransactionRepository 的 属性。

一个事务存储到 Zookeeper,使用 Zookeeper 的持久数据节点

path: + + 。实现代码如下:

data:调用 方法,序列化 Transaction。

version:使用 Zookeeper 数据节点自带版本功能。这里要注意下,Transaction 的版本从 1 开始,而 Zookeeper 数据节点版本从 0 开始。

另外,在生产上暂时不建议使用 ZooKeeperTransactionRepository,原因有两点:

不支持 Zookeeper 安全认证。

使用 Zookeeper 时,未考虑断网重连等情况。

如果你要使用 Zookeeper 进行事务的存储,可以考虑使用 Apache Curator 操作 Zookeeper,重写 ZooKeeperTransactionRepository 部分代码。

3.5 File 事务存储器

,File 事务存储器,将 Transaction 存储到文件系统。

另外,在生产上不建议使用 FileSystemTransactionRepository,因为不支持多节点共享。用分布式存储挂载文件另说,当然还是不建议,因为不支持乐观锁并发更新。

666. 彩蛋

这篇略( 超 )微( 级 )水更,哈哈哈,为《TCC-Transaction 源码分析 —— 事务恢复》做铺垫啦。

使用 RedisTransactionRepository 和 ZooKeeperTransactionRepository 存储事务还是 Get 蛮多点的。

胖友,分享一个朋友圈可好?

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180207G05OV600?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券