36 Redis's Key Technologies and Practices in Supporting High Concurrency Scenarios

36 Redis’s Key Technologies and Practices in Supporting High-concurrency Scenarios #

Flash sale is a very typical activity scene, for example, during e-commerce promotions such as Double 11 and 618, there will be flash sale scenes. The business characteristics of flash sale scenes are limited time and quantity. The business system needs to handle a large amount of high-concurrency requests in an instant, and Redis is often used to support flash sale activities.

However, the flash sale scene includes multiple stages and can be divided into three stages: before, during, and after the flash sale. The request processing requirements for each stage are different, and Redis cannot support every stage of the flash sale scene.

So, which stage does Redis specifically play a supporting role in the flash sale scene? And how does it support it? Only by understanding this question can we know how to use Redis to support high-concurrency pressure and develop a response plan for the flash sale scene.

Next, let’s first understand the load characteristics of the flash sale scene.

Load characteristics of the spike scene and the requirements for the supporting system #

Spike events typically offer heavily discounted products, which attract a large number of users to make purchases. However, the inventory of the products is much less than the number of users wanting to buy them, and users are often limited to a specific time period for making the purchase. This brings two distinct load characteristics to the spike system, and consequently, imposes certain requirements on the supporting system. Let’s analyze them.

The first characteristic is that there is an extremely high level of concurrent access.

Generally, a database can only support thousands of concurrent requests per second, while Redis can handle tens of thousands or even higher numbers of requests per second. Therefore, when a large number of concurrent requests flood into the spike system, we need to use Redis to intercept most of the requests, to avoid overwhelming the database with a large number of direct requests.

The second characteristic is that there are more reads than writes, and the read operations are simple queries.

In the spike scene, users need to check whether there is still inventory for the desired product (i.e., query the remaining inventory of a product based on its ID). Only when there is sufficient inventory can the spike system proceed with inventory deduction and order placement.

Inventory checking operations involve typical key-value queries, and Redis’s efficient support for key-value queries is exactly suited for this operation.

However, in spike events, only a small percentage of users can successfully place orders. Therefore, inventory query operations (read operations) far outnumber inventory deduction and order placement operations (write operations).

Of course, in practical spike scenes, there are usually multiple stages involved, and the inventory check by users we just mentioned is only one of them. So, where exactly can Redis play a role in the entire spike scene? That requires discussing the overall process of a spike event, let’s analyze it.

Where can Redis play a role in the seckill scenario? #

We can generally divide a seckill activity into three stages, and Redis plays a different role in each stage.

The first stage is before the seckill activity.

In this stage, users continuously refresh the product detail page, resulting in a sudden increase in request volume. The solution for this stage is to staticize the page elements of the product detail page as much as possible and cache these static elements using CDN or the browser. In this way, the large number of pre-seckill requests can be directly handled by the CDN or browser cache service, reducing the pressure on the server.

During this stage, CDN and browser cache service requests are sufficient, and we don’t need to use Redis yet.

The second stage is when the seckill activity starts.

At this point, a large number of users click the seckill button on the product detail page, generating numerous concurrent requests to check the inventory. When a request finds available inventory, the system will deduct the inventory and generate an actual order for further processing, such as order payment and logistics services. If a request fails to find inventory, it will return. Users usually continue to click the seckill button to keep checking the inventory.

In short, there are three operations in this stage: inventory verification, inventory deduction, and order processing. Since each seckill request needs to check the inventory, and only when the request finds available inventory, the subsequent inventory deduction and order processing will be executed. Therefore, the highest concurrency pressure in this stage lies in the inventory verification operation.

To support a high volume of concurrent inventory verification requests, we need to use Redis to store the inventory so that requests can directly read the inventory from Redis and perform the verification.

So, can we leave the inventory deduction and order processing to the backend database?

Actually, order processing can be performed in the database, but inventory deduction operation cannot.

The reason for processing orders in the database is relatively simple, let me explain first.

Order processing involves multiple associated operations such as payment, product outbound, and logistics, which themselves involve multiple data tables in the database. To ensure transactional integrity, these operations need to be completed in the database. Besides, the order processing requests have relatively low pressure, and the database can handle these order processing requests.

So why can’t we perform the inventory deduction operation in the database? This is because once a request finds available inventory, it means that the user who sent the request has obtained the purchasing qualification for the product, and the user will proceed to place an order. At the same time, the remaining inventory of the product needs to be reduced by one. If we perform the inventory deduction operation in the database, it will bring about two issues.

  1. Additional costs. Redis stores the inventory, and the latest value of the inventory is maintained by the database, so after the database is updated, it needs to synchronize with Redis. This process adds additional operational logic and incurs additional costs.
  2. Overselling due to orders exceeding actual inventory. Due to the slow processing speed of the database, it cannot update the remaining inventory in a timely manner. This will cause many inventory verification requests to read the stale inventory value and place orders. As a result, the number of orders will exceed the actual inventory, leading to overselling, which does not meet the business requirements.

Therefore, we need to perform the inventory deduction directly in Redis. The specific operation is that once the inventory verification is completed and there is available inventory, we immediately deduct the inventory in Redis. To avoid reading the stale inventory value, the inventory verification and inventory deduction operations need to be atomic.

The third stage is after the seckill activity ends.

In this stage, some users may still refresh the product detail page to wait for other users to cancel their orders. Users who have successfully placed orders will refresh the order details to track the progress of their orders. However, the request volume during this stage has significantly decreased, and the server can generally handle it, so we won’t focus on it.

Now, let’s summarize the requirements of Redis in the seckill scenario.

The seckill scenario can be divided into three stages: before, during, and after the seckill. The demand for Redis is not significant in the pre-seckill and post-seckill stages. However, during the seckill, we need to verify and deduct inventory. The inventory verification faces a large volume of high-concurrency requests, and the inventory deduction needs to be executed together with the inventory verification to ensure atomicity. This is where Redis is needed in the seckill scenario.

The following diagram illustrates the two stages in which Redis is involved in the seckill scenario:

Now that we understand the requirements, the methods for utilizing Redis to support the seckill scenario become clearer. Next, I will introduce you to two methods.

Which methods of Redis can support the spike scene? #

There are two fundamental requirements for Redis operations in a spike scene.

  1. High Concurrency Support. This is simple because the high-speed processing feature of Redis itself can support high concurrency. Moreover, if there are multiple spike products, we can use sharding clusters to store the inventory of different products in different instances. This avoids the problem of all spike requests being concentrated on a single instance. However, it should be noted that when using sharding clusters, we need to first use the CRC algorithm to calculate the Slot corresponding to the key of different spike products. Then, when assigning the Slot and instance mapping relationship, we can allocate the Slots corresponding to different spike products to different instances for storage.
  2. Ensuring Atomic Execution of Inventory Verification and Deduction. For this requirement, we can use Redis’s atomic operations or distributed locks to support.

Let’s first look at how Redis supports the spike scene based on atomic operations.

Supporting the Spike Scene with Atomic Operations #

In the spike scene, the inventory of a product corresponds to two pieces of information: total inventory and already spiked amount. This data model just happens to be a key (product ID) corresponding to two attributes (total inventory and already spiked amount). Therefore, we can use a Hash type key-value pair to store the two pieces of inventory information as shown below:

key: itemID
value: {total: N, ordered: M}

Here, itemID is the product number, total is the total inventory, and ordered is the already spiked amount.

Because inventory verification and inventory deduction need to be executed together, a direct method is to use Redis atomic operations.

In Lecture 29, we learned that atomic operations can be Redis’s own atomic commands or Lua scripts. Because inventory verification and inventory deduction are two separate operations that cannot be completed with a single command, we need to use Lua scripts to execute these two operations atomically.

So how can we implement these two operations in a Lua script? I will provide you with a piece of pseudo code written in Lua script that shows the implementation of these two operations:

# Get the inventory information of the product
local counts = redis.call("HMGET", KEYS[1], "total", "ordered")

# Convert the total inventory to a number
local total = tonumber(counts[1])

# Convert the already spiked inventory to a number
local ordered = tonumber(counts[2])

# If the current inventory plus the already spiked inventory is still less than the total inventory, the inventory can be updated
if ordered + k <= total then

    # Update the already spiked inventory
    redis.call("HINCRBY",KEYS[1],"ordered",k)
    
    return k
end

return 0

With the Lua script, we can use the EVAL command in the Redis client to execute this script.

Finally, the client will determine whether the spike was successful or not based on the return value of the script. If the return value is k, it means the spike was successful; if it is 0, it means the spike failed.

So far, we have learned how to use atomic Lua scripts to implement inventory verification and inventory deduction. In fact, to ensure the atomicity of these two operations, we have another method, which is to use distributed locks to ensure that multiple clients can mutually execute these two operations. Next, let’s take a look at how to use distributed locks to support the spike scene.

Supporting the Spike Scene with Distributed Locks #

The specific approach to using distributed locks to support the spike scene is to first have the client apply for a distributed lock from Redis. Only the client that obtains the lock can perform inventory verification and inventory deduction. This way, a large number of spike requests will be filtered out when competing for the distributed lock. Moreover, inventory verification and deduction do not need to use atomic operations because only one client among multiple concurrent clients can obtain the lock, which guarantees the mutual exclusion of client concurrent access.

You can take a look at the pseudo code below, which shows the process of using distributed locks to perform inventory verification and deduction:

// Use the item ID as the key
key = itemID
// Use the client's unique identifier as the value
val = clientUniqueID
// Apply for the distributed lock, Timeout is the timeout time
lock = acquireLock(key, val, Timeout)
// Only perform inventory verification and deduction when the lock is acquired
if(lock == True) {
   // Inventory verification and deduction
   availStock = DECR(key, k)
   // If the inventory has been deducted to zero, release the lock and return spike failure
   if (availStock < 0) {
      releaseLock(key, val)
      return error
   }
   // If inventory deduction is successful, release the lock
   else{
     releaseLock(key, val)
     // Order processing
   }
}
// If the lock is not obtained, return directly
else
   return

It should be reminded that when using distributed locks, the client needs to request the lock from Redis first. Only when the lock is obtained can inventory verification and other operations be performed. In this way, most of the spike requests will be intercepted because they cannot obtain the lock when competing for the distributed lock.

Therefore, I have a suggestion for you. We can use different instances in the sharding cluster to store the distributed lock and product inventory information separately. Using this storage method, spike requests will first access the instance that stores the distributed lock. If the client does not get the lock, these clients will not query the product inventory, which can reduce the pressure on the instance that stores the inventory information.

Summary #

In this lesson, we learned about the specific application of Redis in a spike (or flash-sale) scenario. Spike scenarios have two load characteristics: instant high-concurrency requests and mainly read operations with few writes. Redis’s good ability to handle high concurrency and its efficient key-value read and write characteristics are ideal for spike scenarios.

In spike scenarios, we can intercept a large number of requests before the spike through front-end CDNs and browser caching. During actual spike activities, inventory checking and deduction are the two operations that bear enormous concurrent request pressure, and their execution needs to ensure atomicity. Redis’s atomic operations and distributed locks can effectively support the needs of spike scenarios.

Of course, using only Redis is not enough for spike scenarios. A spike system is a comprehensive engineering project. Redis provides support for inventory checking and deduction; in addition to this, there are four other areas that we need to handle properly.

  1. Design of front-end static pages. We should strive to staticize all page elements that can be staticized on the spike page so that we can make full use of CDN or browser cache services to handle requests before the spike starts.
  2. Request interception and flow control. At the entrance layer of the spike system, we need to intercept malicious requests to avoid malicious attacks on the system. For example, we can use a blacklist to block access from malicious IP addresses. If the pressure on Redis instances is too high, in order to avoid instance crashes, we also need to implement rate limiting at the entrance layer to control the number of requests entering the spike system.
  3. Expiration of inventory information. The inventory information stored in Redis is actually a cache of the database. To avoid cache penetration, we should not set an expiration time for the inventory information.
  4. Exception handling for database orders. If the database fails to process an order successfully, we can add an order retry mechanism to ensure that the order is eventually processed successfully.

Finally, I have another suggestion for you: the massive request traffic brought by spike activities means that we need to store the inventory information of spike products in a separate Redis instance rather than the same instance used for regular business systems. This can avoid interfering with the normal operation of the business system.

One question per lesson #

As usual, I have a small question for you. Suppose the inventory of a product is 800. We use a cluster of 4 instances, each maintaining a stock of 200. The client’s seckill requests can be distributed to different instances for processing. Do you think this is a good method?

Please write your thoughts and answers in the comments section. Let’s discuss and exchange ideas. If you find today’s content helpful, feel free to share it with your friends or colleagues. See you in the next lesson.