springboot整合redisson实现分布式锁

1、为什么要用分布式锁?

在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数量可能被减少到负数,出现超卖的情况,或者 产生唯一的一个递增ID,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害。这个时候就需要用分布式锁了实现分布式锁的方式很多,我们今天就用redis的分布式锁,redisson也是官方比较推荐的。当然我们其实也可以自己用redis的setntx,delete方式自己写一个。

2、分布式锁的实现方式

大概有三种:

  1. 基于关系型数据库(基于mysql数据库的分布式锁
  2. 基于缓存(本文讲解了基于redis的redisson实现分布式锁)
  3. 基于zookeeper(基于zookeeper实现分布式锁

大部分网站使用的是基于缓存的,有更好的性能,而缓存一般是以集群方式部署,保证了高可用性。

3、原理

在Redisson中,使用key来作为是否上锁的标志,当通过getLock(String key)方法获得相应的锁之后,这个key即作为一个锁存储到Redis集群中,在接下来如果有其他的线程尝试获取名为key的锁时,便会向集群中进行查询,如果能够查到这个锁并发现相应的value的值不为0,则表示已经有其他线程申请了这个锁同时还没有释放,则当前线程进入阻塞,否则由当前线程获取这个锁并将value值加一,如果是可重入锁的话,则当前线程每获得一个自身线程的锁,就将value的值加一,而每释放一个锁则将value值减一,直到减至0,完全释放这个锁。因为底层是基于分布式的Redis集群,所以Redisson实现了分布式的锁机制。

4、实现

4.1、pom引入

        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.5.0</version>
        </dependency>

4.2、redisson配置

spring
  redis:
    database: 1 
    host: 127.0.0.1
    port: 6379
    #password: 12345678

redisson:
  address: redis://127.0.0.1:6379
  #password: web2017

4.2、redisson操作锁的工具

package com.example.mybatiesplus.utils;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @DESCRIPTION redisson操作锁的工具
 * @Author lst
 * @Date 2020-05-22 15:00
 */
@Component
public class RedissonUtil {

    @Autowired
    private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可

    /**
      * 锁住不设置超时时间(拿不到lock就不罢休,不然线程就一直block)
      * @author lst
      * @date 2020-5-24 16:23
      * @param lockKey
      * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    /**
      * leaseTime为加锁时间,单位为秒
      * @author lst
      * @date 2020-5-24 16:23
      * @param lockKey
      * @param leaseTime
      * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return null;
    }

    /**
      * timeout为加锁时间,时间单位由unit确定
      * @author lst
      * @date 2020-5-24 16:24
      * @param lockKey
      * @param unit
      * @param timeout
      * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    /**
      * 尝试获取锁
      * @author lst
      * @date 2020-5-24 16:24
      * @param lockKey
      * @param unit
      * @param waitTime
      * @param leaseTime
      * @return boolean
     */
    public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    /**
      * 通过lockKey解锁
      * @author lst
      * @date 2020-5-24 16:24
      * @param lockKey
      * @return void
     */
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    /**
      * 直接通过锁解锁
      * @author lst
      * @date 2020-5-24 16:25
      * @param lock
      * @return void
     */
    public void unlock(RLock lock) {
        lock.unlock();
    }
}

4.3、redisson基本配置类

@Configuration
public class RedissonConfig {
	
	@Value("${redisson.address}")
	private String addressUrl;
	
	@Bean
	public RedissonClient getRedisson() throws Exception{
		RedissonClient redisson = null;
		Config config = new Config();
		config.useSingleServer()
			  .setAddress(addressUrl);
		redisson = Redisson.create(config);
		
		System.out.println(redisson.getConfig().toJSON().toString());
		return redisson;
	}
	
	
}

4.3、接下来,写一个测试类

package com.example.mybatiesplus.controller;

import com.example.mybatiesplus.result.BaseResponse;
import com.example.mybatiesplus.result.ResultGenerator;
import com.example.mybatiesplus.utils.RedissonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @DESCRIPTION 测试类
 * @Author lst
 * @Date 2020-05-24
 */
@RestController
@RequestMapping("/test")
@Api(value = "TestController", tags = "测试类")
@Slf4j
public class TestController {

    public final static String REDISSON_KEY = "redisson_key";

    @Autowired
    private RedissonUtil redissonUtil;

    /**
      * 通过redisson高并发测试
      * @author lst
      * @date 2020-5-24 16:29
      * @param
      * @return com.example.mybatiesplus.result.BaseResponse
     */
    @GetMapping(value = "/redisson", produces = "application/json; charset=utf-8")
    @ApiOperation(value = "通过redisson高并发测试", notes = "通过redisson高并发测试", code = 200, produces = "application/json")
    public BaseResponse redisson() {
        try{
            log.info("============={} 线程访问开始============",Thread.currentThread().getName());
            //TODO 尝试获取锁,等待3秒,自己获得锁后一直不解锁则5秒后自动解锁
            boolean lock = redissonUtil.tryLock(REDISSON_KEY, TimeUnit.SECONDS, 3L, 5L);
            if (lock) {
                log.info("线程:{},获取到了锁",Thread.currentThread().getName());
                //TODO 获得锁之后可以进行相应的处理  睡一会
                Thread.sleep(100);
                log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
                //redissonUtil.unlock(REDISSON_KEY);
                log.info("=============================" + Thread.currentThread().getName());
            }
        }catch (Exception e){
            log.info("错误信息:{}",e.toString());
            log.info("线程:{} 获取锁失败",Thread.currentThread().getName());
        }
        return ResultGenerator.genSuccessResult();
    }
}

4.4、使用jmeter测试

第一步:创建线程组

第二步:添加http请求,设置并发数(先测试100)

第二步:添加接口信息

4.5、测试数据

在redissonUtil.unlock(REDISSON_KEY);锁未释放的测试下,可能看到后台日志只有线程26获取到了锁。

将redissonUtil.unlock(REDISSON_KEY);释放开在测试,只要某个抢到锁的线程执行完毕并且释放了锁资源,其他的线程很快就会获取到锁。

 

 


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liangshitian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值