redis总结--常见问题与解决办法,推荐等级

因为csdn只支持这种文档形式:不支持思维导图:

更好友好的阅读:可以看我的飞书--------思维导图(这样食用更加):


缓存穿透
    问题原因
       每次从缓存中都查不到数据,而需要查询数据库,同时数据库中也没有查到该数据,也没法放入缓存

如何解决
    布隆过滤器
        
        原理:布隆过滤器可以用于检索一个元素是否在一个集合中。
            结构:概率型数据结构

        使用
            方式一:通过自己写算法实现布隆过滤器
            方式二:使用网上存在的开源包

        推荐指数:⭐⭐⭐(这个技术有些过时了,但是可以用)
        优点:算法简单,可以自己配置
        缺点:
            存在一定的误判率
            不支持删除操作
            查找性能比布谷鸟底
            空间利用率低


    布谷鸟过滤器
        要求条件:适用于对误判率要求较高的场景。
            如网络路由、存储系统等

        原理:
            最简单的布谷鸟哈希结构是一维数组结构,会有两个 hash 算法将新来的元素映射到数组的两个位置。如果两个位置中有一个位置为空,那么就可以将元素直接放进去。但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。
            近似集合数据结构

        如何使用
            方式一:自己写一个算法(我试过,比较复杂)
            方式二:调用别人写的库

        情况
            优点:
                相较于布隆过滤器具有更低的误判率
                支持删除操作
                查找性能高
                空间利用率高

            缺点:
                删除不完美,存在误删的概率。
                    删除的时候只是删除了一份指纹副本,并不能确定此指纹副本是要删除的key的指纹。同时这个问题也导致了假阳性的情况。

                插入复杂度比较高
                    随着插入元素的增多,复杂度会越来越高,因为存在桶满,踢出的操作,所以需要重新计算,但综合来讲复杂度还是常数级别。

                存储空间的大小必须为2的指数
                    让空间效率打了折扣

                同一个元素最多插入kb次(k指哈希函数的个数,b指的桶中能装指纹的个数)
                    如果布谷鸟过滤器支持删除,则必须存储同一项的多个副本。 插入同一项kb+1次将导致插入失败。 这类似于计数布隆过滤器,其中重复插入会导致计数器溢出。



        推荐指数:⭐⭐⭐⭐⭐(绝对好用)

    缓存空对象
        方案一
            要求条件:适中
            推荐指数:⭐⭐⭐⭐(很常见的方式)
            设置缓存的时候,同时设置一个过期时间,这样过期之后,就会重新去数据库查询最新的数据并缓存起来

        方案二
            要求条件:实时性要求非常高
            推荐指数:⭐⭐⭐(要结合实际情况,考虑双写一致性的问题)
            如果对的话,那就写数据库的时候,同时写缓存。这样可以保障实时性

        方案三
            要求条件:实时性要求不是那么高
            推荐指数:⭐⭐⭐⭐(非常实用的方式)
            那就写数据库的时候给消息队列发一条数据,让消息队列再通知处理缓存的逻辑去数据库取出最新的数据


     校验参数
        原理:
            我们可以对用户id做检验。(请求合法性校验)
            比如权限管理

        推荐指数:⭐⭐⭐⭐⭐(这个必须做)



缓存击穿
 问题原因
     缓存击穿问题是由于key过期了导致的

如何解决
    加锁
        原理:在缓存失效的瞬间,通过加锁机制来保证只有一个请求能够访问数据库,其他请求等待获取缓存数据。
        使用:
            同一时刻只有一个请求才能访问某个信息
            并且另一个线程将数据库中查询到的结果,又重新放入缓存中

        推荐指数:⭐⭐⭐(非必要不加锁)

    自动续期
        原理:在key快要过期之前,就自动给它续期
        推荐指数:⭐⭐⭐(本来要过期,但是续期的指令如何识别)

    缓存不失效
        原理:对于很多热门key,其实是可以不用设置过期时间,让其永久有效的。
        推荐指数:⭐⭐⭐⭐(正对某些业务可以设置)



缓存雪崩
原因:有多个热门key同时失效(非常严重):归根结底都是有大量的请求,透过缓存,而直接访问数据库了
     有大量的热门缓存,同时失效。会导致大量的请求,访问数据库。而数据库很有可能因为扛不住压力,而直接挂掉。
    缓存服务器宕机了,可能是机器硬件问题,或者机房网络问题。总之,造成了整个缓存的不可用。

解决办法
    过期时间加随机数
        推荐指数:⭐⭐⭐⭐(较为安全)
        要求不要设置相同的过期时间
            即使在高并发的情况下,多个请求同时设置过期时间,由于有随机数的存在,也不会出现太多相同的过期key


    高可用(分布式部署)
        推荐指数:⭐⭐⭐⭐(能在源头处理最好)
        深层原因:针对缓存服务器宕机的情况,
        原理:通过多个节点来分担缓存的压力,提高系统的容灾性
        解决办法:
            在前期做系统设计时,可以做一些高可用架构
            redis可以使用哨兵模式,或者集群模式,避免出现单节点故障导致整个redis服务不可用的情况


    服务降级(备份机制)
        推荐指数:⭐⭐⭐⭐(做一个数据兜底)
        更深的原因:做了高可用架构,redis服务还是挂了,该怎么办呢?
        解决办法:配置一些默认的兜底数据。
            程序中有个全局开关,比如有10个请求在最近一分钟内,从redis中获取数据失败,则全局开关打开。就直接从配置中心中获取默认的数据。
            还需要有个线程,每隔一定时间去从redis中获取数据


    数据预热
        原理:数据预热就是系统上线后,将相关的缓存数据直接加载到缓存系统,这样就可以避免在用户请求的时候先查询数据库
        方式:在系统低峰期,提前对热点数据进行加载和缓存,避免大量数据同时失效
        推荐指数:⭐⭐⭐⭐(不要等高峰了做预案)

    双层缓存策略
        C1为原始缓存,C2为拷贝缓存,C1失效时可以访问C2,C1缓存失效时间设置为短期,C2缓存失效时间设置为长期
        推荐指数:⭐⭐⭐(万一都宕机就完蛋了)

    定时更新缓存策略
        原理:失效性要求不高的缓存,容器启动初始化加载,采用定时任务更新或移除缓存
        使用:另起一个线程,去做任务处理
        推荐指数:⭐⭐⭐⭐⭐(前期预防永远比后期解决来的好)



 双写一致性(redis与mysql)
原因:分布式常见问题
    数据库和缓存双写,就必然会存在不一致的问题
    最终一致性:
        要保证数据库与缓存可以先后一致

    强一致性:
        有强一致性要求的数据,不能放缓存


解决方法:
    更新缓存
        先写缓存,写数据库
            问题程度:上上(很严重)
            问题原因:刚写完缓存,突然网络出现了异常,导致写数据库失败了
            推荐指数:⭐⭐(用的不多)
            造成影响:容易诞生假数据

        先写数据库,在写缓存
            问题情况:写缓存失败了
                问题程度:上 (严重)
                问题原因:写数据库和写缓存,都属于远程操作。(通常建议写数据库和写缓存不要放在同一个事务中)
                    写数据库成功了,但写缓存失败了,数据库中已写入的数据不会回滚

                推荐指数:⭐⭐ ⭐(接口性能要求不太高的系统)
                造成影响:数据库是新数据,而缓存是旧数据,两边数据不一致的情况

            问题情况:高并发先写数据库,在写缓存
                问题程度:上上 (很严重)
                问题原因:请求b在缓存中的新数据,被请求a的旧数据覆盖了。(请求互相覆盖)
                推荐指数:⭐⭐(高并发场景,不适合)
                造成影响:可能会出现数据库是新值,而缓存中是旧值,两边数据不一致的情况

            问题情况:浪费系统资源
                问题程度:上(严重)
                问题原因:每写一次缓存,都需要经过一次非常复杂的计算
                推荐指数:⭐⭐(计算要求低的情况)
                造成影响:浪费cpu和内存资源



    删除缓存
        先删缓存,在写数据库
            高并发下,单删
                问题程度:中上(适度)
                问题原因:请求A删除完缓存数据Data后,网络卡顿,写入数据库操作暂停。请求B读数据Data,缓存中没有,去数据库读,读的是旧数据。请求A网络好了,写入新数据到数据库
                推荐指数:⭐⭐⭐(低并发下)
                造成影响:请求A的新值并没有被请求B写入缓存,同样会导致缓存和数据库的数据不一致的情况

            高并发下,缓存双删
                问题程度:中下 (良好)
                问题原因:写数据库之前删除缓存一次,写完数据库后,再删除缓存一次
                推荐指数:⭐⭐⭐⭐(看情况推荐使用)
                使用要点:第二次删除缓存,并非立马就删,而是要在一定的时间间隔之后。
                    原因:在另一个请求未生效前,删除这就没有意义了

                造成影响:第二次删除可能失败
                    解决办法:重试机制
                        原理:更新了数据库成功了,但更新缓存失败了,可以立刻重试N次。如果其中有任何一次成功,则直接返回成功。如果N次都失败了,则写入数据库,准备后续再处理。
                        同步重试
                            推荐指数:⭐⭐⭐()
                            原因:接口并发量比较高的时候,可能有点影响接口性能

                        异步重试
                            推荐指数:⭐⭐⭐⭐⭐
                            方式一:每次都单独起一个线程,该线程专门做重试的工作
                                推荐指数:⭐⭐⭐
                                原因:在高并发的场景下,可能会创建太多的线程,导致系统OOM问题

                            方式二:将重试的任务交给线程池处理
                                推荐指数:⭐⭐⭐⭐
                                原因:如果服务器重启,部分数据可能会丢失

                            方式三:将重试数据写表存入数据库(定时任务)
                                推荐指数:⭐⭐⭐⭐(不适合实时性要求特别高的业务场景)
                                使用:定时任务进行重试
                                    重试表(至少得有一下字段):
                                        重试次数字段
                                        是否成功的状态字段

                                    原理:设置初始值,每次删除重试次数字段加1,只要任意有一次成功就返回成功,同时修改状态,等待后续进一步处理

                                缺点:实时性没那么高
                                优点:数据是落库的,不会丢数据

                            方式四:将重试的请求写入mq等消息中间件
                                推荐指数:⭐⭐⭐⭐⭐(实时性还是比较高的)
                                使用:mq的consumer中处理
                                    原理:
                                        操作写完数据库,但删除缓存失败了,产生一条mq消息,发送给mq服务器
                                        mq消费者读取mq消息,如果其中有任意一次成功了,则返回成功。重试N此后还是失败,写入死信队列中,后续需要人工处理

                                    优点:mq的实时性还是比较高的
                                    优化:删除缓存可以完全走异步。即用户的写操作,在写完数据库之后,不用立刻删除一次缓存。而直接发送mq消息,到mq服务器,然后有mq消费者全权负责删除缓存的任务。


                            方式五:订阅mysql的binlog
                                推荐指数:⭐⭐⭐⭐⭐(强烈推荐)
                                原理:订阅者中,如果发现了更新数据请求,则删除相应的缓存(监听binlog)
                                使用:canal等中间件,实现
                                    原理:业务接口中写数据库之后,就不管了,直接返回成功,mysql服务器会自动把变更的数据写入binlog中,binlog订阅者获取变更的数据,然后删除缓存
                                    问题:也会删除失败
                                        解决办法:重试机制
                                            推荐使用方式三和方式四(当推荐方式四)









        先写数据库,再删缓存
            问题程度:下上 (优秀)
            问题原因:缓存过期失效,请求A查询数据发现缓存没有,去数据库查询,但是网络原因,没有及时更新缓存。请求B先写数据库,然后删除缓存。请求A恢复,更新缓存。
                诞生条件(同时满足概率小)
                    缓存刚好自动失效
                    请求A查询数据库旧值以及更新缓存数据的时间,比请求B写入数据库以及删除缓存数据的时间长
                        一般数据库查询比数据库写入时间短(那种复合,聚合查询就不好说了)



            推荐指数:⭐⭐⭐⭐⭐ (推荐使用)
            使用指南:请求查询不要太过复杂
            造成影响:最差的情况就是读取到旧数据(问题不算太大,刷新一下,重新请求,数据就对了),删除可能失效
                解决办法:重试机制
                    原理:更新了数据库成功了,但更新缓存失败了,可以立刻重试N次。如果其中有任何一次成功,则直接返回成功。如果N次都失败了,则写入数据库,准备后续再处理。
                    同步重试
                        推荐指数:⭐⭐⭐(适合低并发)
                        原因:接口并发量比较高的时候,可能有点影响接口性能

                    异步重试
                        推荐指数:⭐⭐⭐⭐⭐(常见操作)
                        方式一:每次都单独起一个线程,该线程专门做重试的工作
                            推荐指数:⭐⭐⭐
                            原因:在高并发的场景下,可能会创建太多的线程,导致系统OOM问题

                        方式二:将重试的任务交给线程池处理
                            推荐指数:⭐⭐⭐⭐
                            原因:如果服务器重启,部分数据可能会丢失

                        方式三:将重试数据写表存入数据库(定时任务)
                            推荐指数:⭐⭐⭐⭐(不适合实时性要求特别高的业务场景)
                            使用:定时任务进行重试
                                重试表(至少得有一下字段):
                                    重试次数字段
                                    是否成功的状态字段

                                原理:设置初始值,每次删除重试次数字段加1,只要任意有一次成功就返回成功,同时修改状态,等待后续进一步处理

                            缺点:实时性没那么高
                            优点:数据是落库的,不会丢数据

                        方式四:将重试的请求写入mq等消息中间件
                            推荐指数:⭐⭐⭐⭐⭐(实时性还是比较高的)
                            使用:mq的consumer中处理
                                原理:
                                    操作写完数据库,但删除缓存失败了,产生一条mq消息,发送给mq服务器
                                    mq消费者读取mq消息,如果其中有任意一次成功了,则返回成功。重试N此后还是失败,写入死信队列中,后续需要人工处理

                                优点:mq的实时性还是比较高的
                                优化:删除缓存可以完全走异步。即用户的写操作,在写完数据库之后,不用立刻删除一次缓存。而直接发送mq消息,到mq服务器,然后有mq消费者全权负责删除缓存的任务。


                        方式五:订阅mysql的binlog
                            推荐指数:⭐⭐⭐⭐⭐(强烈推荐)
                            原理:订阅者中,如果发现了更新数据请求,则删除相应的缓存(监听binlog)
                            使用:canal等中间件,实现
                                原理:业务接口中写数据库之后,就不管了,直接返回成功,mysql服务器会自动把变更的数据写入binlog中,binlog订阅者获取变更的数据,然后删除缓存
                                问题:也会删除失败
                                    解决办法:重试机制
                                        推荐使用方式三和方式四(当推荐方式四)











缓存的并发竞争
原因:多个redis的client同时set key引起的并发问题
    多客户端同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺序变成了4,3,2,最后变成了2。

解决方案:
    乐观锁
        推荐指数:⭐⭐⭐(乐观锁适用于大家一起抢着改同一个key)
        使用:watch 命令可以方便的实现乐观锁
        缺点:如果 redis 使用了数据分片的方式,那么这个方法就不适用了
            什么是数据分片?
                原理:Redis的分片机制允许数据拆分存放在不同的Redis实例上,每个Redis实例只包含所有键的子集
                优点:可以减轻单台Redis的压力,提升Redis扩展能力和计算能力



    分布式锁+时间戳
        原理:加锁的目的实际上就是把并行读写改成串行读写的方式,从而来避免资源竞争
            用一个状态值表示锁,对锁的占用和释放通过状态值来标识

        加锁使用方法:
            使用:使用redis中setnx()函数
                返回1,则客户端获得锁,把锁的键值设置为时间值,表示该键已经被锁定,可以通过DEL lock.foo来释放该锁
                返回 0 ,则表明该锁已经被其他客户端取得,这时返回重试等待对方完成,或者等待锁超时,在或者返回

            推荐指数:⭐⭐⭐⭐(适合分布式环境)
            优点:不用关心 redis 是否为分片集群模式

        时间戳使用:(适合有序场景)
            原理:系统B先抢到锁,将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早于缓存中的时间戳(7:00<7:05),那就不做set操作了
            推荐指数:⭐⭐⭐(适合对于有序情况)
            优点:业务处理有序


    消息队列
        原理:把Redis.set操作放在队列中使其串行化,及一个一个执行(较为通用的解决方案)
        推荐指数:⭐⭐⭐⭐(高并发场景中)
        优点:串行化

你可能感兴趣的:(中间件,redis,数据库,缓存)