欢迎光临散文网 会员登陆 & 注册

第一部分:Redis篇

2023-08-24 17:18 作者:OopsSan  | 我要投稿

一、什么是缓存穿透? 怎么解决?

缓存穿透

缓存穿透是指缓存和数据库都没有数据,导致所有请求都到了数据库,如果从数据库查不到数据且不写入缓存,造成数据库短时间内承受大量的请求而导致宕机。

解决

1、布隆过滤器

将查询的参数存储到一个bitmap中,在查询缓存前,如果 bitmap 存在则进行底层缓存的数据查询,如果不存在则进行拦截,不再进行缓存的数据查询。

优点:内存占用较少,没有多余key;

缺点:实现复杂,存在误判,元素不能删除

2、缓存空对值

如果数据库查询的为空,则依然把这个数据缓存并设置过期时间,当多次访问的时候可以直接返回结果,避免造成多次访问数据库,但要保证当数据库有数据时及时更新缓存。

优点:实现简单;

缺点:消耗内存,可能会发生不一致的问题

布隆过滤器

bitmap(位图):相当于是一个以bit为单位的数组,数组中每个单元只能存储二进制数0或1

布隆过滤器:一种节省空间的概率数据结构,它实际上是由一个很长的二进制向量(位图)和一系列随机映射函数组成,用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间比一般算法要好很多,但也有一定概率的误判性。它会告诉你某东西一定不存在或者可能存在。

实现原理:先初始化一个比较大数组,一开始都是0,当一个key后经过3次hash计算,模于数组长度得到数据的下标然后把数组中原来的0改为1。这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。

二、什么是缓存击穿 ? 怎么解决 ?

缓存击穿

设置了过期时间且为热点的key,在失效的瞬间,恰好对这个Key有大量的并发请求过来,击穿了缓存,直接请求数据库,导致数据库崩溃。

解决

1、使用互斥锁

请求查询A,发现缓存中没有。对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。

2、设置逻辑过期

1、在设置key的时候,设置一个过期时间字段存入缓存中,不给当前 key设置过期时间

2、当查询的时候,从redis取出数据后判断时间是否过期

3、如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新。

三、什么是缓存雪崩 ? 怎么解决 ?

缓存雪崩

某一时刻发生大规模的缓存失效的情况,例如缓存服务宕机、大量key在同一时间过期,这样的后果就是大量的请求直接到数据库,数据库无响应,最后可能导致整个系统的崩溃。

解决

1、利用Redis集群提高服务的可用性

通过集群来提升缓存的可用性,可以利用Redis本身的Redis Cluster或者第三方集群方案如Codis等。

2、给业务添加多级缓存

设置多级缓存,设置一级缓存本地(Guava,Caffeine)缓存,第一级缓存失效的基础上再访问二级缓存redis,每一级缓存的失效时间都不同。

3、过期时间

1、为了避免大量的缓存在同一时间过期,可以把不同的 key 过期时间随机生成,避免过期时间太过集中。

2、热点数据设置逻辑过期。

4、熔断降级

熔断

当缓存服务器宕机或超时响应时,为了防止整个系统出现雪崩,可以使用hystrix 类似 的熔断,暂时停止业务服务访问数据库, 或者其他被依赖的服务,避免 MySQL 被打死。

降级

当出现大量缓存失效,而且处在高并发高的情况下,在业务系统内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。

四、常用的缓存读写策略详解

Cache aside

       先更新完数据库,再删除缓存。这种策略发生数据库和缓存数据不一致的概率很低,因为通常数据库更新操作比内存操作耗时多出几个数量级,读请求的回写缓存速度非常快,通常会在写请求更新数据库之前完成。

读请求

写请求


先更新数据库,再更新缓存

1.       线程A:更新了数据库

2.       线程B:更新了数据库

3.       线程B:更新了缓存

4.       线程A:更新了缓存

数据库数据与缓存数据的不一致

先删缓存,再更新数据库

1.       线程A:删除缓存

2.       线程B:查询缓存,发现缓存中没有想要的数据

3.       线程B:查询数据库中的旧数据

4.       线程B:将查询到的旧数据写入缓存

5.       线程A:将新数据写入数据库

数据库数据与缓存数据的不一致

Read/Write Through

Read/Write Through中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。

写(Write Through)

l  先查 cache,cache 中不存在,直接更新 db。

l  cache 中存在,则先更新 cache,然后 cache 服务自己更新 db(同步更新 cache 和 db)。


读(Read Through)

l  从 cache 中读取数据,读取到就直接返回 。

读取不到的话,先从 db 加载,写入到 cache 后返回响应。

Write Behind

       与Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 db 的读写。但是,两个又有很大的不同:Read/Write是同步更新 cache 和 db,而 Write Behind 则是只更新缓存,不直接更新 db,而是改为异步批量的方式来更新 db。    

       这种方式对数据一致性带来了更大的挑战,比如 cache 数据可能还没异步更新 db 的话,cache 服务可能就就挂掉了。

五、redis做为缓存,数据的持久化是怎么做的?

RDB

RDB全称Redis Database Backup file,也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。

触发方式

①save和bgsave

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。fork采用的是copy-on-write技术:

当主进程执行读操作时,访问共享内存;

当主进程执行写操作时,则会拷贝一份数据,执行写操作。


②自动

Redis会将数据集的快照dump到dump.rdb文件中。 此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率。

#在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。

save 900 1

#在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。

save 300 10

#在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

save 60 10000

AOF

AOF 持久化全称 append only file,以日志形式记录每个写操作,将 redis 执行过的所有写操作指令记录下来(读操作不记录)。

AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:

# 是否开启AOF功能,默认是no

appendonly yes

# AOF文件的名称

appendfilename "appendonly.aof"

AOF的命令记录的频率也可以通过redis.conf文件来配:

# 表示每执行一次写命令,立即记录到AOF文件

appendfsync always

# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案

appendfsync everysec

# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

appendfsync no

可以通过bgrewriteaof 命令,可以将 AOF 文件进行压缩,也可以选择自动触发,在配置文件中配置

# AOF文件比上次文件 增长超过多少百分比则触发重写

auto-aof-rewrite-percentage 100

# AOF文件体积最小多大以上才触发重写

auto-aof-rewrite-min-size 64mb

RDB vs AOF

RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

六、Redis的数据过期删除策略有哪些?

Redis对数据设置有效时间,数据过期以后,就需要将数据从内存中删除掉。可以按照不同的规则进行删除,这种删除规则就被称之为数据的删除策略(数据过期策略)。

Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

惰性删除

惰性删除指的是当我们查询key的时候    才对key进行检测,如果已经达到过期时间,则删除。

优点:对CPU友好,只会在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。

缺点:对内存不友好,如果一个key已经过期,但是一直没有使用,那么该key就会一直存在内存中,内存永远不会释放。

定期删除

指的是redis默认是每隔 100ms (10hz)就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

缺点:难以确定删除操作执行的时长和频率。

七、Redis的数据淘汰策略有哪些?

如果某个key过期后,定期删除没删除成功,然后也没再次去请求key,也就是说惰性删除也没生效。 这时,如果大量过期的key堆积在内存中,redis的内存会越来越高,导致redis的内存块耗尽。那么就应该采用内存淘汰机制。

Redis支持8种不同策略来选择要删除的key:

no-eviction:不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。

volatile-ttl:对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰。

allkeys-random:对全体key,随机进行淘汰。

volatile-random:对设置了TTL的key,随机进行淘汰。

allkeys-lru:对全体key,基于LRU(Least Recently Used,最近最久未使用)算法进行淘汰。

volatile-lru:对设置了TTL的key,基于LRU算法进行淘汰。

allkeys-lfu:对全体key,基于LFU(Least Frequently Used ,最近最少使用)算法进行淘汰。

volatile-lfu:对设置了TTL的key,基于LFU算法进行淘汰。

使用建议:

  1. 优先使用 allkeys-lru 策略。充分利用 LRU 算法的优势,把最近最常访问的数据留在缓存中。如果业务有明显的冷热数据区分,建议使用。

  2. 如果业务中数据访问频率差别不大,没有明显冷热数据区分,建议使用 allkeys-random,随机选择淘汰。

  3. 如果业务中有置顶的需求,可以使用 volatile-lru 策略,同时置顶数据不设置过期时间,这些数据就一直不被删除,会淘汰其他设置过期时间的数据。

  4. 如果业务中有短时高频访问的数据,可以使用 allkeys-lfu 或 volatile-lfu 策略。

八、Redis分布式锁如何实现?

redis分布式锁

redis是一个单独的非业务服务,不会受到其他业务服务的限制,所有的业务服务都可以向redis发送写入命令, 且只有一个业务服务可以写入命令成功,那么这个写入命令成功的服务即获得了锁,可以进行后续对资源的操作,其他未写入成功的服务,则进行其他处理。

# 添加锁,NX是互斥、EX是设置超时时间(设置超时时间避免死锁,用一条命令保证原子性)

SET lock value NX EX 10

# 释放锁,删除即可

DEL key

redisson实现的分布式锁

①执行流程

在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后释放锁就可以了。

在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

②可重入

在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减1。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中field是当前线程的唯一标识,小value是当前线程重入的次数

 ③主从一致性

redis集群中主从数据并不是强一致性。 当主节点宕机后,主节点的数据还未来得及同步到从节点,进行主从切换后,新的主节点并没有旧的主节点的全部数据,这就会导致刚写入到旧的主节点的锁在新的主节点并没有,其他服务来获取锁时还是会加锁成功。此时则会有2个服务都可以操作公共资源,此时的分布式锁是不安全的。

RedLock:要实现高可用的RedLock,不能只在一个redis实例上创建锁,应该是在多个redis实例(n / 2 + 1)上创建锁。高可用的RedLock会导致性能降低,不太建议使用RedLock。

使用redis分布式锁,是追求高性能,在CAP理论中,追求的是AP而不是CP。所以,如果追求高可用,建议使用 zookeeper分布式锁。

九、Redis集群方案之主从同步

全量同步

一般用于初次同步场景,Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。

Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid。

offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。

增量同步

使用psync命令实现。当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点,这样就可以保持主从节点复制的一致性。


十、Redis集群方案之哨兵模式

哨兵模式

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:

监控:Sentinel 会不断检查您的master和slave是否按预期工作。

1、每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线。

2、如果大多数sentinel都认为实例主观下线,则判定服务下线。

自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主。

1、首先选定一个slave作为新的master,执行slaveof no one

2、然后让所有节点都执行slaveof 新master

3、修改故障节点,执行slaveof 新master

通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端。

脑裂

在一个高可用集群中,当多个服务器在指定的时间内,由于网络的原因无法互相检测到对方,而各自形成一个新的小规模集群,并且各小集群当中,会选举新的master节点,都对外提供独立的服务,由于网络断裂的原因,一个高可用集群中,实际上分裂为多个小的集群,这种情况就称为脑裂。

十一、Redis集群方案之分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

•        海量数据存储问题

•        高并发写的问题

使用分片集群可以解决上述问题,分片集群特征:

•        集群中有多个master,每个master保存不同数据

•        每个master都可以有多个slave节点

•        master之间通过ping监测彼此健康状态

•        客户端请求可以访问集群任意节点,最终都会被转发到正确节点

散列插槽

Redis会把每一个master节点映射到0~16383共16384个插槽上。每个key的有效部分通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash 槽。

集群伸缩

Redis集群提供了灵活的节点扩容和收缩方案,可以在不影响集群对外服务的情况下,为集群添加节点进行扩容也可以下线部分节点进行缩容。

其实,集群扩容和缩容的关键点,就在于槽和节点的对应关系,扩容和缩容就是将一部分槽和数据迁移给新节点。

故障转移

Redis集群的故障转移和哨兵的故障转移类似,但是Redis集群中所有的节点都要承担状态维护的任务。

故障发现

Redis集群内节点通过ping/pong消息实现节点通信,集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong 消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则发送节点会认为接收节点存在故障,把接收节点标记为主观下线(pfail)状态。

当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。

故障恢复

故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。

故障恢复流程

1. 资格检查:每个从节点都要检查最后与主节点断线时间,判断是否有资格替换故障的主节点。

2. 准备选举时间:当从节点符合故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程。

3. 发起选举:当从节点定时任务检测到达故障选举时间(failover_auth_time)到达后,发起选举流程。

4. 选举投票:持有槽的主节点处理故障选举消息。投票过程其实是一个领导者选举的过程,如集群内 有N个持有槽的主节点代表有N张选票。由于在每个配置纪元内持有槽的主节点只能投票给一个从节点,因此只能有一个从节点获得N/2+1的选票,保证能够找出唯一的从节点。

5. 替换主节点 当从节点收集到足够的选票之后,触发替换主节点操作。

十二、Redis是单线程的,但是为什么还那么快?

基于内存

Redis是基于内存的缓存数据库,用于存储使用频繁的数据,这样减少访问数据库的次数,提高运行效率。

单线程避免线程切换

多线程在执行过程中需要进行CPU的上下文切换,需要完成一系列工作,这是非常消耗资源的操作,Redis又是基于内存实现的,对于内存来说,没有上下文切换效率就是最高的。

IO多路复⽤

Redis采用网络IO多路复用技术,来保证在多连接的时候系统的高吞吐量。

C语⾔实现高效的数据结构

Redis底层数据结构一共有6种:简单动态字符串,双向链表,压缩列表,哈希表,跳表和整数数组。它们和数据类型的对应关系如下图所示:

十三、能解释一下I/O多路复用模型?

多路指的是多个socket网络连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的、也是目前最好的多路复用技术。采用多路I/O复用技术,可以让单个线程高效处理多个连接请求。

Redis内部使用文件事件处理器(File Event Handler),这个文件事件处理器是单线程的,类似于Netty的Reactor反应器模型。 它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 文件事件处理器的结构包含4个部分:

l  多个socket

l  IO多路复用程序

l  文件事件分派器

l  事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据socket的事件类型交给对应的事件处理器进行处理。

在Redis6.0之后,为了提升更好的性能,在命令回复处理器(请求后的数据通过网络返回)使用了多线程来处理回复事件,在命令请求处理器中(网络请求的接收和解析),将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。

第一部分:Redis篇的评论 (共 条)

分享到微博请遵守国家法律