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();
        }
      }
    }
  }

这样我们同时访问下面三个地址,如果数据库里只有两个商品的话,会有一个请求结果为:商品已经被抢光。

http://localhost:88/Order/PlaceOrder?userName=zhangsan

http://localhost:88/Order/PlaceOrder?userName=lisi

http://localhost:88/Order/PlaceOrder?userName=wangwu

这种处理方式的优点为:执行效率快,相比第二种方式不需要第二个接口来返回查询结果。

缺点:暂时没想到,欢迎大家补充。

说明:该方式只是个人猜想,并非实际项目经验,大家只能作为参考,慎重用于项目。欢迎大家批评指正。

到此这篇关于asp.net通过消息队列处理高并发请求(以抢小米手机为例)的文章就介绍到这了,更多相关asp.net 消息队列处理高并发 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • .net core如何在网络高并发下提高JSON的处理效率详解

    前言 现有的webapi一般都基于JSON的格式来处理数据,由于JSON是一个文本类的序列化协议所以在性能上自然就相对低效一些.在.net中常用Newtonsoft.Json是最常用的组件,由于提供简便基于完整的json的String方法使用起来非常方便:但也正是这原因导致Newtonsoft.Json在性能上一直被说慢,虽然Newtonsoft.Json提供Stream的方式来处理JSON不过想复用writer和reader还是需要一些应用技巧.如果需要在网络通讯中应用JSON,那在这里介绍一

  • asp.net通过消息队列处理高并发请求(以抢小米手机为例)

    网站面对高并发的情况下,除了增加硬件, 优化程序提高以响应速度外,还可以通过并行改串行的思路来解决.这种思想常见的实践方式就是数据库锁和消息队列的方式.这种方式的缺点是需要排队,响应速度慢,优点是节省成本. 演示一下现象 创建一个在售产品表 CREATE TABLE [dbo].[product]( [id] [int] NOT NULL,--唯一主键 [name] [nvarchar](50) NULL,--产品名称 [status] [int] NULL ,--0未售出 1 售出 默认为0

  • PHP+Redis 消息队列 实现高并发下注册人数统计的实例

    前言 现在越来越多的网站开始注重统计和用户行为分析,作为网站经常使用的功能,如何让统计性能更加高,这也是我们需要考虑的事情.本篇通过Redis来优化统计功能(以注册人数统计为例). 传统的统计功能都是直接操作数据库把数据插入表中.这样做,对数据库的性能消耗就会比较大. 思路: 这里我们用到了redis的队列,注册的时候先添加到队列,然后在处理的时候出队,并且把人数添加redis里. 代码: <?php //register.php $redis = new Redis(); $redis->c

  • php处理抢购类功能的高并发请求

    本文以抢购.秒杀为例.介绍如何在高并发状况下确保数据正确. 在高并发请求下容易参数两个问题 1.数据出错,导致产品超卖. 2.频繁操作数据库,导致性能下降. 测试环境 Windows7 apache2.4.9 php5.5.12 php框架 yii2.0 工具 apache bench (apache自带高并发请求工具). 通常处理方法 从控制器可以看出代码思路.先查询商品库存.如果库存大于0 ,则库存减少1,同时生产订单,录入抢购者数据. // 常规代码处理高并发 public functio

  • 基于springcloud异步线程池、高并发请求feign的解决方案

    ScenTaskTestApplication.java package com.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author scen *

  • 使用Redis解决高并发方案及思路解读

    目录 NoSQL Redis 痛点 思路 分布式锁 锁续命 扩展 结语 NoSQL Not Only SQL的简称.NoSQL是解决传统的RDBMS在应对某些问题时比较乏力而提出的. 即非关系型数据库,它们不保证关系数据的ACID特性,数据之间一般没有关联,在扩展上就非常容易实现,并且拥有较高的性能. Redis redis是nosql的典型代表,也是目前互联网公司的必用技术. redis是键值(Key-Value)存储数据库,主要会使用到哈希表.大多数时候是直接以缓存的形式被使用,使得请求不直

  • php+redis消息队列实现抢购功能

    本文实例为大家分享了php+redis消息队列实现抢购的具体代码,供大家参考,具体内容如下 实现功能: 1. 基于redis队列,防止高并发的超卖 2. 基于mysql的事务加排它锁,防止高并发的超卖 基于redis队列工作流程: 1. 管理员根据goods表中的库存,创建redis商品库存队列 2. 客户端访问秒杀API 3. web服务器先从redis的商品库存队列中查询剩余库存重点内容 4. redis队列中有剩余,则在mysql中创建订单,去库存,抢购成功 5. redis队列中没有剩余

  • PHP+MySQL实现消息队列的方法分析

    本文实例讲述了PHP+MySQL实现消息队列的方法.分享给大家供大家参考,具体如下: 最近遇到一个批量发送短信的需求,短信接口是第三方提供的.刚开始想到,获取到手机号之后,循环调用接口发送不就可以了吗? 但很快发现问题:当短信数量很大时,不仅耗时,而且成功率很低. 于是想到,用PHP和MySQL实现一个消息队列,一条一条的发送短信.下面介绍具体的实现方法: 首先,建立一个数据表sms,包含以下字段: id, phone, //手机号 content //短信内容 将需要发送的短信和手机号存入sm

  • 浅谈Java消息队列总结篇(ActiveMQ、RabbitMQ、ZeroMQ、Kafka)

    一.消息队列概述 消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构.目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ. 二.消息队列应用场景 以下介绍消息队列在实际应用中常用的使用场景.异步处理,应用解耦,流量削锋和消息通讯四个场景. 2.1异步处理 场景说明:用户注册后,需要发注册邮件和注册短信.传统的做法有两种 1.串行的方式;2.并行方式 a.串

  • PHP加MySQL消息队列深入理解

    消息队列:在消息的传输过程中保存消息的容器. 消息队列管理器在将消息从它的源中继到它的目标时充当中间人.队列的主要目的是提供路由并保证消息的传递:如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它. 如图所示: 在不使用消息队列的情况下,用户的请求数据直接写入数据库,再高并发的情况下,会对数据库造成巨的压力,同时也使得响应延迟加剧.在使用消息队列后,用户请求的数据发送给消息队列后立即返回,再由消息队列的消费者进程(通常情况下,该进程独立部署在专门的服务器集群上)从消息队列中获取

  • 如何理解软件系统的高并发

    概述 当前,数字化在给企业带来业务创新,推动企业高速发展的同时,也给企业的IT软件系统带来了严峻的挑战.面对流量高峰,不同的企业是如何通过技术手段解决高并发难题的呢? 引言 软件系统有三个追求:高性能.高并发.高可用,俗称三高.三者既有区别也有联系,门门道道很多,全面讨论需要三天三夜,本篇讨论高并发. 高并发(High Concurrency).并发是操作系统领域的一个概念,指的是一段时间内多任务流交替执行的现象,后来这个概念被泛化,高并发用来指大流量.高请求的业务情景,比如春运抢票,电商双十一

随机推荐