Optimization ideas for high-concurrency spike scenarios and implementation of core functions based on redis

Optimization ideas for high-concurrency spike scenarios and implementation of core functions based on redis

Scene analysis

Before considering the detailed design, let's take a look at the spike scene and
discover some of the characteristics of the scene: System, data scene characteristics
1. Read more and less write: The effective operation in the spike scene is far smaller than the invalid operation, the so-called invalid operation That is, the relevant content in the spike scene was accessed, but there was no operation to facilitate the transaction in the end.
2. The burst of traffic flow in time has caused the system to respond slowly and even affect other business functions.
3. The "quantity" of the spike is robbed because of the large number of people robbed.
User behavior characteristics
1. As a Chinese aunt with Chinese characteristics, under the rush mentality of rushing to grab the goods, because the server is under heavy pressure and the response is slower than usual, how can she stop the continuous submission operation until the page has feedback? Don't talk about the aunt, we know the principle, sometimes we can't control our hands, and keep placing orders.
2. There is a "scalper party" in the arena with votes. As a ticket seckill, if the existence of the "scalper party" is missing, it seems that something is missing. The "scalper party" often uses software and scripts to automatically grab tickets, and the software encapsulates scene-related data packets to simulate normal user operations to place orders. Therefore, it not only does not rely on front-end page interaction, but can also initiate a large number of threads in a short period of time to simulate order data package submission.

System level analysis

A simple system includes these layers: 1. Page layer site layer control layer service layer database layer. The control layer in a complex system may also include gateways, service centers, and complex service layers and other components. But no matter what the structure is, the performance bottleneck is at the database layer. Therefore, reducing the operation of the database layer is the core issue of the realization of the entire scene. In order to reduce the server pressure and the impact on other businesses, we try to intercept invalid traffic at the upper layer, and at the same time reduce the performance consumption of each layer as much as possible.

Layer by layer

Based on the above analysis, let's take a look at each layer what we can do to reduce the traffic of the lower layer.
1. Page layer The
page layer is the entrance for normal users (non-scalper party) to visit. Remember the "dot dots" scene in user behavior characteristics? Yes, this scenario will further increase our invalid traffic, especially when the server pressure increases and the response slows down, the invalid traffic increases. To solve this problem is also very easy, add some JS code to the page layer to control multiple submissions within a certain period of time or before the completion of this submission response. Simple, easy and effective, a small piece of JS code may reduce invalid traffic by several times when the server response is slow. Since the implementation is very simple, the code is not posted here.
2. Site level When
a request comes, the site level will process it first. Analyzing the content of the request, it is not difficult to find that in addition to dynamic resources (resources generated by reading data from the background), there are also static resources (CSS, JS, image curing resources, etc.). And if a single server responds uniformly, it will undoubtedly increase the pressure on the server. In particular, a page with simple functions may not account for less static resources than dynamic resources. Therefore, separating static resources will become a good way to reduce server pressure. There are two common ways of separating:
one is the commonly used page cache. Through the localized caching of static resources, the server access to static files is reduced when the page is repeatedly accessed.
If apache, nginx, etc. are used in the project, they can be used to separate static files.
In addition, the site layer can also play other tricks to further reduce traffic:
If the static resources are too large, compression technology can be used to reduce network IO under high concurrency.
If there are too many static resources, you can use static resource packaging to reduce the amount of page requests.
For systems with a large user base, methods such as CDN traffic diversion can also be adopted to reduce the centralization of requests and slow response.
For the "scalper party", the current limit can also be achieved in some scenarios. For example, by extending a custom plug-in by nginx, you can limit the frequency of a certain IP or a certain USER_ID to access the same web page. However, this method can only solve some primary "scalpers", and professional "scalpers" will have their own IP proxy pool and multiple accounts to bypass such restrictions.
When apache and nginx are used as proxies, they can also be combined with the load balancing function they provide to optimize the internal access load.
For large-scale systems, there may be a gateway at the software level, and some strategies can also be used to optimize access to serve as a decompression server.
3. Service layer
1. Core realization: The
service layer must first ensure the correctness of the data, neither overstock, or the red envelope grabbing scenario cannot exceed the total amount of red envelopes, etc. There are two ways of solutions:
(1) simple and crude database concurrency optimistic locking in the case where not:
Update Product amout = amout SET - #amout WHERE ID = #id and amout - #amout> = 0;
this The writing method is to use the optimistic locking mechanism provided by the database itself, and the database guarantees that no less than 0 during the amout modification process. The advantages of this method are simplicity and the strongest reliability (because the database guarantees its reliability). The disadvantages are also obvious, and the amount of concurrency is limited. Generally speaking, in a server with better performance, if it is a high-speed mechanical hard disk, the maximum database concurrency can reach about 300, and the solid state hard disk can reach about 700.
(2) High-concurrency distributed optimistic locking mechanism (CAS mechanism check and set):
When the amount of concurrency greatly exceeds the limit of the database, it is necessary to refer to the distributed cache to solve the problem. Commonly used caches include memcached and Redis. If you only look at the spike scenario, memcached is more suitable than Redis mainly because:
A. Memcache is more suitable from the spike scene, because its model is multi-threaded, which is more suitable for high-concurrency writing scenes. Redis's model is single-threaded, and reading and writing will be relatively slow when there is a large amount of concurrency.
B. Memcache comes with CAS halo, which can be directly called by related functions; although redis has a way to implement it, it still needs to be implemented by itself.
Overall scheme diagram: https://www.processon.com/view/5a2e0435e4b0d8b7bf78d9f2
cas schematic diagram:

Since the cas mechanism may need multiple cycles to try to write the deducted inventory back to redis, when the code is implemented, one more time is added to determine whether the inventory obtained from redis is 0. That is to say, the first judgment of whether the inventory is overstock can be the inventory quantity in another redis instance, and the second judgment whether the overstock is in another redis instance, and it is included in the realization of the cas mechanism. Because they are not in a redis, a thread is needed to synchronize the judgment when the inventory of redis participating in the cas mechanism is 0, and to synchronize to the inventory of redis not participating in the cas mechanism. In this way, the first inventory that does not participate in the cas mechanism is directly limited to achieve the purpose of hotspot isolation. (See the code below for details. Synchronization thread is not implemented, if you need to implement it yourself )
2. Other optimizations
If there are already some components in the project that support current limiting and degradation, such as dubbo, hystrix, etc., you do not need the above methods in the code Control current limit.
In the actual situation, if the request volume is too large and it is much larger than the inventory, you can also process too many requests to reduce the traffic. The processing methods can include: batch release, quantitative release, and random discard requests (reserved requests) The quantity needs to be greater than the inventory quantity) and so on.
3. Optimization of the scalper party.
For some senior scalper parties that bypass the front-end and directly use requests to swipe their votes, traditional methods cannot be well prevented. If you must prevent them, one of the more mainstream methods is through encryption or obfuscation. , So that the scalpers cannot access the back-end resources correctly after bypassing the front-end. But this method hurts the enemy 1,000 by 800 and hurts itself by 800, which is relatively expensive.

Implementation code:

package com.my.miaosa.service.impl;

import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.my.miaosa.entity.dto.FastBuyBusinessDTO;
import com.my.miaosa.entity.dto.Order;
import com.my.miaosa.service.FastBuyService;

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

@Service
public class FastBuyServiceImpl implements FastBuyService {
	private static Logger logger = LoggerFactory.getLogger("");

	/**
	 * 
	 * @param jedis	 redis jedisMain 
	 * @param jedisMain  redis 
	 * @param fastBuyBusinessDTO  
	 * @param orderUserName  ID 
	 * @param orders  
	 * @return
	 */
	public String fastBuyProductOrders(Jedis jedis,Jedis jedisMain,FastBuyBusinessDTO fastBuyBusinessDTO, String orderUserName,int orders) {
		//redis redis 
		//
		if (!this.allowProductAmout(fastBuyBusinessDTO.getMaxAmout(), orders)) {
			return " ";
		}
		
		//redis 
		if(!this.allowFastBuyProduc(fastBuyBusinessDTO.getProductAmoutId(), orders, jedis)) {
			return " ";
		}
		
		return this.fastBuyProduct(jedisMain, fastBuyBusinessDTO.getProductAmoutId(), fastBuyBusinessDTO.getOrderListId(), orderUserName, orders, fastBuyBusinessDTO.getProductId());
		
		
	}
	
	/**
	 *  
	 * return  
	 */
	public boolean allowFastBuyProduc(String productAmoutId,int orders,Jedis jedis) {
		
		//REDIS 
		//
		//<=0 
		int prdNum = Integer.parseInt(jedis.get(productAmoutId));
		
		//
		return this.allowProductAmout(prdNum, orders);
	}
	
	/**
	 * 
	 * @param jedis
	 * @param amoutId redis KEY
	 * @param ordersId redis KEY
	 * @param orders  
	 * @param orderUserName  
	 * @param productId  ID
	 * @return
	 */
	public String fastBuyProduct(Jedis jedis, String amoutId,String ordersId, String orderUserName,int orders,String productId){
		String result = " ";
		if (logger.isInfoEnabled()) {
			logger.info(orderUserName + " -" + orders + "  ");
		}
		
		//TODO  for 
		while (true) {
			int i = 0; i++;
			if (logger.isInfoEnabled()) {
				logger.info(orderUserName + "--- " + i + " ");
			}
			try {
				jedis.watch(amoutId,ordersId);// key  key 
				int prdNum = Integer.parseInt(jedis.get(amoutId));
				if (this.allowProductAmout(prdNum, orders)) {
					//TODO 1 
					//TODO 2 
					
					Transaction transaction = jedis.multi();
					transaction.set(amoutId, String.valueOf(prdNum - orders));//
					// sadd smembers CAS scard
					//JSON 
					transaction.sadd(ordersId, this.createOrdersString(orderUserName, productId, orders));
					List<Object> res = transaction.exec();

					// null key 
					if (res == null || res.isEmpty()) {
						if (logger.isInfoEnabled()) {
							logger.info( orderUserName + "--- " + i  + " ----- ");
						}
					} else {
						result = " ";
						if (logger.isInfoEnabled()) {
							logger.info( orderUserName + "--- " + i  + " ----- -");
						}
						break;
					}
					
				} else {
					result = " 0 88";
					break;
				}
			} catch (Exception e) {
				logger.error(" " + e);
			} finally {
				jedis.unwatch();
				
			}
		}
		
		return result;
	}
	
	
	/**
	 *  
	 * @return  true false
	 */
	private boolean allowProductAmout(int prdNum,int orders) {
		
		//
		if (prdNum <= 0) {return false;}
		//
		if (prdNum - orders < 0) {return false;}
		
		return true;
	}
	
	/**
	 *  JSON 
	 * @param orderUserName
	 * @param productId
	 * @param orderCount
	 * @return
	 */
	public String[] createOrdersString (String orderUserName,String productId,int orderCount) {
		String[] result = new String[orderCount];
		for (int i = 0; i < result.length; i++) {
			result[i] = JSON.toJSONString(new Order(orderUserName,productId,UUID.randomUUID().toString()));
		}
		
		return result;
	}
	
	
}
 
package com.my.miaosa.entity.dto;

import java.io.Serializable;

public class FastBuyBusinessDTO implements Serializable {
	
	private String id;//

	private String productId;//

	private String productAmoutId;//ID   String
	
	private String orderListId;//  
	
	
	//
	private int maxTransaction;//
	
	private int maxTransactionNumber;//
	
	private int maxRepeatBuy;//0. 0 3 3
	
	private int maxAmout;//

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getProductId() {
		return productId;
	}

	public void setProductId(String productId) {
		this.productId = productId;
	}

	public String getProductAmoutId() {
		return productAmoutId;
	}

	public void setProductAmoutId(String productAmoutId) {
		this.productAmoutId = productAmoutId;
	}

	public String getOrderListId() {
		return orderListId;
	}

	public void setOrderListId(String orderListId) {
		this.orderListId = orderListId;
	}

	public int getMaxTransaction() {
		return maxTransaction;
	}

	public void setMaxTransaction(int maxTransaction) {
		this.maxTransaction = maxTransaction;
	}

	public int getMaxTransactionNumber() {
		return maxTransactionNumber;
	}

	public void setMaxTransactionNumber(int maxTransactionNumber) {
		this.maxTransactionNumber = maxTransactionNumber;
	}

	public int getMaxRepeatBuy() {
		return maxRepeatBuy;
	}

	public void setMaxRepeatBuy(int maxRepeatBuy) {
		this.maxRepeatBuy = maxRepeatBuy;
	}

	public int getMaxAmout() {
		return maxAmout;
	}

	public void setMaxAmout(int maxAmout) {
		this.maxAmout = maxAmout;
	}
	
	

}

 
package com.my.miaosa.entity.dto;

import java.text.SimpleDateFormat;

/**
 *  redis 
 * @ClassName Order
 * @Description 
 * @author Administrator
 * @date 2017 12 12   2:05:40
 * @version
 *
 */
public class Order {

	private String oun;//orderUserName ID 
	
	private String opn;//orderProductName ID 

	private String time;
	
	/**
	 *  ID ID 
	 */
	private String id;//ID sadd UUID IO 
	
	//fastjson 
	public Order() {
		
	}
	
	public Order(String orderUserName,String orderProductName,String currectId) {
		this.oun = orderUserName;
		this.opn = orderProductName;
		this.id = currectId;
		this.time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
	}
	
	public String getOun() {
		return oun;
	}

	public void setOun(String oun) {
		this.oun = oun;
	}

	public String getOpn() {
		return opn;
	}

	public void setOpn(String opn) {
		this.opn = opn;
	}

	public String getTime() {
		return time;
	}

	public void setTime(String time) {
		this.time = time;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
}

 

Test class

package com.my.miaosa.test;

import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;

import com.alibaba.fastjson.JSON;
import com.my.miaosa.entity.dto.FastBuyBusinessDTO;
import com.my.miaosa.entity.dto.Order;
import com.my.miaosa.service.FastBuyService;
import com.my.miaosa.service.impl.FastBuyServiceImpl;

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

@ContextConfiguration("classpath:applicationContext.xml")
public class FastBuyTest {
	
	private static Jedis jedis1;
	private static Jedis jedis2;
	private static JedisPool pool;
	private static JedisPoolConfig config;
	
	//
	private static FastBuyBusinessDTO fastBuyBusinessDTO;
	private final static int MAX_AMOUT = 10;//
	
	//
	private static CountDownLatch latch;
	//
	private final static int THREAD_LENG = 200;
	
	@Before
	public void init() {
		initRedisPool();//redis 
		fastBuyBusinessDTO = new FastBuyBusinessDTO();
		initFastBuy();//
		
	}
	
	@After
	public void colseResources() {
		jedis1.close();
		pool.close();
	}
	
	/**
	 *  1 1000 
	 * @throws InterruptedException
	 */
	@Test
	public void fastBuyProductTest() throws InterruptedException {
		System.out.println(" " + jedis1.get(fastBuyBusinessDTO.getProductAmoutId()));
		System.out.println(" " + jedis1.scard(fastBuyBusinessDTO.getOrderListId()));
		
		for(int i =0;i<THREAD_LENG + 1;i++){
			Thread.sleep(20L);
			String orderUserName = "orderUserName_" + i;//
//			String orderUserName = "orderUserName_" + "0";//
			int orders = 3;
			int ordersRandom =  (int)(1 + Math.random()*(4-1 + 1));
			Thread th = new Thread(new TestThread(pool,orderUserName,ordersRandom));
			th.setName("THREADDDDD_"+i);
			System.out.println(th.getName()+"inited...");
			th.start();
			latch.countDown();//
		}
		Thread.sleep(3000);
		
		//order 
		if (true) {
			Set<String> orders = jedis1.smembers(fastBuyBusinessDTO.getOrderListId());
			
			Iterator<String> it = orders.iterator();  
			while (it.hasNext()) {
			  Order order = (Order)JSON.parseObject(it.next(), Order.class);
			  System.out.println("userid:" + order.getOun() + "-----productId:" + order.getOpn()  + "------orderTime:" + order.getTime() );
			}
		}
		
		System.out.println(" " + jedis1.get(fastBuyBusinessDTO.getProductAmoutId()));
		System.out.println(" " + jedis1.scard(fastBuyBusinessDTO.getOrderListId()));
	}
	
	/**
	 *  
	 */
	public static void initFastBuy() {
		fastBuyBusinessDTO.setId("1");
		fastBuyBusinessDTO.setProductAmoutId("product_amout_id_1");
		fastBuyBusinessDTO.setProductId("product_id_1");
		fastBuyBusinessDTO.setOrderListId("order_list_id_1");
		fastBuyBusinessDTO.setMaxAmout(MAX_AMOUT);
		
		String key = fastBuyBusinessDTO.getProductAmoutId();
		String clientList = fastBuyBusinessDTO.getOrderListId();// 
		if (jedis1.exists(key)) {
			jedis1.del(key);
		}
		
		if (jedis1.exists(clientList)) {
			jedis1.del(clientList);
		}
		jedis1.set(key, String.valueOf(MAX_AMOUT));// 
	}
	
	public static void initRedisPool() {
		jedis1 = new Jedis("127.0.0.1",6379);
		jedis2 = new Jedis("127.0.0.1",6379);
		//Redis 
        config = new JedisPoolConfig();
        config.setMaxIdle(10);
        config.setMaxWaitMillis(1000);
        config.setMaxTotal(THREAD_LENG + 1);
        pool = new JedisPool(config, "127.0.0.1", 6379);
       //ExecutorService  
//       service = Executors.newFixedThreadPool(THREAD_LENG);
       //CountDownLatch 
        latch = new CountDownLatch(THREAD_LENG);
	}
	
	public static class TestThread implements Runnable {
		private Jedis cli;
		private JedisPool pool;
		private FastBuyService fs = new FastBuyServiceImpl();
		private String orderUserName;
		private int orders;
		
		public TestThread(JedisPool pool,String orderUserName,int orders) {
			cli = pool.getResource();
            this.pool = pool;
            this.orderUserName = orderUserName;
            this.orders = orders;
		}
		
		public TestThread(Jedis jedis,String orderUserName,int orders) {
			cli = jedis;
			this.orderUserName = orderUserName;
	        this.orders = orders;
		}

		public void run() {
			try{
				latch.await();
				fs.fastBuyProductOrders(cli, cli, fastBuyBusinessDTO,orderUserName , orders);
            }catch(Exception e){
                pool.close();
            }
		}
	}
}

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.my</groupId>
	<artifactId>miaosa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>

		<!-- spring  -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>

		<!-- config jedis data and client jar -->
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-redis</artifactId>
			<version>1.7.1.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.7.2</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.21</version>
		</dependency>

		<!--   -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.3.2.RELEASE</version>
			<scope>test</scope>
		</dependency>

		<!--   -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.10</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.1.2</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>1.1.2</version>
		</dependency>

	</dependencies>


</project>
 

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:redisson="http://redisson.org/schema/redisson"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
		                http://www.springframework.org/schema/context 
		                http://www.springframework.org/schema/context/spring-context-4.3.xsd
		                http://www.springframework.org/schema/cache
                        http://www.springframework.org/schema/cache/spring-cache.xsd
                        http://www.springframework.org/schema/tx 
          				http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
          				http://redisson.org/schema/redisson
          				http://redisson.org/schema/redisson/redisson.xsd">

                        

	<context:property-placeholder location="classpath:db.properties"
		ignore-unresolvable="true"/>

	
	<context:component-scan base-package="com.*">
	</context:component-scan>
	
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
		<property name="maxTotal" value="100"/>
		<property name="maxIdle" value="10"/>
	</bean>

	<bean id="jedisConnectionFactory"
		class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
		destroy-method="destroy">
		<property name="hostName" value="localhost"/>
		<property name="port" value="6379"/>
		<property name="database" value="2"/>
		<property name="timeout" value="3000"/>
		<property name="usePool" value="true"/>
		<property name="poolConfig" ref="jedisPoolConfig"/>
	</bean>

	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory"/>
		<property name="keySerializer">
			<bean
				class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
		</property>
		<property name="valueSerializer">
			<bean
				class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
		</property>
		
		<property name="enableTransactionSupport" value="false"></property>
	</bean>
	
</beans>