Redis的分布式锁自我理解
redis分布式锁存在的意义:
在synchronized存在的条件下,synchronized是能解决单个服务的加锁操作,现在倡导微服务,不同的服务可能部署在不同的服务器上,因此,synchronized不能满足现在的开发需求,因此,redis的分布式锁诞生了
redis分布式锁的发展:
基于redis缓存中的setnx操作,可以将键值对缓存起来,缓存成功会返回1,若存在键,则返回0;因此,初步的redis分布式的锁由此诞生。
public static void firstLock() {
//1.上锁
Jedis redis = getJedis();
Long lockResult = redis.setnx(LOCK_NAME, LOCK_VALUE);
if (1 == lockResult) {
// 2. 执行业务
executeBusiness();
// 3. 释放锁
redis.del(LOCK_NAME);
} else {
// 获取锁失败
System.out.println("Can not get lock");
}
}
上图所示,没有设置过期时间,一旦在释放资源之前的操作中存在宕机的情况,此键值对将永久存在于redis中,其他服务都会处于一直请求锁的状态;因此,给锁加缓存时间的情况出现;
@Service
public class ServerLock {
@Resource
private TollDao tollDao;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/*
redis分布式锁:这种是阻塞时锁,在分布式项目中,多个进程访问同一个资源,再使用synchronized就不管用了,我们可以使用redis分布式锁,redis是单线程的。
当两个进程对同一个资源操作时,就可以让两个进程设置相同的一个属性值,在一段时间,两个进程只能有一个进程能在redis中设置值
当有两个进程A和B,进程A访问资源时,先去redis中去查看一个锁,这个锁就是一个key-value,如果存在,说明进程B已经运行了,进程A就会等待,并
不断去尝试加锁,当B的锁到期,A就会到redis中加锁,然后A进程运行
*/
public String selectById(Integer pid){
// 这就是加锁,就是进程在redis放入一个标识: setIfAbsent():如果里边有相同的值,就不存入,返回0(false).相当于redis的setnx和expire两个方法
// key\value字段就是存储的字段,timeout表示这个值多久过期
Boolean status = stringRedisTemplate.opsForValue().setIfAbsent("product::" + pid, "fy", 3, TimeUnit.SECONDS);
if(status){
try{
Toll toll = tollDao.selectById(pid);
if(toll.getCount()>0){
System.out.println("更改前的剩余"+toll.getCount());
Thread.sleep(5000);
toll.setCount(toll.getCount()-1);
tollDao.updateById(toll);
System.out.println("库存剩余:"+toll.getCount());
return "库存减少成功";
}else {
return "库存不足";
}
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}finally {
//释放锁资源 一定再finally
stringRedisTemplate.delete("product::"+pid);
}
}else {
// System.out.println("服务器正忙请稍后再试..........");
return "服务器正忙请稍后再试..........";
}
}
}
但是,又延伸出来了另一个问题,在设置缓存的过期时间之后,若存在这样的一种情况,就是处理业务逻辑的时间非常长,长到足以大于缓存的过期时间,这样,导致可能业务逻辑还没有处理完成,锁的缓存时间过期,锁被释放,其他线程拿到锁,导致数据不一致或释放其他线程锁的操作,因此,redisson出现了;
引入依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.3</version>
</dependency>
也可以用这个:这个必须在项目中配置RedissonClient类,如启动类中所写
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.3</version>
</dependency>
启动类
@SpringBootApplication
@MapperScan("com.ykq.distributedlock.dao")
public class DistributedLockApplication {
public static void main(String[] args) {
SpringApplication.run(DistributedLockApplication.class, args);
}
@Bean //Configuration
public RedissonClient getRedisson(){
Config config=new Config();
config.useSingleServer().setAddress("redis://192.168.213.188:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
代码实现
@Service
public class TollServerWatchDog {
@Resource
private TollDao tollDao;
@Resource
private RedissonClient redissonClient;//解决了使用redis分布锁的过程中,产生的业务还没执行完,但是锁到期的问题
public String selectById(Integer pid){
// 根据参数创建锁实例
RLock lock = redissonClient.getLock("selectById::" + pid);
try{
lock.lock();
Toll toll = tollDao.selectById(pid);
if (toll.getCount()>0) {
System.out.println("开始前"+toll.getCount());
toll.setCount(toll.getCount()-1);
tollDao.updateById(toll);
System.out.println("剩余"+toll.getCount());
return "库存减少成功";
} else {
return "库存不足";
}
}catch (Exception e){
throw new RuntimeException(e.getMessage());
}finally {
if(lock.isLocked()){//判断锁是否处于锁定
if(lock.isHeldByCurrentThread()){//判断是否时该进程自己的锁
lock.unlock();
}
}
}
}
}
补充说明:
这里默认会获取看门狗机制的过期时间,为30s;
看门狗机制:就是redisson在调用.lock()时,可以设置过期时间,如果设置了时间,则按照设置的时间来过期,如果不设置,会开启看门狗机制,默认时间为30s,在过期时间*(1/3)时间里,看门狗会去判断逻辑是否执行完毕,若没有执行完,将过期时间再次设置为30s,一直到程序完成为止。