When the user initiates a request, nginx will be requested at this time, nginx will access tomcat, and the program in tomcat will perform serial operations, divided into the following steps
1. Query coupons
2. Judging whether the seckill stock is sufficient
3. Query order
4. Check whether it is one person, one order
5. Deduction of inventory
6. Create an order
In these six steps, there are many operations to operate the database, and they are executed serially by one thread, which will cause our program to execute very slowly, so we need asynchronous program execution, so how to speed it up?
Here the author would like to share with you some ideas that are not in the course, and see if any friends think so, for example, can we use asynchronous arrangement to do it, or I open N multi-threading, N multiple threads, one thread Execute query coupons, one executes judgment to deduct inventory, one executes to create orders, etc., and then returns them uniformly. Which method is better in this method or course? The answer is good in the course, because if you use the method I just said, if there are many visitors, then the threads in the thread pool may be consumed at once, and you use the above scheme, the biggest feature is that you think Timeliness will be very important, but do you think about it? No, for example, I just need to make sure that he can do this, and then I can do it slowly later. I don’t need him to finish this in one go, so we should use the message queue in the course way to complete our requirements, instead of using thread pool or asynchronous orchestration to complete this requirement
Optimization plan: We put the time-consuming logical judgment into redis, such as whether the stock is sufficient, such as whether one order per person, such an operation, as long as this logic can be completed, it means that we can definitely place an order to complete Yes, we only need to make quick logical judgments, and we don’t have to wait for the order logic to finish. We directly return success to the user, and then open a thread in the background, and the background thread slowly executes the messages in the queue, so that the program does not Is it super fast? And there is no need to worry about the exhaustion of the thread pool, because there is no manual use of any thread pool in our program. Of course, there are two difficulties here.
The first difficulty is how we can quickly verify one order per person in redis, as well as inventory judgment
The second difficulty is that we have two threads for checking and placing an order with tomct, so how do we know which order is successful in the end, or whether the order is completed? In order to complete this, after the redis operation is completed, we will Return some information to the front end, and at the same time throw the information into the asynchronous queue. In subsequent operations, you can use this id to query whether the order logic in our tomcat is completed.
Let's take a look at the overall idea now: After the user places an order, to determine whether the inventory is sufficient, you only need to guide the redis to find whether the corresponding value is greater than 0 according to the key. If it is not sufficient, it will end directly. If it is sufficient, continue to redis Determine whether the user can place an order. If there is no such data in the set collection, it means that he can place an order. If there is no such record in the set collection, store the userId and coupon in redis and return 0. The whole process It needs to be guaranteed to be atomic, we can use lua to operate
After the above judgment logic is completed, we can judge whether the result returned by the current redis is 0, if it is 0, it means that the order can be placed, then store the information mentioned before in the queue, then return, and then come again A thread places an order asynchronously, and the front end can judge whether the order is placed successfully by the returned order id.
Seckill optimization - Redis completes the qualification judgment of Seckill
need:
-
Save the coupon information to Redis while adding the flash coupon
-
Based on Lua script, judge the flash sale inventory, one order per person, and determine whether the user snapped up successfully
-
If the snap-up is successful, encapsulate the coupon id and user id and store them in the blocking queue
-
Start the thread task, continuously obtain information from the blocking queue, and realize the function of placing an order asynchronously
full lua expression
-- 1.parameter list -- 1.1.coupon id local voucherId = ARGV[1] -- 1.2.user id local userId = ARGV[2] -- 1.3.Order id local orderId = ARGV[3] -- 2.data key -- 2.1.in stock key local stockKey = 'seckill:stock:' .. voucherId -- 2.2.Order key local orderKey = 'seckill:order:' .. voucherId -- 3.script business -- 3.1.Judging whether the stock is sufficient get stockKey if(tonumber(redis.call('get', stockKey)) <= 0) then -- 3.2.Insufficient stock, return 1 return 1 end -- 3.2.Determine whether the user places an order SISMEMBER orderKey userId if(redis.call('sismember', orderKey, userId) == 1) then -- 3.3.Exists, indicating that the order is repeated, return 2 return 2 end -- 3.4.Deduct inventory incrby stockKey -1 redis.call('incrby', stockKey, -1) -- 3.5.Place an order (save user) sadd orderKey userId redis.call('sadd', orderKey, userId) -- 3.6.send a message to the queue, XADD stream.orders * k1 v1 k2 v2 ... redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId) return 0
When the above lua expressions are executed, the rest is to perform our next tasks according to steps 3 and 4
VoucherOrderServiceImpl
@Override public Result seckillVoucher(Long voucherId) { //get user Long userId = UserHolder.getUser().getId(); long orderId = redisIdWorker.nextId("order"); // 1.implement lua script Long result = stringRedisTemplate.execute( SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(), String.valueOf(orderId) ); int r = result.intValue(); // 2.Check whether the result is 0 if (r != 0) { // 2.1.If it is not 0, it means that there is no purchase qualification return Result.fail(r == 1 ? "Inventory shortage" : "Can not repeat the order"); } //TODO save blocking queue // 3.back to order id return Result.ok(orderId); }
Seckill Optimization - Realize Seckill Optimization Based on Blocking Queue
VoucherOrderServiceImpl
Modify the action of placing an order. Now when we place an order, we use the lua expression to atomically execute the judgment logic. If it is judged that I is not 0, then either the stock is insufficient, or the order is repeated, and an error message is returned. If it is 0 , then save the order logic into the queue and execute it asynchronously
//Asynchronous processing thread pool private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor(); //Executed after the class is initialized, because when the class is initialized, it is possible to execute at any time @PostConstruct private void init() { SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }
private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024 * 1024); // Tasks for thread pool processing // When the initialization is complete, it will go to get the information from the column private class VoucherOrderHandler implements Runnable{ @Override public void run() { while (true){ try { // 1.Get order information in the queue VoucherOrder voucherOrder = orderTasks.take(); // 2.Create Order handleVoucherOrder(voucherOrder); } catch (Exception e) { log.error("Handle order exceptions", e); } } }
The principle is: After lua executes and confirms that it can be purchased, add the information to private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024 * 1024);
But ArrayBlockingQueue is
Small summary:
What is the optimization idea of seckill business?
-
First use Redis to complete the inventory balance, one-person-one-order judgment, and complete the order grabbing business
-
Then put the order business into the blocking queue, and use an independent thread to place the order asynchronously
-
What are the problems of asynchronous seckill based on blocking queue?
-
Memory limit problem =============== based on jvm memory, limited storage,
-
Data security issues =============== There is no message confirmation mechanism, and messages are easily lost
So is there any good way to complete decoupling and place orders asynchronously? Message queues do.