为什么要加缓存?
减轻数据库压力;
提升请求速度。
将数据存在缓存中,当再次查询时就可以直接从缓存中取,就不需要请求数据库了,这样既减轻了数据库压力,又提升了请求速度。
Redis简介
分布式缓存技术。最快的缓存技术,单线程。
项目中用到Redis缓存的地方
网站首页。因为网站首页一定是一个网站并发量最高的地方,给首页数据加缓存可以提升网站并发量。
项目中怎样使用Redis缓存
爱回购网站中的首页一加载就会从数据库查询一些必需的数据:商品类型、品牌、商品信息。在查询这些数据的service层中,先查缓存。如果有缓存,直接返回缓存中的数据;如果没有缓存,查数据库,并且将从数据库查询到的数据存入缓存。
这样就只要第一个人查数据库就可以了,后面的人就直接得到缓存中的数据
怎样保证当数据库被修改了缓存同步更新
1、当修改了数据库时,同时修改缓存数据
2、模仿mysql的缓存机制,一旦有修改,删除所有缓存
加缓存可能会存在的问题
缓存穿透 和 缓存雪崩
缓存穿透:并发环境下的大量数据请求绕过缓存直接访问数据库的现象
缓存穿透会带来缓存雪崩。雪崩就是一串连锁反应,后果很严重。 比如由于缓存穿透导致数据库的连接数耗尽,数据库无法使用导致tomcat中线程池的线程释放非常慢,从而导致tomcat的线程数消耗殆尽,进而导致所有用户无法访问网站----这一连串的效应就叫雪崩—因为是缓存引起的所以也叫缓存雪崩
解决缓存穿透
避免数据绕过缓存直接访问数据库
方案:
1、加锁(synchronize)
2、双重检测
3、不论数据库是否查到,都要写入缓存,避免采用无效条件查询数据库,导致缓存失效
4、给查到为空的缓存设置过期时间,时间不宜过长也不宜过短。过长会导致内存不足;过短会导致缓存失效。
项目中关于缓存的代码1
@Component
public class RedisHelper {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public <T> List<T> cache(String key,Type type,IDBResultCallBack<T> idbResultCallBack){
// 从缓存中获取 获取到的是字符串
List<T> query = null;
// 锁住 避免出现当有人已经查了数据但还没保存的时候另外的人还去查
synchronized (this){
System.out.println("排队进来");
String jsonStr = stringRedisTemplate.boundValueOps(key).get();
Gson gson = new Gson();
query = gson.fromJson(jsonStr, type);
if (query == null){
System.out.println("查数据库啦");
query = idbResultCallBack.callback();
// 将查出的数据存入缓存
if (query != null || !query.isEmpty()){
String toJson = gson.toJson(query, type);
stringRedisTemplate.boundValueOps(key).set(toJson);
} else {
// 当从数据库查出的数据是空的时候 也要存入缓存(避免出现缓存穿透)
// 但是如果大量空的key存入缓存 缓存会不够用 所有将空的数据缓存 设置过期时间
String toJson = gson.toJson(query, type);
stringRedisTemplate.boundValueOps(key).set(toJson,30, TimeUnit.SECONDS);
}
}
}
return query;
}
}
存在的问题:
当多线程并发的时候,会造成所有线程都要排队等待进入锁,这样速度会很慢
解决方案:
在锁的外面再加一层检测,如果缓存中存在值,就不用等待进锁,直接返回缓存中的值
@Component
public class RedisHelper {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public <T> List<T> cache(String key,Type type,IDBResultCallBack<T> idbResultCallBack){
List<T> query = null;
Gson gson = new Gson();
String str = stringRedisTemplate.boundValueOps(key).get();
query = gson.fromJson(str, type);
// 第一层检测 防止所有线程都要等待进入锁
if (query == null){
// 锁住 避免出现当有人已经查了数据但还没保存的时候另外的人还去查
synchronized (this){
System.out.println("排队进来");
String jsonStr = stringRedisTemplate.boundValueOps(key).get();
gson = new Gson();
query = gson.fromJson(jsonStr, type);
if (query == null){
System.out.println("查数据库啦");
query = idbResultCallBack.callback();
// 将查出的数据存入缓存
if (query != null || !query.isEmpty()){
String toJson = gson.toJson(query, type);
stringRedisTemplate.boundValueOps(key).set(toJson);
} else {
// 当从数据库查出的数据是空的时候 也要存入缓存(避免出现缓存穿透)
// 但是如果大量空的key存入缓存 缓存会不够用 所有将空的数据缓存 设置过期时间
String toJson = gson.toJson(query, type);
stringRedisTemplate.boundValueOps(key).set(toJson,30, TimeUnit.SECONDS);
}
}
}
}
return query;
}
}
Redis淘汰策略
淘汰策略
类似GC回收,只有当内存不足的时候才会触发。默认采用的淘汰策略是allkeys-lru:当内存不足的时候,会优先淘汰最近很少使用的缓存,从而腾出空间。
Redis有6中淘汰策略(3.2.9):
· noeviction(默认):返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
即使内存满了,也不会淘汰内存中的数据。而新增的数据新增不了会返回异常信息。
· allkeys-lru(比较合适): 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
采用LRU算法,计算最近最少使用的key被淘汰,腾出空间,给新的key使用。
· volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
(只会淘汰过期的key)在过期的key集合中,通过LRU计算哪个可以是最早被回收的,优先删除最早被淘汰的key,腾出空间给新的key
· allkeys-random: 回收随机的键使得新添加的数据有空间存放。
· volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
· volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
weixin_44991320 发布了5 篇原创文章 · 获赞 0 · 访问量 32 私信 关注