Redis

news/2024/11/18 2:41:30/

一、Redis特性


为什么快?

  1. 基于内存操作,操作不需要跟磁盘交互。
  2. 本身就是k-v结构,类似hashMap,所以查询速度接近O(1)。
  3. 同时redis自己底层数据结构支持,比如跳表、SDS。
  4. 命令执行是单线程,同时通信采用多路复用。
  5. lO多路复用,单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流 。

其他特性:

  • 更丰富的数据类型 ,虽然都是k、v结构,但 value可以存储很多的数据类型
  • 完善的内存管理机制、保证数据一致性:持久化机制、过期策略
  • 支持多种编程语言
  • 高可用,集群、保证高可用

二、Redis应用场景

Redis提供五种数据类型:string,hash,list,set及zset(sorted set)。以下简单介绍下各种数据类型的应用场景:

1. String类型的应用

  • 缓存相关场景 (缓存、 token,跟过期属性完美契合)
    Redis性能比MySQL等数据库快,那么我们把第一次从数据库拿到的数据存至Redis,之后查询优先从Redis取,这样是不是会更快,如下所示:
    Rdis缓存架构图
  • 线程安全的计数场景 (软限流、分布式ID等)
    数据库数据ID一般用的是数据库自增的ID,如果分表,会出现不同表出现相同ID的情况,这样ID就不能标志数据的唯一性了。
    所以我们解决思路:把生成ID这个步骤独立出来,不要在表中生成,可以交给第三方生成,并且生成的步骤不能有并发问题。
    而我们的redis里面刚好有个incr 或者incryby的指令 ,递增 并且命令执行是单线程的 所以不会有并发导致ID相同。

2. Hash类型的应用

  • 存储对象类的数据(官网说的) 比如一个对象下有多个字段
  • 统计类的数据 我可以对单个统计数据进行单独操作
  • 购物车 (一般人不会这样做,存在数据丢失)

3. List类型的应用

  • 用户消息时间线,利用List的有序性记录时间线。
  • 消息队列

4. Set类型的应用

  • 抽奖 。spop跟srandmember 随机弹出或者获取元素。
  • 点赞、签到等。 sadd 集合存储
  • 交集并集 。关注等场景

5. ZSet类型的应用(有序Set)

  • 排行榜

三、Redis事务

  • mysql的事务,是保证多条sql要么一起执行,要么都不执行。在mysql里开启事务 begin 提交是commit 、 回滚是rollback 。
  • redis的事务,存在两种情况:
    ①可能排队就错误,比如语法的错误 会报错给用户 并且丢失这次事务。
    ②执行指令的失败,比如你对string类型执行incr 这种事务里面的其他指令会正常执行,并且不提供回滚!

四、时间轮

Redis的定时调度是基于时间轮实现的,这里对时间轮进行简单的介绍。
Redis时间轮

五、Redis管道

我们通常使用 Redis 的方式是,发送命令,命令排队,Redis 执行,然后返回结果,这个过程称为Round trip time(简称RTT, 往返时间)。但是如果有多条命令需要执行时,需要消耗 N 次 RTT,经过 N 次 IO 传输,这样效率明显很低。

于是 Redis 管道(pipeline)便产生了,一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),减少了 RTT,提升了效率

重要说明: 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。

使用场景:

  • 在客户端下次写之前不需要读的场景下,大数据量并发写入。
  • 对性能有要求。
  • 管道里的命令不具备原子性,只能保证某个客户端发送的指令是顺序执行的,但是多客户端的命令会交叉执行。
  • 不需要根据上一个指令的返回结果来判断后续的逻辑。

六、Redis的字典数据结构

1. redisDb数据结构(server.h文件)

数据最外层的结构为redisDb

typedef struct redisDb {dict *dict; /* The keyspace for this DB */ //数据库的键dict *expires; /* Timeout of keys with a timeout set */ //设置了超时时间的键dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/ //客户端等待的keysdict *ready_keys; /* Blocked keys that received a PUSH */dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */int id; /* Database ID */ //所在数据库IDlong long avg_ttl; /* Average TTL, just for stats */ //平均TTL,仅用于统计unsigned long expires_cursor; /* Cursor of the active expire cycle. */list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

2. dict数据结构 (dict.h文件)

数据实际存储在redisDb对象的dict中,结构如下:

typedef struct dict {dictType *type; //理解为面向对象思想,为支持不同的数据类型对应dictType抽象方法,不同的数据类型可以不同实现void *privdata; //也可不同的数据类型相关,不同类型特定函数的可选参数。dictht ht[2]; //2个hash表,用来数据存储 2个dicthtlong rehashidx; /* rehashing not in progress if rehashidx == -1 */ // rehash标记 -1代表不再rehashunsigned long iterators; /* number of iterators currently running */
} dict;

3. dictht结构(dict.h文件)

typedef struct dictht {dictEntry **table; //dictEntry 数组unsigned long size; //数组大小 默认为4 #define DICT_HT_INITIAL_SIZE 4unsigned long sizemask; //size-1 用来取模得到数据的下标值unsigned long used; //改hash表中已有的节点数据
} dictht;

4. dictEntry节点结构(dict.h文件)

typedef struct dictEntry {void *key; //keyunion {void *val;uint64_t u64;int64_t s64;double d;} v; //valuestruct dictEntry *next; //指向下一个节点
} dictEntry;

5. redisObject(server.h文件)

typedef struct redisObject {unsigned type:4; //数据类型 string hash listunsigned encoding:4; //底层的数据结构 跳表unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount; //用于回收,引用计数法void *ptr; //指向具体的数据结构的内存地址 比如 跳表、压缩列表
} robj;

6. 数据结构图

Redis数据结构图

7. Redis发生Hash冲突为什么要头插?

头插在并发情况下会形成死链,所以HashMap在1.8之后使用的尾插。而Redis是单线程执行,使用头插法没有并发问题,也不会形成死链表;
同时,头插的性能比尾插的性能高很多,尾插必须找到链表的最后一个位置,而头插只需要加到数据下标,并且把next指向之前的第一个数据。

七、Redis扩容

1. 为什么要扩容?

dictEntry数组默认大小是4,如果不进行扩容,那么我们所有的数据都会以链表的形式添加至数组下标。随着数据量越来越大,之前只需要hash取模就能得到下标位置,现在得去循环我下标的链表,所以性能会越来越慢。

2. 什么时候扩容?

  • .当没有fork子进程在进行RDB或者AOF持久化(内存的数据保存到磁盘防止丢失,后面详细讲解)时,ht[0]的used大于等于size时,触发扩容。
  • .如果有子进程在进行RDB或者AOF时,ht[0]的used大于等于size的5倍的时候,会触发扩容。解释: 现在操作系统通过写时复制(copy-on-write)来优化子进程的使用效率。 在子线程进入RDB跟AOF时,如果发生大量内存写操作,会导致进程的性能降低 。所以,当在RDB或者AOF时,将扩容阈值放大到5倍。

3. 扩容步骤

①当满足我扩容条件,触发扩容时,判断是否在扩容,如果在扩容,或者扩容的大小小于现在的 ht[0].size,这次扩容不做。
② new一个新的dictht,大小为ht[0].used * 2(但是必须向上2的幂,比如6 ,那么大小为8) ,并且ht[1]=新创建的dictht。
③我们有个更大的table了,但是需要把数据迁移到ht[1].table ,所以将dict的rehashidx(数据迁移的偏移量)赋值为0 ,代表可以进行数据迁移了,也就是可以rehash了。
④等待数据迁移完成,数据不会马上迁移,而是采用渐进式rehash,慢慢的把数据迁移到ht[1]。
⑤当数据迁移完成,ht[0].table=ht[1] ,ht[1] .table = NULL、ht[1] .size = 0、ht[1] .sizemask = 0、 ht[1] .used = 0;
⑥ 把dict的rehashidex=-1。

4. 数据怎么迁移(渐进式Rehash)

假如一次性把数据迁移会很耗时间,会让单条指令等待很久很久。会形成阻塞,所以,Redis采用的是渐进式Rehash,所谓渐进式,就是慢慢的,不会一次性把所有数据迁移。

什么时候进行渐进式Rehash?

  • 每次进行key的crud操作都会进行一个hash桶的数据迁移。
  • 定时任务,进行部分数据迁移。

八、底层数据结构

Redis的5种数据类型(string hash list set zset),每种类型都有不同的数据结构来支持。

1. Strings

Redis中String的底层没有用c的char来实现,而是用了SDS(Simple Dynamic String)的数据结构来作为实现。并且提供了5种不同的类型。
SDS数据结构定义 (sds.h文件),其中sdshdr5 never used。

struct __attribute__ ((__packed__)) sdshdr5 {unsigned char flags; /* 3 lsb of type, and 5 msb ofstring length */char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /* used */ //已使用的长度uint8_t alloc; /* excluding the header and nullterminator */ //分配的总容量 不包含头和空终止符unsigned char flags; /* 3 lsb of type, 5 unused bits*/ //低三位类型 高5bit未使用char buf[]; //数据buf 存储字符串数组
};
struct __attribute__ ((__packed__)) sdshdr16 {uint16_t len; /* used */uint16_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {uint32_t len; /* used */uint32_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits*/char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {uint64_t len; /* used */uint64_t alloc; /* excluding the header and null terminator */unsigned char flags; /* 3 lsb of type, 5 unused bits*/char buf[];
};

优势:

  • c字符串不记录自身的长度,所以获取一个字符串长度的复杂度是O(N),但是SDS记录分配的长度alloc,已使用的长度len,获取长度为O(1)。
    char结构图
    SDS结构图
  • 减少修改字符串带来的内存重分配次数
    例如:huihui 更改为 huihui niubi 字符串必须先申明内存,不然会导致内存溢出!截断,trim(huihui)的时候,需要把不再使用的空间回收,不然会内存泄漏。这样的话,如果操作的频率过多,则会导致性能下降!对于Redis是不能容忍的。
    SDS则通过空间预分配与惰性空间释放2种策略解决了以上问题!
    空间预分配: SDS长度如果小于1MB,预分配跟长度一样的,大于1M,每次跟len的大小多1M。
    惰性空间释放: 截取的时候 不马上释放空间,供下次使用!同时提供相应的释放SDS未使用空间的API。
  • 二进制安全
    C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾。因为c的字符是以空字符来判断这个字符串是否结束的。这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
    SDS字符串是否结束是根据len来,所以也就不会有这样的问题。

2. Hashes

Hashes 的底层数据结构可能有两种,ZipList 压缩列表 或者 ditcht hash表。
ZipList 压缩列表
整体布局:
ziplist的总字节数 | 最后一个字节偏移量 | entry数量 | entry | ziplist结尾

 <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry><zlend>
  • 压缩列表会根据存入的数据的不同类型以及不同大小,分配不同大小的空间。所以是为了节省内存而采用的。
  • 因为是一块完整的内存空间,当里面的元素发生变更时,会产生连锁更新,严重影响我们的访问性能。所以,只适用于数据量比较小的场景。

所以,Redis会有相关配置,Hashes只有小数据量的时候才会用到ziplist。当hash对象同时满足以下两个条件的时候,使用ziplist编码:

  • 哈希对象保存的键值对数量<512个;

  • 所有的键值对的健和值的字符串长度都<64byte(一个英文字母一个字节)。
    redis.conf配置

    hash-max-ziplist-value 64 // ziplist中最大能存放的值长度
    hash-max-ziplist-entries 512 // ziplist中最多能存放的entry节点数量
    

ditcht hash表
ditcht hash表

3. Lists

Lists使用的底层数据结构为:quickList 快速列表
quickList 兼顾了ziplist的节省内存,并且一定程度上解决了连锁更新的问题,我们的quicklistNode节点里面是个ziplist,每个节点是分开的。那么就算发生了连锁更新,也只会发生在一个quicklistNode节点。
quicklist.h

typedef struct
{struct quicklistNode *prev; //前指针struct quicklistNode *next; //后指针unsigned char *zl; //数据指针 指向ziplist结果unsigned int sz; //ziplist大小 /* ziplist size in bytes */unsigned int count : 16; /* count of items in ziplist */ //ziplist的元素unsigned int encoding : 2; /* RAW==1 or LZF==2 */ //是否压缩, 1没有压缩 2 lzf压缩unsigned int container : 2; /* NONE==1 or ZIPLIST==2*/ //预留容器字段unsigned int recompress : 1; /* was this node previous compressed? */unsigned int attempted_compress : 1; /* node can't compress; too small */unsigned int extra : 10; /* more bits to steal for future usage */ //预留字段
} quicklistNode;

整体结构图:
quickList整体结构图
quicklist配置: 每个node的ziplist元素的大小可以通过list-max-ziplist-size进行配置:如果这个配置的值为正数,代表quicklistNode的ziplist的node的数量;如果为负数 固定-5到-1,则代表ziplist的大小。

4. Sets

Redis用intset或hashtable存储set。满足下面条件,就用inset存储。
①如果不是整数类型,就用dictht hash表(数组+链表)。
②如果元素个数超过512个,也会用hashtable存储。跟一个配置有关:

set-max-intset-entries 512

不满足条件就用hashtable。set的key没有value,用hashtable存储时,value存null就好。

5. ZSet(Sorted Sets)

  • 默认使用ziplist编码(hash的小编码,quicklist的Node,也都是ziplist)
    在ziplist的内部,按照score排序递增来存储。插入的时候要移动之后的数据。
    如果元素数量大于等于128个,或者任一member长度大于等于64字节使用skiplist+dict存储。
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

如果不满足条件,采用跳表(skiplist)。结构定义(server.h)如下:

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {sds ele; //sds数据double score; //scorestruct zskiplistNode *backward; //后退指针//层级数组struct zskiplistLevel {struct zskiplistNode *forward; //前进指针unsigned long span; //跨度} level[];
} zskiplistNode;//跳表列表
typedef struct zskiplist {struct zskiplistNode *header, *tail; //头尾节点unsigned long length; //节点数量int level; //最大的节点层级
} zskiplist;

ZSKIPLIST_MAXLEVEL默认32 定义在server.h

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */

原理图:
ZSets 跳表结构原理图
如图,已有数据3.7.11.19.22.27.35.40,假如我找27的数据。

  • 只有一个链表的场景:我需要一个一个遍历,随着数据量越大,效率越慢
  • 随机多层级跳表:找27,会从最外层开始找,在22-40之间,再找第二层,在22到35之间,就能找到27。在外层的数据,查询的速度越高,比如找22,只需要一次。

九、Redis过期删除策略

Redis keys过期有两种方式:被动和主动方式。

**惰性删除策略:**当一些客户端尝试访问过期 key 时,Redis 发现 key 已经过期便删除掉这些 key。

当然,这样是不够的,因为有些过期的 keys,可能永远不会被访问到。无论如何,这些 keys 应该过期,所以 Redis 会定时删除这些 key。

Redis默认每秒会进行10次(可以通过redis.conf中的hz值修改,提高频率会占用更多CPU)的以下操作:

  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步奏1

同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
注意: 如果某一时刻,有大量key同时过期,Redis 会持续扫描过期字典,造成客户端响应卡顿,因此设置过期时间时,就尽量避免这个问题,在设置过期时间时,可以给过期时间设置一个随机范围,避免同一时刻过期

十、Redis内存淘汰

1. 内存淘汰策略

当现有内存大于 maxmemory 时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy ,有如下几种淘汰方式:

  1. volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值;
  2. allkeys-lru:淘汰整个键值中最久未使用的键值;
  3. volatile-random:随机淘汰设置了过期时间的任意键值;
  4. allkeys-random:随机淘汰任意键值;
  5. volatile-ttl:优先淘汰更早过期的键值;
  6. noeviction:不淘汰任何数据,当内存不足时,新增操作会报错,Redis 默认内存淘汰策略;

其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。

2. 内存淘汰策略的使用

  • 优先使用allkeys-lru策略。这样,可以充分利用LRU算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。
  • 如果你的业务数据中有明显的冷热数据的区分,建议使用allkeys-lru策略。
  • 如果业务应用中的数据访问频率不大,没有明显的冷热数据区分,建议使用allkeys-random策略,随机淘汰数据即可。
  • 如果业务有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选。

3. 内存淘汰算法

除了随机删除不删除之外,主要有两种淘汰算法:LRU 算法LFU 算法
LRU

  • LRU 全称是Least Recently Used译为最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。
  • 一般 LRU 算法的实现基于链表结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可。
  • Redis 使用的是一种 近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是给现有的数据结构添加一个额外的字段,用于记录此键值的最后一次访问的时间戳,Redis 内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个。

LFU
除了 LRU 算法,Redis 在 4.0 版本引入了 LFU 算法,就是最不频繁使用(Least Frequently Used,LFU)算法。

  • LRU 算法:淘汰最近最少使用的数据,它是根据时间维度来选择将要淘汰的元素,即删除掉最长时间没被访问的元素
  • LFU 算法:淘汰最不频繁访问的数据,它是根据频率维度来选择将要淘汰的元素,即删除访问频率最低的元素。如果两个元素的访问频率相同,则淘汰最久没被访问的元素。

LFU 的基本原理

  • LFU(Least Frequently Used)算法,即最少访问算法,根据访问缓存的历史频率来淘汰数据,核心思想是“如果数据在过去一段时间被访问的次数很少,那么将来被访问的概率也会很低”。
  • 它是根据频率维度来选择将要淘汰的元素,即删除访问频率最低的元素。如果两个元素的访问频率相同,则淘汰最久没被访问的元素。也就是说 LFU 淘汰的时候会选择两个维度,先比较频率,选择访问频率最小的元素;如果频率相同,则按时间维度淘汰掉最久远的那个元素。

十一、Redis持久化机制

1. RDB快照(Redis Database)

RDB 是 Redis 默认的持久化方案。当满足一定条件的时候,会把当前内存中的数据写入磁盘,生成一个快照文件dump.rdb(默认)。

触发时机

①自动触发,分以下几种情况

  • 配置触发

    save 900 1 900s检查一次,至少有1个key被修改就触发
    save 300 10 300s检查一次,至少有10个key被修改就触发
    save 60 10000 60s检查一次,至少有10000个key被修改
    
  • shutdown正常关闭

  • flushall指令触发
    数据清空指令会触发RDB操作,并且是触发一个 空的 RDB文件,所以,如果在没有开启其他的持久化的时候,flushall是可以删库跑路的,在生产环境慎用。

②手动触发

  • save指令:主线程去进行备份,备份期间不会去处理其他的指令,其他指令必须等待。
  • bgsave指令:子线程去进行备份,其他指令正常执行。
如何备份

①新起子线程,子线程会将当前Redis的数据写入一个临时文件;
②当临时文件写完成后,会替换旧的RDB文件。

优势

RDB恢复与备份都非常的快。

  • 备份是个非常紧凑型的文件,非常适合备份与灾难恢复。
  • 最大限度的提升了性能,会fork一个子进程,父进程永远不会产于磁盘IO或者类似操作。
  • 更快的重启。
不足
  • 数据安全性不是很高,因为是根据配置的时间来备份,假如每5分钟备份一次,也会有5分钟数据的丢失。
  • .经常fork子进程,所以比较耗CPU,对CPU不友好。

2. AOF(Append Only File)

AOF默认关闭,开启后,每次的更改命令都会附加到AOF文件中。

AOF同步机制

AOF记录每个写操作,但并不是必须每次都与磁盘进行交互,Redis提供了几种策略,可以根据需要进行选择:

# appendfsync always 表示每次写入都执行fsync(刷新)函数 性能会非常非常慢 但是非常安全
appendfsync everysec 每秒执行一次fsync函数 可能丢失1s的数据,默认,最多会有1s丢失
# appendfsync no 由操作系统保证数据同步到磁盘,速度最快 你的数据只需要交给操作系统就行
AOF重写机制

由于AOF是追加的形式,所以文件会越来越大,越大的话,数据加载越慢。所以我们需要对AOF文件进行重写。
重写就是做了这么一件事情,把当前内存的数据重写下来,然后把之前的追加的文件删除。

重写流程
在Redis7之前:
① Redis fork一个子进程,在一个临时文件中写入新的AOF (当前内存的数据生成的新的AOF)。
②那么在写入新的AOF的时候,主进程还会有指令进入,那么主进程会在内存缓存区中累计新的指令 (但是同时也会写在旧的AOF文件中,就算重写失败,也不会导致AOF损坏或者数据丢失)。
③ 如果子进程重写完成,父进程会收到完成信号,并且把内存缓存中的指令追加到新的AOF文件中。
④替换旧的AOF文件 ,并且将新的指令附加到重写好的AOF文件中。

重写时机
配置文件redis.conf中

# 重写触发机制
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb 就算达到了第一个百分比的大小,也必须大于 64M

说明:在 aof 文件小于64mb的时候不进行重写,当到达64mb的时候,就重写一次。重写后的 aof 文件可能是40mb。上面配置了auto-aof-rewrite-percentag为100,即 aof 文件到了80mb的时候,进行再次重写。

优势
  • .安全性高,就算默认的持久化同步机制,也最多只会导致1s丢失。
  • AOF由于某些原因,比如磁盘满了等导致追加失败,也能通过redis-check-aof 工具来修复。
  • AOF内容格式都是追加的日志,所以可读性更高。
不足
  • 数据集一般比RDB大。
  • 持久化跟数据加载比RDB更慢。
  • 在7.0之前,重写的时候,新的指令会缓存在内存区,所以会导致大量的内存使用。并且重写期间,会跟磁盘进行2次IO,一个是写入老的AOF文件,一个是写入新的AOF文件。

十二、Redis集群

1. Redis主从

主从的作用:

  1. 故障恢复 主挂了或者数据丢失了,还有数据冗余
  2. 负载均衡 流量分发,可以读写分离,减少单实例的读写压力

2. Redis哨兵模式

Redis sentinel ,是独立于Redis服务的单独的服务,两者之间相互通讯。在集群不可用的时候,Redis sentinel为Redis提供了高可用性。并且提供了监测、通知、自动故障转移、配置提供等功能。
监控: 能够监控Redis各实例是否正常工作
通知: 如果Redis实例出现问题,能够通知给其他实例以及sentinel
自动故障转移: 当master宕机,slave可以自动升级为master
配置提供: sentinel可以提供Redis的master实例地址,那么客户端只需要跟sentinel进行连接,master挂了后会提供新的master

哨兵故障转移流程

①当某个sentinel 跟master通信时(默认1s发送ping),发现在一定时间内(down-after-milliseconds) 没有收到master的有效的回复。这个时候这个sentinel就会认为master是不可用的,对其标记一个状态,这个状态就是SDOWN(Subjectively Downcondition 主观下线)。

② SDOWN时,不会触发故障转移,会去询问其他的sentinel是否能连上master,如果超过Quorum(法定人数)的sentinel都认为master不可用,都标记SDOWN状态,这个时候,master可能就真的是down了。那么就会将master标为ODOWN(Objectively Downcondition 客观下线

③当状态为ODWON时,需要去触发故障转移,但是有这么多的sentinel,我们需要选一个sentinel去做故障转移这件事情,并且这个sentinel在做故障转移的时候,其他sentinel不能进行故障转移。

④ 所以,我们需要选举一个sentinel来做这件事情,其中这个选举过程有2个因素:

  • Quorum如果小于等于一半,那么必须超过半数的sentinel授权,你才能去做故障迁移,比如5台 sentinel,你配置的Quorum=2,那么选举的时候必须有3(5台的一半以上)人同意。
  • Quorum如果大于一半,那么必须Quorum的sentinel授权,故障迁移才能启动。
选哪个slave来变成master?

与master断开连接的时间 :如果slave与主服务器断开的连接时间超过主服务器配置的超时时间
(down-after-milliseconds)的十倍,被认为不适合成为master,直接去除资格。

优先级:配置 replica-priority,replica-priority越小,优先级越高,但是配置为0的时候,永远没有资格升为master。

已复制的偏移量:比较slave的赋值的数据偏移量,数据最新的优先升级为master。

Run ID (每个实例启动都会有个Run ID ):通过info server可以查看。

3. 脑裂问题

Redis脑裂问题
问题描述: 初始129是master,假如129网络断开,跟127.128连接断开后,128sentinel发起故障转移。发现sentinel的个数超过一半,能够发起故障转移。将128升级为master,导致128.129同时2个master并可用。,此时,client会向不同的master写数据,从而在master恢复的时候会导致数据丢失。

解决方案:
在Redis.cfg文件中有2个配置:

min-replicas-to-write 1 至少有1个从节点同步到我主节点的数据,但是由于是异步同步,所以是最终一致性 不会确保有数据写入
min-replicas-max-lag 10 判断上面1个的延迟时间必须小于等于10s

说明

作者在工作之余,花费一个月左右的时间对Redis相关知识点进行了全面梳理,成果来之不易,希望摘要或转载的同学们,能够注明出处,万分感谢。
如果文章内容有遗漏或者不妥之处,欢迎补充和指教。

参考文献

内存淘汰相关:
https://blog.csdn.net/LJZFlying/article/details/124211266
https://www.cnblogs.com/frankyou/p/16283974.html


http://www.ppmy.cn/news/29778.html

相关文章

20230304英语学习

What Would Happen if the Moon Disappeared Tomorrow? 如果明天月球消失了会怎样&#xff1f; The closest object to our planet, the Moon, may seem like Earth’s little sibling.Since its birth, the satellite has mostly just hung around, playing gravitational t…

MySQL全局遍历替换特征字符串

需求&#xff1a;将一个MySQL实例&#xff08;如10.10.10.1:3306&#xff09;范围内所有字段数据中的 .letssing.net 替换为 .kaixinvv.com。 实现&#xff1a; 1. 确定替换规则 replace .letssing.net/ -> .kaixinvv.com/ where column like (%http://%.letssing.net/% …

【概念辨析】结构体内存对齐

一、什么是结构体内存对齐 是使得结构体的每个成员能够在及其访问的特定存储单元上的一种方法。 通过这种方法可以使得机器访问效率加快&#xff0c;也可以使得平台一致性变高。 二、结构体对齐的规则 有两组代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS#include <…

jQuery基础

> &#x1f972; &#x1f978; &#x1f90c; &#x1fac0; &#x1fac1; &#x1f977; &#x1f43b;‍❄️&#x1f9a4; &#x1fab6; &#x1f9ad; &#x1fab2; &#x1fab3; &#x1fab0; &#x1fab1; &#x1fab4; &#x1fad0; &#x1fad2; &#x1…

【Python学习笔记】第二十七节 Python 多线程

一、进程和线程进程&#xff1a;是程序的一次执行&#xff0c;每个进程都有自己的地址空间、内存、数据栈及其他记录运行轨迹的辅助数据。线程&#xff1a;所有的线程都运行在同一个进程当中&#xff0c;共享相同的运行环境。线程有开始、顺序执行和结束三个部分&#xff0c; …

MySQL跨服务器数据映射

MySQL跨服务器数据映射环境准备1. 首先是要查看数据库的federated引擎 开启/关闭 状态2. 打开任务管理器&#xff0c;并重启mysql服务3. 再次查看FEDERATED引擎状态&#xff0c;引擎已启动映射实现问题总结在日常的开发中经常进行跨数据库进行查询数据。 同服务器下跨数据库进…

css-盒模型

巧妙运用margin负值盒模型和怪异盒模型(border padding 包含在内)display: block 能让textarea input 水平尺寸自适应父容器? – 不能 * {box-sizing: border-box; // bs: bb }<textarea/> 是替换元素,尺寸由内部元素决定,不受display水平影响. 当然可以直接设置宽度10…

【CS224W】(task9)图神经网络的表示能力(更新中!!)

note 基于图同构网络&#xff08;GIN&#xff09;的图表征网络。为了得到图表征首先需要做节点表征&#xff0c;然后做图读出。GIN中节点表征的计算遵循WL Test算法中节点标签的更新方法&#xff0c;因此它的上界是WL Test算法。 在图读出中&#xff0c;我们对所有的节点表征&…