springboot利用redisson实现分布式锁
程序员文章站
2022-06-28 17:13:51
最近开发一了个答题抽奖项目,由于部署项目采用了负载均衡策略,分配奖品时必须使用分布式锁,项目开发完成后记录一下利用redisson实现分布式锁的过程一、springboot项目整合redissonredisson pom依赖如下 org.redisson redisson
最近开发一了个答题抽奖项目,由于部署项目采用了负载均衡策略,分配奖品时必须使用分布式锁,项目开发完成后记录一下利用redisson实现分布式锁的过程
一、springboot项目整合redisson
- redisson pom依赖如下,springboot为2.x,如果是更底版本,redisson可能也需要换成更低的版本。
<!--Redis分布式锁--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.0</version> </dependency>
- yml配置文件中的redis配置如下
spring: redis: host: localhost port: 3306 timeout: 6000 database: 5 password: '123456' lettuce: pool: max-active: -1 #最大连接数 max-idle: 8 #最大空闲数 max-wait: 15000 #最大连接阻塞等待时间 min-idle: 0 #最小空闲数
- 配置RedissonClient
创建RedisProperties类,装配redis配置
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { private String host; private int port; private String password; private int database; public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; } }
然后创建RedissionConfig类,配置RedissonClient
import org.apache.log4j.Logger; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedissionConfig { @Autowired private RedisProperties redisProperties; private Logger logger = Logger.getLogger(this.getClass()); @Bean public RedissonClient redissonClient() { RedissonClient redissonClient; Config config = new Config(); String url = "redis://" + redisProperties.getHost() + ":" + redisProperties.getPort(); config.useSingleServer().setAddress(url) .setPassword(redisProperties.getPassword()) .setDatabase(redisProperties.getDatabase()); try { redissonClient = Redisson.create(config); return redissonClient; } catch (Exception e) { logger.error("RedissonClient init redis url:[{}], Exception:" + url); return null; } } }
到此Redisson就整合完成了
二、利用Redisson实现分布式锁
创建DistributedRedisLock类,代码如下
import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /**
* @title 分布式redis锁
* @author gavin
* @date 2020年7月10日
*/ @Component public class DistributedRedisLock { @Autowired private RedissonClient redissonClient; private Logger logger = Logger.getLogger(this.getClass()); // 加锁 public Boolean lock(String lockName) { try { if (redissonClient == null) { logger.info("redissonClient为空"); return false; } RLock lock = redissonClient.getLock(lockName); // 锁30秒后自动释放,防止死锁 lock.lock(30, TimeUnit.SECONDS); logger.info("线程" + Thread.currentThread().getName() + "加锁" + lockName + "成功"); // 加锁成功 return true; } catch (Exception e) { logger.error("加锁异常:" + lockName); e.printStackTrace(); return false; } } // 释放锁 public Boolean unlock(String lockName) { try { if (redissonClient == null) { logger.info("redissonClient为空"); return false; } RLock lock = redissonClient.getLock(lockName); if (lock.isLocked()) { if (lock.isHeldByCurrentThread()) { // 主动释放锁 lock.unlock(); logger.info("线程" + Thread.currentThread().getName() + "解锁" + lockName + "成功"); return true; } } return true; } catch (Exception e) { logger.error(Thread.currentThread().getName() + "解锁异常:" + lockName); e.printStackTrace(); return false; } } }
DistributedRedisLock 类中有两个方法,lock是加锁方法,unlock是释放锁方法。其中有两个点需要注意
- lock.lock(30, TimeUnit.SECONDS),这行代码的作用是,如果加锁的业务代码运行时间超过了30秒,便会自动释放锁,这是为防止业务代码运行时间过长或者出错导致死锁。
- lock.isHeldByCurrentThread(),在释放锁时,这行判断必须要加上,它会判断当前线程释放的锁是否属于当前线程,防止解锁错乱。
三、测试分布式锁
我采用的测试逻辑是,对redis中一个值为100的变量,使用100线程同时对该变量进行减1操作,看最后变量的值是否为0,如果为0,则分布式锁有效。
创建TestLockController测试类,代码如下
import java.util.concurrent.CountDownLatch; import org.springframework.beans.factory.annotation.Autowired; 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; @RestController @RequestMapping("lock") public class TestLockController { @Autowired private DistributedRedisLock distributedRedisLock; // redision分布式锁id private static String lock = "lock_id"; // 测试数据key private static String dataKey = "lock_count"; // 测试数据value private int count = 100; // 模拟线程数 private int threadNumber = 100; @Autowired private StringRedisTemplate stringRedisTemplate; /**
* @title 在redis中初始化设置lock_count=100,用于测试
* @author gavin
* @date 2020年8月26日
*/ @GetMapping("lockInit") public Object testInit() { this.stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(this.count)); return this.stringRedisTemplate.opsForValue().get(dataKey); } /**
* @title 测试不加锁
* @author gavin
* @date 2020年8月26日
* @return
* @throws InterruptedException
*/ @GetMapping("/testNoLock") public Object testNoLock() throws InterruptedException { // 多线程计数器 CountDownLatch latch = new CountDownLatch(this.threadNumber); for(int i=0;i<this.threadNumber;i++) { new Thread(new Runnable() { @Override public void run() { String valueStr = stringRedisTemplate.opsForValue().get(dataKey); int value = Integer.valueOf(valueStr); value = value - 1; stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(value)); // 每个线程执行完毕,计数器减1 latch.countDown(); } }).start(); } // 在此阻塞,等待所有线程结束 latch.await(); System.out.println(this.threadNumber + "个线程全部执行完毕"); // 返回最终的dataKey return stringRedisTemplate.opsForValue().get(dataKey); } /**
* @title 测试分布式锁
* @author gavin
* @date 2020年8月26日
* @return
* @throws InterruptedException
*/ @GetMapping("/testUseLock") public Object testLock() throws InterruptedException { // 多线程计数器 CountDownLatch latch = new CountDownLatch(this.threadNumber); for(int i=0;i<this.threadNumber;i++) { new Thread(new Runnable() { @Override public void run() { boolean lockFlag = distributedRedisLock.lock(lock); // 加分布式锁 if(lockFlag) { String valueStr = stringRedisTemplate.opsForValue().get(dataKey); int value = Integer.valueOf(valueStr); value = value - 1; stringRedisTemplate.opsForValue().set(dataKey, String.valueOf(value)); // 加锁 distributedRedisLock.unlock(lock); // 每个线程执行完毕,计数器减1 latch.countDown(); } } }).start(); } // 在此阻塞,等待所有线程结束 latch.await(); System.out.println(this.threadNumber + "个线程全部执行完毕"); // 返回最终的dataKey return stringRedisTemplate.opsForValue().get(dataKey); } }
测试类中有三个方法,分别是
- lockInit方法,向redis中存储一个key为lock_count,值为100的变量
- testNoLock方法,不加分布式锁进行测试
- testLock方法,使用分布式锁进行测试
启动项目,打开postman,首先执行lockInit,在redis中初始化变量lock_count=100
然后我们先测试一下不加分布式锁的testNoLock方法,运行结果如下图
可以看到,lock_count最终的值为98,说明100个线程运行过程中出现了并发。
再次执行lockInit方法,使lock_count值为100,然后再运行testLock方法,运行结果如下图
可以看到,lock_count的值为0,说明没有出现并发,分布式锁生效了。
本文地址:https://blog.csdn.net/u012693016/article/details/108244005