Redis learning notes - installation configuration, 5 basic data types, Jedis, mobile verification code, seckill

Posted by rodolp13 on Fri, 18 Feb 2022 19:54:12 +0100

1 installation

  1. Download the compressed package on the official website of Redis:
  2. Use Xftp to put the Redis compressed package in the / opt directory and use the command: tar -zxvf Redis-6.2.2 tar. GZ decompression.

  3. Because Redis is implemented based on C + +, it needs to rely on two additional packages: GCC and gcc-c + +. Use the commands yum install gcc and yum install gcc-c + + under the opt directory to install these two dependencies.
  4. Under the Redis decompression directory (redis-6.2.2), use the command: make to compile Redis. After compiling, use the command: make install to install Redis.


    Note 1: if you use the make command before installing GCC and gcc-c + +, an error will be reported. At this time, you need to install GCC and gcc-c + +. After the installation is successful, use the command: make distclean to empty the previous running cache, and then execute step 4.
    Note 2: the installation directory of Redis is: / usr/local/bin. The advantage of installing in this directory is that Redis commands can be executed anywhere in the system (such as the commands to start and close Redis).

2. Start and shut down the client and server

  1. Enter the Redis installation directory (/ opt/Redis-6.2.2) and copy Redis Conf configuration file to a new directory (myRedisConf) under / opt.
  2. Open myredisconf / Redis Conf file, and change the daemon no to daemon yes. After modification, Redis can be started in the background.
  3. Execution: Redis server modified Conf path and start Redis service. Use: ps -ef | grep redis to check whether it is opened successfully.
  4. Use: Redis cli [- H ip address - p port number] to open the client. If the ip address and port number are not specified, the ip address and port number of Redis server will be used by default.
  5. After entering the Redis client, enter: ping. If you output: pong, it means that the connection between the client and the server is successful.
  6. (1) Exit the client, enter: exit, or press: Ctrl + C.
    (2) In the client, shut down the server and enter: shutdown.
    (3) When the client does not enter, shut down the server and enter: redis cli shutdown. When the server has multiple ports, use: redis cli - P to shut down the port number.

3 Database

  1. Redis has 16 databases, similar to array subscripts. These databases start from 0 to 15. Library 0 is used by default. Use the command: select index to switch the database.
  2. Redis unified password management. All databases have the same password. Either all can be connected, or none can be connected.

4 single thread + multiplex IO

5 basic instructions

instructionseffect
keys *View all keys in the current library.
exists kCheck whether there is a key k in the current library. 1 is returned, 0 is not returned.
type kView the type of key k.
del kDelete key k. 1 is returned if the deletion is successful.
expire k timeSets the expiration time for key k in seconds.
ttl kSee how many seconds the key k has left to expire- 1 means it will never expire, - 2 means it has expired.
dbsizeView the total number of keys in the current library.
FlushdbEmpty the current library.
FlushallEmpty all libraries.

6 five basic data types

6.1 String

  1. String is the most basic data type of Redis. The format is in the form of key value pair.
  2. The value of a String can contain any data, such as a picture or a serialized object.
  3. The maximum value of String is 512M.
  4. The subscript of value starts from 0.
  5. Atomicity: refers to operations that will not be interrupted by thread scheduling mechanism. Once the operation starts, it runs until the end.

Common instructions for manipulating strings:

instructionseffect
get kGet v corresponding to k.
set k vAdd key value pairs to the library.
append k vAppend v after the original value of k.
strlen kGets the length of v.
setnx k vWhen k does not exist, set the key value pair.
incr kAdd 1 to the numeric value stored in k. You can only operate on numeric values. If it is empty, it is set to 1.
decr kSubtract the numeric value stored in k by 1. You can only operate on numeric values. If it is empty, it is set to - 1.
incrby/decrby k nAdd or subtract n from the numeric value stored in k. You can only operate on numeric values.
mset k1 v1 k2 v2 ...Add multiple key value pairs at the same time.
mget k1 k2 ...Obtain v of multiple k at the same time.
msetnx k1 v1 k2 v2 ...When k1, k2... Do not exist, set multiple key value pairs at the same time.
getrange k start endFrom start to end, obtain v of k (including end). Equivalent to intercepting substring.
setrange k start vStarting from start, replace the original value of k with v.
setex k expireTime vSet the expiration time of k while setting the key value pair. The unit is seconds.
getset k vGet the key value pair and modify the value of k to v.

6.2 List

  1. List: single key multi value, orderly and repeatable. There is a list if there is a key.
  2. Redis's list is a simple string list, sorted by insertion order.
  3. Only strings can be stored in the List.
  4. List is actually a two-way linked list.
  5. The index of the first value of the List is 1, and the index of the last value is - 1.

Common instructions for operating List:

instructionseffect
lpush/rpush k v1,v2 ...Insert data into the head or tail of the list k.
lpop/rpop kTake out the value of the header or footer. After taking out, the value does not exist in k.
rpoplpush k1 k2Take the ending value of k1 and insert it into the header of k2.
lrange k start endView the [start, end] value of list k from left to right.
lindex k indexView the list k from left to right. The index is the value of index.
llen kGets the length of the list k.
linsert k before|after v nVInsert the new value nV before or after the value v of table k.
lrem k n v(1) When n > 0: delete n v in list k from left to right;
(2) When n < 0: delete n v in list k from right to left;
(3) When n = 0: delete all v in list k.

6.3 Set

  1. The function of Set is similar to that of List, except that it is unordered and cannot be repeated.
  2. Only String can be stored in Set.
  3. Set is actually a hash table.

Common instructions for operating Set:

instructionseffect
sadd k v1,v2 ...Add data v1, v2... To set k. Skip existing data.
smembers kView all values in the set k.
sismember k vJudge whether there is v in set k. 1 is returned, 0 is not returned.
scard kReturns the number of values in the set k.
srem k v1,v2 ...Delete v1, v2 in set k
spop kRandomly take out a value in the set k. After taking out, the value does not exist in k.
srandmember k nRandomly take out n values in the set k. After being taken out, these values will not be deleted.
sinter k1,k2Returns the intersection of k1 and k2.
sunion k1,k2Returns the union of k1 and k2.
sdiff k1,k2Returns the difference set of k1 and k2. k1 - k2: data in k1 but not in k2.

6.4 Hash

  1. Hash is a collection of key value pairs of type String. Similar to map < String, String > in Java

Common instructions for operating Hash:

instructionseffect
hset k f vAssign v to the key f of the k set.
hmset k f1 v1 f2 v2 ...Batch assignment.
hget k fGets the value of key f in set k.
hexists k fDetermine whether the key f exists in the set k.
hkeys kDisplays all keys f of set k.
hvals kDisplays all values v of set k.
hgetall kDisplays all key value pairs of set k.
hincrby k f incrementIncrease the key f in set k by increment. Value must be numeric.
hsetnx k f vWhen the key f does not exist in k, save f v to k.

6.5 Zset

  1. Zset is a non repeatable ordered Set (Set is a non repeatable unordered Set).
  2. Each member of Zset is associated with a score, and Zset sorts the members according to this score.

Common instructions for operating Zset:

instructionseffect
zadd k s1 v1 s2 v2 ...Add members and their corresponding scores to the set k.
(1) s and v are the same: failed to add;
(2) S is different, v is the same: update s of v;
(3) s is the same, v is different: it is added successfully and sorted according to the order of addition.
zrange k start endQuery the data indexed in [start, end] in the set k.
The index of the last value is - 1.
Sort from small to large.
zrevrange k start endSort from large to small.
zrangebyscore k min maxQuery the data scored in [min, max] in set k.
Sort from small to large.
zrevrangebyscore k max minSort from large to small.
zincrby k increment vIncrease the score of value v by increment.
zrem k vDelete v.
zcount k min maxReturns the number of elements with a score between [min, max].
zrank k vGets v the ranking in the collection. Ranking starts from 0.

7 configuration file

  1. Unit of measurement: 1 k = 1000 bytes, 1 kb = 1024 bytes. Rounding without b. Case insensitive.
  2. Include: similar to include in jsp, you can extract the same part of the configuration file.
  3. Bind of ip address: bind 127.0.0.1 by default, only local access is allowed. If you want any address to be accessible, just comment out bind and turn off protected mode. Turn off protected mode: protected model no.
  4. TCP backlog: the total number of queues after the request reaches Redis and before it is processed.
  5. timeout: the number of seconds after an idle client is shut down. 0 means never close.
  6. TCP keepalive: how often do you check whether the client is still connected to the server. The official recommended setting is 60 seconds.
  7. Daemon: whether to set the server as a background process (background startup).
  8. pidfile: the location where the pid file is stored.
  9. loglevel: log level. From low to high: debug, verbose, notice, warning. The higher the level, notice or warning is recommended in the production environment.
  10. logfile: the name of the log file.
  11. syslog: whether to output Redis log to Linux system log.
  12. Syslog ident: Flag of the log.
  13. Syslog facility: the device that outputs the log.
  14. database: the number of Redis libraries. The default is 16.
  15. security:
    (1) Temporary password: after entering Redis client:
    Get password: config get requirepass.
    Set password: config set requirepass "xxx".
    After setting, you can enter the password: auth password.
    (2) Permanent password: in Redis configuration file:
    requirepass: set the login password.
  16. maxclient: the maximum number of client connections.
  17. maxmemory: set the maximum memory that Redis can use.
    When the memory reaches the upper limit, Redis will try to remove the data and release the memory. Removal rules can be set through: maxmemory policy. If Redis is unable to release memory according to the removal rule, or the rule is set to not allow removal, Redis will return error messages for memory application instructions, such as set and lpush.
  18. Maxmemory policy: memory removal policy.
    Volatile LRU: use LRU algorithm (least recently used) to remove the key. Only valid for keys with expiration time set.
    Allkeys LRU: use LRU algorithm to remove keys.
    Volatile random: randomly remove keys. Only valid for keys with expiration time set.
    All keys random: remove keys randomly.
    Volatile ttl: remove the key with the smallest ttl time (remaining expiration time).
    noeviction: do not remove the key. For the instruction of applying for memory, an error message is returned.
  19. Maxmemory samples: select several of the data that meet the requirements of LRU and ttl algorithms as alternative removal data.
    (1) During the operation of LRU and ttl algorithms, there may be multiple data with the same latest minimum usage times or remaining expiration time. These data meet the conditions of removed memory. At this time, you can set maxmemory samples to select n of all the data that meet the conditions for further filtering, and finally remove the most qualified data.
    (2) Maxmemory samples is generally set to 3 - 7. The larger the number, the more alternative data to remove, the more accurate the removal, but the higher the performance consumption.

8 Jedis

  1. In Redis configuration file, comment out bind 127.0.0.1.

  2. It is not recommended to turn off the protection mode. It is recommended to set the Redis login password.

  3. Close the firewall of Linux system: systemctl stop firewalld.

  4. Check the ip address of Linux: ifconfig.

  5. Create maven project and introduce jedis dependency.

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.6.0</version>
        </dependency>
    </dependencies>
    
  6. (1) Create a Jedis object, and the constructor passes in the ip address of the Linux system and the port number of Redis (default: 6379).
    (2) Use the auth method to enter the login password.
    (3) Use the ping method to check whether the connection is successful.
    (4) Perform data operations.
    (5) Close the jedis connection.

    public class TestJedis {
        public static void main(String[] args) {
            //Construction method, incoming ip address and port number
            Jedis jedis = new Jedis("192.168.61.128", 6379);
            jedis.auth("Redis password");
            String ping = jedis.ping();
            System.out.println(ping);//pong
            // jedis.set("jedisKey","jedisVal");
            String jedisKey = jedis.get("jedisKey");
            System.out.println(jedisKey);//jedisVal
            jedis.close();
        }
    }
    

9 case 1: mobile phone verification code


code.html: page.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
	<title>Insert title here</title>
	<script  src="jquery/jquery-3.1.0.js" ></script>
	<link href="bs/css/bootstrap.min.css" rel="stylesheet" />
	<script  src="static/bs/js/bootstrap.min.js"   ></script>
</head>
<body>
<div class="container">
	<div class="row">
		<div id="alertdiv" class="col-md-12">
			<form class="navbar-form navbar-left" role="search" id="codeform">
				<div class="form-group">
					<input type="text" class="form-control" placeholder="Fill in the mobile phone number" name="phone_no">
					<button type="button" class="btn btn-default" id="sendCode">Send verification code</button><br>
					<font id="countdown" color="red" ></font>
					<br>
					<input type="text" class="form-control" placeholder="Fill in the verification code" name="verify_code">
					<button type="button" class="btn btn-default" id="verifyCode">determine</button>
					<font id="result" color="green" ></font><font id="error" color="red" ></font>
				</div>
			</form>
		</div>
	</div>
</div>

</body>
<script type="text/javascript">
	var t=120;//Set the countdown time
	var interval;
	function refer(){
		$("#countdown").text(" please fill in the verification code within "+ t +" seconds); / / the countdown is displayed
		t--; // Counter decrement
		if(t<=0){
			clearInterval(interval);
			$("#countdown").text(" verification code is invalid, please resend! ");
		}
	}

	$(function(){
		$("#sendCode").click( function () {

			$.post("/sendcode",$("#codeform").serialize(),function(data){
				if(data=="true"){
					t=120;
					clearInterval(interval);
					interval= setInterval("refer()",1000);//Start 1 second timing
				}else if (data=="limit"){
					clearInterval(interval);
					$("#countdown").text(" more than times of sending in a single day! ")
				}
			});
		});

		$("#verifyCode").click( function () {

			$.post("/verifycode",$("#codeform").serialize(),function(data){
				if(data=="true"){
					$("#result").attr("color","green");
					$("#result").text(" validation succeeded ");
					clearInterval(interval);
					$("#countdown").text("");
				}else{
					$("#result").attr("color","red");
					$("#result").text(" validation failed ");
				}
			});
		});

	});
</script>
</html>

PageController.java: visit code html.

@Controller
public class PageController {
    @GetMapping("/code")
    public String gotoIndex(){
        return "code";
    }
}

GetCode.java: get the random 6-bit verification code.

public class GetCode {
    //Generate verification code
    public static String getCode(){
        Random random = new Random();
        //Randomly generated 6 as verification code
        String code = "";
        for(int i=0; i<6; i++){
            int anInt = random.nextInt(10);
            code = code + anInt;
        }
        return code;
    }
}

CodeController.java: get the verification code and verify whether the verification code is correct.

/**
 * Handle the business of obtaining verification code and verification code
 */
@Controller
@ResponseBody
public class CodeController {
    private Jedis jedis = new Jedis("192.168.61.128",6379);

    @PostMapping("/sendcode")
    public String sendCode(@RequestParam("phone_no") String phoneNum){
        jedis.auth("Redis password");
        //If count is empty, it means that the verification code is applied for for the first time, the application is successful, and count is set to 1
        String codeKey = "verifycode:code:"+phoneNum;
        String countKey = "verifycode:count:"+phoneNum;
        String count = jedis.get("verifycode:phone:count");
        if(count==null){
            String code = GetCode.getCode();
            //The valid time of verification code is 120 seconds
            jedis.setex(codeKey,120,code);
            //Obtain the verification code three times within 24 hours
            jedis.setex(countKey,24*60*60,"1");
        }else if(Integer.parseInt(count) <= 2){
            //If count < = 2, you can also apply and send the verification code to count+1
            String code = GetCode.getCode();
            jedis.setex(codeKey,120,code);
            jedis.incr(countKey);
        }else if(Integer.parseInt(count) >= 3){
            //If count > = 3, you cannot apply again
            jedis.close();
            return "limit";
        }
        jedis.close();
        return "true";
    }

    /**
     * Verify whether the verification code is correct
     */
    @PostMapping("/verifycode")
    public String verifyCode(@RequestParam("phone_no") String phoneNum,
                             @RequestParam("verify_code") String verifyCode){
        String codeKey = "verifycode:code:"+phoneNum;
        String code = jedis.get(codeKey);
        if(code==null){
            jedis.close();
            return "nocode";
        }else if(code.equals(verifyCode)){
            jedis.close();
            return "true";
        }else{
            jedis.close();
            return "false";
        }
    }
}

10 affairs

  • Redis transaction is a separate isolation operation. All commands in the transaction will be serialized and executed in order. During the execution of a transaction, it will not be interrupted by requests from other clients.
  • The main function of Redis transaction is to concatenate multiple commands, so that the executing command is interrupted by other commands.
  • Transaction command:
    (1) multi: start the transaction and enter the queue state.
    (2) discard: abandon team formation.
    (3) exec: execute the transaction.
    (4)watch k1 k2 ...: Monitor some keys. If these keys are changed before the transaction is executed, the transactions that operate these keys will be cancelled.
    (5) Unwatch: cancels the monitoring of all key s. The exec and discard operations will automatically execute unwatch.
  • Error handling of transactions:
    (1) When forming a queue, a command has a writing error: the whole queue will be cancelled.
    (2) An error occurred in a command during execution: the command in error is cancelled and other commands continue to be executed.
  • Three features:
    (1) Separate isolation operation.
    (2) There is no concept of isolation level.
    (3) Atomicity is not guaranteed.

11 case 2: second kill

11.1 basic code

seckill.html: page.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>iPhoneXsMAX !!!  1 Yuan second kill!!!
</h1>


<form id="msform" action="" th:action="@{/doSecKill}" enctype="application/x-www-form-urlencoded">
	<input type="hidden" id="prodid" name="prodid" value="0101">
	<input type="button"  id="miaosha_btn" name="seckill_btn" value="Kill me"/>
</form>

</body>
<script  type="text/javascript" src="jquery/jquery-3.1.0.js"></script>
<script  type="text/javascript">
$(function(){
	$("#miaosha_btn").click(function(){	 
		var url=$("#msform").attr("action");
	     $.post(url,$("#msform").serialize(),function(data){
     		if(data=="nostock"){
    			alert("It's all gone" );
    			$("#miaosha_btn").attr("disabled",true);
    		}else if(data=="havesuccess"){
				alert("You have succeeded in second kill. You can't second kill again" );
				$("#miaosha_btn").attr("disabled",true);
			}else if(data=="success"){
				alert("Second kill success" );
				$("#miaosha_btn").attr("disabled",true);
			}
		} );    
	})
})
</script>
</html>

PageController.java: visit seckill.com html.

@Controller
public class PageController {
    @GetMapping("/seckill")
    public String gotoSeckill(){
        return "seckill";
    }
}

SecKillController.java

@Controller
public class SecKillController {
    @ResponseBody
    @PostMapping("/doSecKill")
    public String secKill(@RequestParam("prodid") String prodid) throws IOException {
        //Randomly generated userid
        String userid = new Random().nextInt(50000) +"" ;
        String status= SecKill_redis.doSecKill(userid,prodid);
        return status;
    }
}

SecKill_redis.java: handle second kill logic.

public class SecKill_redis {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redis.class) ;
	
	public static String doSecKill(String uid,String prodid) throws IOException {
		//Connect to Redis
		Jedis jedis = new Jedis("192.168.61.128", 6379);
		jedis.auth("Redis password");
		//Save key to Redis
		String stockKey = "seckill:"+prodid+":stock";
		String userKey = "seckill:"+prodid+":user";
		//Get inventory
		String stock = jedis.get(stockKey);
		//Whether the goods are in stock. If not, the end of the second kill will be displayed
		if(stock==null||Integer.parseInt(stock)<=0){
			jedis.close();
			System.out.println("The second kill is over");
			return "nostock";
		}else if(jedis.sismember(userKey,uid)){//Whether the user has succeeded in the second kill. The user who succeeds in the second kill cannot continue the second kill
			jedis.close();
			System.out.println("The second kill has been completed. You can't kill any more");
			return "havesuccess";
		}
		//If other conditions are normal, the inventory will be reduced by 1, and the id of the user who succeeded in the second kill will be added
		jedis.decr(stockKey);
		jedis.sadd(userKey,uid);
		jedis.close();
		System.out.println("Second kill success");
		return "success";
	}
}

11.2 simulate concurrency using ab tool

In 11.1, the basic code does not consider concurrency. Use ab tool to simulate concurrency.
CentOS 6 default installation ab tool; CentOS 7 requires manual installation.

Under Linux system, use the command Yum install httpd tools to install ab tools.
Use the command: ab -n number of requests - c number of concurrency - p file for storing the parameters to be sent - T format request address for sending parameters to simulate concurrency.

In this example, the form sends prodid=0101. Therefore, create a new file locally in Linux, store this parameter, and then use the ab command to send it.

Destination server address:

Set inventory: set seckill:0101:stock 20

Command used: ab -n 2000 -c 200 -p /opt/postfile -T application/x-www-form-urlencoded http://192.168.0.154:8080/doSecKill , send concurrent requests.

View remaining inventory: get seckill:0101:stock

Oversold.

Multiple users send requests at the same time. When processing, it is judged that the inventory quantity is greater than 1, so the second kill is successful. However, the correct scenario should be that only one of them succeeds in the second kill. After the user succeeds in the second kill, the inventory will be reduced by 1, and other concurrent users can no longer perform the second kill.

In addition, connection timeout may occur in the case of more concurrent connections.

11.3 use Redis database connection pool to solve the connection timeout problem

Connection pool parameters:

  • MaxTotal: controls how many jedis instances can be allocated to a pool through pool Getresource(); If the value is - 1, it means there is no limit; If the pool has allocated MaxTotal jedis instances, the status of the pool is exhausted.
  • maxIdle: controls the maximum number of jedis instances in idle status in a pool.
  • MaxWaitMillis: indicates the maximum number of milliseconds to wait when applying for a jedis instance. If the waiting time exceeds, the JedisConnectionException will be thrown directly.
  • Testonmirror: whether to check the connection availability (ping()) when obtaining a jedis instance. If true, all the jedis instances are available.

pom. Introducing connection pool dependency into XML:

<!-- spring2.X integrate redis what is needed common-pool2-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
</dependency>

JedisPoolUtil.java: get database connection pool.

public class JedisPoolUtil {
	private static volatile JedisPool jedisPool = null;

	private JedisPoolUtil() {
	}

	public static JedisPool getJedisPoolInstance() {
		if (null == jedisPool) {
			synchronized (JedisPoolUtil.class) {
				if (null == jedisPool) {
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					poolConfig.setMaxTotal(200);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWaitMillis(100*1000);
					poolConfig.setBlockWhenExhausted(true);
					poolConfig.setTestOnBorrow(true);  // ping  PONG

					jedisPool = new JedisPool(poolConfig, "192.168.61.128", 6379,
							60000,  "Redis password");
				}
			}
		}
		return jedisPool;
	}

	public static void release(JedisPool jedisPool, Jedis jedis) {
		if (null != jedis) {
			jedisPool.returnResource(jedis);
		}
	}
}

SecKill_redis.java

public class SecKill_redis {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redis.class) ;
	
	public static String doSecKill(String uid,String prodid) throws IOException {
		//Use the Redis database connection pool to solve the connection timeout problem.
		//Get database connection pool
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		//Get Jedis connection
		Jedis jedis = jedisPoolInstance.getResource();
		
		//Save key to Redis
		String stockKey = "seckill:"+prodid+":stock";
		String userKey = "seckill:"+prodid+":user";
		//Get inventory
		String stock = jedis.get(stockKey);
		//Whether the goods are in stock. If not, the end of the second kill will be displayed
		if(stock==null||Integer.parseInt(stock)<=0){
			jedis.close();
			System.out.println("The second kill is over");
			return "nostock";
		}else if(jedis.sismember(userKey,uid)){//Whether the user has succeeded in the second kill. The user who succeeds in the second kill cannot continue the second kill
			jedis.close();
			System.out.println("The second kill has been completed. You can't kill any more");
			return "havesuccess";
		}
		//If other conditions are normal, the inventory will be reduced by 1, and the id of the user who succeeded in the second kill will be added
		jedis.decr(stockKey);
		jedis.sadd(userKey,uid);
		jedis.close();
		System.out.println("Second kill success");
		return "success";
	}
}

11.4 use transaction + monitoring to solve oversold problems

To monitor the inventory, multiple concurrent users put the second kill process in the transaction. When one of these concurrent users succeeds in the second kill, the inventory will be modified. At this time, due to the role of monitoring, the transactions of other users will be cancelled. As a result, only one of these concurrent users will succeed in the second kill, so the oversold problem is solved.

SecKillController.java

@Controller
public class SecKillController {
    @ResponseBody
    @PostMapping("/doSecKill")
    public String secKill(@RequestParam("prodid") String prodid) throws IOException {
        //Randomly generated userid
        String userid = new Random().nextInt(50000) +"" ;
        String status= SecKill_redis.doSecKill(userid,prodid);
        return status;
    }
}

SecKill_redis.java

public class SecKill_redis {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redis.class) ;
	
	public static String doSecKill(String uid,String prodid) throws IOException {
		//Use the Redis database connection pool to solve the connection timeout problem.
		//Get database connection pool
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		//Get Jedis connection
		Jedis jedis = jedisPoolInstance.getResource();
		
		//Save key to Redis
		String stockKey = "seckill:"+prodid+":stock";
		String userKey = "seckill:"+prodid+":user";
		
		//Monitor inventory
		jedis.watch(stockKey);
		
		//Get inventory
		String stock = jedis.get(stockKey);
		//Whether the goods are in stock. If not, the end of the second kill will be displayed
		if(stock==null||Integer.parseInt(stock)<=0){
			jedis.close();
			System.out.println("Second kill failed");
			return "nostock";
		}else if(jedis.sismember(userKey,uid)){//Whether the user has succeeded in the second kill. The user who succeeds in the second kill cannot continue the second kill
			jedis.close();
			System.out.println("The second kill has been completed. You can't kill any more");
			return "havesuccess";
		}
		
		//Open transaction
		Transaction transaction = jedis.multi();
		//After a transaction is started, the operations to be performed in the transaction are completed by the transaction object
		//If other conditions are normal, the inventory will be reduced by 1, and the id of the user who succeeded in the second kill will be added
		transaction.decr(stockKey);
		transaction.sadd(userKey,uid);
		//Execute transaction
		List<Object> exec = transaction.exec();
		//Judge whether the transaction is executed successfully. The successful execution List contains the execution results of each command, and the failed execution List is empty or size=0
		if(exec==null || exec.size()==0){
			System.out.println("Second kill failed");
			jedis.close();
			return "nostock";
		}
		
		jedis.close();
		System.out.println("Second kill success");
		return "success";
	}
}


Solved the oversold problem.

However, another problem may occur at this time: inventory legacy.

When the inventory is increased, such as when the inventory is set to 500, after the second kill, the remaining inventory is not 0, but 230.

Only one concurrent process can succeed in the second kill, and other users fail. When the failed process does not continue the second kill, inventory will be left. This is very common in life. For example, there are 5 inventories and 800 requests in total. Every 200 requests is a concurrent process. When 200 concurrent user processes are spike killed, only one spike is successful. At this time, the other 199 users will not continue to spike. If this goes on, only 4 users spike successfully, resulting in one commodity left behind.

Moreover, the second kill realized by using transaction + monitoring is not in line with the reality of life. In the actual second kill, it is each user, whether concurrent or not, whose network speed is fast, who executes the code first, and who succeeds in the second kill. It is impossible to see the situation that the user who kills first fails and the user who kills later succeeds.

11.5 use LUA script to solve inventory leftover problems

  • Lua / Lua is not a powerful script library for embedded application development. However, it is not suitable for C / Lua + + to be used as a script interpreter. On the contrary, it is not suitable for C / Lua + + to provide a complete script. Lua / Lua can also be used as a script interpreter. Many applications and games use Lua as their own embedded scripting language to achieve configurability and scalability. Including Warcraft map, world of Warcraft, Bode's gate, angry birds and many other game plug-ins or plug-ins.

  • Write complex or multi-step redis operations into a Lua script and submit them to redis for execution at one time to reduce the number of repeated connections to redis. Improve performance.

  • Lua script is similar to redis transactions. It has certain atomicity and will not be cut in line by other commands. It can complete some redis transactional operations. However, note that the Lua script function of redis can only be used in redis versions above 2.6.

  • Lua script can be used to solve oversold and inventory problems. In fact, redis uses its single thread feature to solve the problem of multi task concurrency by means of task queue.

SecKill_redisByScript.java: handle second kill logic.

public class SecKill_redisByScript {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

	static String secKillScript ="local userid=KEYS[1];\r\n" + 
			"local prodid=KEYS[2];\r\n" + 
			"local stockKey='seckill:'..prodid..\":stock\";\r\n" +
			"local userKey='seckill:'..prodid..\":user\";\r\n" +
			"local userExists=redis.call(\"sismember\",userKey,userid);\r\n" +
			"if tonumber(userExists)==1 then \r\n" + 
			"   return 2;\r\n" + 
			"end\r\n" + 
			"local num= redis.call(\"get\" ,stockKey);\r\n" +
			"if tonumber(num)<=0 then \r\n" + 
			"   return 0;\r\n" + 
			"else \r\n" + 
			"   redis.call(\"decr\",stockKey);\r\n" +
			"   redis.call(\"sadd\",userKey,userid);\r\n" +
			"end\r\n" + 
			"return 1" ;
			 
	static String secKillScript2 = 
			"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
			" return 1";

	public static String doSecKill(String uid,String prodid) throws IOException {
		//Use the Redis database connection pool to solve the connection timeout problem.
		//Get database connection pool
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		//Get Jedis connection
		Jedis jedis = jedisPoolInstance.getResource();

		String sha1=  jedis.scriptLoad(secKillScript);

		Object result= jedis.evalsha(sha1, 2, uid,prodid);

		String reString=String.valueOf(result);
		if ("0".equals( reString )  ) {
			System.err.println("Empty!!");
			jedis.close();
			return "nostock";
		}else if("1".equals( reString )  )  {
			System.out.println("Rush purchase succeeded!!!!");
			jedis.close();
			return "success";
		}else if("2".equals( reString )  )  {
			System.err.println("This user has robbed!!");
			jedis.close();
			return "havesuccess";
		}else{
			System.err.println("Panic buying exception!!");
			jedis.close();
			return "false";
		}		 
	}
}

SecKillController.java

@Controller
public class SecKillController {
    @ResponseBody
    @PostMapping("/doSecKill")
    public String secKill(@RequestParam("prodid") String prodid) throws IOException {
        String userid = new Random().nextInt(50000) +"" ;
        String status= SecKill_redisByScript.doSecKill(userid,prodid);
        return status;
    }
}


The problems of oversold and inventory left over have been solved.

Topics: Redis