本地锁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";
}
如何保证缓存数据一致性?
双写:修改数据库数据后同时修改缓存
失效:修改数据库数据后直接将缓存失效
对于经常修改的数据,或者对实时性要求高的数据,就不要放缓存了