目录
前言:
在项目中,我们通常为了高并发和高性能,通常会使用缓存来实现。而常见的缓存工具就是Redis了,在使用Redis的过程中必不可少的会涉及到三个问题,那么接下来就让我们一起来回顾一下。
Redis工具类:
package com.powershop.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* redisTemplate封装
*/
@Component
public class RedisClient {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long ttl(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public Boolean exists(String key){
return redisTemplate.hasKey(key);
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
public Boolean del(String key){
return redisTemplate.delete(key);
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().decrement(key, -delta);
}
//================================hash=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key,String item,Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> smembers(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sadd(String key, Object...values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long srem(String key, Object ...values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lrange(String key, long start, long end){
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean rpush(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lpush(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lrem(String key,long count,Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 分布式锁
* @param key
* @param value
* @return
*/
public Boolean setnx(String key, Object value) {
try {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
缓存穿透
描述:
缓存穿透是指在Redis缓存和数据库中都没有相应的数据,用户不断发起对应请求,而这些请求就会穿过缓存直接访问数据库。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。
解决办法:
缓存空对象:当在数据库查询不到数据时,创建空对象放入缓存中,同时设置一个过期时间(避免空值占用太多的存储空间),之后在访问这个数据就会从缓存中取出,保护了后端数据库的安全。
代码:
public TbItem selectItemInfo(Long itemId) {
//查询缓存
TbItem tbItem = (TbItem) redisClient.get("ITEM_INFO "+ ":" + itemId + ":"+ BASE);
if(tbItem!=null){
return tbItem;
}
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
/********************解决缓存穿透************************/
if(tbItem == null){
//把空对象保存到缓存
redisClient.set("ITEM_INFO "+ ":" + itemId + ":"+ "BASE",new TbItem());
//设置缓存的有效期
redisClient.expire("ITEM_INFO "+ ":" + itemId + ":"+ "BASE",30);
return tbItem;
}
//把数据保存到缓存
redisClient.set("ITEM_INFO "+ ":" + itemId + ":"+ "BASE",tbItem);
//设置缓存的有效期
redisClient.expire("ITEM_INFO "+ ":" + itemId + ":"+ "BASE",86400);
return tbItem;
}
缓存击穿
描述:
缓存击穿,指一个key为热点key,在高并发的情况下,这个热点key突然失效,在失效的瞬间大量的请求直接穿破缓存直接访问数据库,就像在有屏障的情况下穿破了一个洞。
解决办法:
1)设置热点数据永不过期
2)使用分布式锁
代码:
public TbItem selectItemInfo(Long itemId) {
/*****************不能修改原有的逻辑*****************/
//1、先查redis,如果有直接return
TbItem tbItem = (TbItem) redisClient.get("ITEM_INFO" + ":" + itemId + ":" + "BASE");
if(tbItem != null){
return tbItem;
}
/*****************解决缓存击穿**********************/
if(redisClient.setnx("SETNX_LOCK_BASE"+":"+itemId,itemId)) {
try {
//2、再查mysql,并缓存到redis再rerun
tbItem = tbItemMapper.selectByPrimaryKey(itemId);
//解决缓存穿透问题
if (tbItem == null) {
tbItem = new TbItem();
redisClient.set("ITEM_INFO"+ ":" + itemId + ":" + "BASE", tbItem);
redisClient.expire("ITEM_INFO"+ ":" + itemId + ":" + "BASE", 30);
return tbItem;
}
redisClient.set("ITEM_INFO"+ ":" + itemId + ":" + "BASE", tbItem);
redisClient.expire("ITEM_INFO"+ ":" + itemId + ":" + "BASE", 86200);
}catch (Exception e) {
e.printStackTrace();
}finally {
//釋放鎖
redisClient.del("SETNX_LOCK_BASE" + ":" + itemId);
}
}else{
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
selectItemInfo(itemId);
}
return tbItem;
}
缓存雪崩
描述:
缓存雪崩指在某一个时间段内,缓存数据集中过期失效。
解决方案:
1)设置热点数据永不过期
2)不同的数据设置不同的过期时间