asp.net Processing high concurrency requests through message queue (taking Xiaomi mobile phone as an example)

Time:2020-9-8

In the case of high concurrency, in addition to adding hardware and optimizing program to improve the response speed, we can also solve the problem by changing parallel to serial. The common practice of this idea is database lock and message queue. The disadvantage of this method is the need to queue, slow response speed, and the advantage is to save cost.

Demonstrate the phenomenon

Create a list of products on sale

CREATE TABLE [dbo].[product](
			[ID] [int] not null, - unique primary key
			[name] [nvarchar] (50) null, - product name
			[status] [int] null, - 0 unsold 1 sold, default to 0
			[username] [nvarchar] (50) null -- single user
			)

Add a record


	insert into product(id,name,status,username) values(1,'Mi phones',0,null)

Create a ticket grabbing program

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 ("failed, the product has been sold out");
			}
			else
			{
			//Simulation database slow causes concurrency problem
			Thread.Sleep(5000);
			product.status = 1;
			product.username= userName;
			db.SaveChanges(); 
			Return content ("successful purchase");
			} 
			} 
	}

If we visit the following two addresses at a time in 5 seconds, the returned results are successful purchases and the username in the data table is Lisi.

/controller/PlaceOrder?username=zhangsan

/controller/PlaceOrder?username=lisi

This is the problem of concurrency.

In the first stage, using thread locks is simple and crude

Web program is multi-threaded, so we can add a lock to the place where concurrency is easy to occur, as shown in the following figure.

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 ("failed, the product has been sold out");
			}
			else
			{
			//Simulation database slow causes concurrency problem
			Thread.Sleep(5000);
			product.status = 1;
			product.username = userName;
			db.SaveChanges();
			Return content ("successful purchase");
			}
			}
			}
	}

In this way, each request is executed in turn, and there is no concurrency problem.

Advantages: it solves the problem of concurrency.

Disadvantages: the efficiency is too slow and the user experience is too poor, which is not suitable for large data volume scenarios.

The second stage, pull message queue, through producer, consumer mode

1. Create order submission entry (producer)

public class HomeController : Controller
			{

			/// <summary>
			///Accept order submission (producer)
			/// </summary>
			/// <returns></returns>
			public ContentResult PlaceOrderQueen(string userName)
			{
			//Write the request directly to the order queue
			OrderConsumer.TicketOrders.Enqueue(userName);
			return Content("wait");
			}

			/// <summary>
			///Query order results
			/// </summary>
			/// <returns></returns>
			public ContentResult PlaceOrderQueenResult(string userName)
			{
			var rel = OrderConsumer.OrderResults.Where(p => p.userName == userName).FirstOrDefault();
			if (rel == null)
			{
			Return content ("still in line");
			}
			else
			{
			return Content(rel.Result.ToString());
			}
			}
	}

2. Create order processor (consumer)

/// <summary>
			///Order processor (consumer)
			/// </summary>
			public class OrderConsumer
			{
			/// <summary>
			///Message queue for booking tickets
			/// </summary>
			public static ConcurrentQueue<string> TicketOrders = new ConcurrentQueue<string>();
			/// <summary>
			///Order result message queue
			/// </summary>
			public static List<OrderResult> OrderResults = new List<OrderResult>();
			/// <summary>
			///Order processing
			/// </summary>
			public static void StartTicketTask()
			{
			string userName = null;
			while (true)
			{
			//If there is no order, take a second off
			if (!TicketOrders.TryDequeue(out userName))
			{
			Thread.Sleep(1000);
			continue;
			}
			//Perform real business logic (such as inserting database)
			bool rel = new TicketHelper().PlaceOrderDataBase(userName);
			//Writes the execution result to the result collection
			OrderResults.Add(new OrderResult() { Result = rel, userName = userName });
			}
			}
	}

3. Create the actual executor of order business

/// <summary>
			///Actual processor of order business
			/// </summary>
			public class TicketHelper
			{
			/// <summary>
			///Physical inventory identification
			/// </summary>
			private bool hasStock = true;
			/// <summary>
			///Execute an order to the database
			/// </summary>
			/// <returns></returns>
			public bool PlaceOrderDataBase(string userName)
			{
			//If there is no inventory, it will directly return false to prevent frequent reading of the library
			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); // the efficiency of the simulated database is relatively slow, and the execution time is relatively long
			product.status = 1;
			product.username = userName;
			db.SaveChanges();
			return true;
			}
			}
			}
			}
			/// <summary>
			///Order processing result entity
			/// </summary>
			public class OrderResult
			{
			public string userName { get; set; }
			public bool Result { get; set; }
			}

4. Start the consumer thread before the program starts

protected void Application_Start()
			{
			AreaRegistration.RegisterAllAreas();
			GlobalConfiguration.Configure(WebApiConfig.Register);
			FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
			RouteConfig.RegisterRoutes(RouteTable.Routes);
			BundleConfig.RegisterBundles(BundleTable.Bundles);

			//Application in global_ Start an individual consumer thread in the start event
			Task.Run(OrderConsumer.StartTicketTask);
	}

The running mode of the program is: the requirements submitted by users will be added to the message queue to be queued for processing, and the program will process the contents in the queue in turn (of course, multiple items can be taken out at a time for processing, so as to improve the efficiency).

Advantages: faster than the previous step.

Disadvantages: it is not fast enough, and you need to poll another interface to determine whether it is successful after placing an order.

In the third stage, the roles of producers and consumers are reversed, the marketable products are put into the queue in advance, and then the submitted orders consume the contents of the queue

1, create a producer and call its initializer before the program starts.

public class ProductForSaleManager
			{
			/// <summary>
			///Product queue for sale
			/// </summary>
			public static ConcurrentQueue<int> ProductsForSale = new ConcurrentQueue<int>();
			/// <summary>
			///Initializing the line of items for sale
			/// </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);

			//Before the program starts, initialize the message queue of products for sale
			ProductForSaleManager.Init();
			}
	}

2. Create consumers

public class OrderController : Controller
			{
			/// <summary>
			///Placing an order
			/// </summary>
			///< param name = "username" > order submitted by
			/// <returns></returns>
			public async Task<ContentResult> PlaceOrder(string userName)
			{
			if (ProductForSaleManager.ProductsForSale.TryDequeue(out int pid))
			{
			await new TicketHelper2().PlaceOrderDataBase(userName, pid);
			Return content ($"order succeeded, corresponding product ID: {PID}");
			}
			else
			{
			await Task.CompletedTask;
			Return content ($"the goods have been robbed");
			}
			}
	}

3. Of course, we also need an actual executor of the business

/// <summary>
			///Actual processor of order business
			/// </summary>
			public class TicketHelper2
			{
			/// <summary>
			///Perform complex order operations (such as databases)
			/// </summary>
			///< param name = "username" > single user < / param >
			///< param name = "PID" > Product 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();
			}
			}
			}
			}

In this way, we can access the following three addresses at the same time. If there are only two products in the database, there will be a request result: the goods have been robbed.

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

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

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

The advantage of this method is that it is efficient and does not need a second interface to return query results compared with the second method.

Disadvantages: I didn’t expect it for the time being.

Note: this method is only a personal guess, not the actual project experience, we can only use it as a reference, carefully used in the project. We welcome criticism and correction.

This is about asp.net Through the message queue processing high concurrency request (take grabbing Xiaomi mobile phone as an example) the article introduced this, more related asp.net Message queuing processing high concurrency content, please search the previous articles of developeppaer or continue to browse the related articles below. I hope you can support developeppaer more in the future!