对于本地模式下的缓存,每次如果负载均衡请求的服务器不相同,那么会有很大的几率不通过缓存,而是直接通过DB进行数据交换。
因此我们将缓存抽离,如下图所示,即操作同一个缓存,这就不会存在数据不一致的问题。
缓存穿透:
指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是 数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不 存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
解决: null结果缓存,并加入短暂过期时间
缓存雪崩:
缓存雪崩是指在我们设置缓存时key采用了相同的过期时间, 导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时 压力过重雪崩。
解决: 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这 样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存穿透:
对于一些设置了过期时间的key,如果这些key可能会在某些 时间点被超高并发地访问,是一种非常“热点”的数据。
如果这个key在大量请求同时进来前正好失效,那么所有对 这个key的数据查询都落到db,我们称为缓存击穿。
解决: 加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他 人获取到锁,先查缓存,就会有数据,不用去db
springboot整合redis进行缓存操作。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
@Override
public Book getById(Integer id) {
String s = stringRedisTemplate.opsForValue().get(String.valueOf(id));
if (!StringUtils.hasText(s)) {
Book book = bookDao.selectById(id);
//book不为null再执行下一步,为null则退出,解决缓存穿透(可使用布隆过滤器)
String toJSONString = JSON.toJSONString(book);
stringRedisTemplate.opsForValue().set(String.valueOf(id), toJSONString, 60, TimeUnit.SECONDS);//解决缓存雪崩
return book;
} else {
Book book = JSON.parseObject(s, new TypeReference<Book>() {
});
return book;
}
}
推荐博客:
https://www.jianshu.com/p/2afeae8cda89?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation.
整合springboot
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.16.8version>
dependency>
单redis节点模式配置:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod="shutdown")
RedissonClient redisson() throws IOException {
//创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
其他集群等配置可参考官网:
https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95#26-%E5%8D%95redis%E8%8A%82%E7%82%B9%E6%A8%A1%E5%BC%8F.
官网介绍:
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
@GetMapping("/findById/{id}")
public Book findById(@PathVariable("id") Integer id) {
/*redisson实现分布式锁*/
Book book = null;
//1获取一把锁,只要锁的名字一样就可以
RLock lock = redissonClient.getLock("my-lock");
//2、加锁
lock.lock();//阻塞式等待:不断尝试获取锁(获取到锁后默认加锁30s)
//锁是自动续期的,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删掉
//加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁(宕机),锁默认在30s以后自动删除
//lock.lock(10, TimeUnit.SECONDS);
//此有参方法并不会自动续期!!!10自动解锁,自动解锁时间一定要大于业务的执行时间
//1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
//2、如果我们没有指定锁的超时时间,就使用30*1000[看门狗的默认时间]
//只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s秒自动续期,直至30s
//internallockleasetime【看门狗时间】/3,10s
try {
book = bookService.findById(id);
return book;
}finally {
lock.unlock();//解锁
}
}
官网介绍:基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
同样具有看门狗限定锁的时间。
保证一定读到最新数据,修改期间,写锁是一个排它锁(互斥锁),写锁没释放读就必须等待。读锁是一个共享锁。读读不互斥;读写、写写均互斥
@GetMapping("/getall/{page}/{size}")
public Page<Book> getall(@PathVariable("page") Integer page, @PathVariable("size") Integer size) {
//获取读写锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
//加读锁
RLock rLock = readWriteLock.readLock();
rLock.lock();
Page<Book> bookPage = null;
try {
bookPage = bookService.getall(page, size);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();//释放锁
}
return bookPage;
}
@PutMapping("/update")
public Integer update(@RequestBody Book book) {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
Integer integer=-1;
//加写锁
RLock rLock = readWriteLock.writeLock();
rLock.lock();
try {
integer = bookService.updateById(book);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return integer;
}
其他锁可参照官网。
推荐阅读:
链接: https://www.cnblogs.com/liuqingzheng/p/11080501.html