乐趣区

关于redis:这次彻底读透-Redis

0. Redis 根底

如果对 Redis 还不理解的同学能够先看一下这篇 Redis 根底文章,这外面介绍了 Redis 是什么,以及怎么用

1. Redis 管道

咱们通常应用 Redis 的形式是,发送命令,命令排队,Redis 执行,而后返回后果,这个过程称为 Round trip time(简称 RTT, 往返工夫)。然而如果有多条命令须要执行时,须要耗费 N 次 RTT,通过 N 次 IO 传输,这样效率显著很低。

于是 Redis 管道(pipeline)便产生了,一次申请 / 响应服务器能实现解决新的申请即便旧的申请还未被响应。这样就能够将 多个命令 发送到服务器,而不必期待回复,最初在一个步骤中读取该回答。这就是管道(pipelining),缩小了 RTT,晋升了效率

重要阐明: 应用管道发送命令时,服务器将被迫回复一个队列回答,占用很多内存。所以,如果你须要发送大量的命令,最好是把他们依照正当数量分批次的解决,例如 10K 的命令,读回复,而后再发送另一个 10k 的命令,等等。这样速度简直是雷同的,然而在回复这 10k 命令队列须要十分大量的内存用来组织返回数据内容。

2. Redis 公布订阅

公布订阅是一种音讯模式,发送者(sub)发送音讯,订阅者(pub)接管音讯

如上图所示,公布订阅基于频道实现的,同一个频道能够有多个订阅者,多个发布者。其中任意一个发布者公布音讯到频道中,所以订阅者都能够收到该音讯。

公布订阅 Redis 演示

我在服务器上启动了 4 个 Redis 客户端,2 个订阅者,2 个发布者,订阅者订阅的频道为 channel01

第一步:发布者 1 往 channel01 中发送音讯 “wugongzi”

订阅者 1 收到音讯:

订阅者 2 收到音讯:

第二步:发布者 2 往频道中公布音讯 “hello-redis”

订阅者 1 收到音讯:

订阅者 2 收到音讯:

Redis 同时反对订阅多个频道:

3. Redis 过期策略

3.1 过期工夫应用

Redis 能够给每个 key 都设置一个过期工夫,过期工夫达到后,Redis 会主动删除这个 key。

理论生产中,咱们还是要求必须要为每个 Redis 的 Key 设置一个过期工夫,如果不设置过期工夫,工夫一久,Redis 内存就会满了,有很多冷数据,仍然存在。

设置过期工夫的命令:EXPIRE key seconds

在应用过程中有一点须要留神,就是在每次更新 Redis 时,都须要从新设置过期工夫,如果不设置,那个 key 就是永恒的,上面给大家演示一下如何应用:

3.2 过期删除策略

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

当一些客户端尝试拜访过期 key 时,Redis 发现 key 曾经过期便删除掉这些 key

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

具体就是 Redis 每秒 10 次做的事件:

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

这是一个平庸的概率算法,基本上的假如是,Redis 的样本是这个密钥控件,并且咱们一直反复过期检测,直到过期的 keys 的百分百低于 25%, 这意味着,在任何给定的时刻,最多会革除 1 / 4 的过期 keys。

4. Redis 事务

4.1 事务根本应用

Redis 事务能够一次执行多条命令,Redis 事务有如下特点:

  • 事务是一个独自的隔离操作:事务中的所有命令都会序列化、按程序地执行。事务在执行的过程中,不会被其余客户端发送来的命令申请所打断。
  • 事务是一个原子操作:事务中的命令要么全副被执行,要么全副都不执行。

Redis 事务通过 MULTIEXECDISCARDWATCH 几个命令来实现,MULTI 命令用于开启事务,EXEC 用于提交事务,DISCARD 用于放弃事务,WATCH 能够为 Redis 事务提供 check-and-set(CAS)行为。

4.2 事务产生谬误

Reids 事务产生谬误分为两种状况

第一种:事务提交前产生谬误,也就是在发送命令过程中产生谬误,看演示

下面我成心将 incr 命令写错,从后果咱们能够看到,这条 incr 没有入队,并且事务执行失败,k1 和 k2 都没有值

第二种:事务提交后产生谬误,也就是在执行命令过程中产生谬误,看演示

下面的事务命令中,我给 k1 设置了一个 d,而后执行自增命令,最初获取 k1 的值,咱们发现第二条命令执行产生了谬误,然而整个事务仍然提交胜利了,从下面景象中能够得出,Redis 事务不反对回滚操作。如果反对的话,整个事务的命令都不应该被执行。

4.3 为什么 Redis 不反对回滚

如果你有应用关系式数据库的教训,那么“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你感觉有点奇怪。

以下是这种做法的长处:

  • Redis 命令只会因为谬误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了谬误类型的键下面:这也就是说,从实用性的角度来说,失败的命令是由编程谬误造成的,而这些谬误应该在开发的过程中被发现,而不应该呈现在生产环境中。
  • 因为不须要对回滚进行反对,所以 Redis 的外部能够放弃简略且疾速。

有种观点认为 Redis 处理事务的做法会产生 bug,然而须要留神的是,在通常状况下,回滚并不能解决编程谬误带来的问题。举个例子,如果你原本想通过 incr 命令将键的值加上 1,却不小心加上了 2,又或者对谬误类型的键执行了 incr,回滚是没有方法解决这些状况的。

4.4 放弃事务

当执行 discard 命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出

4.5 WATCH 命令应用

watch 使得 exec 命令须要有条件地执行:事务只能在所有被监督键都没有被批改的前提下执行,如果这个前提不能满足的话,事务就不会被执行

下面我用 watch 命令监听了 k1 和 k2,而后开启事务,在事务提交之前,k1 的值被批改了,watch 监听到 k1 值被批改,所以事务没有被提交

4.6 Redis 脚本和事务

从定义上来说,Redis 中的脚本自身就是一种事务,所以任何在事务里能够实现的事,在脚本外面也能实现。并且一般来说,应用脚本要来得更简略,并且速度更快。

因为脚本性能是 Redis 2.6 才引入的,而事务性能则更早之前就存在了,所以 Redis 才会同时存在两种处理事务的办法。

5. Reids 长久化

5.1 为什么须要长久化

咱们晓得 Redis 是内存数据库,主打高性能,速度快。相比 Redis 而言,MySQL 的数据则是保留再硬盘中(其实也有内存版的 MySQL 数据库,然而价格极其低廉,个别公司不会应用),速度慢,然而稳定性好。你想想 Redis 数据保留在内存中,一旦服务器宕机了,数据岂不是全副都没了,这将会呈现很大问题。所以 Redis 为了补救这一缺点,提供数据长久化机制,即便服务器宕机,仍然能够保证数据不失落。

5.2 长久化简介

Redis 提供了两种长久化机制 RDB 和 AOF,实用于不同场景

  • RDB 长久化形式可能在指定的工夫距离能对你的数据进行快照存储
  • AOF 长久化形式记录每次对服务器写的操作, 当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF 命令以 redis 协定追加保留每次写的操作到文件开端.Redis 还能对 AOF 文件进行后盾重写, 使得 AOF 文件的体积不至于过大

5.3 RDB

RDB 长久化是通过在指定工夫距离对数据进行快照,比方在 8 点钟对数据进行长久化,那么 Redis 会 fork 一个子过程将 8 点那一刻内存中的数据长久化到磁盘上。触发 RDB 长久化有以下几种办法

5.3.1 RDB 长久化形式

1、执行 save 命令

执行 save 命令进行长久会阻塞 Redis,备份期间 Redis 无奈对外提供服务,个别不倡议应用,应用场景为 Redis 服务器须要停机保护的状况下。

2、执行 bgsave 命令

bgsave 命令不会阻塞 Redis 主过程,长久化期间 Redis 仍然能够失常对外提供服务

3、通过配置文件中配置的 save 规定来触发

save 900 1:900s 内有 1 个 key 发生变化,则触发 RDB 快照

save 300 10:300s 内有 10 个 key 发生变化,则触发 RDB 快照

save 60 10000:60s 内有 10000 个 key 发生变化(新增、批改、删除),则触发 RDB 快照

save “”:该配置示意敞开 RDB 长久化

5.3.2 RDB 长久化原理

Redis 进行 RDB 时,会 fork 一个子过程来进行数据长久化,这样不障碍 Redis 持续对外提供服务,提高效率。

已经面试官出过这样面试题:

如果 Redis 在 8 点触发了 RDB 长久化,长久化用时 2 分钟,在长久化期间,Redis 中有 100 个 key 被批改了,那么 RDB 文件中的 key 是 8 点那一刻的数据,还是变动的呢?

先不要看答案,本人思考 1 分钟,一个问题只有你本人思考了,能力印象粗浅。

好,上面咱们一起来看下这张图:

从图中咱们能够清晰的看到,Redis 备份时,fork 了一个子过程,子过程去做长久化的工作,子过程中的 key 指向了 8 点那一刻的数据,前面 k1 的值批改了,redis 会在内存中创立一个新的值,而后主过程 k1 指针指向新的值,子过程 k1 指针仍然指向 19,这样 Redis 长久化的就是 8 点那一刻的数据,不会发生变化。同时,从图中咱们也能够看到,Redis 长久化时并不是将内存中数据全副拷贝一份进行备份。

5.3.2 RDB 优缺点

长处

  • RDB 是一个十分紧凑的文件, 它保留了某个工夫点得数据集, 十分实用于数据集的备份, 比方你能够在每个小时报保留一下过来 24 小时内的数据, 同时每天保留过来 30 天的数据, 这样即便出了问题你也能够依据需要复原到不同版本的数据集
  • RDB 在保留 RDB 文件时父过程惟一须要做的就是 fork 出一个子过程, 接下来的工作全副由子过程来做,父过程不须要再做其余 IO 操作,所以 RDB 长久化形式能够最大化 redis 的性能
  • 与 AOF 相比, 在复原大的数据集的时候,RDB 形式会更快一些

毛病

  • 如果备份间隔时间较长,RDB 会失落较多的数据。比方 8 点备份一次,8 点半服务器宕机,那么这半小时内的数据就会失落了

5.4 AOF

AOF 长久化是通过日志的形式,记录每次 Redis 的写操作。当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF 命令以 Redis 协定追加保留每次写的操作到文件开端。Redis 还能对 AOF 文件进行后盾重写,使得 AOF 文件的体积不至于过大

5.4.1 AOF 长久化配置

# 是否开启 aof no:敞开;yes: 开启
appendonly no

# aof 文件名
appendfilename "appendonly.aof"

# aof 同步策略
# appendfsync always  # 每个命令都写入磁盘,性能较差
appendfsync everysec  # 每秒写一次磁盘,Redis 默认配置
# appendfsync no      # 由操作系统执行,默认 Linux 配置最多失落 30 秒

# aof 重写期间是否同步
no-appendfsync-on-rewrite no

# 重写触发策略
auto-aof-rewrite-percentage 100 # 触发重写百分比(指定百分比为 0,将禁用 aof 主动重写性能)auto-aof-rewrite-min-size 64mb # 触发主动重写的最低文件体积(小于 64mb 不主动重写)# 加载 aof 时如果有错如何解决
# 如果该配置启用,在加载时发现 aof 尾部不正确是,会向客户端写入一个 log,然而会继续执行,如果设置为 no,发现错误就会进行,必须修复后能力从新加载。aof-load-truncated yes

# aof 中是否应用 rdb
# 开启该选项,触发 AOF 重写将不再是依据以后内容生成写命令。而是学生成 RDB 文件写到结尾,再将 RDB 生成期间的产生的增量写命令附加到文件开端。aof-use-rdb-preamble yes

5.4.2 AOF 文件写入

aof 文件是命令追加的形式,先将命令写入缓冲区,工夫到了再写如磁盘中

appendfsync always    # 每个命令都写入磁盘,性能较差
appendfsync everysec  # 每秒写一次磁盘,Redis 默认配置
appendfsync no        # 由操作系统执行,默认 Linux 配置最多失落 30 秒

下面配置就是何时写入磁盘中

5.4.3 AOF 重写

aof 文件尽管失落的数据少,然而随着工夫的减少,aof 文件体积越来越大,占用磁盘空间越来越大,复原工夫长。所以 redis 会对 aof 文件进行重写,以缩小 aof 文件体积

上面以一个例子阐明

-- 重写前的 aof
set k1 20
set k2 40
set k1 35
set k3 34
set k2 19

-- 这里 k1 最终的值为 35,k2 最终值为 19,所以不须要写入两个命令
-- 重写后
set k1 35
set k3 34
set k2 19

混合长久化

从 Redis 4.0 版本开始,引入了混合长久化机制,纯 AOF 形式、RDB+AOF 形式,这一策略由配置参数aof-use-rdb-preamble(应用 RDB 作为 AOF 文件的前半段)管制,默认敞开(no),设置为 yes 可开启

  • no:依照 AOF 格局写入命令,与 4.0 前版本无差别;
  • yes:先依照 RDB 格局写入数据状态,而后把重写期间 AOF 缓冲区的内容以 AOF 格局写入,文件前半部分为 RDB 格局,后半部分为 AOF 格局。

混合长久化长处如下:

  • 大大减少了 aof 文件体积
  • 放慢了 aof 文件复原速度,后面是 rdb,复原速度快

AOF 数据恢复

第一种:纯 AOF

复原时,取出 AOF 中命令,一条条执行复原

第二种:RDB+AOF

先执行 RDB 加载流程,执行结束后,再取出余下命令,开始一条条执行

5.4.4 AOF 优缺点

长处

  • AOF 实时性更好,失落数据更少
  • AOF 曾经反对混合长久化,文件大小能够无效管制,并进步了数据加载时的效率

毛病

  • 对于雷同的数据汇合,AOF 文件通常会比 RDB 文件大
  • 在特定的 fsync 策略下,AOF 会比 RDB 略慢
  • AOF 复原速度比 RDB 慢

6. Redis 分布式锁

6.1 分布式锁介绍

学习过 Java 的同学,应该对锁都不生疏。Java 中多个线程访问共享资源时,会呈现并发问题,咱们通常利用 synchronized 或者 Lock 锁来解决多线程并发拜访从而呈现的平安问题。仔细的同学可能曾经发现了,synchronized 或者 Lock 锁解决线程平安问题在单节点状况下是可行的,然而如果服务部署在多台服务器上,本地锁就生效了。

分布式场景下,须要采纳新的解决方案,就是明天要说的 Redis 分布式锁。日常业务中,相似抢红包,秒杀等场景都能够应用 Redis 分布式锁来解决并发问题。

6.2 分布式锁特点

分布式在保障平安、高可用的状况下须要具备以下个性

  • 互斥性:任意一个时刻,只能有一个客户端获取到锁
  • 安全性:锁只能被长久的客户端删除,不能被其他人删除
  • 高可用,高性能:加锁和解锁耗费的性能少,工夫短
  • 锁超时:当客户端获取锁后呈现故障,没有立刻开释锁,该锁要可能在肯定工夫内开释,否则其余客户端无奈获取到锁
  • 可重入性:客户端获取到锁后,在长久锁期间能够再次获取到该锁

6.3 解决方案

6.3.1 计划一:SETNX 命令

Redis 提供了一个获取分布式锁的命令 SETNX

setnx key value

如果获取锁胜利,redis 返回 1,获取锁失败 redis 返回 0

客户端应用伪代码

if (setnx(k1,v1) == 1) {
    try{
        // 执行逻辑
        ....
    }catch() {}finally{
        // 执行实现后开释锁
        del k1;
    }
}

这个命令看似能够达到咱们的目标,然而不合乎分布式锁的个性,如果客户端在执行业务逻辑过程中,服务器宕机了,finally 中代码还没来得及执行,锁没有开释,也就象征其余客户端永远无奈获取到这个锁

6.3.2 计划二:SETNX + EXPIRE

该计划获取锁之后,立刻给锁加上一个过期工夫,这样即便客户端没有手动开释锁,锁到期后也会主动开释

咱们来看下伪代码

if (setnx(k1, v1) == 1){expire(key, 10);
    try {//.... 你的业务逻辑} finally {del(key);
    }
}

这个计划很完满,既能够获取到,又不必放心客户端宕机。等等,这外面真的没有问题吗?再认真瞅瞅,一瞅就瞅出问题来了

if (setnx(k1, v1) == 1){
    // 再刚获取锁之后,想要给锁设置过期工夫,此时服务器挂了
    expire(key, 10); // 这条命令没有执行
    try {//.... 你的业务逻辑} finally {del(key);
    }
}

这里的 setnx 命令和 expire 命令不是原子性的,他们之间执行须要肯定的等等工夫,尽管这个工夫很短,然而仍然有极小概率呈现问题

6.3.3 计划三:应用 Lua 脚本

既然 setnx 和 expire 两个命令非原子性,那么咱们让其合乎原子性即可,通过 Lua 脚本即可实现。Redis 应用单个 Lua 解释器去运行所有脚本,并且,Redis 也保障脚本会以原子性 (atomic) 的形式执行:当某个脚本正在运行的时候,不会有其余脚本或 Redis 命令被执行

具体实现如下:

if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
   redis.call('expire',KEYS[1],ARGV[2])
else
   return 0
end;

这样应该没问题了吧,看似下面的几个问题都很好解决了。不对,再想想,必定还有没思考到的

咱们再来看一段伪代码

// 执行 lua 脚本
// 获取 k1 锁,过期工夫 10 s
if (execlua()==1){
    try {buyGoods();
    } finally {del(key);
    }
}

从图中咱们能够很清晰发现问题所在

  1. 客户端 A 还未执行结束,客户端 B 就获取到了锁,这样就可能导致并发问题
  2. 客户端 A 执行结束,开始删除锁。但此时的锁为 B 所有,相当于删除了属于客户端 B 的锁,这样必定会产生问题

6.3.4 计划四:SET EX PX NX + 校验惟一随机值,再删除

既然锁有可能被别的客户端删除,那么在删除锁的时候咱们加上一层校验,判断开释锁是以后客户端持有的,如果是以后客户端,则容许删除,否则不容许删除。

  • EX second:设置键的过期工夫为 second 秒。SET key value EX second 成果等同于 SETEX key second value
  • PX millisecond:设置键的过期工夫为 millisecond 毫秒。SET key value PX millisecond 成果等同于 PSETEX key millisecond value
  • NX:只在键不存在时,才对键进行设置操作。SET key value NX 成果等同于 SETNX key value
  • XX:只在键曾经存在时,才对键进行设置操作。

应用示例:

if(jedis.set(resource_name, random_value, "NX", "EX", 100s) == 1){ // 加锁, value 传入一个随机数
    try {do something  // 业务解决}catch(){}
  finally {
       // 判断 value 是否相等, 相等才开释锁, 这里判断和删除是非原子性, 实在场景下能够将这两步放入 Lua 脚本中执行
       if (random_value.equals(jedis.get(resource_name))) {jedis.del(lockKey); // 开释锁
        }
    }
}

Lua 脚本如下:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

此计划解决了锁被其余客户端解除的问题,然而仍然没有解决锁过期开释,然而业务还没有执行实现的问题

6.3.5 计划五:Redisson 框架

计划四中并没有解决办法未执行实现,锁就超时开释的问题。这里有个计划大家比拟容易想到,那就是锁的超时工夫设置长一点,比方 2min,一个接口执行工夫总不能比 2 min 还长,那你就等着领盒饭吧,哈哈哈。然而这么做,一来是不能每个锁都设置这么久超时工夫,二来是如果接口出现异常了,锁只能 2 min 后能力开释,其余客户端等待时间较长。

这个问题早就有人想到了,并给出了解决方案,开源框架 Redisson 解决了这个问题。

Redisson 在办法执行期间,会一直的检测锁是否到期,如果发现锁快要到期,然而办法还没有执行实现,便会缩短锁的过期工夫,从而解决了锁超时开释问题。

6.3.6 计划六:Redlock

下面所介绍的分布式锁,都是在单台 Redis 服务器下的解决方案。实在的生产环境中,咱们通常会部署多台 Redis 服务器,也就是集群模式,这种状况上述解决方案就生效了。

对于集群 Redis,Redis 的作者 antirez 提出了另一种解决方案,Redlock 算法

Redlock 算法大抵流程如下:

1、获取以后 Unix 工夫,以毫秒为单位。

2、顺次尝试从 N 个实例,应用雷同的 key 和随机值获取锁。在步骤 2,当向 Redis 设置锁时, 客户端应该设置一个网络连接和响应超时工夫,这个超时工夫应该小于锁的生效工夫。例如你的锁主动生效工夫为 10 秒,则超时工夫应该在 5 -50 毫秒之间。这样能够防止服务器端 Redis 曾经挂掉的状况下,客户端还在死死地期待响应后果。如果服务器端没有在规定工夫内响应,客户端应该尽快尝试另外一个 Redis 实例。

3、客户端应用以后工夫减去开始获取锁工夫(步骤 1 记录的工夫)就失去获取锁应用的工夫。当且仅当从大多数(这里是 3 个节点)的 Redis 节点都取到锁,并且应用的工夫小于锁生效工夫时,锁才算获取胜利。

4、如果取到了锁,key 的真正无效工夫等于无效工夫减去获取锁所应用的工夫(步骤 3 计算的后果)。

5、如果因为某些起因,获取锁失败(没有 在至多 N /2+ 1 个 Redis 实例取到锁或者取锁工夫曾经超过了无效工夫),客户端应该在所有的 Redis 实例上进行解锁(即使某些 Redis 实例基本就没有加锁胜利)。

总结: 简略总结一下就是客户端向 Redis 集群中所有服务器发送获取锁的申请,只有半数以上的锁获取胜利后,才代表锁获取胜利,否则锁获取失败

7. Redis 集群

7.1 Redis 集群的三种模式

在生产环境中,咱们应用 Redis 通常采纳集群模式,因为单机版 Redis 稳定性可靠性较低,而且存储空间无限。

Redis 反对三种集群模式

  • 主从复制
  • 哨兵模式
  • Cluster 模式

7.2 主从复制

7.2.1 主从复制概念

主从复制模式,有一个主,多个从,从而实现读写拆散。主机负责写申请,从机负责读申请,加重主机压力

7.2.2 主从复制原理

  • 从数据库启动胜利后,连贯主数据库,发送 SYNC 命令;
  • 主数据库接管到 SYNC 命令后,开始执行 BGSAVE 命令生成 RDB 文件并应用缓冲区记录尔后执行的所有写命令;
  • 主数据库 BGSAVE 执行完后,向所有从数据库发送快照文件,并在发送期间持续记录被执行的写命令;
  • 从数据库收到快照文件后抛弃所有旧数据,载入收到的快照;
  • 主数据库快照发送结束后开始向从数据库发送缓冲区中的写命令;
  • 从数据库实现对快照的载入,开始接管命令申请,并执行来自主数据库缓冲区的写命令;(从数据库初始化实现
  • 主数据库每执行一个写命令就会向从数据库发送雷同的写命令,从数据库接管并执行收到的写命令(从数据库初始化实现后的操作
  • 呈现断开重连后,2.8 之后的版本会将断线期间的命令传给重数据库,增量复制。
  • 主从刚刚连贯的时候,进行全量同步;全同步完结后,进行增量同步。当然,如果有须要,slave 在任何时候都能够发动全量同步。Redis 的策略是,无论如何,首先会尝试进行增量同步,如不胜利,要求从机进行全量同步。

7.2.3 主从复制优缺点

长处

  • 反对主从复制,主机会主动将数据同步到从机,能够进行读写拆散
  • Slave 同样能够承受其它 Slaves 的连贯和同步申请,这样能够无效的分载 Master 的同步压力
  • Master Server 是以非阻塞的形式为 Slaves 提供服务。所以在 Master-Slave 同步期间,客户端依然能够提交查问或批改申请

毛病

  • 主从不具备容错和恢复能力,一旦主机挂了,那么整个集群解决可读状态,无奈解决写申请,会失落数据
  • 主机宕机后无奈主动复原,只能人工手动复原
  • 集群存储容量无限,容量上线就是主库的内存的大小,无奈存储更多内容

7.3 哨兵集群

7.3.1 哨兵概念

哨兵,咱们常常在电视剧中看到一些放哨的,哨兵的作用和这些放哨的差不多,起到监控作用。一旦 Redis 集群呈现问题了,哨兵会立刻做出相应动作,应答异常情况。

哨兵模式是基于主从复制模式上搭建的,因为主从复制模式状况下主服务器宕机,会导致整个集群不可用,须要人工干预,所以哨兵模式在主从复制模式下引入了哨兵来监控整个集群,哨兵模式架构图如下:

7.3.2 哨兵性能

监控(Monitoring):哨兵会一直地查看主节点和从节点是否运作失常。

主动故障转移(Automatic failover):当主节点不能失常工作时,哨兵会开始主动故障转移操作,它会将生效主节点的其中一个从节点降级为新的主节点,并让其余从节点改为复制新的主节点。

配置提供者(Configuration provider):客户端在初始化时,通过连贯哨兵来取得以后 Redis 服务的主节点地址。

告诉(Notification):哨兵能够将故障转移的后果发送给客户端。

7.3.3 下线判断

Redis 下线分为主观下线和主观下线两种

  • 主观下线:单台哨兵工作主库处于不可用状态
  • 主观下线:整个哨兵集群半数以上的哨兵都认为主库处于可不必状态

哨兵集群中任意一台服务器判断主库不可用时,此时会发送命令给哨兵集群中的其余服务器确认,其余服务器收到命令后会确认主库的状态,如果不可用,返回 YES,可用则返回 NO,当有半数的服务器都返回 YES,阐明主库真的不可用,此时须要从新选举

7.3.4 主库选举

当哨兵集群断定主库下线了,此时须要从新选举出一个新的主库对外提供服务。那么该由哪个哨兵来实现这个新库选举和切换的动作呢?

留神:这里不能让每个哨兵都去选举,可能会呈现每个哨兵选举出的新主库都不同,这样就没法断定,所以须要派出一个代表

哨兵代表抉择

哨兵的选举机制其实很简略,就是一个 Raft 选举算法:选举的票数大于等于 num(sentinels)/2+ 1 时,将成为领导者,如果没有超过,持续选举

  • 任何一个想成为 Leader 的哨兵,要满足两个条件:

    • 第一,拿到半数以上的赞成票;
    • 第二,拿到的票数同时还须要大于等于哨兵配置文件中的 quorum 值。

以 3 个哨兵为例,假如此时的 quorum 设置为 2,那么,任何一个想成为 Leader 的哨兵只有拿到 2 张赞成票,就能够了。

新库抉择

下面曾经选举出了哨兵代表,此时代表须要实现新主库的抉择,新库的抉择须要满足以下几个规范

  • 新库须要处于衰弱状态,也就是和哨兵之间放弃失常的网络连接
  • 抉择 salve-priority 从节点优先级最高(redis.conf)的
  • 抉择复制偏移量最大,只复制最残缺的从节点

7.3.5 故障转移

下面一大节哨兵曾经选举出了新的主库,故障转移要实现新老主库之间的切换

故障转移流程如下:

7.3.6 哨兵模式优缺点

长处

  • 实现了集群的监控,故障转移,实现了高可用
  • 领有主从复制模式的所有长处

毛病

  • 集群存储容量无限,容量上线就是主库的内存的大小,无奈存储更多内容

7.4 Cluser 集群

Redis 的哨兵模式实现了高可用了,然而每台 Redis 服务器上存储的都是雷同的数据,节约内存,而且很难实现容量上的扩大。所以在 redis3.0 上退出了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容

Redis 集群的数据分片

Redis 集群没有应用一致性 hash, 而是引入了 哈希槽 的概念.

Redis 集群有 16384 个哈希槽, 每个 key 通过 CRC16 校验后对 16384 取模来决定搁置哪个槽. 集群的每个节点负责一部分 hash 槽, 举个例子, 比方以后集群有 3 个节点, 那么:

  • 节点 A 蕴含 0 到 5500 号哈希槽.
  • 节点 B 蕴含 5501 到 11000 号哈希槽.
  • 节点 C 蕴含 11001 到 16384 号哈希槽.

这种构造很容易增加或者删除节点. 比方如果我想新增加个节点 D, 我须要从节点 A, B, C 中得局部槽到 D 上. 如果我想移除节点 A, 须要将 A 中的槽移到 B 和 C 节点上, 而后将没有任何槽的 A 节点从集群中移除即可. 因为从一个节点将哈希槽挪动到另一个节点并不会进行服务, 所以无论增加删除或者扭转某个节点的哈希槽的数量都不会造成集群不可用的状态.

8. Redis 集群实战

环境:

  • Vmware 虚拟机
  • CentOS 7
  • Redis 6.0.6

因为我是在本机上演示的,所以用的虚拟机

8.1 主从复制

集群信息如下:

节点 配置文件 端口
master redis6379.conf 6379
slave1 redis6380.conf 6380
slave1 redis6381.conf 6380

第一步:筹备三个 redis.conf 配置文件,配置文件信息如下

# redis6379.conf    master
# 蕴含命令,有点复用的意思
include /soft/redis6.0.6/bin/redis.conf
pidfile redis_6379.pid
port    6379
dbfilename dump6379.rdb
logfile "redis-6379.log"

# redis6380.conf    slave1
include /soft/redis6.0.6/bin/redis.conf
pidfile redis_6380.pid
port    6380
dbfilename dump6380.rdb
logfile "redis-6380.log"
# 最初一行设置了主节点的 ip 端口
replicaof 127.0.0.1 6379

# redis6381.conf    slave2
include /soft/redis6.0.6/bin/redis.conf
pidfile redis_6381.pid
port    6381
dbfilename dump6381.rdb
logfile "redis-6381.log"
# 最初一行设置了主节点的 ip 端口
replicaof 127.0.0.1 6379

## 留神 redis.conf 要调整一项,设置后盾运行,对咱们操作比拟敌对
daemonize yes

第二步:启动服务器

-- 首先启动 6379 这台服务器,因为他是主库(启动命令在 redis 装置目录的 bin 目录下)../bin/redis-server redis6379.conf
-- 接口启动 6380 和 6381
../bin/redis-server redis6380.conf
../bin/redis-server redis6381.conf

第三步:用客户端连贯服务器

cd bin
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381

这里我开了三个窗口别离连贯三台 redis 服务器,不便查看

在 6379 客户端输出命令:info replication 可用查看集群信息

第四步:数据同步

当初集群曾经搭建好了,咱们在 6379 服务器写入几条数据,看下可不可以同步到 6380 和 6381

6379:

6380:

6381:

从图中可用看出,数据曾经胜利同步了

8.2 哨兵模式

哨兵集群是在主从复制的根底上构建的,相当于是主从 + 哨兵

搭建哨兵模式分为两步:

  1. 搭建主从复制集群
  2. 增加哨兵配置

哨兵模式节点信息如下,一主二从,三个哨兵组成一个哨兵集群

节点 配置 端口
master redis6379.conf 6379
slave1 redis6380.conf 6380
slave2 redis6381.conf 6381
sentinel1 sentinel1.conf 26379
sentinel2 sentinel2.conf 26380
sentinel3 sentinel3.conf 26381

主从复制集群的配置同上,这里就不再赘述,上面次要介绍下哨兵的配置,哨兵的配置文件其实非常简单

# 文件内容
# sentinel1.conf
port 26379
sentinel monitor mymaster 127.0.0.1 6379 1
# sentinel2.conf
port 26380
sentinel monitor mymaster 127.0.0.1 6379 1
# sentinel3.conf
port 26381
sentinel monitor mymaster 127.0.0.1 6379 1

配置文件创立好了当前就能够启动了,首先启动主从服务器,而后启动哨兵

../bin/redis-server redis6379.conf
../bin/redis-server redis6380.conf
../bin/redis-server redis6381.conf

-- 启动哨兵
../bin/redis-sentinel sentinel1.conf 
../bin/redis-sentinel sentinel2.conf 
../bin/redis-sentinel sentinel3.conf 

从哨兵的启动日志中咱们可用看到主从服务器的信息,以及其余哨兵节点的信息

故障转移

主从同步性能下面曾经演示过了,这里次要测试一下哨兵的故障转移

当初我手动将主节点停掉,在 6379 上执行 shutdown 命令

此时咱们察看一下哨兵的页面:

哨兵检测到了 6379 下线,而后选举出了新的主库 6380

此时咱们通过 info replication 命令查看集群信息,发现 6380 曾经是主库了,他有一个从节点 6381

当初我手动将 6379 启动,看下 6379 会不会从新变成主库

重新启动后,咱们发现 6379 变成了 80 的从库

8.3 Cluser 集群

官网举荐,Cluser 集群至多要部署 3 台以上的 master 节点,最好应用 3 主 3 从

节点 配置 端口
cluster-master1 redis7001.conf 7001
cluster-master2 redis7002.conf 7002
cluster-master3 redis7003.conf 7003
cluster-slave1 redis7004.conf 7004
cluster-slave2 redis7006.conf 7005
cluster-slave3 redis7006.conf 7006

配置文件内容如下,6 个配置文件信息基本相同,编辑好一份后其余文件间接复制批改端口即可

# 端口
port 7001  
# 启用集群模式
cluster-enabled yes 
# 依据你启用的节点来命名,最好和端口保持一致,这个是用来保留其余节点的名称,状态等信息的
cluster-config-file nodes_7001.conf 
# 超时工夫
cluster-node-timeout 5000
appendonly yes
# 后盾运行
daemonize yes
# 非保护模式
protected-mode no 
pidfile  redis_7001.pid

而后别离启动 6 个节点

../bin/redis-server redis7001.conf
../bin/redis-server redis7002.conf
../bin/redis-server redis7003.conf
../bin/redis-server redis7004.conf
../bin/redis-server redis7005.conf
../bin/redis-server redis7006.conf

启动集群

# 执行命令
# --cluster-replicas 1 命令的意思是创立 master 的时候同时创立一个 slave

$ redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006  --cluster-replicas 1

启动过程有个中央须要输出 yes 确认:

启动胜利后可用看到控制台输入后果:

3 个 master 节点,3 个 slave 节点,

master[0]槽位:0-5460

master[1]槽位:5461-10922

master[2]槽位:10923-16383

数据验证

连贯 7001 服务器

redis-cli -p 7001 -c 集群模式下须要加上 -c 参数

从图中可用看出,k1 被放到 7003 主机上了,咱们此时获取 k1,可用失常获取到

登录 7003 也能够失常拿到数据

9. Redis 缓存问题

在服务端中,数据库通常是业务上的瓶颈,为了进步并发量和响应速度,咱们通常会采纳 Redis 来作为缓存,让尽量多的数据走 Redis 查问,不间接拜访数据库。同时 Redis 在应用过程中也会呈现各种各样的问题,面对这些问题咱们该如何解决?

  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩
  • 缓存净化

9.1 缓存穿透

1、定义:

缓存穿透是指,当缓存和数据中都没有对应记录,然而客户端却始终在查问。比方黑客攻击零碎,一直的去查问零碎中不存在的用户,查问时先走缓存,缓存中没有,再去查数据库;或者电商零碎中,用户搜寻某类商品,然而这类商品再零碎中基本不存在,这次的搜寻应该间接返回空

2、解决方案

  • 网关层减少校验,进行用户鉴权,黑名单管制,接口流量管制
  • 对于同一类查问,如果缓存和数据库都没有获取到数据,那么可用用一个空缓存记录下来,过期工夫 60s,下次遇到同类查问,间接取出缓存中的空数据返回即可
  • 应用布隆过滤器,布隆过滤器能够用来判断某个元素是否存在于汇合中,利用布隆过滤器能够过滤掉一大部分有效申请

9.2 缓存击穿

1、定义:

缓存击穿是指,缓存中数据生效,在高并发状况下,所有用户的申请全副都打到数据库上,短时间造成数据库压力过大

2、解决方案:

  • 接口限流、熔断
  • 加锁,当第一个用户申请到时,如果缓存中没有,其余用户的申请先锁住,第一个用户查询数据库后立刻缓存到 Redis,而后开释锁,这时候其余用户就能够间接查问缓存

9.3 缓存雪崩

1、定义

缓存雪崩是指 Redis 中大批量的 key 在同一时间,或者某一段时间内一起过期,造成多个 key 的申请全副无奈命中缓存,这些申请全副到数据库中,给数据库带来很大压力。与缓存击穿不同,击穿是指一个 key 过期,雪崩是指很多 key 同时过期。

2、解决方案

  • 缓存过期工夫设置成不同工夫,不要再对立工夫过期
  • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。

9.4 缓存净化

1、定义

缓存净化是指,因为历史起因,缓存中有很多 key 没有设置过期工夫,导致很多 key 其实曾经没有用了,然而始终寄存在 redis 中,工夫久了,redis 内存就被占满了

2、解决方案

  • 缓存尽量设置过期工夫
  • 设置缓存淘汰策略为最近起码应用的准则,而后将这些数据删除
退出移动版