Redis常见面试题
Redis知识框架:
1. 缓存穿透
缓存穿透
:去redis查询一个不存在
的数据,数据库查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库。
1.1 缓存空数据
缓存空数据
,查询返回的数据为空,仍把这个空结果进行缓存;
优点:简单;
缺点:消耗内存,可能会导致数据不一致
的问题;
1.2 布隆过滤器
布隆过滤器
,当一个查询请求过来时,先经过布隆过滤器进行判断,如果判断请求查询值存在,则继续查;如果判断请求查询不存在,直接丢弃;
优点:内存占用较少,没有多余key;
缺点:实现复杂,可能存在误判
;
布隆过滤器底层是bitmap(位图)
:相当于是一个以(bit)位为单位的数组,数组中每个单元只能存储二进制数0或1
布隆过滤器作用:布隆过滤器可以用于检索一个元素是否在一个集合中
。
误判率:数组越小误判率就越大,数组越大误判率就越小,但是同时带来了更多的内存消耗。
应用Redis的布隆过滤器:
/**
* 根据主键id查询
* Cacheable注解是spring缓存注解,在方法执行前 Spring 会先查看缓存中是否有key,如果有key,则直接返回缓存数据;
* 若没有key,调用方法并将方法返回值放到缓存中。
* condition设置当查询结果不为null时生效;
* cacheNames设置默认前缀,完整key为article:id
*
* @param id 主键id
* @return 文章实体
*/
@Cacheable(cacheNames = "article", key = "#id", condition = "#result!=null")
@Override
public Article getById(Long id) {
// bloomFilter中不存在该key,为非法访问
if (!bloomFilter.contains(id)) {
/**
* condition = "#result!=null"并在非法访问的时候返回null的目的是不将该次查询返回的null使用
* 所以我们需要在缓存中添加一个可容忍的短期过期的null或者是其它自定义的值,使得短时间内直接读取缓存中的该值。
*/
long ttl = new Random().nextInt(500) + 1000; // 随机过期时间
Article article = new Article();
stringRedisTemplate.opsForValue().set("article:" + id, JSONUtil.toJsonStr(article), ttl, TimeUnit.SECONDS);
return null;
}
// 不是非法访问,可以访问mysql数据库
Article article = this.lambdaQuery().eq(Article::getId, id).one();
return article;
}
以下为测试Redisson
的布隆过滤器代码:
package com.example.redisson;
import com.example.redisson.domain.po.Article;
import com.example.redisson.service.IArticleService;
import com.example.redisson.util.BloomFilterUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest(classes = RedissonDemoApplication.class)
@Slf4j
class RedissonDemoApplicationTests {
static long expectedInsertions = 2000L; // 预期插入数量
static double falseProbability = 0.01; // 误判率
@Autowired
private RedissonClient redissonClient;
@Autowired
private BloomFilterUtil bloomFilterUtil;
@Autowired
private IArticleService articleService;
private RBloomFilter<Long> bloomFilter = null;
/**
* 初始化布隆过滤器
*/
public void init() {
log.info("开始初始化布隆过滤器...");
// 查询数据库,用于启动项目时初始化bloomFilter
List<Article> articleList = articleService.list();
// 创建布隆过滤器,参数依次为:过滤器名称、预测插入数量和误判率
bloomFilter = bloomFilterUtil.create("idBloomFilter", expectedInsertions, falseProbability);
for (Article article : articleList) {
bloomFilter.add(article.getId());
}
log.info("初始化布隆过滤器完成...");
}
@Test
void test() {
init(); // 初始化布隆过滤器
long count = 0;
long totalChecks = 2000; // 测试数据共2000个,1000真1000假,计算误判率
// 测试数据id从1-1000是真实存在的,会初始化到布隆过滤器;id大于1000不存在,检测误判率
for (long i = 1; i <= totalChecks; i++) {
if ((i <= 1000 && !bloomFilter.contains(i))
|| (i > 1000 && bloomFilter.