zoukankan      html  css  js  c++  java
  • 面试知识点总结之redis

    redis持久化方式:

      1. RDB:RDB持久化机制,是堆redis中的数据执行周期性的持久化

      2. AOF:AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集

    redis的6中数据淘汰策略:redis内存数据集大小上升到一定大小,实行数据淘汰策略

      1. volatile-lru:从一设置过期时间的数据集(server,db[i].expires)中挑选最近最少使用的数据淘汰

      2. volatile-ttl:挑选将要过期的数据淘汰

      3. volatile-random:任意选择数据淘汰

      4. allkeys-lru:从数据集(server.db[].dict)中挑选最近最少使用的数据淘汰

      5. allkeys-random:从数据集(server.db[].dict)中挑选任意选择数据淘汰

      6. no-enviction():禁止驱逐数据(默认配置)

    redis缓存穿透,缓存击穿,缓存雪崩:

    缓存系统过程:前台请求,后台先从缓存中取出数据,渠道直接返回结果,取不到从数据库中取,数据库取到更新缓存,并返回结果,数据库没取到,直接返回空结果;

    缓存穿透:指缓存和数据库中都没有数据,用户不断发起请求。流量大时,DB可能挂掉,要是有人用不存在的key频繁攻击,就是漏洞,如发起’id = -1‘或者id特别大

    解决方案:接口增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;从缓存取不到的数据在数据库也没有取到,可将key-value对写成key-null,缓存有效时间设置短,如30秒,这样可以防止同一个id暴力攻击。

    缓存击穿:缓存击穿指缓存中没有但数据库中有的数据(一般时缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时取数据库取数据,引起数据库压力瞬间增大,造成过大压力。

    解决方案:

    1. 设置热点数据永远不过期(并不是很好的变法)

    2. 接口限流与降级,熔断;重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要做好降级准备,当接口中某些服务不可用时候,进行熔断,失败快速返回机制

    3. 布隆过滤器。bloomfilter类似于一个hashset,用于快速判断某个元素是否存在于集合中,其典型应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键在于hash算法和容器大小

    4. 加互斥锁:缓存中有数据直接返回,缓存中没数据,第一个进入的线程,获取锁并从数据库获取数据,没释放锁之前其他并行进入的线程会等待100MS,重新去缓存取数据,这样就防止都去数据库重复取数据,重复往缓存中更新数据的情况出现。

    缓存雪崩:是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机,和缓存穿透不同的是,缓存击穿指并发查同一条数据,缓存雪崩式不同数据都过期了,很多数据都查不到从而查数据库

    解决方案:

    1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生

    2. 如果缓存数据库式分布式部署,将热点数据均匀分布在不同缓存数据库中

    3. 设置热点数据永远不过期

    redis与mysql如何保证数据的一致性

    如果不是严格要求“缓存和数据库”必须保持一致性的话,最好不要做这个方案,即读请求和写请求串行化,串到一个内存队列里面去,串行化可以保证一定不会出现不一致的情况,但会导致系统吞吐量大幅度降低。

    Cache Aside Pattern

      1. 读的时候先读缓存,如果缓存不存在的话就读数据库,取出数据库后更新缓存;如果存在的话直接读取缓存的信息。

      2. 写的时候,先更新数据库,再删除缓存,说到这个问题,会出现①为什么是删除缓存,而不是更新缓存②为什么是先更新数据库,再删除缓存,不是先删除缓存,再更新数据库?

    写的时候为什么是删除缓存而不是更新缓存?

    很多时候复杂的缓存场景,缓存不是仅仅从数据库中取出来值。可能是关联多张表的数据并通过计算才是缓存需要的值。并且,更新缓存的代价有时候很高。对于需要频繁写操作,而读操作很少的时候,每次进行数据库的修改,缓存也要随之更新,会造成系统吞吐的下降,但此时缓存并不会频繁访问到,用到的缓存才去算缓存。

      删除缓存而不是更新缓存时一种懒加载的思想,不是每次都重复更新缓存,只有用到的时候才去更新缓存,同时即使有大量的读请求,实际也就更新了一次,后面的请求不会重复读。

    Cache Aside Pattern存在的问题

    问题:先更新数据库,再删除缓存,如果更新缓存失败了,导致数据库中是新数据,缓存中是旧数据,就出现数据不一致的问题。

    解决思路:先删除缓存,再更新数据库

    缓存删除失败:如果缓存删除失败,那么数据库信息没有被修改,保持了数据一致性

    缓存删除成功,数据库更新失败,此时数据库里的是旧数据,缓存是空的,查询时发现缓存不存在,就查数据库并更新缓存,数据保持一致性

    问题:上面的方案存在不足,如删除完缓存更新数据库时,如果一个请求过来查询数据,缓存不存在,就查询数据库的旧数据,更新旧数据到缓存中。随后数据更新完成,修改了数据库的数据,此时不采用给缓存设置过期时间策略,该数据永远都是脏数据。

    解决方案:采用双删除策略。写请求先删除缓存,再去更新数据库,等待一段时间后异步删除缓存,这样可以保证在读取错误数据时能及时被修正过来。

    还有一种策略就是:写请求先修改缓存为指定值,然后再去更新数据库,再更新缓存。读请求过来后,先读缓存,判断是指定值后就进入循环读取状态,等到写请求更新缓存。如果循环超时就取数据库读取数据,更新缓存。这种方案保证了读写的一致性,但由于读请求等待写请求的完成,会降低系统的吞吐量。

    缓存穿透击穿与雪崩实现代码:

    (1)使用的是Google的Bloom Filter
    <1>引入依赖
    <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    </dependency>
    (2)使用双重验证锁解决高并发环境下的缓存穿透问题
    @Service
    public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentMapper studentMapper;
    //springboot自动配置的,直接注入到类中即可使用
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    /**
    * 查询所有学生信息,带有缓存
    * @return
    */
    public List<Student> getAllStudent() {
    //在高并发条件下,会出现缓存穿透
    List<Student> studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent");
    if (null == studentList) {
    //5个人, 4个等,1个进入
    synchronized (this) {
    //双重检测锁,假使同时有5个请求进入了上一个if(null == studentList),
    //加了锁之后one by one 的访问,这里再次对缓存进行检测,尽一切可能防止缓存穿透的产生,但是性能会有所损失
    studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent");
    if (null == studentList) {
    studentList = studentMapper.getAllStudent();
    redisTemplate.opsForValue().set("allStudent", studentList);System.out.println("请求的数据库。。。。。。");} else {//System.out.println("请求的缓存。。。。。。");
    }}} else {System.out.println("请求的缓存。。。。。。");
    }return studentList;}}


    (1)加互斥锁,互斥锁参考代码如下:
    static Lock reenLock = new ReentrantLock();
    public List<String> getData04() throws InterruptedException {
    List<String> result = new ArrayList<String>();
    // 从缓存读取数据
    result = getDataFromCache();
    if (result.isEmpty()) {
    if (reenLock.tryLock()) {
    try {
    System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
    // 从数据库查询数据
    result = getDataFromDB();
    // 将查询到的数据写入缓存
    setDataToCache(result);
    } finally {
    reenLock.unlock();// 释放锁
    }
    } else {
    result = getDataFromCache();// 先查一下缓存
    if (result.isEmpty()) {
    System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
    Thread.sleep(100);// 小憩一会儿
    return getData04();// 重试
    }
    }
    }
    return result;
    }
  • 相关阅读:
    @@IDENTITY,SCOPE_IDENTITY和IDENT_CURRENT的辨析
    Blue Jeans[poj3080]题解
    绿色通道题解
    后缀数组
    Power Strings[poj2406]题解
    KMP算法
    Life Forms[poj3294]题解
    STM32固件库文件编程结构思想的理解
    GPIO设置
    HTML_v2
  • 原文地址:https://www.cnblogs.com/cgy-home/p/14525989.html
Copyright © 2011-2022 走看看