Help Center/ Distributed Cache Service/ Best Practices/ Flashing E-commerce Sales with DCS
Updated on 2024-02-27 GMT+08:00

Flashing E-commerce Sales with DCS

Scenario

An e-commerce flash sale is like an online auction. To attract customers, merchants release a small number of scarce offerings on the platform. Platforms receive dozens or even hundreds of more order placements than usual. However, only a few customers can place orders successfully. The traffic distribution process of an e-commerce flash sales system is as follows:

  1. User requests: When users place orders, the requests enter the load balancing server.
  2. Load balancing: The load balancing server distributes requests to multiple backend servers based on certain algorithms. The algorithms include round robin, random, and least connections.
  3. Service processing logic: Backend servers receive requests and verify the requested quantity and user identity.
  4. Inventory deduction: If the inventory is robust, the backend server deducts stocks, generates an order, and returns a success message to the user. If the inventory is insufficient, the backend server returns a failure message.
  5. Order processing: Backend servers save the order information to the database and perform asynchronous processing such as notifying users of the order status.
  6. Cache update: Backend servers update the inventory information in the cache for the next flash sale request.

The database is accessed multiple times during the flash sale process. Row-level locking is usually used to restrict access. The database can be accessed and an order can be placed only after a lock is obtained. However, the database is often blocked by the sheer number of order requests.

Solution

As the cache of the database, DCS for Redis has the following advantages for clients to access Redis for inventory query and order placement:

  • Redis offers high read/write speed and concurrency performance to meet the high concurrency requirements of e-commerce flash sales systems.
  • Redis supports high-availability architecture such as master/standby and cluster. Data persistence is supported, so data can be restored even if the server breaks down.
  • Redis supports transactions and atomic operations to guarantee the consistency and accuracy of operations.
  • Redis caches offering and user information to reduce the database load.

In this example, the hash structure of Redis shows the offering information. total refers to the total amount, booked refers to the number of placed orders, and remain refers to the inventory.

"product": {
"total": 200
"booked":0
"remain":200
}

During inventory deduction, the server sends a request to Redis for placing an order. Redis is single-threaded, and Lua can guarantee the atomicity of multiple commands. Run the following Lua script to deduct inventory:

local n = tonumber(ARGV[1])
if not n  or n == 0 then
   return 0
end
local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\");
local booked = tonumber(vals[2])
local remain = tonumber(vals[3])
if booked <= remain then
   redis.call(\"HINCRBY\", KEYS[1], \"booked\", n)
   redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n)
   return n;
end
return 0

Prerequisites

  • You have created an ECS. To create an ECS, see Creating an ECS.
  • You have created a DCS Redis instance in the same VPC, subnet, and security group as the ECS. To create an instance, see Buying a DCS Redis Instance.

Procedure

  1. Log in to the prepared ECS. To log in, see Logging In to an ECS.
  2. Install JDK1.8 (or later) and IntelliJ IDEA on the ECS. Download the Jedis client.

    The development tools and clients mentioned in this document are for example only.

  3. Run IntelliJ IDEA on the ECS. Create a Maven project, create a SecondsKill.java file, and paste the sample code into it. In pom.xml, import Jedis:

    <dependency>
          <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
          <version>4.2.0</version>
    </dependency>

  4. Compile and run the following demo (this example uses Java):

    package com.huawei.demo;
    import java.util.ArrayList;
    import java.util.*;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    public class SecondsKill {
    	private static void InitProduct(Jedis jedis) {
    		jedis.hset("product", "total", "200");
    		jedis.hset("product", "booked", "0");
    		jedis.hset("product","remain", "200");
    	}
    
    	private static String LoadLuaScript(Jedis jedis) {
    		String lua = "local n = tonumber(ARGV[1])\n"
    			+ "if not n  or n == 0 then\n"
    			+ "return 0\n"
    			+ "end\n"
    			+ "local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\");\n"
    			+ "local booked = tonumber(vals[2])\n"
    			+ "local remain = tonumber(vals[3])\n"
    			+ "if booked <= remain then\n"
    			+ "redis.call(\"HINCRBY\", KEYS[1], \"booked\", n)\n"
    			+ "redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n)\n"
    			+ "return n;\n"
    			+ "end\n"
    			+ "return 0";
    		String scriptLoad = jedis.scriptLoad(lua);
    
    		return scriptLoad;
    	}
    
    	public static void main(String[] args) {
    		JedisPoolConfig config = new JedisPoolConfig();
    		// Maximum connections
    		config.setMaxTotal(30);
    		// Maximum idle connections
    		config.setMaxIdle(2);
    		// Connect to Redis via the actual address and port.
    		JedisPool pool = new JedisPool(config, "127.0.0.1", 6379);
    		Jedis jedis = null;
    		try {
    			jedis = pool.getResource();
    			System.out.println(jedis);
    
    			// Initialize product information.
    			InitProduct(jedis);
    
    			// Load the Lua script.
    			String scriptLoad = LoadLuaScript(jedis);
    
    			List<String> keys = new ArrayList<>();
    			List<String> vals = new ArrayList<>();
    			keys.add("product");
    
    			// Request 15 items.
    			int num = 15;
    			vals.add(String.valueOf(num));
    
    			// Run the Lua script.
    			jedis.evalsha(scriptLoad, keys, vals);
    			System.out.println("total:"+jedis.hget("product", "total")+"\n"+"booked:"+jedis.hget("product",
    				"booked")+"\n"+"remain:"+jedis.hget("product","remain"));
    
    		} catch (Exception ex) {
    			ex.printStackTrace();
    		} finally {
    			if (jedis != null) {
    				jedis.close();
    			}
    		}
    	}
    }

    Result:

    total:200
    booked:15
    remain:185