Redisson实现分布式锁学习

一、SpringBoot集成Redisson

1.1查看Maven依赖

image-20210201141900974

<!-- 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>

1.2 SpringBoot容器注入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: 使用Redisson来实现分布式锁
 * @Date: 2021/2/1 15:26
 */
@Configuration
public class RedissonConfig {
    //使用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(){
        //此为单机模式-SingleServer
        Config config = new Config();
        config.useSingleServer().setAddress("redis://"+host+":"+port).setDatabase(0).setPassword(pwd);
        return (Redisson)Redisson.create(config);
    }
}

二、业务中使用Redisson工具

主要就三行代码:

//1.获取锁对象
RLock redissonLock = redisson.getLock(lockKey);
//2.加锁
redissonLock.lock();
//3.释放锁
redissonLock.unlock();

具体代码:

@Autowired
private Redisson redisson;

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

    //1.获取锁对象
    RLock redissonLock = redisson.getLock(lockKey);
    try{
        //2.使用redissonLock加锁
        redissonLock.lock();

        //加锁成功(获得了锁),那么执行业务逻辑
        log.info("使用Redisson加锁成功!执行业务代码=>");
        lockTestService.reduceStock(id);
        return Result.ok().Message("购买成功!");
    }catch (Exception e){
        throw e;
    }
    finally {
        //3.释放锁
        redissonLock.unlock();
        log.info("<=业务代码执行完毕,释放当前锁!");
    }
}

三、实验,使用JMeter实现高并发访问场景

模拟场景,有20个用户同时向后台服务发送下订单的请求,并将20个请求通过nginx转发到两个后台服务中——实现分布式,现库存数量为15,正常情况下,当用户下一个订单,库存量减一,订单数加一,直到库存量为0;当请求全部处理且结束后,数据库中库存量为0,且订单数为15时,说明实验成功,反之实验失败。

3.1实验准备阶段

硬件与软件

window、云服务器

IDEA+Java、nginx、JMeter(windows下跑)

mysql、redis(云服务器),当然可以设置在本机win下

nginx配置80端口向8085、8086端口转发、开启nginx

nginx配置
#配置一个app名字
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/;
    }

image-20210201185830934

Idea运行两个服务类,分别在8085、8086端口

mysql数据库库存表:

image-20210201191308443

mysql数据库订单表:

image-20210201191300210

业务(下订单)代码:

/**
 * @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: 商品id
     * @return
     */
    @Transactional
    public void reduceStock(Integer id){
        //1.获取库存
        LockTestProduct product = mapper.getProduct(id);

        //模拟耗时业务
        sleep(1000);

        if(product.getNumber()<=0){
            log.info("所购商品已无库存!");
            throw new RuntimeException("所购商品已无库存!");
        }

        //2.如果库存还有就减库存
        if(mapper.updateProduct(id)>0){
            LockTestOrder order = new LockTestOrder();
            order.setUserId("YaoDao");
            order.setPid(id);
            mapper.insertOrder(order);
            log.info("成功下单!,产品信息:{},订单信息:{}",product,order);
        }else {
            log.info("减库存操作失败!");
            throw new RuntimeException("减库存操作失败!");
        }
    }

    /**
     * create by: YaoDao
     * description: 模拟耗时业务
     * 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();
        }
    }
}

mapper代码:

接口
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: 该接口用于操作 数据库中的lock_test_product和lock_test_order表
 * 用于实验高并发下  如何对数据库中的信息处理  以库存表stock_table为例
 * 模拟场景  高并发下对商品的抢购
 * @Date: 2021/1/28 15:05
 */
@Repository
@Mapper
public interface LockTestMapper {
    /**
     * create by: YaoDao
     * description: 获取产品的信息(库存信息)
     * create time: 2021/1/28 15:10
     * @Param: null
     * @return
     */
    LockTestProduct getProduct(@Param("id") Integer id);


    /**
     * create by: HaoNan
     * description: 在原基础的库存上减1
     * create time: 2021/1/28 15:37
     * @Param: null
     * @return
     */
    int updateProduct(@Param("id") Integer id);


    /**
     * create by: YaoDao
     * description: 生成订单
     * 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);
}
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>

3.2带有缺陷的Redis锁实验

实验代码

/**
 * @Author: YaoDao
 * @Package: 模拟高并发减库存  的入口
 * @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{
            //使用setIfAbsent方法,如果没有这个key值则set加上一个Key,返回True,当作加锁成功。如果有这个Key,那就返回False;
            //Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"redisLock");
            //如果获得锁之后,执行业务代码时宕机了,那么这个锁就会一直存在。为了避免死锁,为这把锁加一个过期时间。
            //stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);

            //将上面两行代码变成一条原子指令
            Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,threadId,10, TimeUnit.SECONDS);

            if(!isLocked){
                //加锁未成功
                throw new RuntimeException("系统繁忙请稍后再试!");
            }

            //加锁成功(获得了锁),那么执行业务逻辑
            log.info("加锁成功!执行业务代码=>");
            lockTestService.reduceStock(id);
            return Result.ok().Message("购买成功!");
        }catch (Exception e){
            throw e;
        }
        finally {
            if (threadId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                //只删掉自己设置的锁
                stringRedisTemplate.delete(lockKey);
                log.info("<=业务代码执行完毕,释放当前线程锁!"+threadId);
            }
        }
    }
}

JMeter请求

设置10个线程

image-20210201184112332

JMeter请求结果和控制台日志

image-20210201190039974

image-20210201190110811

虽然没有出现超卖问题,但是同时访问时,由于没有设置循环请求获得锁,导致只有一个线程获得锁,成功执行了业务。当然可以添加while循环去循环不断请求拿到锁,这里就不进行实验了。

3.3使用Redisson锁

代码
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: 模拟高并发减库存  的入口
 * @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.获取锁对象
        RLock redissonLock = redisson.getLock(lockKey);
        try{
            //2.使用redissonLock加锁
            redissonLock.lock();

            //加锁成功(获得了锁),那么执行业务逻辑
            log.info("使用Redisson加锁成功!执行业务代码=>");
            lockTestService.reduceStock(id);
            return Result.ok().Message("购买成功!");
        }catch (Exception e){
            throw e;
        }
        finally {
            //3.释放锁
            redissonLock.unlock();
            log.info("<=业务代码执行完毕,释放当前锁!");
        }
    }

}

JMeter(线程设置为20):

image-20210201191111109

image-20210201190622536

实验结果:

image-20210201201903836

日志结果:

image-20210201202013150

数据库:

image-20210201202337483

image-20210201202348852

满足实验性结果。证明Redisson是可以在高并发场景作为分布式锁的。

四、原理分析

4.1Redisson分布式锁实现原理流程图

image-20210201181341095

用户进行下订单操作,调用后台服务,执行业务代码前进行加锁操作,如果加锁成功则执行业务代码,并同时开启一个后台线程来不断给锁续命,以免导致业务代码执行结束前锁就失效了;反之,如果加锁失败,则一直尝试加锁,不断循环。

4.2源码分析

不好意思,还没看懂。

最后

欢迎大家到我的个人博客下找我哦:www.yaodao666.xyz
同时希望大家点个赞,支持一下原创博文,多谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值