首页 > 编程语言 >c# 通过消息队列处理高并发请求实列

c# 通过消息队列处理高并发请求实列

时间:2024-04-23 16:45:25浏览次数:26  
标签:userName product return c# db 实列 队列 new public

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

演示一下现象
创建一个在售产品表
CREATE TABLE [dbo].[product](
[id] [int] NOT NULL,--唯一主键
[name] nvarchar NULL,--产品名称
[status] [int] NULL ,--0未售出 1 售出 默认为0
[username] nvarchar 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(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,创建订单处理者(消费者)

///


/// 订单的处理者(消费者)
///

public class OrderConsumer
{
///
/// 订票的消息队列
///

public static ConcurrentQueue TicketOrders = new ConcurrentQueue();
///
/// 订单结果消息队列
///

public static List OrderResults = new List();
///
/// 订单处理
///

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,创建订单业务的实际执行者

///


/// 订单业务的实际处理者
///

public class TicketHelper
{
///
/// 实际库存标识
///

private bool hasStock = true;
///
/// 执行一个订单到数据库
///

///
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;
}
}
}
}
///
/// 订单处理结果实体
///

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
{
///


/// 待售商品队列
///

public static ConcurrentQueue ProductsForSale = new ConcurrentQueue();
///
/// 初始化待售商品队列
///

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
{
///


/// 下订单
///

/// 订单提交者
///
public async Task 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,当然还需要一个业务的实际执行者

///


/// 订单业务的实际处理者
///

public class TicketHelper2
{
///
/// 执行复杂的订单操作(如数据库)
///

/// 下单用户
/// 产品id
///
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:8080/Order/PlaceOrder?userName=zhangsan

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

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

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

缺点:暂时没想到,欢迎大家补充
转载至:https://www.cnblogs.com/chenxizhaolu/p/12543376.html

标签:userName,product,return,c#,db,实列,队列,new,public
From: https://www.cnblogs.com/yzlong520/p/18153195

相关文章

  • 并发编程(ReentrantLock)
    ReentrantLock是独占锁,每次只能有一个线程能获取到锁(支持重入)。其他未获取锁的线程会放入的CLH队列中,等待当前线程唤醒;主要分为公平锁和非公平锁,由内部类FairSync和NoFairSync来实现。主要的区别在于非公平锁每次都会尝试竞争,竞争不到锁才会放入到CLH队列中NonfairSync类......
  • leedcode-数字转换为十六进制数
    自己写的,先整数转二进制,再切片二进制转16进制classSolution:deftoHex(self,num:int)->str:#处理特殊情况:当num为0时,直接返回'0'ifnum==0:return'0'#定义十六进制字母的映射关系my_dict={10:......
  • c# 任意对象生成excel
    1publicstaticvoidExportExcel<T>(List<T>ts,stringfilename)2{3varmodel=ts.FirstOrDefault();4List<PropertyInfo>titles=model.GetType().GetProperties().ToList();5usi......
  • Fluent:Stiff chemistry solver
    适用场景在处理对流场中的化学反应速率敏感的问题时,可以在FLUENT中使用Stiffchemistrysolver。这类问题通常在燃烧模拟或类似涉及快速反应化学物质的场合中出现。具体含义IntegrationParameters:这部分涉及方法的选择ISAT:代表"InSituAdaptiveTabulation",这是一......
  • GRE隧道和ipsec隧道
    GRE隧道GRE隧道实现原理:GRE(通用路由封装)隧道的实现原理是通过在封装数据包的头部添加额外的信息来创建一个虚拟的点对点连接,从而在不同网络之间传输数据。下面是GRE隧道的基本实现原理:封装数据包:当数据包从源主机发送时,GRE路由器会接收该数据包,并在原始数据包的头部添加GRE......
  • GO中的sync.Cond
    条件变量是基于互斥锁的,它必须基于互斥锁才能发挥作用,条件变量的初始化离不开互斥锁,并且它的方法有点也是基于互斥锁的//使当前goroutine进入阻塞状态,等待其他goroutine唤醒func(c*Cond)Wait(){}//唤醒一个等待该条件变量的goroutine,如果没有goroutine在等待,则该方法会立......
  • 全民众测月开启!Comate来送钱了!
    在AIGC工具井喷爆发的时代,你是否缺少一款足够趁心的智能编码助手,让自己的开发效率大大提升?BaiduComate与CSDN联合,推出全民众测活动!BaiduComate智能编码助手是基于文心大模型打造的强大编码辅助工具,现已在全网免费开放使用!精通100+编程语言,支持VSCode、JetBrains全系列、Xcod......
  • 欧拉系统-安装Docker
    欧拉系统-安装Docker[toc]零、资料https://lab.huaweicloud.com/experiment-detail_2417?ticket=ST-92642093-vahMts7MDOKnplPdCsCFfCrs-sso一、步骤wgethttps://download.docker.com/linux/static/stable/x86_64/docker-18.09.9.tgztarzxfdocker-18.09.9.tgzmvd......
  • 古希腊掌管数据的神!铁威马全新SPC功能来袭!
    在数字世界的深处,铁威马全新操作系统TOS6如同一位强大的守护者,守护着无数珍贵的数据宝藏。而在这个守护者的队伍中,有一位特别的成员——SPC。它像是一位冷静而敏锐的守门人,时刻警惕着任何可能对数据安全造成威胁的入侵者。它的存在,让TOS6更加坚不可摧,为用户的数字生活提供了前......
  • JMeter通过JDBC链接数据库并实现批量造数据
      在JMeter做自动化接口测试,需要对数据库进行增删改查等操作时,我们是无法像navicat一样直接写SQL的,需要通过一系列操作,才可以。 1、首先,第一步就是,在TestPlan中引用对应数据库的jar包,jar包可以在网上找,本文以MySQL为例,步骤如下:   2、第二步,在线程组下面添加JDBCC......