Redis_ Business_ Second kill case

Redis_ Business_ Second kill case

1. Solve the transaction operations of counters and personnel records


Simple version:
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>iPhone 13 Pro !!!  1 Yuan second kill!!!
</h1>


<form id="msform" action="${pageContext.request.contextPath}/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="${pageContext.request.contextPath}/script/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=="false"){
    			alert("It's all gone" );
    			$("#miaosha_btn").attr("disabled",true);
    		}
		} );    
	})
})
</script>
</html>

SecKill_redis

package com.xiaoqiu;

import redis.clients.jedis.Jedis;

import java.io.IOException;

public class SecKill_redis {

    //Second kill process
    public static boolean doSecKill(String uid, String prodid) throws IOException {
        //1. Judgment of uid and prodid non null
        if (uid == null || prodid == null) {
            return false;
        }

        //2. Connect to redis
        Jedis jedis = new Jedis("192.168.174.132", 6379);

        //3 splicing key
        // 3.1 inventory key
        String kcKey = "sk:" + prodid + ":qt";
        // 3.2 second kill successful user key
        String userKey = "sk:" + prodid + ":user";

        //4. Get the inventory. If the inventory is null, the second kill has not started yet
        String kc = jedis.get(kcKey);
        if (kc == null) {
            System.out.println("The second kill hasn't started yet, please wait");
            jedis.close();
            return false;
        }

        // 5 judge whether the user repeats the second kill operation
        if (jedis.sismember(userKey, uid)) {
            System.out.println("The second kill has been successful. You can't repeat the second kill");
            jedis.close();
            return false;
        }

        //6. Judge if the commodity quantity and inventory quantity are less than 1, the second kill is over
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("The second kill is over");
            jedis.close();
            return false;
        }

        //7 second kill process
        //7.1 inventory-1
        jedis.decr(kcKey);
        //7.2 add successful users to the list
        jedis.sadd(userKey, uid);

        System.out.println("The second kill succeeded..");
        jedis.close();
        return true;
    }
}

SecKillServlet

package com.xiaoqiu;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Random;

/**
 * Second kill case
 */
public class SecKillServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String userid = new Random().nextInt(50000) +"" ;
		String prodid =request.getParameter("prodid");
		
		boolean isSuccess=SecKill_redis.doSecKill(userid,prodid);
		response.getWriter().print(isSuccess);
	}

}

test

2.Redis transaction - second kill concurrency simulation

Use tool ab to simulate the test
CentOS6 is installed by default
CentOS7 requires manual installation

2.1. Networking: Yum install httpd tools
2.2. No network
(1) Enter cd /run/media/root/CentOS 7 x86_64/Packages (the path is different from centos6)
(2) Sequential installation
apr-1.4.8-3.el7.x86_64.rpm
apr-util-1.5.2-6.el7.x86_64.rpm
httpd-tools-2.4.6-67.el7.centos.x86_64.rpm

2.3.1. Pass ab test
vim postfile simulates form submission parameters, ending with & symbol; Store the current directory.
Content: prodid = 0101&

ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://172.56.119.21:8080/Seckill/doseckill



2.3.2. Oversold

3. Oversold problem

4. Use optimistic lock to eliminate users and solve the oversold problem.


SecKill_redis

package com.atguigu;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

import java.io.IOException;
import java.util.List;

/**
 *
 */
public class SecKill_redis {

    //Second kill process
    public static boolean doSecKill(String uid, String prodid) throws IOException {
        //1. Judgment of uid and prodid non null
        if (uid == null || prodid == null) {
            return false;
        }

        //2. Connect to redis
       Jedis jedis = new Jedis("192.168.174.132", 6379);
       

        //3 splicing key
        // 3.1 inventory key
        String kcKey = "sk:" + prodid + ":qt";
        // 3.2 second kill successful user key
        String userKey = "sk:" + prodid + ":user";

        //Monitor inventory
        jedis.watch(kcKey);

        //4. Get the inventory. If the inventory is null, the second kill has not started yet
        String kc = jedis.get(kcKey);
        if (kc == null) {
            System.out.println("The second kill hasn't started yet, please wait");
            jedis.close();
            return false;
        }

        // 5 judge whether the user repeats the second kill operation
        if (jedis.sismember(userKey, uid)) {
            System.out.println("The second kill has been successful. You can't repeat the second kill");
            jedis.close();
            return false;
        }

        //6. Judge if the commodity quantity and inventory quantity are less than 1, the second kill is over
        if (Integer.parseInt(kc) <= 0) {
            System.out.println("The second kill is over");
            jedis.close();
            return false;
        }

        //7 second kill process
        //Use transaction
        Transaction multi = jedis.multi();

        //Team operation
        multi.decr(kcKey);
        multi.sadd(userKey,uid);

        //implement
        List<Object> results = multi.exec();

		if(results == null || results.size()==0) {
			System.out.println("The second kill failed....");
			jedis.close();
			return false;
		}
        System.out.println("The second kill succeeded..");
        jedis.close();
        return true;
    }
}



5. Continue to add concurrent tests

1. Limited connection

Add the - r parameter, - r Don't exit on socket receive errors

ab -n 2000 -c 200 -r -p ~/postfile -T application/x-www-form-urlencoded http://172.56.119.21:8080/Seckill/doseckill
2. It has run out of seconds, but there is still inventory
It's been seconds, but there's still inventory. The reason is that optimistic locks cause many requests to fail. The first one didn't arrive, and the second one may arrive.
3. The connection timeout is resolved through the connection pool
4. Connection pool
Save the consumption caused by each connection to the redis service, and reuse the connected instances. Manage the behavior of connections through parameters.

  • Link pool parameters
  • MaxTotal: controls how many jedis instances can be allocated to a pool, which can be obtained through pool.getResource(); If the value is - 1, it means no restriction; 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 a jedis instance is browsed. 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;

JedisPoolUtil

package com.xiaoqiu;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

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.174.132", 6379, 60000 );
				}
			}
		}
		return jedisPool;
	}

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

}

SecKill_redis

//2. Connect to redis
//Get jedis object through connection pool
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();

6. Solve the remaining inventory problems

6.1.LUA script

  • Lua is a small script language. Lua script can be easily called by C/C + + code or call C/C + + functions in turn. Lua does not provide a powerful library. A complete Lua interpreter is only 200k, so Lua is not suitable for developing independent applications, but as an embedded script language.
  • Many applications and games use LUA as their own embedded scripting language to achieve configurability and scalability.
  • This includes many game plug-ins or plug-ins such as Warcraft map, world of Warcraft, Bode's gate, angry birds and so on.
  • https://www.w3cschool.cn/lua/

6.2. Advantages of lua script in Redis

  • Write complex or multi-step redis operations into a script and submit them to redis for execution at one time to reduce the number of repeated connections to redis. Improve performance.
  • LUA scripts are similar to redis transactions. They have certain atomicity and will not be cut in line by other commands. They can complete some redis transactional operations.
  • Note that the lua script function of redis can only be used in redis version 2.6 or above.
  • Use lua script to eliminate users and solve the oversold problem.
  • After version 2.6 of redis, the contention problem is solved through lua script. In fact, redis uses its single thread feature to solve the multi task concurrency problem by means of task queue.

    SecKill_redisByScript
package com.xiaoqiu;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.joran.conditional.ElseAction;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

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

	public static void main(String[] args) {
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
 
		Jedis jedis=jedispool.getResource();
		System.out.println(jedis.ping());
		
		Set<HostAndPort> set=new HashSet<HostAndPort>();
		
	}
	
	static String secKillScript ="local userid=KEYS[1];\r\n" + 
			"local prodid=KEYS[2];\r\n" + 
			"local qtkey='sk:'..prodid..\":qt\";\r\n" + 
			"local usersKey='sk:'..prodid..\":usr\";\r\n" + 
			"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + 
			"if tonumber(userExists)==1 then \r\n" + 
			"   return 2;\r\n" + 
			"end\r\n" + 
			"local num= redis.call(\"get\" ,qtkey);\r\n" + 
			"if tonumber(num)<=0 then \r\n" + 
			"   return 0;\r\n" + 
			"else \r\n" + 
			"   redis.call(\"decr\",qtkey);\r\n" + 
			"   redis.call(\"sadd\",usersKey,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 boolean doSecKill(String uid,String prodid) throws IOException {

		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.getResource();

		 //String sha1=  .secKillScript;
		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!!");
		}else if("1".equals( reString )  )  {
			System.out.println("Rush purchase succeeded!!!!");
		}else if("2".equals( reString )  )  {
			System.err.println("The user has robbed!!");
		}else{
			System.err.println("Panic buying exception!!");
		}
		jedis.close();
		return true;
	}
}

SecKillServlet

public class SecKillServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String userid = new Random().nextInt(50000) +"" ;
		String prodid =request.getParameter("prodid");
		boolean isSuccess= SecKill_redisByScript.doSecKill(userid,prodid);
		response.getWriter().print(isSuccess);
	}

}




Tags: Linux network Redis server

Posted on Mon, 29 Nov 2021 17:46:18 -0500 by seoreferrals