EF+SQLSERVER控制并发下抢红包减余额(改进)
最近几年想必大家一听到哪里有抢红包可以抢,马上会拿起手机点去~~~~然后问题来了。。。
如何控制在同一时间保证数据库中扣减红包余额不会出错。之前我们的做法是直接锁程序,这样子带来的坏处就是等待时间太长,每当一个线程进去之后要经过以下几个过程。
过程分别是
1. 查表
2. 校验信息
3. 发送微信服务器
4. 等待反馈
5. 更新表
等这些过程结束之后才轮到下面这个过程。想必这样要等到花儿都谢了~
另外发送微信服务器这个过程时间在0s至9s时间不等。会产生大量的空闲时间,这里CPU会产生大量的空闲。而且这种情况也无法继续做负载均衡,如果有多个站点部署必定会产生数据库并发问题。
若在查表之前加锁更新后释放掉,虽然说不会产生数据库并发。但是在第二个线程进入查询的时候他会一直在等待,其耗时则与更锁程序差不多。
改进
这个想法源于分布式事务的设计,采用预扣红包余额的方式来保证无需等待微信服务器反馈,让下一个线程可继续执行相关任务。当微信服务器反馈回来时,才开始另外一个事务去更改交易状态。若反馈结果为FAIL则需要预扣的红包余额进行还原操作。
粗略写了模拟实际环境的测试代码,模拟抢红包动作
private void task() { for (int i = 0; i < 50; i++) { string tradeNo = Qxun.Framework.Utility.CreateOrderNo.DateTimeAndNumber(); try { using (var trans = new TransactionScope()) { using (var dbContext = new ActivityDbContext()) { //加锁 var model = dbContext.Database.SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013>(@"select * from VIPPassRedBag013 with(updlock) where ActivitySceneID=199").FirstOrDefault(); var mode = dbContext.Database.SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013Mode>(@"select * from VIPPassRedBag013Mode with(updlock) where ActivitySceneID=199").ToList(); //模拟校验延迟 Thread.Sleep(5); //得到领取红包的金额 VIPPassRedBag013Mode currentMode = null; foreach (var modeItem in mode) { if (modeItem.RemainCount > 0) { currentMode = modeItem; break; } } //判断是否领完 if (currentMode != null && model != null && model.RedBagBalance >= currentMode.Money) { VIPPassRedBag013Play currentPlayModel = new VIPPassRedBag013Play();//本次的参与记录对象 currentPlayModel.VIPPassRedBag013ModeID = currentMode.ID; currentPlayModel.WeixinUserID = Thread.CurrentThread.ManagedThreadId; currentPlayModel.Money = Convert.ToInt32(currentMode.Money * 100);//要支付的金额(存入到表的) currentPlayModel.TradeNumber = tradeNo; currentPlayModel.Status = (int)TradeStatus.Trading; currentPlayModel.VIPPassRedBag013ModeID = currentMode.ID; currentPlayModel.ActivitySceneID = 199; dbContext.Insert<VIPPassRedBag013Play>(currentPlayModel); currentMode.RemainCount -= 1; dbContext.Update<VIPPassRedBag013Mode>(currentMode); model.RedBagBalance -= currentMode.Money; dbContext.Update<Qxun.Activity.Contract.VIPPassRedBag013>(model); trans.Complete(); } else { trans.Complete(); } } } } catch (Exception ex){} //提交至微信 string returnCode = "SUCCESS"; Random ran = new Random(); int time = ran.Next(100); if (time <= 1) { returnCode = "FAIL"; } //模拟网络延迟 Thread.Sleep(time * 100); //设置重新尝试次数 bool retry = true; int retryCount = 0; do { Qxun.Activity.Contract.VIPPassRedBag013 model = null; VIPPassRedBag013Play playModel = null; VIPPassRedBag013Mode mode = null; try { using (var trans = new TransactionScope()) { using (var dbContext = new ActivityDbContext()) { //这里获取很容易异常 model = dbContext.Database.SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013>(@"select * from VIPPassRedBag013 with(updlock) where ActivitySceneID=199").FirstOrDefault(); playModel = dbContext.Database.SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013Play>(@"select * from VIPPassRedBag013Play with(updlock) where TradeNumber='" + tradeNo + "'").FirstOrDefault(); mode = dbContext.Database.SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013Mode>(@"select * from VIPPassRedBag013Mode with(updlock) where ID=" + playModel.VIPPassRedBag013ModeID).FirstOrDefault(); if (returnCode == "SUCCESS") { playModel.Status = (int)TradeStatus.Success; playModel.Remark = "retry=" + retryCount + ",success;time=" + DateTime.Now.ToString(); playModel.FinishTime = DateTime.Now; dbContext.Update<VIPPassRedBag013Play>(playModel); trans.Complete(); retry = false; } else { model.RedBagBalance += mode.Money; dbContext.Update<Qxun.Activity.Contract.VIPPassRedBag013>(model); playModel.Status = (int)TradeStatus.Fail; playModel.Remark = "retry=" + retryCount + ",fail;time=" + DateTime.Now.ToString(); playModel.FinishTime = DateTime.Now; dbContext.Update<VIPPassRedBag013Play>(playModel); mode.RemainCount += 1; dbContext.Update<VIPPassRedBag013Mode>(mode); trans.Complete(); retry = false; } } } } catch (Exception ex) { //如果之前的线程请求数据库时阻塞 //如果执行失败 retryCount++; retry = true; } if (retryCount > 5) { break; } } while (retry); } }
模拟100个人并发抢红包
public ActionResult Excute() { for (int i = 0; i < 100; i++) { Thread thread = new Thread(new ThreadStart(task)); thread.Start(); } return Content("完成!"); }
上面代码还用了一个retry变量控制防止由于长等待产生的超时,好让每个订单都能够处理的到。但是实际上当线程数量为100-200时候,会有10至20个VIPPassRedBag013Play订单状态一直为Trading。当线程数量大于200的时候就变得及不稳定,目前一直没有找到是什么原因。希望有缘人指点一二。
为了解决这种现象,我在Global写了周期去查找10分钟前的VIPPassRedBag013Play,且订单状态为Trading的单子(都10分钟了还没有处理,那就是处理不到了)。得到订单号,去反查微信的红包交易记录。通过微信红包反馈的结果去更新数据库的交易状态。
public ActionResult Check() { using (var dbContext = new ActivityDbContext()) { //查询十分钟之前状态仍为交易中的订单 var playModel = dbContext.Database .SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013Play>(@"select * from VIPPassRedBag013Play with(nolock) where ActivitySceneID=199 and[status] = 2 and DATEDIFF(MINUTE, CreateTime, GETDATE()) > 10").ToList(); if (playModel != null && playModel.Count > 0) { foreach (var item in playModel) { using (var trans = new TransactionScope()) { //提交至微信查询 string returnCode = "SUCCESS"; Random ran = new Random(); int time = ran.Next(100); if (time <= 1) { returnCode = "FAIL"; } //去查询微信红包的信息 //模拟网络延迟 Thread.Sleep(time * 100); if (returnCode == "SUCCESS") { item.Status = (int)TradeStatus.Success; item.Remark = "success;time=" + DateTime.Now.ToString(); item.FinishTime = DateTime.Now; dbContext.Update<VIPPassRedBag013Play>(item); trans.Complete(); } else { Qxun.Activity.Contract.VIPPassRedBag013 model = dbContext.Database .SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013>(@"select * from VIPPassRedBag013 with(updlock) where ActivitySceneID=199") .FirstOrDefault(); VIPPassRedBag013Mode mode = dbContext.Database .SqlQuery<Qxun.Activity.Contract.VIPPassRedBag013Mode>(@"select * from VIPPassRedBag013Mode with(updlock) where ID=" + item.VIPPassRedBag013ModeID).FirstOrDefault(); model.RedBagBalance += item.Money; dbContext.Update<Qxun.Activity.Contract.VIPPassRedBag013>(model); item.Status = (int)TradeStatus.Fail; item.Remark = "fail;time=" + DateTime.Now.ToString(); item.FinishTime = DateTime.Now; dbContext.Update<VIPPassRedBag013Play>(item); mode.RemainCount += 1; dbContext.Update<VIPPassRedBag013Mode>(mode); trans.Complete(); } } } } } return View(); }
PS:经过这样改进,应该比之前的好多了。当然这样还是很远远不够的。希望各位路过的大神能够指点一二,甚是感谢!
EF+SQLSERVER控制并发下抢红包减余额(改进)相关推荐
- 一套WPF+EF+SQLServer 会员卡管理系统源码
今天分享的是一套WPF+EF+SQLServer 会员卡管理系统源码,界面使用Panuon.WPF.UI,图表工具使用LiveCharts.Wpf,麻雀虽小五脏俱全,该源码作Panuon.WPF.UI ...
- java 怎么保证余额_高并发下怎么做余额扣减?
余额操作在大多数系统都是不可缺少和不允许出现问题的 如何修改余额 , 这个问题可能在实际项目中 没那么简单; 如何修改余额 假设一个用户数据 : id⇒12 | user_name⇒放放 | fee⇒ ...
- 怎么解决高并发下抢红包和商品超卖问题?
一.场景模拟 在抢红包或秒杀商品的时候,肯定会有高并发的情况出现,程序中如果出现库存重复减扣的情况,那肯定是不行的!接下来模拟一下高并发下的库存重复减扣问题以及相应的解决方案. 1. 在测试前,需要 ...
- SQLServer控制用户访问权限表
一.需求 在管理数据库过程中,我们经常需要控制某个用户访问数据库的权限,比如只需要给这个用户访问某个表的权限,甚至是CRUD的权限,更小粒度的还可以去到某几个字段的访问权限.写这篇文章就是说明下这个操 ...
- ef sqlserver切换到mysql_可以为MySql和SqlServer使用EF上下文吗?
我有两个实体框架上下文,一个用于MySql,一个用于sql.如果我运行该应用程序,则会出现以下错误 The default DbConfiguration instance was used by t ...
- html上下箭头控制文本框加减,HTML5去掉输入框type为number时的上下箭头的实现方法...
html5中,input type="number"时 右边会有一个上下小箭头,介绍去掉这个箭头的方法,完成浏览器的兼容,页面效果的统一 一.公共样式 去掉输入框type为numb ...
- 没有发布服务器的 rpc 安全信息,或该信息无效,SQLServer之创建分布式事务
分布式事务创建注意事项 指定一个由 Transact-SQL 分布式事务处理协调器 (MS DTC) 管理的 Microsoft 分布式事务的起点. 执行 BEGIN DISTRIBUTED TRAN ...
- java 余额_关于java:Java并发编程根源为什么转账后余额总是对不上
你开发了一套转账零碎,转账的流程没问题,通过了内部测试,上线后看起来也没问题. 然而,过了一段时间,用户竟然能够忽视余额,间接提现.眼看就要就业了,问题到底出在哪里呢? 通过一番查看,你发现每次出事的 ...
- sqlserver Distributed Transaction 分布式事务
在webapi+ef+sqlserver开发项目时,利用transcope实现应用层级的事务时,偶尔会报分布式事务错误,而且很而复现,特别蛋疼.现将自己的解决方法初步整理下. 分析原因:搭建repos ...
最新文章
- python macd背离_Python 精简多品种 MACD 趋势策略
- 深入浅出:Linux设备驱动之字符设备驱动
- Huawei S8512
- 同步I/O 和 异步I/O
- 山西计算机应用能力,《山西省计算机应用能力考试练习系统》使用说明
- 开课吧9.9元学python靠谱吗-开课吧的python课程怎么样,值得报名吗?
- 如何在 macOS 中使用选项卡?
- 排序算法之九 基数排序(C++版本)
- Unity代码分享——一个快速显示所有Sprite为Image_icon的帮助类
- 网易云音乐代码如何写入html,如何将网易云音乐加入到自己的网站!
- 黑客穷追不舍攻击“谷姐” [转]
- 80后一代开始结婚 独生子女开始承担新的责任
- 推荐几个短网址赚钱站
- ArcGIS配图/地图符号化的一些技巧与相关资料
- 处处吻(粤语汉字英译)
- empyrical 模块的学习与分析 note4
- 关于设计抗混叠滤波器的三个指导原则
- 参数方程求二阶导时候不能直接把y,x分别对t求二阶导然后再相除作为d²y/dx²的原因
- 数说故事香氛品类分析及行业新趋势、消费者需求洞察
- mybatis 的 insert
热门文章
- CSS3 设置特殊字体的方法@font-face
- 数据可视化之不婚主义,34%的不婚人群把婚姻和恋爱分为两件事
- 供电门面怎么实施RPA提高产品分类准确率
- python opencv数字识别_基于模板匹配的手写数字识别(python+opencv)
- Manico—这才是你需要的Mac快速启动器
- 防火墙硬件_硬件防火墙提供的保护
- 2022-2027年中国海上保险行业发展监测及投资战略研究报告
- 电脑剪切的文件不显示如何恢复
- DivX/XviD编解码器说明
- 【pandas数据清洗与处理】项目7-国产烂片深度分析