一:redisTemplate.opsForValue().setIfAbsent问题
1.1 说明
1 2 3 4 5 6 7
| 当自己直接使用redisTemplate.opsForValue().setIfAbsent进行获取锁的 方法,当并发量不是很大的时候,这里的还是能使用的,不过一般作为生产 的分布式锁不建议这么使用,一旦超高并发就会存在各种问题。主要是考虑 如下几点: 1.设置锁超时时间 2.设置clientId,保证谁锁定的谁去释放 3.finally 确保可以释放锁
|
1.2 测试代码
1 2 3
| 如何代码在超高并发的时候也是存在问题的,如果在timeout时间外第一个线程 尚未执行完毕,第二个线程拿到锁以后就可以继续往后面执行,如果timeout设置 的过长,如果一些特殊原因,比如应用重启等会导致一直被锁定。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.tohours.miniprogram.controller;
import java.util.UUID; import java.util.concurrent.TimeUnit;
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import com.tohours.miniprogram.exception.TohoursException; import com.tohours.miniprogram.support.TohoursResponse; import com.tohours.miniprogram.support.TohoursResult;
import lombok.extern.slf4j.Slf4j;
/** * @desc redis相关 * @author RenJie * @date 2023-02-27 * */ @RestController @RequestMapping("/redis") @SuppressWarnings("rawtypes") @Slf4j public class RedisController extends BaseController {
@Autowired private StringRedisTemplate stringRedisTemplate; private Boolean isFirst = true;
/** * @desc redis lock测试 * @return */ @RequestMapping(value = "testLock") public TohoursResult<Object> testLock() {
String prizeNumKey = "prizeNum1"; String lockKey = "prize_1"; String clientId = UUID.randomUUID().toString(); Long timeout = 30L; // 获取锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout, TimeUnit.SECONDS); if (!lock) {// 未获取到锁 log.info("未获取到锁,结束"); throw new TohoursException("未获取到锁,结束"); } try { String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey); if (isFirst) { tmp = "300"; isFirst = false; } int prize1Num = Integer.parseInt(tmp); if (prize1Num > 0) { prize1Num--; stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num)); log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num); }
} finally { // 释放锁 String cacheClientId = stringRedisTemplate.opsForValue().get(lockKey); if (clientId.equals(cacheClientId)) {// 谁锁定得谁释放 stringRedisTemplate.delete(lockKey); } } return TohoursResponse.success(); }
}
|
二:使用redssion做分布式锁
2.1 介绍
1 2 3 4 5 6
| redis自动2.6版本以后支持了Lua语言,redission是在redis基础操作的基础上, 做了超时后自动延迟超时时间,并把基础的超时时间设置,线程ID设置,释放锁 等都统一做了处理,所以代码里面我们只要引入使用即可。
如果线程A获取到了锁,正在执行任务,尚未释放锁,此时线程B也去获取锁, 如果获取锁失败,则会等待锁的释放。
|
2.2 springboot引入redssion
1 2 3 4 5
| <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.19.3</version> </dependency>
|
1
| 在基于引入redis的基础上做的配置,此距离是单例模式
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.tohours.miniprogram.config;
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
/** * @desc redisson bean管理 * @author RenJie * @date 2023-02-28 */ @Configuration public class RedissonConfig {
@Value("${spring.redis.host}") private String host;
@Value("${spring.redis.port}") private String port;
@Value("${spring.redis.password}") private String password;
@Bean public RedissonClient getRedisson() { Config config = new Config(); config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password); // 添加主从配置 // config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""}); return Redisson.create(config); } }
|
2.3 使用redisson分布式锁
1 2 3 4
| 1.redisson首先获取lock实例 2.加锁 3.释放锁 4.当线程未获取到锁的时候会等待
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| package com.tohours.miniprogram.controller;
import java.util.UUID; import java.util.concurrent.TimeUnit;
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import com.tohours.miniprogram.exception.TohoursException; import com.tohours.miniprogram.support.TohoursResponse; import com.tohours.miniprogram.support.TohoursResult;
import lombok.extern.slf4j.Slf4j;
/** * @desc redis相关 * @author RenJie * @date 2023-02-27 * */ @RestController @RequestMapping("/redis") @SuppressWarnings("rawtypes") @Slf4j public class RedisController extends BaseController {
@Autowired private StringRedisTemplate stringRedisTemplate; private Boolean isFirst = true;
/** * @desc redis lock测试 * @return */ @RequestMapping(value = "testLock") public TohoursResult<Object> testLock() {
String prizeNumKey = "prizeNum1"; String lockKey = "prize_1"; String clientId = UUID.randomUUID().toString(); Long timeout = 30L; // 获取锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout, TimeUnit.SECONDS); if (!lock) {// 未获取到锁 log.info("未获取到锁,结束"); throw new TohoursException("未获取到锁,结束"); } try { String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey); if (isFirst) { tmp = "300"; isFirst = false; } int prize1Num = Integer.parseInt(tmp); if (prize1Num > 0) { prize1Num--; stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num)); log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num); }
} finally { // 释放锁 String cacheClientId = stringRedisTemplate.opsForValue().get(lockKey); if (clientId.equals(cacheClientId)) {// 谁锁定得谁释放 stringRedisTemplate.delete(lockKey); } } return TohoursResponse.success(); }
@Autowired private RedissonClient resissonClient;
/** * @desc redisson Lock测试 * @return */ @RequestMapping(value = "redissonLock") public TohoursResult<Object> redissonLock() {
String prizeNumKey = "prizeNum1"; String lockKey = "prize_1"; // 获取锁 RLock redissonLock = resissonClient.getLock(lockKey); redissonLock.lock();// 锁定 try { String tmp = stringRedisTemplate.opsForValue().get(prizeNumKey); if (isFirst) { tmp = "300"; isFirst = false; } int prize1Num = Integer.parseInt(tmp); if (prize1Num > 0) { prize1Num--; stringRedisTemplate.opsForValue().set(prizeNumKey, String.valueOf(prize1Num)); log.info(Thread.currentThread().getName() + "剩余数量:" + prize1Num); }
} finally { // 释放锁 redissonLock.unlock(); } return TohoursResponse.success(); }
}
|
2.4 读写锁
1 2 3
| 读写锁: 保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。 读锁是一个共享锁。 写锁没释放,读锁就必须等待。 针对读数据时,只要当前正在修改数据,读数据就会等待写完成后,拿到最新数据。
|
1 2 3 4 5
| 读 + 读 :相当于无所 n请求可以同时加锁成功 redis中会记录着进来锁的状态 写 + 读 :等待写锁释放 才能读到新数据(读被阻塞) 写 + 写 : 阻塞方式(后面写锁等待前面写锁释放) 读 + 写 :有读锁 写也需要等待(写锁:等你读完这一次 我再改写) 只要有写锁的存在,都必须等待。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //获取读写锁 RReadWriteLock readWriteLock = resissonClient.getReadWriteLock(lockKey); //获取读锁 RLock rLock = readWriteLock.readLock(); //加锁 rLock.lock(); //释放锁 rLock.unlock(); //获取写锁 rLock = readWriteLock.writeLock(); //加锁 rLock.lock(); //释放锁 rLock.unlock();
|
*************感谢您的阅读*************