在本文中,将介绍一次遇到的.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. DG:Oracle查看是否搭建DataGuard

    Oracle查看是否是DataGuard 1.查看归档路径 show parameter log_archive_dest LOG_ARCHIVE_DEST_n, 归档文件的生成路径, LOCATIO ...

  2. 如何通过Java应用程序在PPT中创建SmartArt图形

    SmartArt其实就是一个文字的可视化工具,用户可在PowerPoint,Word,Excel中使用该特性创建各种图形图表.SmartArt 图形是信息和观点的视觉表示形式.可以通过从多种不同布局中 ...

  3. 尝试CentOS8---部署集群(生产环境7.9为好)

    一.LVS集群简介 什么是集群 通过网络将很多服务器集中起来,提供同一种服务,在客户端看来就像是只有一个服务器 二.LVS-NAT集群 1.环境准备 启动3台虚拟机,禁用selinux和firewal ...

  4. Simulation-计算统计——Monte Carlo

    Monte Carlo Integration 找到原函数,再计算 无法找到原函数,MC积分 Assume that we can generate \(U_1, . . . , U_n \sim U ...

  5. .net 6 使用 NEST 查询,时间字段传值踩坑

    0x01业务描述 说明: 同事搭建的业务系统,最开始使用 log4net  记录到本地日志. 然后多个项目为了日志统一,全部记录在 Elasticsearch ,使用  log4net.Elastic ...

  6. golang中一种不常见的switch语句写法

    最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下. 注意这里讨论的不是typed switch,也就是case语句后面是类型的那种. 直接看代码: func (s *systemd) ...

  7. Prism Sample 10 10-CustomRegistrations

    作用同上节,这里是用修改注册的方式自定义View和ViewModel的关联. protected override void ConfigureViewModelLocator() { base.Co ...

  8. Django笔记三十六之单元测试汇总介绍

    本文首发于公众号:Hunter后端 原文链接:Django笔记三十六之单元测试汇总介绍 Django 的单元测试使用了 Python 的标准库:unittest. 在我们创建的每一个 applicat ...

  9. 2022-11-01:给定一个只由小写字母和数字字符组成的字符串str。 要求子串必须只含有一个小写字母,数字字符数量随意。 求这样的子串最大长度是多少?

    2022-11-01:给定一个只由小写字母和数字字符组成的字符串str. 要求子串必须只含有一个小写字母,数字字符数量随意. 求这样的子串最大长度是多少? 答案2022-11-01: 经典的滑动窗口问 ...

  10. 【工作随手记】并发之synchronized

    synchronized对于java同学肯定都是耳熟能详的必修课了.但是不管对于新手还是老手都有一些容易搞错的点.这里权做一点记录. 锁的是代码还是对象? 同步块一般有两种写法. 1是直接加以方法体上 ...