网站面对高并发的情况下,除了增加硬件, 优化程序提高以响应速度外,还可以通过并行改串行的思路来解决。这种思想常见的实践方式就是数据库锁和消息队列的方式。这种方式的缺点是需要排队,响应速度慢,优点是节省成本。

演示一下现象

创建一个在售产品表

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== ).FirstOrDefault();
if (product.status == )
{
return Content("失败,产品已经被卖光");
}
else
{
//模拟数据库慢造成并发问题
Thread.Sleep();
product.status = ;
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 == ).FirstOrDefault();
if (product.status == )
{
return Content("失败,产品已经被卖光");
}
else
{
//模拟数据库慢造成并发问题
Thread.Sleep();
product.status = ;
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();
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 == ).FirstOrDefault();
if (product == null)
{
hasStock = false;
return false;
}
else
{
Thread.Sleep();//模拟数据库的效率比较慢,执行插入时间比较久
product.status = ;
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 == ).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 = ;
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 c# 通过消息队列处理高并发请求(以抢小米手机为例)的更多相关文章

  1. PHP中利用redis实现消息队列处理高并发请求

    将请求存入redis 为了模拟多个用户的请求,使用一个for循环替代 //redis数据入队操作 $redis = new Redis(); $redis->connect('127.0.0.1 ...

  2. 达达O2O后台架构演进实践:从0到4000高并发请求背后的努力

    1.引言   达达创立于2014年5月,业务覆盖全国37个城市,拥有130万注册众包配送员,日均配送百万单,是全国领先的最后三公里物流配送平台. 达达的业务模式与滴滴以及Uber很相似,以众包的方式利 ...

  3. Web大规模高并发请求和抢购的解决方案

    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们 ...

  4. go-channel处理高并发请求

    目录 go-channel处理高并发请求 一.Channel简介 二.处理包并发请求 三.测试 1.测试工具 2.测试结果 go-channel处理高并发请求 最近看了一篇文章讲解怎样使用go-cha ...

  5. 基于Raft深度优化,腾讯云金融级消息队列CMQ高可靠算法详解

    背景介绍 分布式系统是指一组独立的计算机,通过网络协同工作的系统,客户端看来就如同单台机器在工作.随着互联网时代数据规模的爆发式增长,传统的单机系统在性能和可用性上已经无法胜任,分布式系统具有扩展性强 ...

  6. 关于MQ的几件小事:如何保证消息队列的高可用

    原文:https://www.cnblogs.com/jack1995/p/10908797.html 1.RabbitMQ的高可用 RabbitMQ基于主从模式实现高可用.RabbitMQ有三种模式 ...

  7. 关于MQ的几件小事(二)如何保证消息队列的高可用

    1.RabbitMQ的高可用 RabbitMQ基于主从模式实现高可用.RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式. (1)单机模式: 单机模式就是demo级别的,生产中不会有人使 ...

  8. 一、消息队列之ActiveMQ的安装、配置和C#样例代码

    最近有时间了,研究一下消息队列ActvieMQ,结合自己的实践和网上的一些大家内容,整理如下,所有步骤和链接均是正确的. 1.ActiveMQ ActiveMQ 是Apache出品,最流行的,能力强劲 ...

  9. 支持10W高并发请求的IIS Web服务器常用设置

    支持高并发的IIS Web服务器常用设置   适用的IIS版本:IIS 7.0, IIS 7.5, IIS 8.0 适用的Windows版本:Windows Server 2008, Windows ...

随机推荐

  1. 吴裕雄--python学习笔记:BeautifulSoup模块

    import re import requests from bs4 import BeautifulSoup req_obj = requests.get('https://www.baidu.co ...

  2. QQbug--QQ截图不显示保存类型

    QQ软件bug--QQ截图不显示保存类型,设置显示后缀名也没用 问题:     QQ截图截后,不通过对话框直接保存时,不显示保存类型,文件名下面的类型的框框是一片空白,在文件夹选项设置显示后缀名也没用 ...

  3. 在 mac osx 上安装OpenOffice并以服务的方式启动

    OpenOffice是Apache基金会旗下的一款先进的开源办公软件套件,包含文本文档.电子表格.演示文稿.绘图.数据库等.包含Microsoft office所有功能.它不仅可以作为桌面应用供普通用 ...

  4. 修改 Cucumber HTML 报告

    后台服务是 JSON-RPC 风格的,所以 Scenario 都是这样的 Scenario: login successful When I set request body from "f ...

  5. Lambda表达式和函数试接口的最佳实践 · LiangYongrui's Studio

    1.概述 本文主要深入研究java 8中的函数式接口和Lambda表达式,并介绍最佳实践. 2.使用标准的函数式接口 包java.util.function中的函数是接口已经可以满足大部分的java开 ...

  6. Vimium - 让你体验Geek般的浏览体验

    相信很多电脑高手们都会寻找一一些快捷高效的操作方式,如经常利用键盘的快速操作,让你脱离鼠标,可以让你不用花太多精力地去移动细小的指针进行操作,使得工作的效率提高许多. 不过,实际上很多时候我们还是不得 ...

  7. 达拉草201771010105《面向对象程序设计(java)》第二周学习总结

    达拉草201771010105<面向对象程序设计(java)>第二周学习总结 一.理论知识学习部分          这一周我们学习的是书上第三章java的基本程序设计结构的内容,在这一章 ...

  8. iMX287A开发环境搭建

    目录 1.开发套件简介 2.说明: 3.主机搭建交叉编译环境 4.编译第一个ARM Linux程序--Hello World 5.开发板运行U盘中的可执行文件 6.配置交叉编译工具到环境变量 7.sc ...

  9. linux 下修改最大文件数

    环境为centosV7系列 1.查看进程的打开最大文件数,默认为1024 [root@localhost ~]# ulimit -a core file size (blocks, -c) 0 dat ...

  10. 某图片站反爬加密字段x-api-key破解

    前言 此次逆向的是某“你们都懂”领域的图片站,目前此站限制注册,非会员无法访问:前两天偶然搞到了份邀请码,进入后发现质量还可以,于是尝试爬取,在爬虫编写过程中发现此站点采用了不少手段来阻止自动化脚本( ...