这可能是B站讲的最好的Redis实战教程(2023年最新版)


synchronized只能保证在单一tomcat容器内线程安全,若是分布式微服务并发访问这个接口,还是会出现超卖的问题
@RestController
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
synchronized (this) {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣减成功,剩余库存:" + realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}
return "end";
}
}
解决方案--分布式锁

@RestController
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式锁的key值
String lockKey = "lock:product:101";
//分布式锁
//设置key超时时间,避免系统执行业务时宕机导致死锁
boolean result= stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zhuge",10,TimeUnit.SECONDS);
if(!result){
return "error_code";
}
try{
//获取库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣减成功,剩余库存:" + realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally{
//释放分布式锁
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}
新问题:1、在10s时间内,库存操作还没有完成,但分布式锁已经超时释放,当执行释放锁的操作时,会报空指针;
2、当线程1在线程2执行加锁之后,将锁释放,会导致线程2的分布式锁失效
解决方法如下:
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式锁的key值
String lockKey = "lock:product:101";
//区分是不同的请求来保证分布式锁
String clientId = UUID.randomUUID().toString();
//分布式锁
//设置key超时时间,避免系统执行业务时宕机导致死锁
boolean result= stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS);
if(!result){
return "error_code";
}
try{
//获取库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣减成功,剩余库存:" + realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally{
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
//释放分布式锁
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
}
新问题:如果当线程1请求9.9s之后执行到31行代码,下一个请求在设置key的瞬间,线程1执行33行代码,导致下一次请求锁失效;
所以释放分布式锁需保证原子性。以上所有问题都是由key的失效时间导致,为解决这个问题出现了锁续命方法,在加锁
之后开启一个子线程执行每10s的时间间隔内来判断锁是否存在,如果存在,将锁超时时间设置为30s。
最强分布式锁工具:Redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>

public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式锁的key值
String lockKey = "lock:product:101";
//Redisson锁对象
RLock redissonLock = redisson.getLock(lockKey);
//加锁
redissonLock.lock();
try{
//获取库存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣减成功,剩余库存:" + realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally{
//释放分布式锁
redissonLock.lock();
}
return "end";
}
}
问题:当java程序加锁之后,redis主节点直接宕机,分布式锁信息被没有同步给从节点,如何保证redis分布式锁不失效?
RedLock可以解决锁丢失问题(zookeeper也可以实现分布式锁)
RedLock实现原理

@RestController
public class IndexController {
@Resource
private Redisson redisson;
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
//分布式锁的key值
String lockKey = "lock:product:101";
//Redisson锁对象
RLock redissonLock = redisson.getLock(lockKey);
//加锁
redissonLock.lock();
try{
//获取库存 //相当于jedis.get("stock")
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock >0){
int realStock = stock - 1;
//相当于jedis.set(key , value)
stringRedisTemplate.opsForValue().set("stock",realStock + "");
System.out.println("扣减成功,剩余库存:" + realStock);
}else {
System.out.println("扣减失败,库存不足");
}
}finally{
//释放分布式锁
redissonLock.lock();
}
return "end";
}
@RequestMapping("/redlock")
public String redlock(){
//分布式锁的key值
String lockKey = "lock:product:101";
//这里需要自己实例化不同redis实例的redisson客户端连接,这里只是伪代码用一个redisson客户端简化了
RLock lock1 = redisson.getLock(lockKey);
RLock lock2 = redisson.getLock(lockKey);
RLock lock3 = redisson.getLock(lockKey);
//根据多个 RLock 对象构建 RedissonRedLock (最核心的差别就在这里)
RedissonRedLock redLock = new RedissonRedLock(lock1,lock2,lock3);
try {
/**
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* LeaseTime锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
*/
boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
//成功获得锁,在这里处理业务
}
} catch (Exception e) {
throw new RuntimeException("lock fail");
} finally {
//无论如何,最后都要解锁
redLock.unlock();
}
return "end";
}
}