seata redis模式重构之全局事务更新
关于重构全局事务信息存储重构过程中一个问题的思考。
1.watch的必要性
jedis.hmset命令的语义: 如果这个map存在,就更新这个多个值; 如果这个map不存在,则新建map,然后设置键值对;
同时将多个 field-value (域-值)对设置到哈希表 key 中。 此命令会覆盖哈希表中已存在的域。 如果 key不存在,一个空哈希表被创建并执行 HMSET 操作。 如果命令执行成功,返回 OK 。 当 key 不是哈希表(hash)类型时,返回一个错误。
在更新全局事务session的map时,如果多tc情况下,事务前不watch这个全局事务的key,那么,当其他tc和当前tc都来更新这个全局事务时,或者由于某种情况,一个tc把这个global session删除了,那么,这个hmset是会出问题的,他会新建一个map,就这状态和时间两个值,这个map会成为游离状态,也不会被删除了。
所以这个地方,要做好防范,watch全局key,确保这个hmset过程中,map一定存在,让他update,而不会add。
private boolean updateGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
String xid = globalTransactionDO.getXid();
String globalKey = buildGlobalKeyByTransactionId(globalTransactionDO.getTransactionId());
try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
String previousStatus = jedis.hget(globalKey, REDIS_KEY_GLOBAL_STATUS);
if (StringUtils.isEmpty(previousStatus)) {
throw new StoreException("Global transaction is not exist, update global transaction failed.");
}
//Defensive watch to prevent other TC server operating concurrently,Fail fast
//jedis.watch(globalKey);
String previousGmtModified = jedis.hget(globalKey, REDIS_KEY_GLOBAL_GMT_MODIFIED);
Transaction multi = jedis.multi();
Map<String,String> map = new HashMap<>(2);
map.put(REDIS_KEY_GLOBAL_STATUS,String.valueOf(globalTransactionDO.getStatus()));
map.put(REDIS_KEY_GLOBAL_GMT_MODIFIED,String.valueOf((new Date()).getTime()));
multi.hmset(globalKey,map);
multi.lrem(buildGlobalStatus(Integer.valueOf(previousStatus)),0, xid);
multi.rpush(buildGlobalStatus(globalTransactionDO.getStatus()), xid);
List<Object> exec = multi.exec();
String hmset = exec.get(0).toString();
long lrem = (long)exec.get(1);
long rpush = (long)exec.get(2);
if (OK.equalsIgnoreCase(hmset) && lrem > 0 && rpush > 0) {
return true;
} else {
// If someone failed, the succeed operations need rollback
if (OK.equalsIgnoreCase(hmset)) {
Map<String,String> mapPrevious = new HashMap<>(2);
mapPrevious.put(REDIS_KEY_GLOBAL_STATUS,previousStatus);
mapPrevious.put(REDIS_KEY_GLOBAL_GMT_MODIFIED,previousGmtModified);
jedis.hmset(globalKey,mapPrevious);
}
if (lrem > 0) {
jedis.rpush(buildGlobalStatus(Integer.valueOf(previousStatus)),xid);
}
if (rpush > 0) {
jedis.lrem(buildGlobalStatus(globalTransactionDO.getStatus()),0,xid);
}
return false;
}
} catch (Exception ex) {
throw new StoreException(ex);
}
}
2.hmset hset hsetnx
在重构的过程中,发现jedismock无法mock watch命令,那测试时,只能拿掉watch,就在想,如果没有watch命令,那这个地方如何来确保事务也是正确的。 我们对比下三个set命令的区别:
hmset
同时将多个 field-value (域-值)对设置到哈希表 key 中。 此命令会覆盖哈希表中已存在的域。 如果 key不存在,一个空哈希表被创建并执行 HMSET 操作。 如果命令执行成功,返回 OK 。 当 key 不是哈希表(hash)类型时,返回一个错误。
这个命令,看起来不行,如果global session被删除了,这个命令会新建一个map.
hsetnx
当且仅当域 field 尚未存在于哈希表的情况下, 将它的值设置为 value 。 如果给定域已经存在于哈希表当中, 那么命令将放弃执行设置操作。 如果哈希表 hash 不存在, 那么一个新的哈希表将被创建并执行 HSETNX 命令。 HSETNX命令在设置成功时返回 1 , 在给定域已经存在而放弃执行设置操作时返回 0 。
这个命令,看起来不行,如果global session被删除了,这个命令会新建一个map.
hset
将哈希表 hash 中域 field 的值设置为 value 。 如果给定的哈希表并不存在, 那么一个新的哈希表将被创建并执行 HSET 操作。 如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value 覆盖。 当 HSET 命令在哈希表中新创建 field 域并成功为它设置值时, 命令返回 1 ; 如果域 field 已经存在于哈希表, 并且HSET 命令成功使用新值覆盖了它的旧值, 那么命令返回 0
这个命令,我们看下实际情况,当map123不存在时,和map123存在但是key-value的key不存在时,返回值都是1,我们无法取分这个命令的操作成功究竟是不是新建了一个map.所以看起来也不行。
阿里云redis:0>hset map123 key va
"1"
阿里云redis:0>hset map123 key1 va1
"1"
阿里云redis:0>hset map123 key1 va2
"0"
所以,暂时还是没有想到,什么好方法。watch是目前能想到的唯一方式。
- Compass: 在你的应用中集成搜索功能
- 列表选择Spinner
- 巧用CSS3 :target 伪类制作Dropdown下拉菜单(无JS)
- 开源的虚拟机软件 VirtualBox v1.5.2
- Farseer:一个用于Silverlight和XNA的开源物理引擎
- Visual Studio 2008 debug的时候发生郁闷的错误ContextSwitchDeadlock was detected
- LINQ TO XML
- 不被未来折叠掉:AI时代下的思考
- Google 分析的基准化测试
- WebFont 三宗罪之一:WebFont 与 FOUT
- 探究基于声明的身份标识
- 深化“互联网+先进制造业”发展工业互联网的系列解读二:打造平台体系
- WebFont 三宗罪之二:吹毛求疵的WebFont 渲染差异
- IDC发布IT转型报告,现代化、自动化、转型三要素必不可少
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法