asp.net通过消息队列处理高并发请求(以抢小米手机
网站面对高并发的情况下,除了增加硬件, 优化程序提高以响应速度外,还可以通过并行改串行的思路来解决。这种思想常见的实践方式就是数据库锁和消息队列的方式。这种方式的缺点是需要排队,响应速度慢,优点是节省成本。
演示一下现象
创建一个在售产品表
CREATE TABLE [dbo].[product]( [id] [int] NOT NULL,--唯一主键 [name] [nvarchar](50) NULL,--产品名称 [status] [int] NULL ,--0未售出 1 售出 默认为0 [username] [nvarchar](50) NULL--下单用户 )
添加一条记录
insert into product(id,name,status,username) values(1,'小米手机',0,null)
创建一个抢票程序
public ContentResult PlaceOrder(string userName) { using (RuanMou2020Entities db = new RuanMou2020Entities()) { var product = db.product.Where<product>(p => p.status== 0).FirstOrDefault(); if (product.status == 1) { return Content("失败,产品已经被卖光"); } else { //模拟数据库慢造成并发问题 Thread.Sleep(5000); product.status = 1; product.username= userName; db.SaveChanges(); return Content("成功购买"); } } }
如果我们在5秒内一次访问以下两个地址,那么返回的结果都是成功购买且数据表中的username是lisi。
/controller/PlaceOrder?username=zhangsan
/controller/PlaceOrder?username=lisi
这就是并发带来的问题。
第一阶段,利用线程锁简单粗暴
Web程序是多线程的,那我们把他在容易出现并发的地方加一把锁就可以了,如下图处理方式。
private static object _lock = new object(); public ContentResult PlaceOrder(string userName) { using (RuanMou2020Entities db = new RuanMou2020Entities()) { lock (_lock) { var product = db.product.Where<product>(p => p.status == 0).FirstOrDefault(); if (product.status == 1) { return Content("失败,产品已经被卖光"); } else { //模拟数据库慢造成并发问题 Thread.Sleep(5000); product.status = 1; product.username = userName; db.SaveChanges(); return Content("成功购买"); } } } }
这样每一个请求都是依次执行,不会出现并发问题了。
优点解决了并发的问题。
缺点效率太慢,用户体验性太差,不适合大数据量场景。
第二阶段,拉消息队列,通过生产者,消费者的模式
1,创建订单提交入口(生产者)
public class HomeController : Controller { /// <summary> /// 接受订单提交(生产者) /// </summary> /// <returns></returns> public ContentResult PlaceOrderQueen(string userName) { //直接将请求写入到订单队列 OrderConsumer.TicketOrders.Enqueue(userName); return Content("wait"); } /// <summary> /// 查询订单结果 /// </summary> /// <returns></returns> public ContentResult PlaceOrderQueenResult(string userName) { var rel = OrderConsumer.OrderResults.Where(p => p.userName == userName).FirstOrDefault(); if (rel == null) { return Content("还在排队中"); } else { return Content(rel.Result.ToString()); } } }
2,创建订单处理者(消费者)
/// <summary> /// 订单的处理者(消费者) /// </summary> public class OrderConsumer { /// <summary> /// 订票的消息队列 /// </summary> public static ConcurrentQueue<string> TicketOrders = new ConcurrentQueue<string>(); /// <summary> /// 订单结果消息队列 /// </summary> public static List<OrderResult> OrderResults = new List<OrderResult>(); /// <summary> /// 订单处理 /// </summary> public static void StartTicketTask() { string userName = null; while (true) { //如果没有订单任务就休息1秒钟 if (!TicketOrders.TryDequeue(out userName)) { Thread.Sleep(1000); continue; } //执行真实的业务逻辑(如插入数据库) bool rel = new TicketHelper().PlaceOrderDataBase(userName); //将执行结果写入结果集合 OrderResults.Add(new OrderResult() { Result = rel, userName = userName }); } } }
3,创建订单业务的实际执行者
/// <summary> /// 订单业务的实际处理者 /// </summary> public class TicketHelper { /// <summary> /// 实际库存标识 /// </summary> private bool hasStock = true; /// <summary> /// 执行一个订单到数据库 /// </summary> /// <returns></returns> public bool PlaceOrderDataBase(string userName) { //如果没有了库存,则直接返回false,防止频繁读库 if (!hasStock) { return hasStock; } using (RuanMou2020Entities db = new RuanMou2020Entities()) { var product = db.product.Where(p => p.status == 0).FirstOrDefault(); if (product == null) { hasStock = false; return false; } else { Thread.Sleep(10000);//模拟数据库的效率比较慢,执行插入时间比较久 product.status = 1; product.username = userName; db.SaveChanges(); return true; } } } } /// <summary> /// 订单处理结果实体 /// </summary> public class OrderResult { public string userName { get; set; } public bool Result { get; set; } }
4,在程序启动前,启动消费者线程
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //在Global的Application_Start事件里单独开启一个消费者线程 Task.Run(OrderConsumer.StartTicketTask); }
这样程序的运行模式是用户提交的需求里都会添加到消息队列里去排队处理,程序会依次处理该队列里的内容(可以一次取出多条来进行处理,提高效率)。
优点比上一步快了。
缺点不够快,而且下单后需要轮询一个接口判断是否成功。
第三阶段 反转生产者消费者的角色,把可售产品提前放到队列里,然后让提交的订单来消费队列里的内容
1,创建生产者并且在程序启动前调用其初始化程序
public class ProductForSaleManager { /// <summary> /// 待售商品队列 /// </summary> public static ConcurrentQueue<int> ProductsForSale = new ConcurrentQueue<int>(); /// <summary> /// 初始化待售商品队列 /// </summary> public static void Init() { using (RuanMou2020Entities db = new RuanMou2020Entities()) { db.product.Where(p => p.status == 0).Select(p => p.id).ToList().ForEach(p => { ProductsForSale.Enqueue(p); }); } } } public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //程序启动前,先初始化待售产品消息队列 ProductForSaleManager.Init(); } }
2,创建消费者
public class OrderController : Controller { /// <summary> /// 下订单 /// </summary> /// <param name="userName">订单提交者</param> /// <returns></returns> public async Task<ContentResult> PlaceOrder(string userName) { if (ProductForSaleManager.ProductsForSale.TryDequeue(out int pid)) { await new TicketHelper2().PlaceOrderDataBase(userName, pid); return Content($"下单成功,对应产品id为{pid}"); } else { await Task.CompletedTask; return Content($"商品已经被抢光"); } } }
3,还需要一个业务的实际执行者
/// <summary> /// 订单业务的实际处理者 /// </summary> public class TicketHelper2 { /// <summary> /// 执行复杂的订单操作(如数据库) /// </summary> /// <param name="userName">下单用户</param> /// <param name="pid">产品id</param> /// <returns></returns> public async Task PlaceOrderDataBase(string userName, int pid) { using (RuanMou2020Entities db = new RuanMou2020Entities()) { var product = db.product.Where(p => p.id == pid).FirstOrDefault(); if (product != null) { product.status = 1; product.username = userName; await db.SaveChangesAsync(); } } } }
这样我们访问狼蚁网站SEO优化三个地址,如果数据库里只有两个商品的话,会有一个请求结果为商品已经被抢光。
http://localhost:88/Order/PlaceOrder?userName=zhangsan
http://localhost:88/Order/PlaceOrder?userName=lisi
http://localhost:88/Order/PlaceOrder?userName=wangwu
这种处理方式的优点为执行效率快,相比第二种方式不需要第二个接口来返回查询结果。
缺点暂时没想到,欢迎大家补充。
说明该方式只是个人猜想,并非实际项目经验,大家只能作为参考,慎重用于项目。欢迎大家批评指正。
到此这篇关于asp.通过消息队列处理高并发请求(以抢小米手机为例)的文章就介绍到这了,更多相关asp. 消息队列处理高并发 内容请搜索狼蚁SEO以前的文章或继续浏览狼蚁网站SEO优化的相关文章希望大家以后多多支持狼蚁SEO!
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程