赞
踩
新建GrabbingOrdersController.cs控制器,可以开始制作测试接口了
Demo一共有8个接口,两个只做查询(不在文章里描述后续会上源代码),纯db接口、lock锁、服务器缓存+队列、Redis+队列、Redis_lua、rabbit
开始搬砖了
Controllers文件夹新建控制器(api 控制器 空),引用日志接口和数据库连接类
namespace MessageQueuing.Controllers { /// <summary> /// 抢单 /// </summary> [Route("api/[controller]")] [ApiController] public class GrabbingOrdersController : ControllerBase { private readonly ILogger<GrabbingOrdersController> logger; private readonly DbHelperContext dbHelper; public GrabbingOrdersController(ILogger<GrabbingOrdersController> logger, DbHelperContext dbHelper) { this.logger = logger; this.dbHelper = dbHelper; } } }
一.纯DB接口:在控制器中直接处理查询和新增订单等业务
业务流程:接口触发->DB查询数据->新增订单\扣除库存
功能分析:
压力测试:
线程数为10,100,1000三种情况进行测试,Ramp-Up时间空,循环次数为1
代码:
[HttpGet] [Route("[action]")] public string SetOrder(string userId, string proId) { try { var product = dbHelper.SeckillProduct.Where(x => x.productId == proId).FirstOrDefault(); if (product.productStockNum - 1 > 0) { product.productStockNum--; Models.Order order = new Models.Order(); order.id = Guid.NewGuid().ToString("N"); order.userId = userId; order.orderNum = Guid.NewGuid().ToString("N"); order.productId = product.id; order.orderTotal = Convert.ToDecimal(product.productPrice); order.addTime = DateTime.Now; order.orderStatus = 0; order.orderPhone = "1565555555"; order.orderAddress = "test"; order.delFlag = 0; dbHelper.Add(order); dbHelper.SaveChanges(); return "下单成功"; } else { return "下单成功"; } } catch (Exception ex) { throw new Exception(ex.Message); } }
测试结果:1000并发下异常为0说明都正常触发了,但如果查看数据会发现库存并没有扣1000
二、lock锁: 使用lock锁,在处理业务时不让其它进程出入,直到进程结束为止,这样能解决库存错误导致多卖的情况
业务流程:
接口触发-> 单线程进入其余等待->DB查询数据->新增订单\扣除库存->业务完成,释放线程->其它线程进入
功能分析:
压力测试:
线程数为10,100,1000三种情况进行测试,Ramp-Up时间空,循环次数为1
代码:
首先控制器里建一个静态只读的object
新建SetOrderLock,lock调用建好的(object)
[HttpGet] [Route("[action]")] public string SetOrderLock(string userId, string proId) { try { lock (objlock) { var product = dbHelper.SeckillProduct.Where(x => x.productId == proId).FirstOrDefault(); if (product.productStockNum - 1 > 0) { product.productStockNum--; Models.Order order = new Models.Order(); order.id = Guid.NewGuid().ToString("N"); order.userId = userId; order.orderNum = Guid.NewGuid().ToString("N"); order.productId = product.id; order.orderTotal = Convert.ToDecimal(product.productPrice); order.addTime = DateTime.Now; order.orderStatus = 0; order.orderPhone = "1565555555"; order.orderAddress = "test"; order.delFlag = 0; dbHelper.Add(order); dbHelper.SaveChanges(); return "下单成功"; } else { return "下单成功"; } } } catch (Exception ex) { throw new Exception(ex.Message); } }
测试结果:慢于纯db接口,但不会出现库存错误的情况
三、服务器缓存+队列:生产者和消费者模式,接口只负责向计算库存和新增随机订单号然后写入缓存中,会建两个后台任务进行处理插入和查询
业务流程:
接口触发-> 利用lock来处理简单的库存和新增随机订单号->利用后台任务进行查询数据->新增订单\扣除库存
功能分析:
压力测试:
线程数为10,100,1000三种情况进行测试,Ramp-Up时间空,循环次数为1
代码:
在项目里新建Common文件夹,然后新建CacheBackService.cs、CustomerService.cs和MyQueue.cs
MyQueue.cs:基于内存的队列
private static ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
public static ConcurrentQueue<string> GetQueue()
{
return _queue;
}
CacheBackService.cs:用来初始化库存等信息
public class CacheBackService : BackgroundService { private IMemoryCache _cache; private IConfiguration _Configuration; public CacheBackService(IMemoryCache cache, IConfiguration Configuration) { _cache = cache; _Configuration = Configuration; } protected async override Task ExecuteAsync(CancellationToken stoppingToken) { // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的 // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文 var optionsBuilder = new DbContextOptionsBuilder<DbHelperContext>(); optionsBuilder.UseSqlServer(_Configuration[string.Join(":", new string[] { "DBConnection", "ConnectionStrings", "Dbconn" })]); DbHelperContext context = new DbHelperContext(optionsBuilder.Options); //初始化库存信息,连临时写在这个位置,充当服务器启动的时候初始化 var data = await context.SeckillProduct.Where(u => u.id == "21e86c6cc32b4e7bb80f96c98e4e7994").FirstOrDefaultAsync(); //服务器缓存 _cache.Set<int>($"{data.productId}-sCount", data.productStockNum); } }
CustomerService.cs:处理消费业务
public class CustomerService: BackgroundService { private IConfiguration _Configuration; public CustomerService(IConfiguration Configuration) { _Configuration = Configuration; } protected async override Task ExecuteAsync(CancellationToken stoppingToken) { // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的 // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文 var optionsBuilder = new DbContextOptionsBuilder<DbHelperContext>(); optionsBuilder.UseSqlServer(_Configuration[string.Join(":", new string[] { "DBConnection", "ConnectionStrings", "Dbconn" })]); DbHelperContext context = new DbHelperContext(optionsBuilder.Options); Console.WriteLine("下面开始执行消费业务"); while (true) { try { string data = ""; MyQueue.GetQueue().TryDequeue(out data); if (!string.IsNullOrEmpty(data)) { List<string> tempData = data.Split('-').ToList(); //1.扣减库存---禁止状态追踪 var sArctile =await context.SeckillProduct.Where(u => u.id == "21e86c6cc32b4e7bb80f96c98e4e7994").FirstOrDefaultAsync(); sArctile.productStockNum = sArctile.productStockNum - 1; context.Update(sArctile); //2. 插入订单信息 Order tOrder = new Order(); tOrder.id = Guid.NewGuid().ToString("N"); tOrder.userId = tempData[0]; tOrder.orderNum = Guid.NewGuid().ToString("N"); tOrder.productId = tempData[1]; tOrder.orderTotal = 50; tOrder.addTime = DateTime.Now; tOrder.orderStatus = 0; tOrder.orderPhone = "1565555555"; tOrder.orderAddress = "test"; tOrder.delFlag = 0; context.Add<Order>(tOrder); int count = await context.SaveChangesAsync(); //释放一下 context.Entry<SeckillProduct>(sArctile).State = EntityState.Detached; Console.WriteLine($"执行成功,条数为:{count},当前库存为:{ sArctile.productStockNum}"); } else { Console.WriteLine("暂时没有订单信息,休息一下"); await Task.Delay(TimeSpan.FromSeconds(1)); } } catch (Exception ex) { Console.WriteLine($"执行失败:{ex.Message}"); } } } }
控制器引入IMemoryCache接口,
private static readonly object objlock = new object();
private readonly ILogger<GrabbingOrdersController> logger;
private readonly DbHelperContext dbHelper;
private readonly IMemoryCache cache;
public GrabbingOrdersController(ILogger<GrabbingOrdersController> logger, DbHelperContext dbHelper, IMemoryCache cache)
{
this.logger = logger;
this.dbHelper = dbHelper;
this.cache = cache;
}
然后新建SetOrderServercache
[HttpGet] [Route("[action]")] public string SetOrderServercache(string userId, string proId) { try { lock (objlock) { int count = cache.Get<int>($"{proId}-sCount"); if (count - 1 >= 0) { count--; cache.Set<int>($"{userId}-sCount", count); var orderNum = Guid.NewGuid().ToString("N"); MyQueue.GetQueue().Enqueue($"{userId}-{proId}-{orderNum}"); return "下单成功"; } else { return "下单成功"; } } } catch (Exception ex) { throw new Exception(ex.Message); } }
最后到Startup.cs注册后台任务
//注册后台任务
services.AddHostedService<CacheBackService>();
services.AddHostedService<CustomerService>();
测试结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。