Redis学习 2021-09-02

Redis详解

redis 知识结构图

在这里插入图片描述

数据结构

String

  • 数据结构:通过SDS储存,类似Java中的ArrayList,预分配冗余空间的方式来减少内存的频繁分配。
  • 常见操作:set/get
  • 应用场景:
    1. 缓存功能:配合数据库作为储存层,利用本身支持高并发的特点加快系统读写速度,降低数据库压力
    2. 计数器:
    3. 共享用户Session:
  • 思考:
    1. SDS是什么?

Hash

  • 数据结构:类似Java中Map的一种结构,存储结构化数据,读写可以操作字段
  • 常见操作:hset/hget
  • 应用场景:
    1. 购物车商品列表
    2. 储存对象
  • 思考:
    1. 怎么解决hash冲突的?

List

  • 数据结构:有序列表,类似linkList
  • 常用操作:lpush/rpush/lrange
  • 应用场景:
  1. 消息队列:lpush左进数据,brpop阻塞抢占获取尾端数据
  2. 简易分页
  • 思考:

Set

  • 数据结构:无序去重集合,类似set,写入的时候给定一个分数
  • 常用操作:sadd/scard
  • 应用场景:
    1. 去重
    2. 交集(sinter)、并集(sunion)、差集(sdiff)
  • 思考:

Sorted Set

  • 数据结构:有序set
  • 常用操作:zadd/zrange
  • 应用场景:
    1. 排行榜,区分不同榜单维度
    2. 带权重队列,优先级执行

功能

Bitmap

布隆过滤器

HyperLogLog

非精确去重计数,做大规模数据去重统计

Geospatial

位置距离计算,附近的人,最优地图路径

pub/sub

发布订阅功能,消息队列

Pipeline

批量执行一组指令,一次性返回结果,减少请求

Lua

支持提交lua脚本原子执行,秒杀功能

事务

非严格事务,串行执行命令,并保证执行完成,但失败不回滚

持久化策略

redis保存数据在磁盘流程:

  1. 客户端向服务端发送写操作(数据在客户端内存中)
  2. 数据库服务端接收到写请求的数据(数据在服务端的内存中)
  3. 服务端调用write方法将数据往磁盘上写(数据在系统内存缓冲区)
  4. 操作系统将缓冲区的数据转移到磁盘控制器上(数据在磁盘缓存中)
  5. 磁盘控制器将数据写到磁盘的物理介质中(数据落到磁盘上)

RDB

RDB其实就是把数据以快照的形式保存在磁盘上。在指定的时间间隔内将内存中的数据集快照写入磁盘,也是默认的持久化的方式,快照为二进制文件,默认文件名为dump.rdb

  1. save触发方式
    在这里插入图片描述
    阻塞当前Redis服务器,执行save命令期间无法处理其它命令直至RDB完成。执行完成后替代老的RDB文件。
  2. bgsave触发方式
    在这里插入图片描述
    redis在后台生成fork线程异步执行快照,期间redis可以响应其它请求。唯一阻塞只是在进行fork的过程,时间很短。
  3. 自动触发
    通过redis.conf配置文件进行配置设置。

在这里插入图片描述
4. RDB的优劣势

  • 优势:
    1. RDB文件紧凑,全量备份,非常适合用作灾备
    2. 生成RDB文件时redis主进程会fork子进程处理所有持久化工作,主进程不进行任何磁盘IO
    3. RDB文件时二进制文件,恢复大数据集的时候速度比AOF快很多
  • 劣势:
    子进程时根据父进程内存进行快照持久化的,在执行期间,父进程对内存的修改子进程无法感知,这段数据时不会保存的,所以可能会出现数据丢失。

AOF

  1. 原理
    日志记录,即redis将收到的每一个写命令都通过write函数追加到文件中保存
    在这里插入图片描述
    redis为了解决aof文件过大的问题,提供了bgrewriteaof命令,将内存中的数据以命令的方式保存到临时文件中,同事fork出一条新进程重写文件,并不会读取旧文件在这里插入图片描述
  2. 触发机制
    • always:同步持久化,每次数据变化都会记录。性能差但数据完整性高
    • everysec:异步操作,每秒记录
    • no:不同步
      在这里插入图片描述
  3. 优劣势
  • 优点:
    1. 数据完整度较高
    2. 无任何磁盘寻址,写入性能非常高,文件不易损坏
    3. 记录或者重写时都不会影响客户端的读写
    4. 文件可读性高,适合做紧急恢复
  • 缺点:
    1. 相比较RDB文件AOF日志文件比较大
    2. AOF支持的写QPS比RDB方式低
    3. 可能会出现bug,无法恢复出完全相同的数据

持久化机制的选择

在这里插入图片描述

失效机制

定期删除

redis默认每隔100ms就随机抽取一些设置了过期时间的key检测是否过期进行删除。
这个时间在redis.conf文件中“hz”属性,默认为10,表示1s执行10次定期删除,可修改。
在这里插入图片描述
抽取数量则是由文件中maxmemory-samples属性决定,默认为5.在这里插入图片描述
为了防止设置过期时间的key数量过大,全部检测导致CPU负载过大,redis挂掉,所以抽取部分不是全部

惰性删除

指redis不会主动删除,而是在获取某个key的时候检测这个key是否过期

内存淘汰机制

在redis内存占用过高时去进行内存淘汰:

  • no-eviction:内存不足,新写入报错,一般不采用
  • allkeys-lru:内存不足,移除最近最少使用的key,最常用的
  • allkeys-random:随机移除,一般也很少用
  • allkeys-lfu:移除最少使用的key,4.0之后新增
  • volatile-lru:在设置了过期时间的key中移除最近最少使用的key
  • volatile-random:在设置了过期时间的key中随机移除某个key
  • volatile-lfu:在设置了过期时间的key中移除最少使用的key
  • volatile-ttl:在设置了过期时间的key中,移除过期时间最早的key

QA:

  • 什么时候会执行淘汰策略?
    redis.conf配置文件中maxmemory属性设置了最大内存使用量
  • 内存淘汰策略配置:
    redis.conf配置文件maxmemory-policy属性,默认为no-eviction

其它场景对过期key的处理

  • 快照RDB生成:过期key不会被保存在RDB文件中
  • 服务重启载入RDB文件:master载入时不会载入过期键,slave会全部载入,在主从同步时删除
  • AOF文件写入:命令文件,redis没有执行删除操作,aof文件也不会删除过期key,执行了就会同步到aof文件内中
  • 重写aof文件:过期键不会被记录到aof文件
  • 中从同步:master会像slave发送del命令同步,slave读取过期键时不会删除,必须当收到master通知才会删除,保证主从数据一致

LRU&LFU&TTL

  • LRU
    • java实现:

      public class LRUCache {
      
          private class DLinkedNode {
              String key;
              int value;
              DLinkedNode pre;
              DLinkedNode post;
          }
      
          private Hashtable<String, DLinkedNode> cache = new Hashtable<String, DLinkedNode>();
          private int count;
          private int capacity;
          private DLinkedNode head, tail;
      
          public LRUCache(int capacity) {
              this.count = 0;
              this.capacity = capacity;
      
              head = new DLinkedNode();
              head.pre = null;
      
              tail = new DLinkedNode();
              tail.post = null;
      
              head.post = tail;
              tail.pre = head;
          }
      
          public int get(String key) {
      
              DLinkedNode node = cache.get(key);
              if (node == null) {
                  // should raise exception here.
                  return -1;
              }
      
              // move the accessed node to the head;
              this.moveToHead(node);
      
              return node.value;
          }
      
          public void set(String key, int value) {
              DLinkedNode node = cache.get(key);
      
              if (node == null) {
      
                  DLinkedNode newNode = new DLinkedNode();
                  newNode.key = key;
                  newNode.value = value;
      
                  this.cache.put(key, newNode);
                  this.addNode(newNode);
      
                  ++count;
      
                  if (count > capacity) {
                      // pop the tail
                      DLinkedNode tail = this.popTail();
                      this.cache.remove(tail.key);
                      --count;
                  }
              } else {
                  // update the value.
                  node.value = value;
                  this.moveToHead(node);
              }
          }
      
          /**
           * Always add the new node right after head;
           */
          private void addNode(DLinkedNode node) {
              node.pre = head;
              node.post = head.post;
      
              head.post.pre = node;
              head.post = node;
          }
      
          /**
           * Remove an existing node from the linked list.
           */
          private void removeNode(DLinkedNode node) {
              DLinkedNode pre = node.pre;
              DLinkedNode post = node.post;
      
              pre.post = post;
              post.pre = pre;
          }
      
          /**
           * Move certain node in between to the head.
           */
          private void moveToHead(DLinkedNode node) {
              this.removeNode(node);
              this.addNode(node);
          }
      
          // pop the current tail.
          private DLinkedNode popTail() {
              DLinkedNode res = tail.pre;
              this.removeNode(res);
              return res;
          }
      }```
      
      
    • redis实现
      正统的LRU算法实现需要额外存放next和pre指针,内存消耗很大,所以redis采取随机若干样本,根据热度值排序淘汰

      • lru字段:redis中所有的对象结构都有一个lru字段记录对象热度值,每当对象被创建和访问时都会记录或者更新,设置为全局变量server.lruclock的值
      • lruclock:redis默认每100ms更新一次lru时钟的值,记录当前系统时间戳
      • 热度值:评估对象的热度值就是计算lru字段值和lru时钟之间的差值,差值越大,热度越低。但是这里注意lru时钟只有24位最大储存194天,超过时就会重新计算,此时计算热度值时相加不是相减
      • 淘汰池:为了增加淘汰效率,3.0版本之后新增,是一个大小等同于maxmemory_samples的数组,每次随机得到的key列表会和淘汰吃合并,淘汰掉合并后的列表中最旧的保留剩余较旧的放入列表等待下一轮循环
  • LFU
    redis中的lfu算法位每个key维护一个计数器,每次key被访问的时候计数器增大,表示访问频率。
    在lfu中对应中也有一个24位的属性,其中高16位时记录最近一次计数器降低的时间,单位时分钟,低8位时记录计数器值counter。

    新增两个配置:
    lfu-log-factor可以调整计数器counter的增长速度,lfu-log-factor越大,counter增长的越慢。
    lfu-decay-time是一个以分钟为单位的数值,可以调整counter的减少速度

    • counter减少
      • 首先取得高16 bits的最近降低时间ldt与低8 bits的计数器counter,然后根据配置的lfu_decay_time计算应该降低多少。
      • LFUTimeElapsed用来计算当前时间与ldt的差值:
      • 当前时间转化成分钟数后取低16 bits,然后计算与ldt的差值now-ldt。当ldt > now时,默认为过了一个周期(16 bits,最大65535),取值65535-ldt+now。
      • 然后用差值与配置lfu_decay_time相除,LFUTimeElapsed(ldt) / server.lfu_decay_time,已过去n个lfu_decay_time,则将counter减少n,counter - num_periods。
    • counter增加:
      counter并不是简单的访问一次就+1,而是采用了一个0-1之间的p因子控制增长。counter最大值为255。
      • 取一个0-1之间的随机数r与p比较,当r<p时,才增加counter
      • p取决于当前counter值与lfu_log_factor因子,counter值与lfu_log_factor因子越大,p越小,r<p的概率也越小,counter增长的概率也就越小。
      • 可见counter增长与访问次数呈现对数增长的趋势,随着访问次数越来越大,counter增长的越来越慢。
    • 新生key策略
      当创建新对象的时候,对象的counter如果为0,很容易就会被淘汰掉,还需要为新生key设置一个初始counter
      • counter会被初始化为LFU_INIT_VAL,默认5。
    • pool:与lru算法思想一致

TTL:生存时间

储存数据结构:
  • redisDb对象
    	typedef struct redisDb {
    
        // 数据库键空间,保存着数据库中的所有键值对
        dict *dict;                 /* The keyspace for this DB */
    
        // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
        dict *expires;              /* Timeout of keys with a timeout set */
    
        // 正处于阻塞状态的键
        dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */
    
        // 可以解除阻塞的键
        dict *ready_keys;           /* Blocked keys that received a PUSH */
    
        // 正在被 WATCH 命令监视的键
        dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    
        struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */
    
        // 数据库号码
        int id;                     /* Database ID */
    
        // 数据库的键的平均 TTL ,统计信息
        long long avg_ttl;          /* Average TTL, just for stats */
    } redisDb;
    
  • dict对象:就是一个hashtable
    /*
     * 字典
     */
    typedef struct dict {
    
        // 类型特定函数
        dictType *type;
    
        // 私有数据
        void *privdata;
    
        // 哈希表
        dictht ht[2];
    
        // rehash 索引
        // 当 rehash 不在进行时,值为 -1
        int rehashidx; /* rehashing not in progress if rehashidx == -1 */
    
        // 目前正在运行的安全迭代器的数量
        int iterators; /* number of iterators currently running */
    
    } dict;
    
  • dictht对象
    /*
     * 哈希表
     *
     * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
     */
    typedef struct dictht {
        
        // 哈希表数组
        dictEntry **table;
    
        // 哈希表大小
        unsigned long size;
        
        // 哈希表大小掩码,用于计算索引值
        // 总是等于 size - 1
        unsigned long sizemask;
    
        // 该哈希表已有节点的数量
        unsigned long used;
    
    } dictht;
    
  • dictEntry对象:相当于hashtable中的hash桶
设置过期时间方法
  • expire:相对时间,单位秒
  • expireat:绝对时间,单位秒
  • pexpire:相对时间,单位毫秒
  • pexpireat:绝对时间,单位毫秒

相对时间的基准时间是当前时间,绝对时间的基准时间是0.

设置过程
  • 不同策略最后都会换算成绝对时间,毫秒进行计算
  • setExpire:对redisDb中的expire字段设置dictEntry的key-value
  • 查询时就很简单通过ttl或者pttl使用key从expires字段中找到过期时间对象然后跟当前系统时间相比计算差值

Redis 集群

主从模式

节点说明
  • Master:可进行读写操作,同时可拥有多个Slave节点
  • Slave:推荐只有读操作且只能拥有一个master节点,但可以拥有slave节点,即级联结构。
主从复制原理

在这里插入图片描述

  • 复制初始化:从节点首次启动发生
    1. 向主节点发送SYNC命令
    2. 主节点接受命令后触发RDB,同时保存期间收到的命令缓存
    3. 完成RDB后将快照文件和缓存命令发送给从节点
    4. 从节点接收执行数据同步同时执行缓存命令保持数据一致

    2.6之前每次短线都要初始化,浪费性能,2.8之后断线只要将期间的命令执行同步

乐观复制

一般主数据库接受到请求后会立即返回,同时异步将命令同步给从数据库,期间如果网络断开就会产生中从数据不一致,所谓乐观就是指集群默认不会产生这种情况。

  • min-slave-to-write 3:设置三个从节点完成主节点数据同步后主节点才是可写状态,否则报错
  • min-slaves-max-lag 10 :从数据库最长失联时间(单位s)
增量复制

环形积压队列:存储最新的操作数据

  1. 主节点RDB之外还会维护一个环形积压队列、队列写索引和从节点同步全局offset。
  2. 从节点会自动存住主节点运行id
  3. 主节点同步命令时同时会存放到积压队列并记录偏移量
  4. 从节点接收同步命令的同时也会记录存储偏移量

2.8版本之后主从复制同步时发送的是PSYNC命令,主从断开重连后,从节点会将维护的offset以及之前链接的主节点id发送给主节点,验证满足条件后进行增量复制:

  • 断开连接前后主节点run id一致
  • 主节点中的环形积压队列可以找到对应offset

相关配置:

  1. repl-backlog-size 1mb:默认情况下积压队列的大小为1MB
  2. repl-backlog-ttl 3600 : 没有slave需要同步的时候,多久可以释放环形队列,默认一小时
主从特点
  • 优点:
    1. 高可靠性:采用双机主备,自动主备切换;数据持久化功能和备份策略,可有效解决误操作及异常造成的数据丢失问题;
    2. 读写分离:系统伸缩性高,提升高并发量读操作
  • 缺点:
    1. 故障恢复复杂:手动晋升主节点,且业务方需要变更配置,其它从节点也需要复制新主节点信息,需要人为干预,操作繁琐
    2. 主节点写能力受到单机限制,可分片
    3. 主节点储存能力受到单机限制,可考虑Pika
    4. 主从复制时通过psnyc同步,不成功的话就会进行全量复制,这里主机点进行备份的时候会造成毫秒或者秒级的阻塞;并且持久化机制在极端情况中会产生主节点内存溢出,导致异常退出或宕机;同时也会因为大量IO和压缩消耗CPU等系统资源,大数据传输也会导致服务器出口带宽暴增,请求阻塞等问题

哨兵模式

哨兵的作用
  1. 监控主从节点状态是否正常
  2. 主节点故障自动切换子节点转换为主节点

普通主从模式手动切换

  1. 使用slave no one 命令将从节点提升为主节点
  2. 重启奔溃的主节点,使用slave of 命令将其设置为新主节点的从节点
    开启主从复制且主节点关闭持久化功能时,一定不能使用Suoervisor自动重启功能,主节点数据清空后从节点数据也会被清空导致从节点持久化失去意义
集群哨兵

防止哨兵单点故障,使用多个哨兵进行监控,且它们直之间也可以相互监控
在这里插入图片描述

哨兵实现原理
  1. 读取配置找到监控主节点

    sentinel monitor master-name ip port quorum

    • master-name:主节点名字
    • ip、port:当前主节点地址和端口
    • quorum:执行故障恢复前需要多少哨兵节点统一
  2. 建立连接
    • 订阅主节点_sentinel_hello频道获取其它监控该节点的哨兵节点信息
    • 定期向主节点发送info命令,获取主节点本身信息
  3. 完成连接后执行三个操作
    1. 每隔10s向主、从节点发送info命令。获取当前节点信息;
    2. 每隔2s向主从节点的_sentinel_hello频道发送自身信息,分享信息:<哨兵地址>,<哨兵端口>,<哨兵运行id>,<哨兵配置版本>,<主数据库名字>,<主数据库地址>,<主数据库端口>,<主数据库配置版本>
    3. 每隔1s会向所有节点及哨兵发送ping命令,监控节点是否存活。down-after-millisecond实现
主观下线和客观下线
  • 主观下线:若超过down-after-millisecond时间节点未回复,哨兵会主管认为改节点已经下线。
  • 客观下线:哨兵主观认为某个节点下线了,如果这个节点时主节点,发送sentinel is-master-down-by-addr命令向其它哨兵确认该主节点是否主观下线,当达到sentinel monitor master-name ip port quorum的quorum参数设定的数量时,认定该节点客观下线。

当主节点客观下线时就需要进行主从切换,步骤如下:

  1. 领头哨兵选举
  2. 领头哨兵在现在的从节点中选择一个发送slave no one 命令升级主节点并发送slaveof 命令将其它从节点的主节点设置为新的主节点
  3. 从节点选择优先级:
    1. slave-priority优先级设定
    2. 复制偏移量越大越优先
    3. run id 较小的

上述过程中,领头哨兵的的选举用了Raft算法,具体思想如下:

  1. 发送主节点客观下线的哨兵会主动向其它哨兵命令,要求选择自己为领头
  2. 如果没有选择过其它哨兵,同意请求
  3. 超过半数并且超过quorum设置数量的哨兵同意,则选举成功

如果选举失败,各个哨兵都会在一个随机时间参选,一致到成功。

哨兵模式特点
  1. 不时地监控redis是否按照预期良好地运行;
  2. 如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);
  3. 能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
  4. 哨兵为客户端提供服务发现,客户端链接哨兵,哨兵提供当前master的地址然后提供服务,如果出现切换,也就是master挂了,哨兵会提供客户端一个新地址。

哨兵本身也是支持集群的,数量要满足2n+1奇数个。

哨兵模式优缺点
  • 优点:
    1. 部署简单
    2. 解决主从模式中高可用主从切换问题
    3. 很方便实现redis数据节点线性扩展
    4. 实现一套sentinel监控一组或者多组redis数据节点
  • 缺点:
    1. 原理相对于主从模式更复杂
    2. 资源浪费,哨兵模式中的slave节点做备份节点不提供服务
    3. 哨兵模式针对主节点做高可用主备切换,但是从节点只有主观下线不会做故障转移
    4. 无法解决读写分离问题,想要实现的话相对复杂

Redus Cluster模式

  • 为什么要引入Cluster?
    为了解决主节点单机写能力和储存能力限制,扩展整个redis集群承载上限

  • 什么是Redis Cluster?
    可以简单理解为n个主从架构组合,至少3个master,每个master至少一个slave。在这里插入图片描述这里注意,Cluster模式读写都是再master节点完成,slave只是充当数据备份脚色,不提供服务

  • 如何进行负载均衡?选择哪个master节点储存?

    1. 简单Hash算法:对key进行hash计算得出一个hash值,然后对master数量取模进行负载均衡。但这种算法一旦集群中一台master节点宕机那么取模数量变化会导致所有的key都会失效。
    2. 一致性hash算法:区别于简单hash算法,一致性hash算法是对(232)取模,取值范围在[0,2^32 -1]之间。这样就将范围抽象成了一个圆环,再通过CRC16算法计算出hash值落在圆环的某个地方。然后顺时针找到第一个redis实例完成key的节点分配。
  • 一致性hash算法怎么解决节点宕机问题?
    在这里插入图片描述
    如上,假如B节点挂了,那么会自动将B节点的流量转移到C节点上,节点A和C原本的数据不受影响

  • 一致性hash怎么解决节点部分不均衡问题呢?
    在这里插入图片描述
    如上图,节点A和B的压力较大,C节点资源利用不充分

    • 虚节点机制:通过增加虚节点的方式使得实际节点在圆环上分布更加均匀,虚节点会映射到对应实际节点
      在这里插入图片描述
  • Redis Cluster采用什么算法?
    类一致性hash算法:相对于一致性hash算法,redis是对2^14(也就是16384)取模,通过CRC16算法计算出hash值跟16384取模得到对应的槽位,然后每个节点会负责一部分的槽位。
    例如3个节点

    节点处理槽位
    A0-5000
    B5001-10000
    A10001-16383

    每个redis实例都会维护自己的一份slot-Redis节点映射关系,不管在哪个节点设置key,计算出来的槽位是哪个节点维护那就会在哪个节点操作在这里插入图片描述

  • Redis Cluster怎么高可用?

    • 集群扩容:在redis内部由redis-trib负责,向获取slot节点和被转移slot节点发送命令进行reshard,然后通知所有主节点槽位变更信息,以此实现扩容。
    • 故障转移:原理和sentinel模式如出一辙,简单来说就是其它master节点相当于哨兵节点,如果其它主节点发现某个主节点宕机了,标记为主观下线,然后进行集群确认,超过半数都标记这个节点主观下线,那么久标记成客观下线。然后其它节点投票从该节点的从节点中选举一个切换成新的master对外提供服务。
      在这里插入图片描述
      对于Cluster而言,某个key只需找到对应slot,然后跟关联节点绑定实例就可以。
  • 那么什么是pong消息?

    • redis cluster通信协议:gossip,主要解决分布式数据库中各个副本节点之间的数据同步问题。
    • redis节点每秒都会向其它节点发送ping,被ping的节点回一个pong
      在这里插入图片描述
    • gossip协议消息类型
    消息类型消息内容
    MEET给某个节点发送MEET消息,请求接收消息的节点加入到集群中
    PING每隔一秒钟,选择5个最久没有通信的节点,发送PING消息,检测对应的节点是否在线;同时还有一种策略是,如果某个节点的通信延迟大于了cluster-node-time的值的一半,就会立即给该节点发送PING消息,避免数据交换延迟过久
    PONG当节点接收到MEET或者PING消息之后,会回一个PONG消息给发送方,代表自己收到了MEET或者PING消息。同时,节点也可以主动的通过PONG消息向集群中广播自己的信息,让其他节点获取到自己最新的属性,就像完成了故障转移之后新的master向集群发送PONG消息一样
    FAIL用于广播自己的对某个节点的宕机判断,假设当前节点对A节点判断为宕机,就会立即向Redis Cluster广播自己对于A节点的判断,所有收到消息的节点就会对A节点做标记
    PUBLISH用于向指定的Channel发送消息,某个节点收到PUBLISH消息之后会直接在集群内广播,这样一来,客户端无论连接到任何节点都能够订阅这个Channel
总结

总的来说Cluster就是相当于把主从和Sentinel集成到了一块,基础还是主从和Sentinel。

Redis常见三大问题

缓存穿透

  • 原因:大概率是恶意攻击,比如使用大量不存在的用户id频繁请求,缓存无法命中,大量请求穿透缓存直接访问DB导致服务异常
  • 解决方式:
    1. 增加空对象标记,防止相同ID重复访问,但是同意造成缓存中大量无用数据
    2. 布隆过滤器

缓存击穿

  • 原因:某个非常热点的key扛着大并发,集中对这个点进行访问,这个key失效的瞬间大量请求穿破缓存直接访问DB
  • 解决方案:
    1.采用分布式锁,进行一个自旋操作
    2.定时刷新设计,请求只查缓存,永远不会请求DB
    3.JVM互斥锁,防止同一个进程中出现大量并发请求同时访问DB

缓存雪崩

  • 原因:同一时间大量缓存失效导致大量请求访问DB
  • 解决方案:
    1. 分布式锁、jvm锁等方式避免单个key大量请求
    2. 在缓存和db之间加入消息队列
    3. 多级缓存,redis+ehcache等多级缓存方案
    4. 分析数据,设置不同的key失效时效
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值