seata redis模式重构之全局事务更新
发布日期:2021-06-30 11:03:33 浏览次数:2 分类:技术文章

本文共 4833 字,大约阅读时间需要 16 分钟。

关于重构全局事务信息存储重构过程中一个问题的思考。

备注:这个问题的根源在于可能多tc并发执行,在多tc时,如果能够选出一个leader,由leader统一处理请求,那么这个问题便顺利解决了。目前seata的多tc集群下,没有主从的区别。后期可能会引入jraft,使用raft协议来选个leader,那么代码里的一些防御性处理,便可以拿掉了。

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。

如果使用pipeline,tc1和tc2都判断全局事务存在了,然后开始去做更新操作。

tc1开始操作,hmset,lrem,rpush,然后pipeline返回结果后,假设tc1操作成功,这时,tc2pipeline也开始操作,由于hmset的原因,这个地方会出现问题。

/**     * Update the global transaction.     * It will update two parts:     *  1.the global session map     *  2.the global status list     * If the update failed,the succeed operates will rollback     * @param globalTransactionDO     * @return     */    private boolean updateGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
String xid = globalTransactionDO.getXid(); String globalKey = buildGlobalKeyByTransactionId(globalTransactionDO.getTransactionId()); try (Jedis jedis = JedisPooledFactory.getJedisInstance()) {
// Defensive watch to prevent other TC server operating concurrently,Fail fast jedis.watch(globalKey); 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."); } String previousGmtModified = jedis.hget(globalKey, REDIS_KEY_GLOBAL_GMT_MODIFIED); Transaction multi = jedis.multi(); Map
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
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)) {
// Defensive watch to prevent other TC server operating concurrently,give up this operate jedis.watch(globalKey); String xid2 = jedis.hget(globalKey, REDIS_KEY_GLOBAL_XID); if (StringUtils.isNotEmpty(xid2)) {
Map
mapPrevious = new HashMap<>(2); mapPrevious.put(REDIS_KEY_GLOBAL_STATUS,previousStatus); mapPrevious.put(REDIS_KEY_GLOBAL_GMT_MODIFIED,previousGmtModified); Transaction multi2 = jedis.multi(); multi2.hmset(globalKey,mapPrevious); multi2.exec(); } } 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 RedisException(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是目前能想到的唯一方式。

转载地址:https://it4all.blog.csdn.net/article/details/108425249 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:深入理解Redis Cluster和Jedis Cluster
下一篇:grafana踩坑记录

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月23日 14时37分38秒