SpringBoot integration with Redisson

1.1 Viewing Maven Dependencies

<! -- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson-spring-boot-starter</artifactId>
	<version>3.15. 0</version>
</dependency>
Copy the code

1.2 SpringBoot container injects Redisson

package com.yaodao.decorationdesign.config;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/ * * *@Author: YaoDao
 * @Package:
 * @Description: Use Redisson to implement distributed locking@Date: 2021/2/1 15:26 * /
@Configuration
public class RedissonConfig {
    // Use the configuration in properties
    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String pwd;


    @Bean
    public Redisson redisson(a){
        // This is single-machine mode -SingleServer
        Config config = new Config();
        config.useSingleServer().setAddress("redis://"+host+":"+port).setDatabase(0).setPassword(pwd);
        return(Redisson)Redisson.create(config); }}Copy the code

Second, use Redisson tool in business

Just three lines of code:

//1. Obtain the lock object
RLock redissonLock = redisson.getLock(lockKey);
/ / 2. Lock
redissonLock.lock();
/ / 3. Release the lock
redissonLock.unlock();
Copy the code

Specific code:

@Autowired
private Redisson redisson;

@PostMapping("/reduceStockByRedisson")
@ResponseBody
public Result reduceStockByRedisson(Integer id) throws Exception{
    String lockKey = "lockKey";

    //1. Obtain the lock object
    RLock redissonLock = redisson.getLock(lockKey);
    try{
        //2. RedissonLock
        redissonLock.lock();

        // The lock was successfully locked, then the business logic is executed
        log.info("Locking with Redisson succeeded! Execute business code =>");
        lockTestService.reduceStock(id);
        return Result.ok().Message("Purchase successful!");
    }catch (Exception e){
        throw e;
    }
    finally {
        / / 3. Release the lock
        redissonLock.unlock();
        log.info("<= business code completed, release current lock!"); }}Copy the code

Third, experiment, using JMeter to achieve high concurrent access scenarios

Simulation scenario, there are 20 users at the same time to the background service to place an order request, and 20 requests through nginx forward to two background services — to achieve distributed, the current inventory is 15, under normal circumstances, when the user places an order, the inventory is reduced by one, order number plus one, until the inventory is 0; When all requests are processed, the inventory in the database is 0, and the order number is 15, the experiment succeeds; otherwise, the experiment fails.

3.1 Experimental preparation stage

Hardware and Software

Windows, cloud server

IDEA+Java, Nginx, JMeter

Mysql, Redis (cloud server), of course, can be set in the local WIN

Nginx configures port 80 to forward port 8085 and port 8086, and enables nginx

Nginx configuration

Upstream app{server localhost:8085; server localhost:8086; } server { listen 80; server_name localhost; location / { root html; index index.html index.htm; proxy_pass http://app/; }Copy the code

Idea runs two service classes, on ports 8085 and 8086

Mysql > select * from ‘mysql’;

Mysql > select * from ‘mysql’;

Business (Place order) code:

/ * * *@Author: YaoDao
 * @Package:
 * @Description:
 * @Date: 2021/1/28 15:42 * /
@Service
@Slf4j
public class LockTestServiceImp implements LockTestService {
    @Autowired
    private LockTestMapper mapper;

    /** * create by: YaoDao * description: * create time: 2021/1/28 15:45 *@Param: Product ID *@return* /
    @Transactional
    public void reduceStock(Integer id){
        //1. Get inventory
        LockTestProduct product = mapper.getProduct(id);

        // Simulate time-consuming services
        sleep(1000);

        if(product.getNumber()<=0){
            log.info("Purchased items out of stock!");
            throw new RuntimeException("Purchased items out of stock!");
        }

        //2. Reduce inventory if available
        if(mapper.updateProduct(id)>0){
            LockTestOrder order = new LockTestOrder();
            order.setUserId("YaoDao");
            order.setPid(id);
            mapper.insertOrder(order);
            log.info("Successful order! , product information: {}, order information: {}",product,order);
        }else {
            log.info("Inventory reduction operation failed!);
            throw new RuntimeException("Inventory reduction operation failed!); }}/** * create by: YaoDao * description: Simulate time-consuming services * create time: 2021/1/28 15:55 *@Param: null
     * @return* /
    private void sleep(int i) {
        long time = Long.parseLong(String.valueOf(i));
        try{
            Thread.sleep(time);
        }catch(InterruptedException e){ e.printStackTrace(); }}}Copy the code

Mapper code:

interface

package com.yaodao.decorationdesign.dao.Test;

import com.yaodao.decorationdesign.object.entity.Test.LockTest.LockTestOrder;
import com.yaodao.decorationdesign.object.entity.Test.LockTest.LockTestProduct;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;


/ * * *@Author: YaoDao
 * @Package:
 * @Description: This interface is used to operate the lock_test_product and lock_TEST_ORDER tables in the database * is used to experiment how to process the information in the database under the high concurrency table stock_table as an example * Simulation scenario under the high concurrency of goods buying *@Date: 2021/1/28 15:05
 */
@Repository
@Mapper
public interface LockTestMapper {
    /** * create by: YaoDao * description: Get product info * create time: 2021/1/28 15:10 *@Param: null
     * @return* /
    LockTestProduct getProduct(@Param("id") Integer id);


    /** * Create by: HaoNan * description: Subtract 1 from the original base inventory * Create time: 2021/1/28 15:37 *@Param: null
     * @return* /
    int updateProduct(@Param("id") Integer id);


    /** * create by: YaoDao * description: create order * create time: 2021/1/28 15:36 *@Param: null
     * @return* /
    @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
    @Insert("insert into lock_test_order(pid,user_id) values(#{pid},#{userId})")
    int insertOrder(LockTestOrder order);
}
Copy the code

xml

<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE mapper PUBLIC"- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yaodao.decorationdesign.dao.Test.LockTestMapper">
    <update id="updateProduct">
        update lock_test_product set number = number-1 where id = #{id}
    </update>

    <select id="getProduct" resultType="com.yaodao.decorationdesign.object.entity.Test.LockTest.LockTestProduct">
        select * from lock_test_product where id = #{id}
    </select>
</mapper>
Copy the code

3.2 Redis lock experiment with defects

The experimental code

/ * * *@Author: YaoDao
 * @Package: Simulate high concurrent destocking entry *@Description:
 * @Date: 2021/1/28 15:56 * /
@RestController
@RequestMapping("/test/lockTest")
@Slf4j
public class LockTestController {
    @Autowired
    private LockTestService lockTestService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @PostMapping("/reduceStockByRedisLock")
    @ResponseBody
    public Result reduceStockByRedisLock(Integer id) throws Exception{
        String lockKey = "lockKey";
        String threadId = UUID.randomUUID().toString();

        try{
            // Use the setIfAbsent method. If the key is absent, set with a key and return True. If the Key is present, return False;
            //Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"redisLock");
            // If the business code fails after the lock is acquired, the lock will remain in place. To avoid deadlocks, add an expiration date to the lock.
            //stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

            // Convert the above two lines of code into an atomic instruction
            Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,threadId,10, TimeUnit.SECONDS);

            if(! isLocked){// Lock failed
                throw new RuntimeException("System busy please try again later!");
            }

            // The lock was successfully locked, then the business logic is executed
            log.info("Lock successful! Execute business code =>");
            lockTestService.reduceStock(id);
            return Result.ok().Message("Purchase successful!");
        }catch (Exception e){
            throw e;
        }
        finally {
            if (threadId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                // Delete only the locks you set
                stringRedisTemplate.delete(lockKey);
                log.info("<= business code completed, release current thread lock!"+threadId); }}}}Copy the code

JMeter requests

Set 10 threads

JMeter requests results and console logs

Although there was no oversold problem, only one thread acquired the lock and successfully executed the business because there was no loop request to obtain the lock. Of course, we can add a while loop to keep asking for the lock, so we don’t experiment here.

3.3 Using Redisson Locks

code

package com.yaodao.decorationdesign.controller.Test;

import com.yaodao.decorationdesign.service.Test.LockTestService;
import com.yaodao.decorationdesign.util.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/ * * *@Author: YaoDao
 * @Package: Simulate high concurrent destocking entry *@Description:
 * @Date: 2021/1/28 15:56 * /
@RestController
@RequestMapping("/test/lockTest")
@Slf4j
public class LockTestController {
    @Autowired
    private LockTestService lockTestService;
    
    @Autowired
    private Redisson redisson;


    @PostMapping("/reduceStockByRedisson")
    @ResponseBody
    public Result reduceStockByRedisson(Integer id) throws Exception{
        String lockKey = "lockKey";

        //1. Obtain the lock object
        RLock redissonLock = redisson.getLock(lockKey);
        try{
            //2. RedissonLock
            redissonLock.lock();

            // The lock was successfully locked, then the business logic is executed
            log.info("Locking with Redisson succeeded! Execute business code =>");
            lockTestService.reduceStock(id);
            return Result.ok().Message("Purchase successful!");
        }catch (Exception e){
            throw e;
        }
        finally {
            / / 3. Release the lock
            redissonLock.unlock();
            log.info("<= business code completed, release current lock!"); }}}Copy the code

JMeter(thread set to 20):

Experimental results:

Log result:

Database:

The experimental results are satisfied. Prove that Redisson can be used as a distributed lock in high concurrency scenarios.

Four, principle analysis

4.1 Flowchart of Redisson distributed lock implementation principle

The user places an order, invokes the background service, and locks the service code before executing it. If the lock is successfully locked, the service code is executed, and a background thread is opened to renew the life of the lock continuously, so as not to cause the lock to become invalid before the execution of the service code ends. Otherwise, if the lock fails, keep trying to lock, repeating the loop.

4.2 Source Code Analysis

Sorry, I don’t get it.

The last

My blog, this article has been included in the blog, we can see my previous article, like to support oh, thank you! www.yaodao666.xyz