Redis

 

  • 1. Redis主从        

    • 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

       
    • 1.1.主从集群结构        

      • 如果是写操作,应该访问master节点,master会自动将数据同步给两个slave节点.

      • 如果是读操作,建议访问各个slave节点(现在叫replica),从而分担并发压力

    • 1.2搭建主从集群

      • 利用3个docker 容器来创建主从集群        

      • 1.2.1.启动多个Redis实例

      • 创建 docker-compose.yaml文件

      • 内容如下:

        • version: "3.2" 
          services:
             r1: 
                  image: redis 
                  container_name: r1 
                  network_mode: "host" 
                  entrypoint: ["redis-server", "--port", "7001"]
             r2: 
                  image: redis
                  container_name: r2 
                  network_mode: "host" 
                  entrypoint: ["redis-server", "--port", "7002"] 
             r3: 
                  image: redis
                  container_name: r3 
                  network_mode: "host" 
                  entrypoint: ["redis-server", "--port", "7003"]
      • 将文件传入虚拟机 然后再该文件所在目录执行命令: docker compose up -d
    • 1.2.2.建立集群

      • 通过以下命令来形成主从关系:        

        # Redis5.0以前 slaveof <masterip> <masterport>

        # Redis5.0以后 replicaof <masterip> <masterport>

      • 有临时和永久两种模式:

        • 永久生效:在redis.conf文件中利用slaveof命令指定master节点

        • 临时生效:直接利用redis-cli控制台输入slaveof命令,指定master节点

    • 1.3.主从同步原理

                                      
      • 1.3.1.全量同步

      •  主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程:

      • master判断一个节点是否是第一次同步的依据,就是看replid是否一致

    • 1.3.2.增量同步

    • 全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步

    • 1.3.3.repl_baklog原理

      • 这就要说到全量同步时的repl_baklog文件了。这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。

      • repl_baklog中会记录Redis处理过的命令及offset,包括master当前的offset,和slave已经拷贝到的offset

        •                 

        • slave与master的offset之间的差异,就是salve需要增量拷贝的数据了

  • 1.4.主从同步优化

    • 主从同步可以保证主从数据的一致性,非常重要。

    • 可以从以下几个方面来优化Redis主从就集群:

      • 在master中配置repl-diskless-sync  yes 启用无磁盘复制,避免全量同步时的磁盘IO。
      • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO

      • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步

      • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

      • 主-从-从架构图:

      • 简述全量同步和增量同步区别?

      • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。

      • 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

      • 什么时候执行全量同步?

      • slave节点第一次连接master节点时

      • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

      • 什么时候执行增量同步?

      • slave节点断开又恢复,并且在repl_baklog中能找到offset时

  •  2. Redis哨兵

  • 2.1.哨兵工作原理

    Redis提供了哨兵Sentinel)机制来监控主从集群监控状态,确保集群的高可用性。

  • 2.1.1.哨兵作用

    • 哨兵集群作用原理图:

    • 哨兵的作用如下:

    • 状态监控Sentinel 会不断检查您的masterslave是否按预期工作

    • 故障恢复(failover):如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后会成为slave

    • 状态通知Sentinel充当Redis客户端的服务发现来源,当集群发生failover时,会将最新集群信息推送给Redis的客户端

  • 2.1.2.状态监控

    • Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个节点发送ping命令,并通过实例的响应结果来做出判断:

    • 主观下线(sdown):如果某sentinel节点发现某Redis节点未在规定时间响应,则认为该节点主观下线。

    • 客观下线(odown):若超过指定数量(通过quorum设置)的sentinel都认为该节点主观下线,则该节点客观下线。quorum值最好超过Sentinel节点数量的一半,Sentinel节点数量至少3台。

    • 一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

      • 首先会判断slave节点与master节点断开时间长短,如果超过down-after-milliseconds * 10则会排除该slave节点

      • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举(默认都是1)。

      • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高

      • 最后是判断slave节点的run_id大小,越小优先级越高(通过info server可以查看run_id)。

  • 2.1.3.选举leader 

    • 问题来了,当选出一个新的master后,该如何实现身份切换呢?

      大概分为两步:

      • 在多个sentinel中选举一个leader

      • leader执行failover

    • 第一个确认master客观下线的人会立刻发起投票,一定会成为leader。
  • 2.1.4.failover

    • 我们举个例子,有一个集群,初始状态下7001为master,7002和7003为slave

    • 假如master发生故障,slave1当选。则故障转移的流程如下:

      1)sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

  • sentinel给所有其它slave发送slaveof 192.168.150.101 7002 命令,让这些节点成为新master,也就是7002slave节点,开始从新的master上同步数据。

  • 最后,当故障节点恢复后会接收到哨兵信号,执行slaveof 192.168.150.101 7002命令,成为slave

  • 2.2.总结

  • Sentinel的三个作用是什么?

    • 集群监控

    • 故障恢复

    • 状态通知

  • Sentinel如何判断一个redis实例是否健康?

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

    • 如果大多数sentinel都认为实例主观下线,则判定服务客观下线(odown

  • 故障转移步骤有哪些?

    • 首先要在sentinel中选出一个leader,由leader执行failover

    • 选定一个slave作为新的master,执行slaveof no one,切换到master模式

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

    • 修改故障节点配置,添加slaveof 新master

  • sentinel选举leader的依据是什么?

    • 票数超过sentinel节点数量1半

    • 票数超过quorum数量

    • 一般情况下最先发起failover的节点会当

  • sentinel从slave中选取master的依据是什么?

    • 首先会判断slave节点与master节点断开时间长短,如果超过down-after-milliseconds * 10则会排除该slave节点

    • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举(默认都是1)。

    • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高

    • 最后是判断slave节点的run_id大小,越小优先级越高(通过info server可以查看run_id)。

  • 3. Redis分片集群

    • 可以用来解决 :
      • 海量数据存储

      • 高并发写

    • 要解决这两个问题就需要用到分片集群了。分片的意思,就是把数据拆分存储到不同节点,这样整个集群的存储数据量就更大了。

    • 分片集群特征:

      • 集群中有多个master,每个master保存不同分片数据 ,解决海量数据存储问题

      • 每个master都可以有多个slave节点 ,确保高可用

      • master之间通过ping监测彼此健康状态 ,类似哨兵作用

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

  • 3.2.散列插槽

    • 数据要分片存储到不同的Redis节点,肯定需要有分片的依据,这样下次查询的时候才能知道去哪个节点查询。很多数据分片都会采用一致性hash算法。而Redis则是利用散列插槽(hash slot)的方式实现数据分片。

    • Redis分片集群如何判断某个key应该在哪个实例?

      • 将16384个插槽分配到不同的实例

      • 根据key计算哈希值,对16384取余

      • 余数作为插槽,寻找插槽所在实例即可

    • 如何将同一类数据固定的保存在同一个Redis实例?

      • Redis计算key的插槽值时会判断key中是否包含{},如果有则基于{}内的字符计算插槽

      • 数据的key中可以加入{类型},例如key都以{typeId}为前缀,这样同类型数据计算的插槽一定相同

  • 4 .Redis数据结构

  • 我们常用的Redis数据类型有5种,分别是:

    • String

    • List

    • Set

    • SortedSet

    • Hash

      还有一些高级数据类型,比如Bitmap、HyperLogLog、GEO等,其底层都是基于上述5种基本数据类型。因此在Redis的源码中,其实只有5种数据类型。
  • 4.1.RedisObject

    • redisObjec 结构体:
      • 仅仅是对象头信息,内存占用的大小为4+4+24+32+64 = 128bit

        也就是16字节,然后指针ptr指针指向的才是真实数据存储的内存地址。

    • 属性中的encoding就是当前对象底层采用的数据结构编码方式,可选的有11种之多:

    • Redis中的5种不同的数据类型采用的底层数据结构和编码方式如下:

  • 4.2.SkipList

  • SkipList(跳表)首先是链表,但与传统链表相比有几点差异:

    • 元素按照升序排列存储

    • 节点可能包含多个指针,指针跨度不同。

  • 4.3.SortedSet

  • Redis的SortedSet底层的数据结构是怎样的?

    • SortedSet是有序集合,底层的存储的每个数据都包含element和score两个值。score是得分,element则是字符串值。SortedSet会根据每个element的score值排序,形成有序集合。

      它支持的操作很多,比如:

    • 根据element查询score值

    • 按照score值升序或降序查询element

    • 不过,ZipList存在连锁更新问题,因此而在Redis7.0版本以后,ZipList又被替换为Listpack(紧凑列表)。

      要实现根据element查询对应的score值,就必须实现element与score之间的键值映射。SortedSet底层是基于HashTable来实现的。

      要实现对score值排序,并且查询效率还高,就需要有一种高效的有序数据结构,SortedSet是基于跳表实现的。

      加分项:因为SortedSet底层需要用到两种数据结构,对内存占用比较高。因此Redis底层会对SortedSet中的元素大小做判断。如果元素大小小于128每个元素都小于64字节,SortedSet底层会采用ZipList,也就是压缩列表来代替HashTableSkipList

  • 内存结构图

  • 5. Redis内存回收

  • 可以通过修改redis.conf文件,添加下面的配置来配置Redis的最大内存:

     maxmemory 1gb
  • 5.1.内存过期处理

    • 5.1.1.过期命令

    • Redis中通过expire命令可以给KEY设置TTL(过期时间)

    • 5.1.2.过期策略

    • Redis如何判断KEY是否过期呢?

    • 在Redis中会有两个Dict,也就是HashTable,其中一个记录KEY-VALUE键值对,另一个记录KEY和过期时间。要判断一个KEY是否过期,只需要到记录过期时间的Dict中根据KEY查询即可。

    • Redis的过期KEY删除策略有两种:

      • 惰性删除:

        • Redis会在每次访问KEY的时候判断当前KEY有没有设置过期时间,如果有,过期时间是否已经到期。如果过期则删除。

      • 周期删除 :

        • 顾明思议是通过一个定时任务,周期性的抽样部分过期的key,然后执行删除。

          执行周期有两种:

        • SLOW模式:Redis会设置一个定时任务serverCron(),按照server.hz的频率来执行过期key清理

        • FAST模式:Redis的每个事件循环前执行过期key清理(事件循环就是NIO事件处理的循环)。

  • 5.2.内存淘汰策略

    1. 当内存使用达到阈值时就会主动挑选部分KEY删除以释放更多内存。这叫做内存淘汰机制2.Redis每次执行任何命令时,都会判断内存是否达到阈值
  • 5.2.2.淘汰策略

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

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

    • allkeys-random:对全体key ,随机进行淘汰。也就是直接从db->dict中随机挑选

    • volatile-random:对设置了TTL的key ,随机进行淘汰。也就是从db->expires中随机挑选。

    • allkeys-lru: 对全体key,基于LRU算法进行淘汰

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

    • allkeys-lfu: 对全体key,基于LFU算法进行淘汰

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

  • 比较容易混淆的有两个算法:

    • LRULeast Recently Used),最近最久未使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

    • LFULeast Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。

  • 这就要聊起Redis的逻辑访问次数算法了,LFU的访问次数之所以叫做逻辑访问次数,是因为并不是每次key被访问都计数,而是通过运算:

    • ① 生成[0,1)之间的随机数R

    • ② 计算 1/(旧次数 * lfu_log_factor + 1),记录为Plfu_log_factor默认为10

    • ③ 如果 R < P ,则计数器 +1,且最大不超过255

    • ④ 访问次数会随时间衰减,距离上一次访问时间每隔 lfu_decay_time 分钟(默认1) ,计数器-1

  • 总结

  • Redis如何判断KEY是否过期呢?

    :在Redis中会有两个Dict,也就是HashTable,其中一个记录KEY-VALUE键值对,另一个记录KEY和过期时间。要判断一个KEY是否过期,只需要到记录过期时间的Dict中根据KEY查询即可。

  • Redis何时删除过期KEY?如何删除?

    :Redis的过期KEY处理有两种策略,分别是惰性删除和周期删除。

    惰性删除是指在每次用户访问某个KEY时,判断KEY的过期时间:如果过期则删除;如果未过期则忽略。

    周期删除有两种模式:

  • SLOW模式:通过一个定时任务,定期的抽样部分带有TTL的KEY,判断其是否过期。默认情况下定时任务的执行频率是每秒10次,但每次执行不能超过25毫秒。如果执行抽样后发现时间还有剩余,并且过期KEY的比例较高,则会多次抽样。

  • FAST模式:在Redis每次处理NIO事件之前,都会抽样部分带有TTL的KEY,判断是否过期,因此执行频率较高。但是每次执行时长不能超过1ms,如果时间充足并且过期KEY比例过高,也会多次抽样

  • 当Redis内存不足时会怎么做

    :这取决于配置的内存淘汰策略,Redis支持很多种内存淘汰策略,例如LRU、LFU、Random. 但默认的策略是直接拒绝新的写入请求。而如果设置了其它策略,则会在每次执行命令后判断占用内存是否达到阈值。如果达到阈值则会基于配置的淘汰策略尝试进行内存淘汰,直到占用内存小于阈值为止。

  • 那你能聊聊LRULFU

    LRU是最近最久未使用。Redis的Key都是RedisObject,当启用LRU算法后,Redis会在Key的头信息中使用24个bit记录每个key的最近一次使用的时间lru。每次需要内存淘汰时,就会抽样一部分KEY,找出其中空闲时间最长的,也就是now - lru结果最大的,然后将其删除。如果内存依然不足,就重复这个过程。

    由于采用了抽样来计算,这种算法只能说是一种近似LRU算法。因此在Redis4.0以后又引入了LFU算法,这种算法是统计最近最少使用,也就是按key的访问频率来统计。当启用LFU算法后,Redis会在key的头信息中使用24bit记录最近一次使用时间和逻辑访问频率。其中高16位是以分钟为单位的最近访问时间,后8位是逻辑访问次数。与LFU类似,每次需要内存淘汰时,就会抽样一部分KEY,找出其中逻辑访问次数最小的,将其淘汰。

  • 逻辑访问次数是如何计算的

    :由于记录访问次数的只有8bit,即便是无符号数,最大值只有255,不可能记录真实的访问次数。因此Redis统计的其实是逻辑访问次数。这其中有一个计算公式,会根据当前的访问次数做计算,结果要么是次数+1,要么是次数不变。但随着当前访问次数越大,+1的概率也会越低,并且最大值不超过255.

    除此以外,逻辑访问次数还有一个衰减周期,默认为1分钟,即每隔1分钟逻辑访问次数会-1。这样逻辑访问次数就能基本反映出一个key的访问热度了。

  • 6 .缓存问题

  • 如何保证缓存的双写一致性

    :缓存的双写一致性很难保证强一致,只能尽可能降低不一致的概率,确保最终一致。我们项目中采用的是Cache Aside模式。简单来说,就是在更新数据库之后删除缓存;在查询时先查询缓存,如果未命中则查询数据库并写入缓存。同时我们会给缓存设置过期时间作为兜底方案,如果真的出现了不一致的情况,也可以通过缓存过期来保证最终一致。

    追问:为什么不采用延迟双删机制?

    :延迟双删的第一次删除并没有实际意义,第二次采用延迟删除主要是解决数据库主从同步的延迟问题,我认为这是数据库主从的一致性问题,与缓存同步无关。既然主节点数据已经更新,Redis的缓存理应更新。而且延迟双删会增加缓存业务复杂度,也没能完全避免缓存一致性问题,投入回报比太低。

  • 如何解决缓存穿透问题

    :缓存穿透也可以说是穿透攻击,具体来说是因为请求访问到了数据库不存在的值,这样缓存无法命中,必然访问数据库。如果高并发的访问这样的接口,会给数据库带来巨大压力。

    我们项目中都是基于布隆过滤器来解决缓存穿透问题的,当缓存未命中时基于布隆过滤器判断数据是否存在。如果不存在则不去访问数据库。

    当然,也可以使用缓存空值的方式解决,不过这种方案比较浪费内存。

  • 如何解决缓存雪崩问题

    :缓存雪崩的常见原因有两个,第一是因为大量key同时过期。针对问这个题我们可以可以给缓存key设置不同的TTL值,避免key同时过期。

    第二个原因是Redis宕机导致缓存不可用。针对这个问题我们可以利用集群提高Redis的可用性。也可以添加多级缓存,当Redis宕机时还有本地缓存可用。

  • 如何解决缓存击穿问题

    :缓存击穿往往是由热点Key引起的,当热点Key过期时,大量请求涌入同时查询,发现缓存未命中都会去访问数据库,导致数据库压力激增。解决这个问题的主要思路就是避免多线程并发去重建缓存,因此方案有两种。

    第一种是基于互斥锁,当发现缓存未命中时需要先获取互斥锁,再重建缓存,缓存重建完成释放锁。这样就可以保证缓存重建同一时刻只会有一个线程执行。不过这种做法会导致缓存重建时性能下降严重。

    第二种是基于逻辑过期,也就是不给热点Key设置过期时间,而是给数据添加一个过期时间的字段。这样热点Key就不会过期,缓存中永远有数据。

    查询到数据时基于其中的过期时间判断key是否过期,如果过期开启独立新线程异步的重建缓存,而查询请求先返回旧数据即可。当然,这个过程也要加互斥锁,但由于重建缓存是异步的,而且获取锁失败也无需等待,而是返回旧数据,这样性能几乎不受影响。

    需要注意的是,无论是采用哪种方式,在获取互斥锁后一定要再次判断缓存是否命中,做dubbo check. 因为当你获取锁成功时,可能是在你之前有其它线程已经重建缓存了。

        

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值