缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
指的是在短时间内,大量的键过期导致请求直接打到数据库,或者是Redis宕机,导致请求直接打到数据库,而数据库无法处理如此大量的请求,导致数据库宕机,进而造成整个系统或者服务的不可用。牵一发而动全身。
解决方案:
缓存击穿就是单个热点key突然失效或者过期,导致大量的请求未命中Redis之后打在数据库上,导致数据库压力剧增
解决方案
缓存穿透指的是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本
没有经过缓存这⼀层,并且在数据库中也没有该跳数据,导致也没有继续回写到缓存中,导致大量请求直接打到数据库层面
解决方案:
想要尽量避免缓存穿透,一个办法就是对数据进行预校验,在对Redis和数据库进行操作前,先检查数据是否存在,如果不存在就直接返回。如果我们想要查询一个元素是否存在,要保证查询效率,可以选择HashSet,但是如果有10亿个数据,都用HashSet进行存储,内存肯定是无法容纳的。这时就需要布隆过滤器了
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(bit数组)和一系列随机映射函数(hash)。布隆过滤器可以用于检索一个元素是否在一个集合中
特点:
① 初始化
布隆过滤器 本质上 是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0
当我们向布隆过滤器中添加数据时,为了尽量地址不冲突,会使用多个 hash 函数对 key 进行运算,算得一个下标索引值,然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
例如,我们添加一个字符串wmyskxz
向布隆过滤器查询某个key是否存在时,先把这个 key 通过相同的多个 hash 函数进行运算,查看对应的位置是否都为 1,
只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在;
如果这几个位置全都是 1,那么说明极有可能存在;
为什么说极有可能呢?
因为这些位置的 1 可能是因为其他的 key 存在导致的,也就是hash冲突
就比如我们在 add 了字符串wmyskxz数据之后,很明显1/3/5 这几个位置的 1 ,如上图,是因为第一次添加的 wmyskxz 而导致的;此时我们查询一个没添加过的不存在的字符串inexistent-key,它有可能计算后坑位也是1/3/5 ,这时候经过hash函数计算以及查询发现1 /3 / 5 的位置都是1,所以布隆过滤器认为该数据是存在的,就发生了误判
因为该key经过运算放入bit数组中的值,并不一定是你产生的,有可能其他数值也共用着这个数位,如果你删除了该位置,别的key进行查询时可能导致查询失败
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>23.0version>
dependency>
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:模拟秒杀,演示布隆过滤器业务逻辑比较简单
* @Date 2021/6/20 17:26
* @@author: A.iguodala
*/
@RestController
public class SeckillGuavaBloomFilterDemoController implements InitializingBean {
/**
* 模拟秒杀服务,真实中用自动注入
* @Autowired
*/
private SeckillService seckillService = new SeckillService();
@Autowired
private RedisTemplate redisTemplate;
/**
* 布隆过滤器
* 三个参数 (Funnel super T> funnel, long expectedInsertions, double fpp)
* Funnel super T> funnel : 数据类型 (long 保存商品ID)
* long expectedInsertions : 希望插入值的个数
* double fpp : 错误率,不设置默认为0.03 ,过小的话误判太多,造成数据库接受到大量请求,过大数组长度过长效率降低
*/
BloomFilter<Long> filter = BloomFilter.create(Funnels.longFunnel(), 100,0.03);
/**
* 模拟获取商品详情请求
* @param goodsId
* @return
*/
@RequestMapping(value = "/toDetail/{goodsId}")
public Map<String, Object> toDetail(@PathVariable(value = "goodsId") Long goodsId) {
Map<String, Object> map = new HashMap<>();
/**
* 先判断该商品是存在,存在则查询缓存或者数据库,不存在直接返回
*/
if (filter.mightContain(goodsId)) {
SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.opsForValue().get("goods:" + goodsId);
if (seckillGoods == null) {
seckillGoods = seckillService.get();
redisTemplate.opsForValue().set("goods:" + goodsId, seckillGoods);
}
map.put("data", seckillGoods);
map.put("code", 200);
return map;
}else {
map.put("code", 400);
return map;
}
}
/**
* 缓存预热,将秒杀商品加入到redis以及boolmFilter中
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
List<SeckillGoods> goodsList = seckillService.list();
if (CollectionUtils.isEmpty(goodsList)) {
return;
}
// 循环将数据加入过滤器
goodsList.forEach(goods -> {
filter.put(goods.getId());
});
}
}
/**
* 模拟秒杀服务
*/
class SeckillService {
/**
* 模拟查询所有商品
* @return
*/
List<SeckillGoods> list() {
return null;
}
/**
* 模拟查询商品
* @return
*/
SeckillGoods get() {
return null;
}
}
/**
* 模拟秒杀商品
*/
@Data
class SeckillGoods {
/**
* 商品ID
*/
private Long id;
}
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.13.4version>
dependency>
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:模拟秒杀获取商品详情功能
* @Date 2021/6/20 22:27
* @@author: A.iguodala
*/
@RestController
public class SeckillRedissonBloomFilterDemoController implements InitializingBean {
/**
* 操作redisson客户端(jedis)
*/
static RedissonClient redissonClient = null;
/**
* redis版内置的布隆过滤器
*/
static RBloomFilter rBloomFilter = null;
/**
* 模拟秒杀服务,真实中用自动注入
* @Autowired
*/
private SeckillService seckillService = new SeckillService();
@Autowired
private RedisTemplate redisTemplate;
static
{
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
// 集群版
//config.useClusterServers().addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:6379");
//构造redisson
redissonClient = Redisson.create(config);
//通过redisson构造rBloomFilter
rBloomFilter = redissonClient.getBloomFilter("seckillGoodsBloomFilter",new StringCodec());
rBloomFilter.tryInit(10000,0.03);
}
/**
* 模拟获取商品详情请求
* @param goodsId
* @return
*/
@RequestMapping(value = "/toDetail/{goodsId}")
public Map<String, Object> toDetail(@PathVariable(value = "goodsId") Long goodsId) {
Map<String, Object> map = new HashMap<>();
/**
* 先判断该商品是存在,存在则查询缓存或者数据库,不存在直接返回
*/
if (rBloomFilter.contains(goodsId)) {
SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.opsForValue().get("goods:" + goodsId);
if (seckillGoods == null) {
seckillGoods = seckillService.get();
redisTemplate.opsForValue().set("goods:" + goodsId, seckillGoods);
}
map.put("data", seckillGoods);
map.put("code", 200);
return map;
}else {
map.put("code", 400);
return map;
}
}
/**
* 缓存预热,将秒杀商品加入boolmFilter中
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
List<SeckillGoods> goodsList = seckillService.list();
if (CollectionUtils.isEmpty(goodsList)) {
return;
}
// 循环将数据加入过滤器
goodsList.forEach(goods -> {
rBloomFilter.add(goods.getId());
});
}
}
/**
* 模拟秒杀服务
*/
class SeckillService {
/**
* 模拟查询所有商品
* @return
*/
List<SeckillGoods> list() {
return null;
}
/**
* 模拟查询商品
* @return
*/
SeckillGoods get() {
return null;
}
}
/**
* 模拟秒杀商品
*/
@Data
class SeckillGoods {
/**
* 商品ID
*/
private Long id;
}
安装一个redis的插件rebloom,可以通过命令行来对redis操作布隆过滤器,比较简单就不演示了