Redis

数据类型

String

set key value:设置值 get key:获取值 del key:删除key
strlen key:获取该key值对应的value值的长度
append key “xxx”:在该key对应的value值追加上xxx
incr key:该key对应的value自增1 decr key:该key对应的value自减1’
incrby key xx:该key对应的value增加xx decrby key xx:减少xx
getrange key x1 x2: 获取x1-x2范围内的值,类似between…and的关系,从零到负一表示全部

String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。常规key-value缓存应用: 常规计数:微博数,粉丝数等

List

所有的list命令都是用l开头的
lpush 集合名 xx:将xx插入到集合头部 rpush:将xx插入到集合尾部
lrange 集合名 x y:获取集合里x-y的值 (0 -1为获取集合中所有的值)
lpop 集合名:移除该集合的左值 rpop:移除该集合的右值
lindex 集合名 x:获取该集合中下标为x的值 llen 集合名:获取集合的长度
lset 集合名 index xx:该集合下标为index的值更新为xx(不能添加)
linsert 集合名 before yy xx:在集合中的yy值前面插入一个xx值
linsert 集合名 after yy xx:在集合中的yy值后插入一个xx值

总结:

1、它是一个字符串链表,left,right 都可以插入添加
2、如果键不存在,创建新的链表 如果键已存在,新增内容
3、如果值全移除,对应的键也就消失了
4、链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。

list就是链表使用Lists结构,我们可以轻松地实现最新消息排队等功能。List的另一个应用就是消息队列,可以利用List的PUSH操作,将任务存在List中,然后工 作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删 除List中某一段的元素。 Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部 添加或者删除元素,这样List即可以作为栈,也可以作为队.

Set

set中的值是不能重复的
sadd 集合名 值:向set集合中添加值 smembers 集合名:查看该set集合中所有的值
srem 集合名 值:移除set集合中的某个值 srandmember 集合名:随机取出集合中的一个值
spop 集合名:随机删除一些set集合中的元素 smove set1 set2 “xx”:把set1中的xx移动到set2
sdiff set1 set2:差集 sinter set1 set2:交集 sunion set1 set2:并集

Hash

hset 集合名 key1 value1:给map集合中添加一个或者多个key-value键值对
hget 集合名 key:获取对应的key的value值 hgetall 集合名:获取集合中所有的键值对
hexists 集合名 key:判断该集合中的指定key是否存在 hkeys 集合名:获取集合中所有的key

Zset有序集合

zadd 集合名   scroe值    xx:想集合中增加xx值,排序的时候通过score的值进行排序

zrange 集合名 0 -1:查询集合中所有的值,默认按照score值的升序排列
zrangebyscore 集合名 -inf +inf:按score值升序排列,可以用具体值替换±inf,例如[-inf,2000]
zrevrangebyscore 集合名 +inf -inf:按照score的值降序排列
zrem 集合名 值:移除该集合中的指定元素 zcard 集合名:获取有序集合中的元素的个数

为什么快

基于内存实现

Redis 是基于内存的数据库,那不可避免的就要与磁盘数据库做对比。对于磁盘数据库来说,是需要将数据读取到内存里的,这个过程会受到磁盘 I/O 的限制。

而对于内存数据库来说,本身数据就存在于内存里,也就没有了这方面的开销。

高效的数据结构

在这里插入图片描述

简单动态字符串SDS

长度记录
用一个 len 字段记录当前字符串的长度。想要获取长度只需要获取 len 字段即可无需遍历。
内存重新分配
通过预分配来减小字符串增长内存扩充时重分配的性能消耗,小于1M时额外分配len,大于1M时额外分配1M。通过惰性空间释放来减小字符串缩短内存释放时重分配的性能消耗,用 free 字段将多出来的空间记录下来,下次变化直接使用

双端链表

能够同时承担队列和链表,前后指针查找O(1)
头节点保存指向头节点和尾节点指针和长度,常用数据O(1)

压缩列表

ziplist是由一系列特殊编码的连续内存块组成的顺序存储结构,类似于数组,ziplist在内存中是连续存储的,但是不同于数组,为了节省内存 ziplist的每个元素所占的内存大小可以不同。
通过entry自身格式判断长度,进行往后遍历,通过prevlen进行往前遍历,解决entry长度不一致带来的遍历问题
在这里插入图片描述
在这里插入图片描述
连锁更新:
前一个entry的字节长度在小于254时,prevlen只需要一个字节存储,当在其前面新增一个255以上节点时,后面的entry会从1个字节变为5个自己存储长度,如果此entry也刚好接近255,则后续entry也需要继续更新。prevlen长度重分配需要O(n)复杂度,后面每个entry更新prevlen长度也为O(n)总共O(n2)

字典

Redis 作为 K-V 型数据库,所有的键值都是用字典来存储的。

Redis 的字典 dict 中包含两个哈希表 dictht,这是为了方便进行 rehash 操作。在扩容时,将其中一个 dictht 上的键值对 rehash 到另一个 dictht 上面,完成之后释放空间并交换两个 dictht 的角色。
rehash 操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的 rehash 操作给服务器带来过大的负担。

渐进式 rehash 通过记录 dict 的 rehashidx 完成,它从 0 开始,然后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],这一次会把 dict[0] 上 table[rehashidx] 的键值对 rehash 到 dict[1] 上,dict[0] 的 table[rehashidx] 指向 null,并令 rehashidx++。

在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。

随着字典操作次数的增加,rehashidx的值不断增大,渐渐将ht[0]中的键值对都rehash到ht[1]中,并且当执行的操作为添加时是直接在ht[1]数组中执行的,而其他操作为先到ht[0]中执行,如果ht[0]到没有要执行的键值对,则到ht[1]中查询

跳跃表

作为 Redis 中特有的数据结构-跳跃表,其在链表的基础上增加了多级索引来提升查找效率。每一层都有一条有序的链表,最底层的链表包含了所有的元素。这样跳跃表就可以支持在 O(logN) 的时间复杂度里查找到对应的节点。
在这里插入图片描述
特点
(1) 跳表结合了链表和类似二分查找的思想;

(2) 有很多层结构,由原始链表和一些通过“跳跃”生成的链表组成;

(3) 每一层都是一个有序的链表;

(4) 最底层(Level 1)的链表包含所有元素,越上层“跳跃”的越高,元素(索引)越少;

(5) 查找时从顶层向下,不断缩小搜索范围;

(6) 上层链表是下层链表的子序列;

(7) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

为啥不用B+树和红黑树
B+树充分利用了磁盘预读的功能,最大限度的降低磁盘的IO。而Redis是内存中读取数据,不涉及IO。
插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度和跳表是一样的。按照区间查找数据这个操作,红黑树的效率没有跳表高。跳表(查看实现原理)可以在 O(logn) 时间复杂度定位区间的起点,然后在原始链表中顺序向后查询就可以了。跳跃表的实现相较于红黑树更加简洁,内存占用更有优势。

I/O 多路复用

Redis 中使用 I/O 多路复用程序同时监听多个套接字,并将这些事件推送到一个队列里,然后逐个被执行。最终将结果返回给客户端。没有上下文切换。

单线程

在这里插入图片描述

redis多线程是怎么做到无锁的?

上面说了多线程的地方都是互相独立互不影响的。但是每个线程的队列就存在两个两个线程访问的情况:主线程向队列中写数据,子线程消费,redis的实现有点反直觉。按正常思路来说,主线程在往队列中写数据的时候加锁;子线程复制队列&并将队列清空,这个两个动作是加锁的,子线程消费复制后的队列,这个过程是不需要加锁的,按理来说主线程和子线程的加锁动作都是非常快的。但是redis并没有这么实现,那么他是怎么实现的呢?

redis多线程的模型是主线程负责搜集任务,放入全局读队列clients_pending_read和全局写队列clients_pending_write,主线程在将队列中的任务以轮训的方式分发到每个线程对应的队列(list *io_threads_list[128])
1:一开始子线程的队列都是空,主线程将全对队列中的任务分发到每个线程的队列,并设置一个队列有数据的标记(_Atomic unsigned long io_threads_pending[128]),io_threads_pending[1]=5表示第一个线程的队列中有5个元素
2:子线程死循环轮训检查io_threads_pending[index] > 0,有数据就开始处理,处理完成之后将io_threads_pending[index] = 0,没数据继续检查
3:主线程将任务分发到子线程的队列中,自己处理自己队列中的任务,处理完成后,等待所有子线程处理完所有任务,继续收集任务到全局队列,在将任务分发给子线程,这样就避免了主线程和子线程同时访问队列的情况,主线程向队列写的时候子线程还没开始消费,子线程在消费的时候主线程在等待子线程消费完,子线程消费完后主线程才会往队列中继续写,就不用加锁了。因为任务是平均分配到每个队列的,所以每个队列的处理时间是接近的,等待的时间会很短。

合理的数据编码

对于每一种数据类型来说,底层的支持可能是多种数据结构,什么时候使用哪种数据结构,这就涉及到了编码转化的问题。

String:存储数字的话,采用int类型的编码,如果是非数字的话,采用 raw 编码;

List:字符串长度及元素个数小于一定范围使用 ziplist 编码,任意条件不满足,则转化为 linkedlist 编码;

Hash:hash 对象保存的键值对内的键和值字符串长度小于一定值及键值对;

Set:保存元素为整数及元素个数小于一定范围使用 intset 编码,任意条件不满足,则使用 hashtable 编码;

Zset:zset 对象中保存的元素个数小于及成员长度小于一定值使用 ziplist 编码,任意条件不满足,则使用 skiplist 编码。

Memcache 与 Redis 的区别都有哪些?

1、存储方式 Memecache 把数据全部存在内存之中, 断电后会挂掉, 数据不能超过内存大小。 Redis有部份存在硬盘上, 这样能保证数据的持久性
2、数据支持类型 Memcache 对数据类型支持相对简单。 Redis 有复杂的数据类型。
3、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis 直接自己构建了 VM 机制
4、Memcached 不支持分布式。Redis Cluster 实现了分布式的支持。

事务

一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。
事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。
Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。

事务失败

Redis在执行事务命令的时候,在命令入队的时候, Redis 就会检测事务的命令是否正确,如果不正确则会产生错误。无论之前和之后的命令都不会执行。

当命令格式正确,而因为操作数据结构引起的错误 ,则该命令执行出现错误,而其之前和之后的命令都会被正常执行。这点和数据库很不一样,这是需注意的地方。

利用Watch实现Redis乐观锁

​ 乐观锁基于CAS(Compare And Swap)比较并替换思想,不会产生锁等待而消耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis来实现乐观锁(秒杀)。具体思路如下:

1、利用redis的watch功能,监控这个redisKey的状态值
2、获取redisKey的值,创建redis事务,给这个key的值+1
3、执行这个事务,如果key的值被修改过修改失败,key不加1

什么是缓存击穿、缓存穿透、缓存雪崩

缓存穿透

读请求访问时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查询请求都会穿透到数据库,这就是缓存穿透。

避免缓存穿透
1.如果是非法请求,对参数进行校验,过滤非法值。
2.如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效)
3.使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。

布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。判断存在的时候不一定真存在。

缓存雪崩

缓存雪崩: 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。

缓存雪崩一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值
Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群。

缓存击穿

缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。

解决方案:
1.使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
2. “永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
3. 数据库限流保护

热Key问题

访问频率高的key,称为热点key。如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务

识别

  • 凭经验判断哪些是热Key;
  • 客户端统计上报;
  • 服务代理层上报;

解决

  • 读写分离架构:增加分片副本,均衡读流量;
  • 将热key分散到不同的服务器中;
  • 使用二级缓存,即JVM本地缓存,减少Redis的读请求; 需要提前获知热点,可能遗漏,容量有限,不一致性问题

大Key问题

所谓的大key问题是某个key的value比较大,所以本质上是大value问题。key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。

影响
1.资源使用不均(该大key可能会使用该实例相当多的内存,浪费相当大的Cpu,)
2.带宽使用极大(比如假如我们有个功能展示上面例子中点赞的所有文章,一下查出来全部,肯定会使用非常大的带宽)
3.影响该实例其他key的操作(基于redis的单线程处理机制,大家都在排队,前面的慢,自然影响其他数据的操作)

1k开始有影响10k大幅度影响

产生
大key的产生往往是业务方设计不合理,没有预见vaule的动态增长问题:

一直往value塞数据,没有删除机制,迟早要爆炸
数据没有合理做分片,将大key变成小key

发现
增加内存&流量&超时等指标监控
使用bigkeys命令以遍历的方式分析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key
redis-rdb-tools离线分析工具来扫描RDB持久化文件,虽然实时性略差,但是完全离线对性能无影响
解决
1.数据结构拆分,比如我们这里有个活动数据,活动有活动商品数据,这俩就进行了拆分,并没有放一起
2.数据分片,比如后面加序号,进行多实例的拆分
删除大key
4.0后支持,unlink 命令是 del 的异步版本,由 Lazyfree 机制实现。Lazyfree 机制的原理是在删除的时候只进行逻辑删除,把 key 释放操作放在 bio (Background I/O)单独的子线程中惰性处理,减少删除大 key 对 redis 主线程的阻塞

过期策略和内存淘汰策略

过期策略

定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期
每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis中同时使用了惰性过期和定期过期两种过期策略。

假设Redis当前存放30万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。
因此,redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。
但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。
但是,如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存内存,直接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量使用,内存直接不够了,Redis用8种内存淘汰策略保护自己

内存淘汰策略

volatile-lru:当内存不足以容纳新写入数据时,从设置了过期时间的key中使用LRU(最近最少使用)算法进行淘汰;
allkeys-lru:当内存不足以容纳新写入数据时,从所有key中使用LRU(最近最少使用)算法进行淘汰。
volatile-lfu:4.0版本新增,当内存不足以容纳新写入数据时,在过期的key中,使用LFU算法进行删除key。
allkeys-lfu:4.0版本新增,当内存不足以容纳新写入数据时,从所有key中使用LFU算法进行淘汰;
volatile-random:当内存不足以容纳新写入数据时,从设置了过期时间的key中,随机淘汰数据;。
allkeys-random:当内存不足以容纳新写入数据时,从所有key中随机淘汰数据。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的key中,根据过期时间进行淘汰,越早过期的优先被淘汰;
noeviction:默认策略,当内存不足以容纳新写入数据时,新写入操作会报错。

持久化机制

RDB:Redis DataBase缩写快照

RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。

优点:
只有一个文件 dump.rdb,方便持久化。
容灾性好,一个文件可以保存到安全的磁盘。
性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单 独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
相对于数据集大时,比 AOF 的启动效率更高。

缺点:
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数 据丢失。所以这种方式更适合数据要求不严谨的时候。

AOF:Append Only File持久化

AOF持久化则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复

优点:
数据安全,aof 持久化可以配置 appendfsync 属性,有always,每进行一次命令操作就记录 到 aof 文件中一次。
通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一 致性问题。
AOF机制的rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flflushall))

缺点:
AOF 文件比 RDB 文件大,且恢复速度慢。
数据集大的时候,比 rdb 启动效率低。

对比

AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF

缓存一致性问题

缓存相当于数据库数据的一层备份,需要尽可能保证缓存数据和数据库数据的一致性。尤其是在数据修改的场景。

删除缓存

变更数据库后通过删除缓存,然后下次查询时从数据库查出后放入缓存。此处可以通过延迟双删提高删除的

消息队列

增加消息队列,将redis更新操作交给kafka,由消息队列保证可靠性,再搭建一个消费服务,来异步更新redis

需要注意消息顺序问题

订阅binlog

通过订阅binlog来更新redis,把我们搭建的消费服务,作为mysql的一个slave,订阅binlog,解析出更新内容,再更新到redis。

集群

主从复制模式

工作机制
slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(即上文所介绍的RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
master将保存的快照文件发送给slave,并继续记录执行的写命令
slave接收到快照文件后,加载快照文件,载入数据
master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性

优点:
master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求

缺点:
不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
难以支持在线扩容,Redis的容量受限于单机配置

Sentinel(哨兵)模式

基本原理
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。

工作机制

  1. 下线判断
    检测主观下线状态
    ​ Sentinel每秒一次向所有与它建立了命令连接的实例(主服务器、从服务器和其他Sentinel)发送PING命令.实例在down-after-milliseconds毫秒内返回无效回复Sentinel就会认为该实例主观下线(SDown)

    检查客观下线状态
    ​ 当一个Sentinel将一个主服务器判断为主观下线后 ,Sentinel会向监控这个主服务器的所有其他Sentinel发送查询主机状态的命令。如果达到Sentinel配置中的quorum数量的Sentinel实例都判断主服务器为主观下线,则该主服务器就会被判定为客观下线(ODown)。

  2. 选举Leader Sentinel
    ​ 当一个主服务器被判定为客观下线后,监视这个主服务器的所有Sentinel会通过选举算法(raft),选出一个Leader Sentinel去执行failover(故障转移)操作。
    Raft算法
    Raft协议是用来解决分布式系统一致性问题的协议。 Raft协议描述的节点共有三种状态:Leader, Follower, Candidate。 Raft协议将时间切分为一个个的Term(任期),可以认为是一种“逻辑时间”。 选举流程:
    ①Raft采用心跳机制触发Leader选举系统启动后,全部节点初始化为Follower,term为0
    ②节点如果收到了RequestVote或者AppendEntries,就会保持自己的Follower身份
    ③节点如果一段时间内没收到AppendEntries消息,在该节点的超时时间内还没发现Leader,Follower就会转换成Candidate,自己开始竞选Leader。 一旦转化为Candidate,该节点立即开始下面几件事情:
    ​ --增加自己的term,启动一个新的定时器
    ​ --给自己投一票,向所有其他节点发送RequestVote,并等待其他节点的回复。
    ④如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时通过 AppendEntries,向其他节点发送通知。
    ⑤每个节点在一个term内只能投一票,采取先到先得的策略,Candidate投自己, Follower会投给第一个收到RequestVote的节点。
    ⑥Raft协议的定时器采取随机超时时间(选举的关键),先转为Candidate的节点会先发起投票,从而获得多数票。

  3. 故障恢复
    选出领头哨兵后,领头者开始对系统进行故障恢复,从出现故障的master的从数据库中挑选一个来当选新的master,选择规则如下:
    所有在线的slave中选择优先级最高的,优先级可以通过slave-priority配置
    如果有多个最高优先级的slave,则选取复制偏移量最大(即复制越完整)的当选
    如果以上条件都一样,选取id最小的slave
    挑选出需要继任的slave后,领头哨兵向该数据库发送命令使其升格为master,然后再向其他slave发送命令接受新的master,最后更新数据。将已经停止的旧的master更新为新的master的从数据库,使其恢复服务后以slave的身份继续运行。

优点
哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
哨兵模式下,master挂掉可以自动进行切换,系统可用性更高

缺点
同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务

Cluster模式

基本原理

哨兵模式解决了主从复制不能自动故障转移,达到高可用的问题,但还是存在难以在线扩容,Redis容量受限于单机配置的问题。Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题

工作机制

在Redis的每个节点上,都有一个插槽(slot),取值范围为0-16383
当我们存取 key的时候,Redis会根据CRC16的算法得出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。集群数据以数据分布表的方式保存在各个slot上。集群只有在16384个slot都有对应的节点才能正常工作。
读取时客户端任意访问一个节点,如果key所在的slot刚好在该节点上,则能够直接执行成功。如果slot不在该节点,则节点会返回MOVED错误,同时把该slot对应的节点告诉客户端。
为了保证高可用,Cluster模式也引入主从复制模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,当 slave 发现自己的 master 变为 FAIL 状态时,便尝试进行 Failover,以期成为新的 master,当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了同意slave请求。如果主节点A和它的从节点都宕机了,那么该集群就无法再提供服务了

Cluster模式集群节点最小配置6个节点(3主3从,因为需要半数以上),其中主节点提供读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。

优点
无中心架构,数据按照slot分布在多个节点。
集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
可线性扩展到1000多个节点,节点可动态添加或删除
能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换

缺点
客户端实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度。目前仅JedisCluster相对成熟,异常处理还不完善,比如常见的“max redirect exception”
节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
数据通过异步复制,不保证数据的强一致性
slave充当“冷备”,不能缓解读压力
批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
key事务操作支持有线,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db 0
增加新的master时,需要重新制定每个master负责的slot的范围,已有master会将相应slot对应的数据复制到新的master节点。由于redis数据分片算法相对简单,因此横向扩展时,有可能会导致所有节点数据的重新分布

使用场景

一个简单论坛系统

使用HASH结构缓存文章信息
文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 HASH 来存储每种信息以及其对应的值的映射。

使用SET结构缓存文章点赞人进行计数和防重复
当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还必须记录该用户已经对该文章进行了点赞,防止用户点赞次数超过 1。可以建立文章的已投票用户集合来进行记录。
为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。

使用ZSET结构对文章点赞数进行排序
为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值