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

2025-05-29 0 31

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

演示一下现象

创建一个在售产品表

?

1

2

3

4

5

6
CREATE TABLE [dbo].[product](

[id] [int] NOT NULL,--唯一主键

[name] [nvarchar](50) NULL,--产品名称

[status] [int] NULL ,--0未售出 1 售出 默认为0

[username] [nvarchar](50) NULL--下单用户

)

添加一条记录

?

1
insert into product(id,name,status,username) values(1,'小米手机',0,null)

创建一个抢票程序

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20
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程序是多线程的,那我们把他在容易出现并发的地方加一把锁就可以了,如下图处理方式。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24
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,创建订单提交入口(生产者)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31
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,创建订单处理者(消费者)

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47
/// <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,在程序启动前,启动消费者线程

?

1

2

3

4

5

6

7

8

9

10

11
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,创建生产者并且在程序启动前调用其初始化程序

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36
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,创建消费者

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21
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,当然还需要一个业务的实际执行者

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25
/// <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 消息队列处理高并发 内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

原文链接:https://www.cnblogs.com/chenxizhaolu/p/12543376.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 asp.net通过消息队列处理高并发请求(以抢小米手机为例) https://www.kuaiidc.com/98636.html

相关文章

发表评论
暂无评论