redis问题集合

缓存雪崩,缓存穿透与缓存击穿

一、缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 给每一个缓存数据增加响应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。耗费cpu比较大
  3. 缓存预热,系统初启动,不对外服务,先查数据,加载数据到缓存
  4. 互斥锁,锁一个方法或者锁一个key,第一个获取锁,查到数据后,数据加载到缓存,然后剩余的都到缓存中取数据。
    将 key 的值设为 value,当且仅当 key 不存在。
    若给定的 key 已经存在,则 SETNX 不做任何动作。
    SETNX 是SET if Not eXists的简写。
    返回值:
    返回整数,具体为
    1,当 key 的值被设置
    0,当 key 的值没被设置
    在这里插入图片描述

二、缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
  3. 使用布隆过滤器

三、缓存击穿
描述: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:设置热点数据永远不过期。加互斥锁。

缓存过期的策略

缓存过期策略:
定时过期: 每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即删除,该策略可以立即清除过期的数据,对内存友好,但是会占用大量CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量

惰性过期:当有查询时,发现查询的key已过期,再进行删除该策略可是最大化的节省CPU资源,但是会消耗内存,许多的过期数据都还存在内存中,极端情况出现大量的过期key没有再次访问,从而不会被清除,占用大量内存

定期过期:将所有设置了过期策略的key存储在一个独立的字典中,然后每秒扫描10次,每次扫描随机选择20个,将其中已经过期的数据删除,如果删除的数据超过了1/4,那再重复扫描删除步骤。该策略是定时过期和惰性过期的折中方案,通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得cpu和内存资源达到最优的中间效果。

常见的缓存淘汰算法

FIFO,先进先出,根据缓存被存储的时间,最先放进缓存的数据最先被淘汰
LRU:最近最少使用算法
LFU:使用频率最少算法
内存淘汰策略
(1)noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
(2)allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
(3)volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
(4)allkeys-random:加入键的时候如果过限,从所有key随机删除
(5)volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
(6)从配置了过期时间的键中驱逐马上就要过期的键
(7)从所有配置了过期时间的键中驱逐使用频率最少的键
(8)从所有键中驱逐使用频率最少的键

Redis中的LRU与常规的LRU实现并不相同,常规LRU会准确的淘汰掉队头的元素,但是Redis的LRU并不维护队列,只是根据配置的策略要么从所有的key中随机选择N个(N可以配置),要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。

如何保证数据库和缓存的数据一致性

1、先更新数据库,再更新缓存,缓存可能更新失败,读到老数据。
例如,同时有线程A和线程B对数据进行更新操作,可能会出现下面的执行顺序。
(1) 线程A更新了数据库 (2) 线程B更新了数据库 (3) 线程B更新了缓存 (4) 线程A更新了缓存
此时就会出现数据库中的数据与缓存的数据不一致的情况,这是因为线程A先更新了数据库,可能因为网络等异常情况,线程B更新完数据库进而更新了缓存,当线程B更新完缓存后,线程A才更新缓存,这就导致了数据库数据与缓存数据的不一致。

首先一个业务场景就是数据库写多读少的场景,这种场景下采用先更新数据库再更新缓存的策略,就会导致缓存并未被读取就会被频繁的更新,极大的浪费了服务器的性能。

在一个业务场景就是数据库中的数据不是直接写入缓存的,而是需要大量的复杂运算,将运算结果写入缓存。如果这种场景下使用先更新数据库再更新缓存的策略,也会造成服务器资源的浪费。

2、先删除缓存再更新数据库
先删除缓存再更新数据库的方案也存在着线程安全的问题,例如,线程A更新缓存,同时,线程B读取缓存的数据。可能会出现下面的执行顺序。
(1) 线程A删除缓存
(2) 线程B查询缓存,发现缓存中没有想要的数据
(3) 线程B查询数据库中的旧数据
(4) 线程B将查询到的旧数据写入缓存
(5) 线程A将新数据写入数据库
此时,就出现了数据库中的数据和缓存中的数据不一致的情况。如果删除缓存失败,也会出现数据库数据和缓存数据不一致的现象。

3、先更新数据库再删除缓存
首先,这种方式也有极小的概率发生数据库数据和缓存数据不一致的情况,例如,线程A做查询操作,线程B执行更新操作,其执行的顺序如下所示。
(1)缓存刚好失效
(2)请求A查询数据库,获取到数据库中的旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
如果上述顺序一旦发生,就会造成数据库中的数据和缓存中的数据不一致的情况发生。

4、延时双删
先删除缓存,再更新数据库,休眠1s,再次删除缓存。写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可,这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的脏数据,并发还是可能读到旧值覆盖缓存。

5、终极方案
将访问操作串行化
1、先删除缓存,将更新数据库的操作方剂有序队列中
2、从缓存查不到的查询操作,都进入有序队列
会面临的问题:
1、读请求挤压,大量超时,导致数据库的压力:限流,熔断
2、如何保证大量请求积压:将队列水平拆分,提高并行度
3、保证相同请求路由正确

什么是服务降级,什么是熔断

降级是解决系统资源不足和海量业务请求之间的矛盾
在暴增的流浪请求下,对一些非核心业务流程业务,非关键业务,进行有策略的放弃,以此来释放系统资源,保证核心业务的正常运行,尽量避免这种系统资源分配的不平衡,打破二八策略,让更多的及其资源,承载主要的业务请求。服务降级不是一个常态策略,而是应对非正常情况下的应急策略。服务降级的结果,通常是对一些业务请求,返回一个统一的结果,可以理解为是一种failOver快速失败的策略,一般通过配置中心配置开关实现开启降级。

高并发场景下如何实现系统限流

限流一般需要结合容量规划和压测来进行,当外部请求接近或者达到系统的最大阈值时,触发限流,采取其他的手段进行降级,保证系统不被压垮。常见的降级策略包括延迟处理,拒绝服务,随机拒绝等。

技术器法:
1、将时间划分为固定的窗口大小,例如1s
2、在窗口时间内,每来一个请求,对计数器增加1
3、当计数器达到设定限制后,该窗口时间内的之后的请求都被丢弃处理
4、该窗口时间结束后,计数器清零,从新开始计数
问题:1s内前90%时间闲置,请求全部都在后10%时间内到达,导致压力过大

滑动窗口计数法:
1、将时间划分为学习力度的区间,每个区间维持一个计数器,每进入一个请求则将计数器加一。
2、多个区间组成一个时间窗口,每流逝一个区间,纳入新区建
3、若当窗口的区间计数总和超过设定的限制数量,则本窗口内的后续请求都被丢弃

漏桶算法:如果外部请求超过当前阈值,则会在容器里积蓄,一直到溢出,系统不关心溢出的流量。从出口处限制请求速率,并不存在计数器算法的临界问题,请求曲线始终是平滑的。无法应对突发流量,相当于一个空桶+固定处理线程

令牌桶算法:假设一个大小恒定的桶,这个桶的容量和设定的阈值有关,桶里放着很多令牌,通过一个固定的速率,往里面放入令牌,如果桶满了,就把令牌丢掉,最后桶中可以保存的最大令牌数永远不会超过桶的大小,当有令牌进入时,就尝试从桶中取走一个令牌,如果桶是空的,那么这个请求就会被拒绝

redis持久化机制

一、RDB:Redis DataBase将某一个时刻的内存快照,以二进制的方式写入磁盘。
手动触发:

  1. save命令,使Redis处于阻塞状态,知道RDB持久化完成,才会响应其他客户端发来的命令,所以生产环境慎用
  2. bgsave命令,fork出一个子进程进行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建后,主进程就可以响应客户端请求了。使用COW(copy only write),把超过备份时间的写命令都放在副本内操作,等到备份完再写回redis,这样就保证了子进程备份当时时间的数据不会被修改

自动触发:
1、save m n:在m秒内,如果有n个键发生变化,则自动触发持久化,通过bgsave执行,如果设置多个,只要满足其一就会触发,配置文件有默认配置(可以注释掉)
2、flushall:用于清空redis所有数据库,flushdb清空当前redis所在数据库,默认是0号,会清空RDB文件,同时也会生成dump.rdb,内容为空

3、主从同步:全量同步时会自动触发bgsave命令,生成rdb文件发送给从节点

优点:
1、整个Redis数据库只包含一个文件dump.rdb,方便持久化
2、容灾性好,方便备份
3、性能最大化,fork子进程完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程进行持久化,朱继承不会进行任何IO操作,保证了redis的高性能
4、对于数据集大时,比AOF的启动效率更高

缺点:
1、数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失,所以这种方式更适合数据要求不严谨的时候
2、由于RDB是通过fork自己成来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至1秒,会占用CPU

二、AOF:Append Only File,以日志的形式记录服务器所处理的每一个写,删除操作,查询操作不会记录,以文本的方式记录,可以打开文档看到详细的操作记录,调操作系统命令进程刷盘(命令记录缓冲到磁盘)。
先把命令写进缓冲区,然后根据一定的策略将缓冲区内的命令再写入到磁盘的log文件中
1、所有写命令会追加到AOF缓存中
2、AOF缓存区根据对应的策略向硬盘进行同步操作
3、随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。例如将三条写入list命令合并为一条命令。
4、当redis重启时,可以加在AOF文件进行文件恢复

缓冲到磁盘的同步策略:
每秒同步:异步完成,效率非常高,一旦系统出现宕机现象,那么这一秒内修改的数据将会丢失

每修改同步:同步持久化,每次发生的数据变化都会被立即记录到磁盘中,最多丢失一条

不同步:由操作系统控制,可能丢失较多数据

优点:1、数据安全
2、通过append模式写文件,及时中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof工具解决数据一致性问题(恢复AOF文件)
3、AOF机制的rewrite模式,定期对AOF文件进行重启,以达到压缩的目的

缺点:
1、AOF文件比RDB文件大,恢复速度慢
2、数据集大的时候,比RDB启动效率低
3、运行效率没有RDB高

AOF文件比RDB更新频率高,优先使用AOF还原数据
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了,优先加载AOF

redis主从同步机制

1、从节点执行salveof masterip port(可配置文件,可运行命令),从节点保存主节点信息
2、从节点中的定时任务发现主节点信息,建立和主节点的socket链接
3、从节点发送信号,主节点返回,两边能相互通信
4、连接建立,主节点将所有数据发送给从节点(数据同步)
5、主节点把当前的数据同步给从节点后,便完成了复制过程,接下来,主节点就会持续地把写命令发送给从节点,保证数据的一致性

runid:每个redis节点都会生成唯一的uuid,每次redis启动后,runid都会发生变化

offset:主从节点各自维护自己的复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度,从节点在接收主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。主节点同时保存自己的offset和从节点的offset,通过对比offset来判断从节点数据是否一致

repl_backlog_size:保存在主节点上的一个固定长度的先进先出队列,默认大小时1M

全量复制:
从节点发送psync命令,psync runid offset(由于是第一次,runid为?,offset为1)
主节点返回FULLRESYNC RUNID OFFSET, RUNID是主节点的RUNID,OFFSET是主节点目前的OFFSET,从节点保存信息
主节点启动bgsave命令fork子进程进行EDB持久化
主节点将RDB文件发送到从节点,到从节点加载数据完成之前,写命令写入缓冲区
从节点清理本地数据并加载EDB,如果开启了AOF会重写AOF

部分复制(主要是复制写命令缓冲区内的命令):
复制偏移量:psync runid offset
复制积压缓冲区:当从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制

如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的redis节点并不是当前的主及诶单,只能全量复制

分布式缓存寻址算法

1、hash算法:根据keyj进行hash函数运算,结果对分片取模,确定分片,适合固定分片数的场景
扩展分片或者减少分片时,所有数据都需要重新计算分片,存储
2、一致性hash,将整个hash值的区间组织成一个闭合的圆环,计算每台服务器的hash值,映射到圆环中,使用相同的hash算法计算数据的hash值,映射到圆环,顺时针寻找,找到的第一个服务器就是数据存储的服务器
新增及减少节点时只会影响节点到他逆时针最近的一个服务器之间的值
存在hash环倾斜的问题,即服务器分布不均匀,可以通过虚拟节点解决(可以使用多个hash计算得到)

3、hash slot:将数据与服务器隔离开,数据与slot映射,slot与服务器映射,数据进行hash决定存放的slot新增及删除节点时,将slot记性迁移即可

redis高可用方案

解决单点故障和性能存储瓶颈
主从,无自动切换机制,解决读请求
主从哨兵集群搭建:https://blog.csdn.net/xch_yang/article/details/104019552
一、哨兵模式:sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:
1、集群监控:负责监控redis master和slave进程是否正常工作。
2、消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知管理员。
3、故障转移(master转移):如果master node挂掉了,那么会自动转移到slave node上
4、配置中心(通知新地址):如果故障转移发生了,通知client客户端新的master地址(应用配置哨兵地址)
哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群运行,互相协同工作。
故障转移时,判断一个masternode是否宕机了,需要大部分的哨兵同意才行,涉及到了分布式选举
即使部分哨兵挂掉了,哨兵集群还是能正常工作的
哨兵通常需要3个实例,来保证自己的健壮性
哨兵+redis主从的部署架构,是不保证数据0丢失的,这能保证Redis集群的高可用性。
对于哨兵+redis主从复制这种复杂的部署架构,尽量在测试环境和生产环境,都进行充分的测试和演练

二、redis cluster是一种服务器sharding奇数,3.0版本正式开始提供,采用slot槽的概念,分成16384个槽,将请求发送到任意节点,接收到请求的节点会将查询请求大送到正确的节点上执行

redis sharding是redis cluster出来之前,业界普遍使用的富哦redis实例集群方案,其主要思想是采用哈希算法将redis数据的key进行散列,通过hash函数,特定的key会映射到特定的redis节点上。

优点:优点在于非常简单,服务端的redis实例彼此独立,相互无关联,每个redis实例像单服务器一样运行,易于线性扩展,系统的灵活性很强。

缺点:由于sharding处理放到客户端,规模进一步扩大时会给运维带来挑战。
客户端sharding不支持动态增删节点。服务端redis实例群拓扑结构有变化时,每个客户端都需要更新调整。链接不能共享,当应用规模增大时,资源浪费制约优化。

三、rediscluster是一种服务端sharding技术,3.0版本开始正式提供,采用slot槽的概念,共分成16384槽,将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行
方案说明
通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384个槽位
每个数据分片会存储在多个互为主从的多节点上
数据写入先写主节点,在同步到从节点(支持配置为阻塞同步)
同一分片多个节点间的数据不保持强一直性
读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
扩容时需要把旧节点的数据迁移一部分到新节点

在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加1W的端口号,比如16379

16379端口号是用来记性节点间通信的,也就是cluster bus的通信,用来进行故障检测,配置更新,故障转移授权。cluster bus用了另外一种二进制的协议,gossip协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

优点:无中心架构,支持动态扩容,对业务透明
具备sentinel的监控和自动failover(故障转移)能力

简述redis事务实现

1、事务开始
multi命令的执行,标识着一个事务的开始。multi命令会将客户端状态的flags属性中打开REDIS_MULTI标识来完成的。
2、命令入队
当一个客户端切换到事务状态之后,服务器会根据这个客户端发来的命令来执行不同的操作,如果客户端发送的命令为MULTI,EXEC,WATCH,DISCARD中的一个,立即执行这个命令,否则将命令放入一个事务队列里面,然后向客户端返回QUEUED回复
如果客户端发送的命令为EXEC,DISCARD,WATCHmMULTI四个命令的其中一个,那么服务器立即执行这个命令。
如果客户端发送的是四个命令以外的其他命令,那么服务器并不立即执行这个命令。首先检查此命令的格式是否正确,如果不正确,服务器会在客户端状态的flags属性关闭REDIS_MULTI标识,并且返回错误信息给客户端。
如果正确,将这个命令放入一个事务队列里面,然后向客户端返回QUEUED回复

3、事务执行
客户端发送EXEC命令,服务器执行EXEC命令逻辑。
如果客户端状态的flags属性不包含REDIS_MULTI标识,或者包含REDIS_DIRTY_CAS或者REDIS_DIRTY_EXEC标识,那么就直接取消事务的执行。
否则客户端处于事务状态(FLAGS有REDIS_MULTI标识),服务器会遍历客户端的事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端。

redis不支持事务回滚机制,但是他会检查每个每一个事务中的命令是否错误
redis事务不支持检查那些程序员自己逻辑错误,例如对string类型的数据库键执行对hashmap类型的操作

watch命令是一个乐观锁,可以为redis事务提供check-and-set行为,可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到exec命令。
multi命令用于开启一个事务,他总是返回ok,multi执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即执行,而是被放到一个队列中,当exec命令被调用时,所有队列中的命令才会被执行
exec:执行所有事务块内的命令,返回事务块内所有命令的返回值,按命令执行的先后顺序排列,当操作被打断时,返回空值nil
通过调用discard,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出
unwatch命令可以取消watch对所有key的监控

简述redis数据结构
string list hash set sorted set
bitmap:布隆过滤器
Geohash:坐标
hyperloglog:统计不重复数据,用于大数据基数统计
streams:内存版的kafka

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值