面试问题之redis篇 (持续补充)

目录

哨兵和集群模式,优缺点

epoll原理

主从同步,主从同步数据不一致怎么处理

穿透,击穿,雪崩问题产生和解决方案

redis 主从同步与从从同步

redis高可用哨兵方案

redis高可用集群方案

redis持久化

redis 解决异步复制和脑裂导致数据丢失

布隆过滤器原理和带来的问题

redis对于大value怎么处理

删除机制和lru

热点数据怎么处理和发现

自己设计redis分布式锁实现

red lock

redis如何减少网络开销

redis双写一致性



哨兵和集群模式,优缺点

哨兵

优点:支持高可用。

缺点: 主从切换的瞬间存在访问瞬断的情况,等待时间比较长,至少十来秒不可用,会有并发瓶颈,主节点内存过大,影响数据恢复速度

集群

可以支持高可用,通过分片支持高并发

epoll原理

主从同步,主从同步数据不一致怎么处理

参考下面主从同步 和脑裂数据丢失

穿透,击穿,雪崩问题产生和解决方案

缓存雪崩:

事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃

事中

1.本地reentrantLock 按照key 加锁,保证少量线程 查找msyql + hystrix限流&降级,避免MySQL被打死(主要 网关限流降级,redis没有命中,查询本地cache,cache没有命中,查询mysql 并且增加cache和redis 缓存,有个坑不要开启mybatis的二级缓存)

//线程安全的
private ConcurrentHashMap<String, Lock> locks = new ConcurrentHashMap<>();

   //解锁
 private void releaseLock(String userCode) {
        ReentrantLock oldLock = (ReentrantLock) locks.get(userCode);
        if(oldLock !=null && oldLock.isHeldByCurrentThread()){
            oldLock.unlock();
        }
    }
//按照搜索条件加锁
private void doLock(String lockcode) {//给一个搜索条件,对应一个锁
    //provinceid有不同的值,参数多样化
    //provinceid相同的,加一个锁,---- 不是同一个key,不能用同一个锁
    ReentrantLock newLock = new ReentrantLock();//创建一个锁
    Lock oldLock = locks.putIfAbsent(lockcode, newLock);//若已存在,则newLock直接丢弃
    if(oldLock == null){
        newLock.lock();
    }else{
        oldLock.lock();
    }
}

2. 缓存预热

常用值 使用http接口预热错峰加载

3. 被动失效改为主动失效

什么时候失效 我说了算

事后:redis持久化,快速恢复缓存数据

缓存穿透 (根据公司来订具体的方式)

        大量请求绕过缓存(缓存中没有这个数据,并且数据库也没有这个数据。所以不会更新焕春),造成大量请求直接访问数据库。造成数据库崩溃。

缓存穿透的解决方案

1.网关限流同一IP 限制访问次数。

2.数据库中查询不到 向缓存中设置一个特殊值,并且固定过期时间。或者增加布隆过滤器

redis 主从同步与从从同步

        redis 集群满足ap原则,保证最终一致性,从节点会努力追赶主节点。如果断网从节点与主节点出现大量不一致,一旦网络回复,从节点会采用多钟策略努力追赶

  1. 增量同步:

    同步指令流,redis主节点会维护一个定长的环形数组,记录指令流信息,数组满了就覆盖前面的内容,如果因为网络不同,指令被覆盖掉,这个时候会使用更复杂的同步机制-快照同步

  2. 快照同步:

    主节点进行一个bgsave操作,将之前内存的全部快照存入磁盘文件,然后将文件同步到从节点,从节点接收快照文件后,执行一次全量加载。

    注意:这个过程中,主节点的复制buffer还在进行,同步时间过长或者复制buffer太小,会导致同步期间,增量同步指令被覆盖,从而继续快照同步,极有可能进入快照同步死循环,务必配置一个合适的buffer大小参数

        新增从节点:先进行快照同步,完成后进行增量同步。

 

redis高可用哨兵方案

 服务端:当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。Sentinel由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。

客户端

  1. 客户端连接哨兵,并且可以通过任意的哨兵发现主节点的地址,连接池创建连接的时候,会去查询主节点的地址,然后跟内存的主节点地址进行比较,如果变更断开所有连接,重新建立连接。
  2. 主节点降级,如果遇到修改操作会抛出异常ReadOnlyError,通过捕获异常重新连接。

redis高可用集群方案

服务端:cluster 去中心化的,每个节点负责整体数据的一部分,集群将所有数据划分为16384个槽位,每个节点负责一部分槽位,并且槽位的信息存储于每个节点中,

槽位定位算法:默认会对key值使用crc16算法进行hash,得到整数然后对整数对16384取模来获得具体位置。集群也允许强制把key挂在特定的槽位上,通过key字符串里面嵌入tag标记,这就可以强制key所挂的槽位等于tag所在槽位。

跳转:客户端像一个错误的节点发出指令之后,节点会发送一个特殊的指令-MOVED携带目标地址,告诉客户端连接这个节点获取数据,客户端收到MOVED指令后,立即纠正本地槽位的映射表,后续所有的key使用新的地址

迁移:redis cluster 提供了工具 redis-trib 可以让运维人员手动调节槽位

扩容新增加节点。重新分配槽位,redis迁移的单位值槽,redis是一个槽一个槽的进行迁移,当一个槽正在迁移的时候,这个槽位就处于中间过渡状态,这个槽的源节点状态为migration,目标节点状态为importing。表示数据从源流向目标。然后一次性的获取源节点所有key列表,然后挨个key进行迁移,每个key再源节点执行dump指令得到序列化内容,然后发送到目标节点发送restore指令,目标节点接收指令反序列化,将内容存入内存中,并且确认消息,然后源节点删除key,完成单个key的迁移。迁移过程中如果每个key的value非常小,migrate指令很快被执行,不会影响用户的操作。migrate指令是阻塞指令

迁移过程中查询,先查询源节点。如果源节点不存在,会返回客户端一个-ask重定向指令,客户端收到重定向指令后,去目标节点执行一个不带任何参数的asking指令,然后再目标节点重新执行原先的操作指令。

容错 :主节点故障,集群会自动选举出从节点升级为主节点。如果主节点没有从节点。并且主节点故障。整个集群处于不可用状态。 redis提供一个参数cluster-require-full-coverage 可以允许部分节点发生故障,其他节点还可以继续使用。

如果某个槽归属的小群内都不可用时,整个服务仍然是不可用的!通过cluster-require-full-coverageyes 控制该特性, 默认yes 即需要集群完整,方可对外提供服务,设置为no ,其他的小集群仍然可以对外提供服务。

网络抖动:避免网络抖动频繁切换,redis cluster 提供了 cluster-node-timeout 表示某个节点持续timeout的时间失联时,才认定改节点出现故障。需要主从切换。cluster-slave-validity-factor 可以倍乘系数放大超时时间。

可能下线(PFail)与确定下线(Fail) : redis 集群是去中心化的, 一个节点的认为某个节点失联了并不代表所有节点都认为它失联,集群经过一次协商,只有大多数节点都认定失联。该节点才会主从切换。

集群的节点通过gossip协议广播自己的状态以及改变对整个集群的认知。

选举机制:当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争选举成为master的过程, 其过程如下: 1. slave发现自己的master变为FAIL,将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息; 2. 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack; 3. 尝试failover的slave收集FAILOVER_AUTH_ACK,超过半数后该slave变成新的master,并广播通知其他集群节点。 slave并不是在master一进入FAIL状态就立马尝试发起选举,而是有一定延迟,这个延迟用来确保等待FAIL状态在集群中传播,slave如果立即尝试选举,其它master可能尚未意识到这个master的FAIL状态,从而拒绝投票

客户端:cluster是去中心化的,有多个节点组成,构造的时候客户端可以只用一个节点地址忙就可以自动获取其他地址。如果提供多个节点地址,安全性会更好。

redis持久化

aof:

rdb:

混合持久化:

redis 解决异步复制和脑裂导致数据丢失

1、两种数据丢失的情况

主备切换的过程,可能会导致数据丢失

(1)异步复制导致的数据丢失

因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了

(2)脑裂导致的数据丢失

脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着

此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master

这个时候,集群里就会有两个master,也就是所谓的脑裂

此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了

因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据

2、解决异步复制和脑裂导致的数据丢失

min-slaves-to-write 1

min-slaves-max-lag 10

要求至少有1个slave,数据复制和同步的延迟不能超过10秒

如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了

上面两个配置可以减少异步复制和脑裂导致的数据丢失

(1)减少异步复制的数据丢失

有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内

(2)减少脑裂的数据丢失

如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求

这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失

上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求

因此在脑裂场景下,最多就丢失10秒的数据

布隆过滤器原理和带来的问题

redis对于大value怎么处理

什么是 big value

  1. 字符串类型:一般认为超过 10k 的就是 bigkey,但是这个值和具体的 OPS 相关。
  2. 非字符串类型:体现在哈希,列表,集合类型元素过多。

危害

内存空间不均匀:比如在 Redis cluster 或者 codis 中,会造成节点的内存使用不均匀。

阻塞:因为 Redis 单线程特性,如果操作某个 Bigkey 耗时比较久,则后面的请求会被阻塞。

过期时可能阻塞:如果 Bigkey 设置了过期时间,当过期后,这个 key 会被删除,假如没有使用 Redis 4.0 的过期异步删除,就会存在阻塞 Redis 的可能性,并且慢查询中查不到(因为这个删除是内部循环事件)

SLOWLOG GET

redis> SLOWLOG GET 1)
 1) (integer) 4 # 日志的唯一标识符(uid)
 2) (integer) 1378781447 # 命令执行时的 UNIX 时间戳
 3) (integer) 13 # 命令执行的时长,以微秒计算
 4) 1) "SET" # 命令以及命令参数 2) "database" 3) "Redis" 2) 1) (integer) 3 2) (integer) 1378781439 3) (integer) 10 4) 1) "SET" 2) "number" 3) "10086"

解决方案

可以尝试将对象分拆成几个key-value,

hash, set,zset,list 中存储过多的元素 可以拆分数据 具体业务具体分析

删除机制和lru

定期删除+惰性删除

        所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。

        1.从过期字典中随机选出20个key

        2.删除20个key中已经过期的key

        3.如果过期的key超过比例的1/4,继续步骤1

但是问题是,定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?

        所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下

        通过上述两种手段结合起来,保证过期的key一定会被干掉。

        但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?答案是:走内存淘汰机制。

内存淘汰

如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

  1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了
  2. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
  3. allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊
  4. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个我公司在用,巨恶心。开发中我使用redis做发号器。差点没酿成事故)
  5. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
  6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

热点数据怎么处理和发现

自己设计redis分布式锁实现

分布式锁设计考虑几个问题 可以参考redisson实现 

  1. key设计
  2. 是否是同一个服务同一个线程
  3. 可重入
  4. 过期超时

red lock

redis如何减少网络开销

pepline和lua脚本

redis双写一致性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值