1.什么是缓存
缓存是计算机内存里的一段数据
2.缓存的特点
由于缓存数据在内存中 所以读写缓存中的数据非常快
3.为什么项目中使用缓存
- 项目中使用缓存可以在一定程度上减轻数据库压力
- 可以提高现有网站中查询效率 加快网站响应速度
4.项目中是不是所有数据都需要缓存
不是 加入缓存主要数据:查询居多 增删改极少
5.mybatis自身缓存缺陷
- 自身实现缓存的数据会占用一定应用服务器内存 导致应用服务器处理请求的速度可能变慢 这种缓存也叫本地缓存。
- 本地缓存在集群架构下不能实现缓存共享。
- 为了解决本地缓存存在的问题可以实现分布式缓存服务器 memcached redis
6.现有项目中使用redis做分布式缓存的实现
1.基于现有mybatis提供cache接口实现自定义缓存
- 自定义缓存实现类 implements Cache
- 要求必须存在一个带有String类型id参数构造方法
- 要求实现getId方法 这个方法必须返回值当前namespace
public class RedisCache implements Cache {
private final String id;
private Jedis cache = new Jedis("192.168.160.129",6379);
public RedisCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
// 返回缓存所有键值对的数量
@Override
public int getSize() {
Long dbSize = cache.dbSize();
return dbSize.intValue();
}
// 向缓存中存入数据
@Override
public void putObject(Object key, Object value) {
System.out.println("put object key:"+key);
// 将对象序列化成字节数组
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = SerializationUtils.serialize((Serializable) value);
cache.set(keyBs, valueBs);
}
// 从缓存中获取数据
@Override
public Object getObject(Object key) {
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = cache.get(keyBs);
if (valueBs != null) { // 第一次到缓存找数据的时候 , 返回的是null
return SerializationUtils.deserialize(valueBs);
}
return null;
}
// 清除缓存
@Override
public Object removeObject(Object key) {
// 先获取一下删除的对象
byte[] keyBs = SerializationUtils.serialize((Serializable) key);
byte[] valueBs = cache.get(keyBs);
Object obj = SerializationUtils.deserialize(valueBs);
cache.del(keyBs);// 执行删除操作
return obj;
}
// 清空缓存
@Override
public void clear() {
cache.flushDB();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
- 在mapper文件中指定使用自定义缓存
<!--使用自定义二级缓存-->
<cache type="com.baizhi.cache.RedisCache"></cache>
7.redis结合mybatis实现缓存存在的问题
1. 缓存穿透
1.概念
缓存穿透是指用户查询数据,在数据库中没有,自然缓存中也不会存在。这样就导致在缓存中查找后,又进行数据库的查找,进行了两次无用的操作。
2.解决方案
- 使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被bitmap拦截掉,从而避免了对底层存储系统查询的压力。
- 空数据也缓存,但他的缓存过期时间很短,最长不超过5分钟,进而避免空数据进行数据库的查询。
2.缓存雪崩
1.概念
由于大量的缓存,在同一时刻到达失效时间,而新缓存未到,形成大面积的缓存过期。所有原本应该访问缓存的请求全部,同时去查询数据库,造成数据库CPU和内存过大的并发压力,严重造成宕机,造成系统崩溃。
2.解决方案
- 队列、加锁: 保证不会在同一时刻对数据库的大量访问。
- 分散失效时间:在原有失效时间的基础上加上一个随机值,比如1-5分钟随机,这样可以避免大面积缓存在同一时间过期。
- 添加缓存标记:给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果失效,则更新数据缓存。
3.缓存预热
1.概念
缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
2.思路
- 直接写个缓存刷新页面,上线时手工操作
- 数据量不大,可以在项目启动的时候自动进行加载
- 定时刷新缓存
4.缓存降级
1.概念
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
2.降级原则
参考日志级别设置预案
- 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级
- 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警
- 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级
- 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级