在本文中,将介绍一次遇到的.Net分布式事务死锁现象以及解决方法。我们将首先了解事务框架的构成,然后分析导致死锁的代码,最后提出解决方法。

事务框架

本次开发框架JMSFramework将分布式事务划分为4个阶段,分别是:执行、确认、提交和重试。

1、执行

调用微服务来执行相关的业务操作。如果其中任何一个服务执行抛出异常或者宕机,那么所有的事务都会回滚。

2、确认

这个阶段会向各个微服务发送确认请求,主要目的是校验一下当前的网络是否正常,微服务有没有宕机,如果这个阶段有任何异常,那么所有的事务都会回滚。

3、提交

首先JMSFramework会通知网关,标识这次分布式事务为成功状态。(如果通知网关失败,则回滚所有事务)

然后通知各个微服务提交事务,确保所有的操作都已经完成。

如果某个服务提交事务失败,不会影响其他服务提交事务。

4、重试

有很小的机率会执行到此阶段。

当某个微服务在真正提交事务的时候发生意外宕机,导致事务没有成功提交。一旦服务器重新启动后,它会向网关咨询,并得知该事务已经被标记为成功状态。这时,系统会自动重新执行相关的业务代码,并提交事务,以确保数据的一致性。

产生死锁的代码

接下来我们看看触发死锁的代码:

            using ( var client = new RemoteClient(gatewayAddrs))
{
//标识后面的调用需要启用分布式事务控制
client.BeginTransaction(); //获取服务接口
var accountService = await client.GetMicroServiceAsync("UserAccountService");
var giftService = await client.GetMicroServiceAsync("UserAccountService"); //扣除指定用户100积分
accountService.InvokeAsync("UseMemberPoints", userid , 100 ); //把某个礼品赠予指定用户
var ret = await giftService.InvokeAsync<GiftResult>("GiveGiftToUser", giftid, userid);
if(ret.GiftsReceivedCount > 10)
{
//如果累计收取礼品大于10个,升级用户vip等级
accountService.InvokeAsync("UpgradeVip" , userid);
} //提交分布式事务
await client.CommitTransactionAsync();
}

这是一段调用几个微服务的代码,功能是:扣除用户100积分,同时把一个礼品转给这个用户,如果发现此用户累计已经兑换超过10个礼品,那么提升此用户的vip等级。

代码很简单,看不出有什么问题,并且上面所调用的三个接口函数,也都是通过了单元测试。

但运行后却发现卡死在client.CommitTransactionAsync()。

看来是调用某个服务卡住了,当我把每个调用都加上await后,发现其实是卡在了await accountService.InvokeAsync("UpgradeVip" , userid);

经过查阅代码,得知该函数会去更新用户信息表,而扣除用户100积分的函数也会去更新用户信息表。由于这两个函数在不同的线程中执行(服务调用属于远程请求,每次请求都是一个独立的线程),它们都要锁定同一个资源,因此只会有第一个线程成功更新,但由于数据库事务不会立即提交,而是最后一起提交,因此该用户数据会一直被锁定。第二个线程无法获取锁,更新语句也一直无法执行。由于第二个线程被阻塞,代码无法执行到client.CommitTransactionAsync(),导致事务无法提交,锁也不会被释放,这就形成了死锁。

解决方法

问题的根本不是两个线程锁定同一个资源,而是两个不同的数据库对象锁定同一个资源。如果这两个线程使用的是同一个数据库对象和同一个数据库事务,就不会出现这个问题。

因此,解决方法如下:

  • 数据库对象作用域管理:使用AddScope方式依赖注入数据库对象,确保在同一个作用域中获取的数据库对象是同一个。

  • 利用JMSFramework特性:利用JMSFramework 5.0及以上版本的特性,自动将同一个分布式事务范围内的远程调用整合到一个网络请求中。这样,如果调用的服务属于同一个进程,它们将被安排在同一个作用域中,从而保证获取到的数据库对象是同一个。

通过以上解决方法,可以有效避免分布式事务死锁的发生。

记一次.Net分布式事务死锁现象以及解决方法的更多相关文章

  1. I2C死锁原因及解决方法(转)

    源:http://blog.csdn.net/zyboy2000/article/details/5603091 死锁总线表现为:SCL为高,SDA一直为低 现象:单片机采用硬件i2c读取E2PROM ...

  2. libnids关于计算校验和引起的抓不到包的现象的解决方法

    libnids关于计算校验和引起的抓不到包的现象的解决方法: nids.h中有这么一段: struct nids_chksum_ctl { u_int netaddr; u_int mask; u_i ...

  3. 将不确定变为确定~transactionscope何时提升为分布式事务?(sql2005数据库解决提升到MSDTC的办法)

    回到目录 对于transactionscope不了解的同学,可以看我的相关文章 第二十六回   将不确定变为确定~transactionscope何时提升为分布式事务? 第二十七回   将不确定变为确 ...

  4. sqlserver 死锁原因及解决方法

    其实所有的死锁最深层的原因就是一个:资源竞争 表现一: 一个用户A 访问表A(锁住了表A),然后又访问表B,另一个用户B 访问表B(锁住了表B),然后企图访问表A,这时用户A由于用户B已经锁住表B,它 ...

  5. TCP协议的粘包现象和解决方法

    # 粘包现象 # serverimport socket sk = socket.socket()sk.bind(('127.0.0.1', 8005))sk.listen() conn, addr ...

  6. Mysql并发时经典常见的死锁原因及解决方法

    1.    mysql都有什么锁 MySQL有三种锁的级别:页级.表级.行级. 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现死锁 ...

  7. MySQL学习笔记(五)并发时经典常见的死锁原因及解决方法

    MySQL都有什么锁? MySQL有三种锁的级别:页级.表级.行级. 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现死锁:锁定粒度 ...

  8. C++(八)— 死锁原因及解决方法

    1.死锁原因 死锁问题被认为是线程/进程间切换消耗系统性能的一种极端情况.在死锁时,线程/进程间相互等待资源,而又不释放自身的资源,导致无穷无尽的等待,其结果是任务永远无法执行完成. 打个比方,假设有 ...

  9. hadoop 完全分布式 下 datanode无法启动解决方法

    问题描述: 在集群模式下更改节点后,启动集群发现 datanode一直启动不起来. 我集群配置:有5个节点,分别为master slave1-5 . 在master以Hadoop用户执行:start- ...

  10. ORA-02049: 超时: 分布式事务处理等待锁的解决方法

    是其他地方执行了操作没有提交,把其他地方提交了就好了

随机推荐

  1. Kubernetes入门实践(Pods)

    为了解决多应用联合运行的问题,同时还要不破坏容器的隔离,就要再对多个容器进行打包.Pod就是对容器的打包,里面的容器可以看成是一个整体,总是能一起调度.一起运行,绝不会出现分离的情况,而Pod属于Ku ...

  2. 生产事故-走近科学之消失的JWT

    入职多年,面对生产环境,尽管都是小心翼翼,慎之又慎,还是难免捅出篓子.轻则满头大汗,面红耳赤.重则系统停摆,损失资金.每一个生产事故的背后,都是宝贵的经验和教训,都是项目成员的血泪史.为了更好地防范和 ...

  3. blog图片资源

  4. ES6 新增的一些特性

    还有symbol和set,map, bind,call,apply 1. let关键字 (1)基本用法:let关键字用来声明变量,它的用法类似于var,都是用来声明变量. (2)块级作用域:let声明 ...

  5. 关于vue3 上传图片到七牛云

    引子:前端程序猿,很少写博客,担心有一些技术很牛逼的大佬看不上,还喜欢怼人,玻璃心容易影响心情,这个是我自己在项目上遇到的,也百度参考了很多大佬的文章,感觉多少有点不全,然后就自己整理一下,当一个笔记 ...

  6. 2022-05-19:给定一个数组arr,给定一个正数M, 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对。 返回arr中M整除对的总数量。 来自微软。

    2022-05-19:给定一个数组arr,给定一个正数M, 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对. 返回arr中M整除对的总数量. 来自 ...

  7. 2022-04-16:在一个10^6 * 10^6的网格中, source = [sx, sy]是出发位置,target = [tx, ty]是目标位置, 数组blocked是封锁的方格列表,被禁止的

    2022-04-16:在一个10^6 * 10^6的网格中, source = [sx, sy]是出发位置,target = [tx, ty]是目标位置, 数组blocked是封锁的方格列表,被禁止的 ...

  8. 2021-04-25:给定一个数组arr,和一个正数M,返回在arr的子数组在长度不超过M的情况下,求最大的累加和。

    福大大 答案2021-04-25: 前缀和+左大右小的双端队列.时间太晚了,所以写得简单. 代码用golang编写.代码如下: package main import ( "containe ...

  9. Hugging News #0512: 🤗 Transformers、🧨 Diffusers 更新,AI 游戏是下个新热点吗

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  10. 从零开始使用 Astro 的实用指南

    在这个实用的Astro指南中,我将指导你完成设置过程,并告诉你如何构造你的文件.你将学习如何添加页面.交互式组件,甚至是markdown文章.我还会告诉你如何从服务器上获取数据,创建布局,并使用van ...