当前位置: 首页>后端>正文

redisson

本地锁synchronized实现数据缓存时,在分布式环境下,每个应用还是要查询一次数据库

分布式锁思想:使用redis的setnx

使用redis的setnx key value当作分布式锁,当key已经存在时,会返回null
当有大量并发请求时,保证只查询一次数据库

public Map<String, List<Catelog2Vo>> getLock1() throws InterruptedException {

        // 加锁并设置过期时间,原子性操作
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock != null) {
            // 获得锁,查询数据库
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();

            // lua脚本解锁,原子性操作
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList("lock"), uuid);
            return dataFromDb;
        } else {
            // 再次尝试
            TimeUnit.MILLISECONDS.sleep(100);
            return getCatalogJsonFromDbWithRedisLock();
        }
    }

使用redisson实现redis分布式锁

注入RedissonClient

@Configuration
public class MyRedissonConfig {
    
    @Bean(destroyMethod="shutdown")
    public RedissonClient redissonClient()  {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.77.130:6379");
        return Redisson.create(config);
    }

}
@ResponseBody
    @GetMapping(value = "/hello")
    public String hello() {

        //1、获取锁,只要锁的名字一样,就是同一把锁
        RLock myLock = redisson.getLock("my-lock");
        // 公平锁
        // RLock fairLock = redisson.getFairLock("fair-lock");

        //2、加锁
        myLock.lock();     
        // myLock.lock(10,TimeUnit.SECONDS);   //10秒钟自动解锁,在锁时间到了以后,不会自动续期。所以使用时自动解锁时间一定要大于业务执行时间

        try {
            System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
            TimeUnit.SECONDS.sleep(20);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("释放锁..." + Thread.currentThread().getId());
            myLock.unlock();
        }

        return "hello";
    }

redisson的锁默认30s过期,如果时间不够,还会自动续期。因此不会产生死锁,也不用担心锁会提前释放。
但是,推荐自己指定过期时间,如果业务在期望时间内没有完成,应考虑是否哪个环节出现了问题,而不是无限续期。

读写锁

@GetMapping(value = "/write")
@ResponseBody
public String writeValue() {
    String uuid = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    //改数据加写锁
    RLock writeLock = readWriteLock.writeLock();
    try {
        //只要没有完成,其他不可读,也不可写
        writeLock.lock();
        uuid = UUID.randomUUID().toString();
        stringRedisTemplate.opsForValue().set("uuid", uuid);
        //写后休眠10s,在此期间不可读
        TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        writeLock.unlock();
    }

    return uuid;
}

@GetMapping(value = "/read")
@ResponseBody
public String readValue() {
    String uuid = "";
    RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
    //读数据加读锁
    RLock readLock = readWriteLock.readLock();
    try {
        //等待写锁释放后才能继续
        readLock.lock();
        uuid = stringRedisTemplate.opsForValue().get("uuid");
        TimeUnit.MINUTES.sleep(10);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        readLock.unlock();
    }

    return uuid;
}

读写锁保证一定能读到最新数据

读 + 读 :相当于无锁,都会同时加锁成功
写 + 读 :必须等待写锁释放
写 + 写 :阻塞,只能有一个写锁
读 + 写 :有读锁,写也需要等待

总结:写锁是一个排它锁,读锁是一个共享锁; 只要有读或者写,都必须等待

分布式闭锁

/**
 * 分布式闭锁
 */
@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.trySetCount(5);
    door.await(); 
    return "放假了...";
}

@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
    RCountDownLatch door = redisson.getCountDownLatch("door");
    door.countDown(); //计数-1
    return id + "班的人都走了...";
}

分布式信号量

/**
 * 分布式信号量案例:车库停车
 */
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
    //在redis中需要存在park
    RSemaphore park = redisson.getSemaphore("park");
    //获取一个信号,占一个车位
    park.acquire();
    return "park";
}

@GetMapping(value = "/go")
@ResponseBody
public String go() {
    RSemaphore park = redisson.getSemaphore("park");
    //释放一个车位
    park.release();
    return "go";
}
/**
 * 分布式信号量案例:限流
 */
@GetMapping(value = "/limit")
@ResponseBody
public String limit() throws InterruptedException {
    //假设redis中limit为1000
    //表示最多允许的并发请求数
    RSemaphore park = redisson.getSemaphore("limit");
    //尝试获取信号量,如果没有了,返回false,即被限流
    //不同于acquire,tryAcquire不会阻塞等待,直接返回true或者false
    if (park.tryAcquire()) {
        System.out.println("获得了信号量");
        // do something
    }
    return "park";
}

如何保证缓存数据一致性?
双写:修改数据库数据后同时修改缓存
失效:修改数据库数据后直接将缓存失效

对于经常修改的数据,或者对实时性要求高的数据,就不要放缓存了


https://www.xamrdz.com/backend/3y91924359.html

相关文章: