SpringBoot learning record (xiv) -------- Spring Boot Redis integrated development: high concurrent simulation spike activity

Business Process spike

The user clicks the merchandise list page of merchandise into the product details page, if the spike is successful, the jump order details page. Instant concurrency very large systems are possible problems, bottlenecks in the database (plus cache, asynchronous database technology to reduce pressure and prevent direct penetration into the database)

Spike architecture design

Limiting: Given that only a small number of users to spike success, so most of the traffic restrictions, allowing only a small part of the flow into the back-end service.
Clipping: spike system for instantaneous influx of large numbers of users have, so start there will be a rush to buy high instantaneous peak. Peak flow system is overwhelmed very important reason, so how high flow instantly becomes smooth flow over time is also very important design ideas spike system. A method implemented using a conventional buffer are clipped and messaging middleware technology.
Asynchronous processing: spike system is a highly concurrent systems, the use of asynchronous processing mode can greatly improve system concurrency, in fact, is an implementation of asynchronous processing of clipping.
Memory cache: the biggest bottleneck in the system are generally spike database read and write, because the database disk read and write belongs to IO, low performance, if we can put some data or business logic into the cache memory, the efficiency will be greatly improved.
May expand: Of course, if we want to support more users and greater concurrency, it will be the best system is designed to elastically expand, if traffic comes, expand the machine just fine. It will increase when a large number of machines to deal with high transaction as Taobao, Jingdong double eleven activities.

Spike system architecture design ideas

The request interceptor system in the upstream, downstream pressure lowering: spike system is characterized by a great amount of concurrency, but the actual spike in the number of requests are rarely successful, so if the interception is not likely to cause the front end database to read and write locks conflict, final request timed out.
Using caching: the use of caching can greatly increase the speed of read and write system.
Message queues: a message queue can be peak clipping, will intercept a large number of concurrent requests, this is an asynchronous process, according to their background traffic handling capacity from the message queue of the active pull service processing request message.

Testing Tutorial

first step:

Create test application Redis springboot-redis, Redis introduction of dependencies, pom.xml configuration information is as follows:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.4.3</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
</dependencies>

Because there is need to introduce three ways to solve the problem spike oversold, so the introduction of three Redis dependency.

Step two:

Application.yml modify the project configuration file, main configuration and application information redis service information, configuration is as follows:

server:
  port: 8908

spring:
  application:
    name: springboot-redis
  jackson:
    # 指定时间格式
    date-format: 'yyyy-MM-dd HH:mm:ss'
    # 排除结果中属性值是 null 的属性
    default-property-inclusion: non_null
  redis:
    database: 0 #Redis数据库索引(默认为0)
    host: 127.0.0.1 #Redis服务器地址
    port: 6379 #Redis服务器连接端口
    password: # Redis服务器连接密码(默认为空)
    timeout: 5000 #连接超时时间(毫秒)
    jedis:
      pool:
        max-active: 8 #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 #连接池中的最大空闲连接
        min-idle: 0 #连接池中的最小空闲连接

third step:

Creating Redisson configuration class, the code is as follows:

package com.test.redis.config;

import io.netty.channel.nio.NioEventLoopGroup;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;

@Configuration
public class RedissonConfig {

    private String address = "redis://127.0.0.1:6379";
    private int connectionMinimumIdleSize = 10;
    private int idleConnectionTimeout = 10000;
    private int pingTimeout = 1000;
    private int connectTimeout = 10000;
    private int timeout = 5000;
    private int retryAttempts = 3;
    private int retryInterval = 1500;
    private int reconnectionTimeout = 3000;
    private int failedAttempts = 3;
    private String password = null;
    private int subscriptionsPerConnection = 5;
    private String clientName = null;
    private int subscriptionConnectionMinimumIdleSize = 1;
    private int subscriptionConnectionPoolSize = 50;
    private int connectionPoolSize = 64;
    private int database = 1;
    private boolean dnsMonitoring = false;
    private int dnsMonitoringInterval = 5000;

    private int thread; //当前处理核数量 * 2

    private String codec = "org.redisson.codec.JsonJacksonCodec";

    @Bean(destroyMethod = "shutdown")
    RedissonClient redisson() throws Exception {
        Config config = new Config();
        config.useSingleServer().setAddress(address)
                .setConnectionMinimumIdleSize(connectionMinimumIdleSize)
                .setConnectionPoolSize(connectionPoolSize)
                .setDatabase(database)
                .setDnsMonitoring(dnsMonitoring)
                .setDnsMonitoringInterval(dnsMonitoringInterval)
                .setSubscriptionConnectionMinimumIdleSize(subscriptionConnectionMinimumIdleSize)
                .setSubscriptionConnectionPoolSize(subscriptionConnectionPoolSize)
                .setSubscriptionsPerConnection(subscriptionsPerConnection)
                .setClientName(clientName)
                .setFailedAttempts(failedAttempts)
                .setRetryAttempts(retryAttempts)
                .setRetryInterval(retryInterval)
                .setReconnectionTimeout(reconnectionTimeout)
                .setTimeout(timeout)
                .setConnectTimeout(connectTimeout)
                .setIdleConnectionTimeout(idleConnectionTimeout)
                .setPingTimeout(pingTimeout)
                .setPassword(password);
        Codec codec = (Codec) ClassUtils.forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance();
        config.setCodec(codec);
        config.setThreads(thread);
        config.setEventLoopGroup(new NioEventLoopGroup());
        config.setUseLinuxNativeEpoll(false);
        return Redisson.create(config);
    }
}

the fourth step:

Create a service interface spike, spike processing method includes, as follows:

package com.test.redis.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@RestController
@RequestMapping("/api")
public class SeckillController {

    @Resource(name = "stringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;
    /**
     * 购买成功数量
     */
    private AtomicInteger sellCount = new AtomicInteger(0);

    /**
     * 初始化商品库存数量
     * @return
     */
    @GetMapping("/initcount")
    public String initcount() {
        stringRedisTemplate.opsForValue().set("product_count", "5");
        sellCount.set(0);
        return "初始化库存成功";
    }

    /**
     * 加入事务的减少库存方式
     * @return
     */
    @GetMapping("/sell1")
    public String sell1() {
        stringRedisTemplate.setEnableTransactionSupport(true);
        List<Object> results = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.watch("product_count");
                String product_count = (String) operations.opsForValue().get("product_count");
                operations.multi();
                operations.opsForValue().get("product_count");
                Integer productCount = Integer.parseInt(product_count);
                productCount = productCount - 1;
                if (productCount < 0) {
                    return null;
                }
                operations.opsForValue().set("product_count", productCount.toString());
                return operations.exec();
            }
        });

        if (results != null && results.size() > 0) {
            return "减少库存成功,共减少" + sellCount.incrementAndGet();
        }
        return "库存不足";
    }


    /**
     * 直接用jredis加入事务的减少库存方式
     * @return
     */
    @GetMapping("/sell2")
    public String reduceSku3() {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        List<Object> result;
        Transaction transaction = null;
        try {
            jedis.watch("product_count");
            int product_count = Integer.parseInt(jedis.get("product_count"));
            if (product_count > 0) {
                transaction = jedis.multi();
                transaction.set("product_count", String.valueOf(product_count - 1));
                result = transaction.exec();
                if (result == null || result.isEmpty()) {
                    log.error("Transaction error...");  //可能是watch-key被外部修改,或者是数据操作被驳回
                    //transaction.discard();  //watch-key被外部修改时,discard操作会被自动触发
                    return "Transaction error...";
                }
            } else {
                return "库存不足";
            }
            return "减少库存成功,共减少" + sellCount.incrementAndGet();
        } catch (Exception e) {
            log.error(e.getMessage());
            transaction.discard();
            return "fail";
        }
    }

    /**
     * 通过加锁方式减少库存方式
     * @return
     */
    @GetMapping("/sell3")
    public String sell3() {
        RLock rLock = redissonClient.getLock("product_count");
        try {
            rLock.lock();
            Integer product_count = Integer.parseInt(stringRedisTemplate.opsForValue().get("product_count"));
            product_count = product_count - 1;
            if (product_count < 0) {
                return "库存不足";
            }
            stringRedisTemplate.opsForValue().set("product_count", product_count.toString());
            return "减少库存成功,共减少" + sellCount.incrementAndGet();
        } finally {
            rLock.unlock();
        }
    }

    /**
     * 销售成功的数量
     * @return
     */
    @GetMapping("/sellcount")
    public String sellcount() {
        return "成功抢到的商品数量:" + sellCount.get();
    }

}

Code analysis:

First initialize the number of commodity stocks

There are three methods to prevent the sale of goods oversold

Read a number of sales success

Use redisTemplate enforcement branch spring, you need to execute a query redis of (non-real value) after opening the transaction because:

spring exec to redis transaction () method returns the result to do a deal with (the return value of the OK result deleted).

So that only the set such as update operations, the transaction fails and results in the successful return of the same transaction.

Redis query during a transaction value back only after the implementation of successful transactions. And in the course of the transaction will return null

the fifth step:

Then start the application and Redis service, the application starts successfully open a browser to access the service interface in the following order:

Commodity interface initialization: http: //127.0.0.1: 8908 / api / initcount

Reduce inventory number (Redis Affairs): http: //127.0.0.1: 8908 / api / sell1

Reduce inventory number (Jredis plus Affairs): http: //127.0.0.1: 8908 / api / sell2

Reduce inventory number (Redisson lock): http: //127.0.0.1: 8908 / api / sell3

Quantity sales success: http: //127.0.0.1: 8908 / api / sellcount

Interface to access data return sequence shown below:

Here Insert Picture DescriptionHere Insert Picture DescriptionHere Insert Picture DescriptionHere Insert Picture Description
Here Insert Picture Description
Open Redis desktop management software, view product inventory information, you can see the merchandise inventory normal, as shown below:
Here Insert Picture Description

These are today as we explain the highly concurrent spike activity test, to solve the problem in three ways oversold commodity, to simulate the 1000 request by Jmeter test software, the final test result merchandise inventory number is 0, the success of sales 5, We can see three kinds of methods can prevent oversold.
Reprinted Address: https://mp.weixin.qq.com/s/o_C7JHi78qmKRZuYjdImew

Published 47 original articles · won praise 7 · views 7013

Guess you like

Origin blog.csdn.net/GaoXiR/article/details/104416507