(二)redis高阶知识/redis在高并发缓存架构中的地位

redis高阶知识/redis在高并发缓存架构中的地位

  • redis
    • redis安装
    • Redis持久化
      • RDB和AOF两只持久化机制
        • RDB
        • AOF机制
      • RDB持久化配置和数据恢复
        • 如何配置RDB持久化机制
        • RDB持久化机制的工作流程
        • 基于RDB持久化机制的数据恢复实验
      • AOF持久化配置和数据恢复
        • AOF持久化的配置
        • AOF持久化的数据恢复实验
        • AOF rewrite
        • AOF破损文件的修复
        • AOF和RDB同时工作
    • Redis在项目中的应用
      • 企业级的持久化的配置策略
      • 企业级的数据备份方案
      • 数据恢复方案
  • 坑:使用rdb备份进行数据恢复的时候失败:
    • redis读写分离
      • redis `replication`
      • 主从架构的核心原理
      • 主从复制的断点续传
      • 无磁盘化复制
      • 过期key处理
    • replication流程深入讲解
      • 复制的完整流程
      • 数据同步相关的核心机制
      • 全量复制
      • 增量复制
      • heartbeat
      • 异步复制
    • redis主从架构的搭建
      • 启用复制,部署slave node
      • 强制读写分离
      • 集群安全认证
      • 读写分离架构的测试
    • 主从架构QPS压测和水平扩容
      • QPS压测,redis-benchmark
      • 水平扩容redis读节点,提升度吞吐量
    • redis主从架构的高可用
      • 什么是99.99%高可用?
      • redis不可用是什么?单实例不可用?主从架构不可用?不可用的后果是什么?
      • redis怎么才能做到高可用?
    • redis哨兵架构的相关基础知识
      • 哨兵的介绍
      • 哨兵的核心知识
      • 为什么redis哨兵集群只有2个节点无法正常工作?
    • redis准备切换数据的丢失:异步复制和集群脑裂
      • 两种数据丢失的情况
      • 解决异步复制和脑裂导致的数据丢失
    • redis哨兵的核心底层原理
      • sdown和odown转换机制
      • 哨兵集群的自动发现机制
      • slave配置的自动纠正
      • slave->master选举算法
      • quorum和majority
      • configuration epoch
      • configuraiton传播
    • 三节点主从企业级配置
      • 正式的配置
      • 启动哨兵进程
      • 检查哨兵状态
      • 哨兵节点的增加和删除
      • slave的永久下线
      • slave切换为Master的优先级
      • 基于哨兵集群架构下的安全认证
      • 容灾演练
      • 哨兵的生产环境部署
    • redis如何在保持读写分离+高可用的架构下,还能横向扩容master支撑1T+海量数据
      • 单机master在海量数据面前的瓶颈?
      • 怎么才能够突破单机瓶颈,让redis支撑海量数据?
      • redis cluster vs. replication + sentinal
    • redis cluster的分布式数据存储算法
      • redis cluster介绍
    • 多master node的集群环境搭建
      • redis cluster的重要配置
      • 在三台机器上启动6个redis实例
      • 创建集群
      • 读写分离+高可用+多master
    • redis cluster搭建的多master集群环境检验
      • 实验多master写入 -> 海量数据的分布式存储
      • 实验自动故障切换 -> 高可用性
      • redis cluster下的水平扩容
        • 加入新master
        • reshard一些数据过去
        • 添加node作为slave
        • 删除node
      • redis cluster自动化slave迁移
    • redis cluster的核心原理分析
      • 节点间的内部通信机制
        • 基础通信原理
        • gossip协议
        • ping消息深入
      • 面向集群的jedis内部实现原理
        • 基于重定向的客户端
        • smart jedis
      • 高可用性与主备切换原理
    • redis实践中的问题和优化
      • fork耗时导致高并发请求延时
      • AOF的阻塞问题
      • 主从复制延迟问题
      • 主从复制风暴问题
      • vm.overcommit_memory
      • swapiness
      • 最大打开文件句柄
      • tcp backlog
    • redis阶段总结
      • 讲解redis是为了什么?
    • 讲解的redis可以实现什么效果?
      • redis的第一套企业级的架构
      • redis的第二套企业级架构
      • 我们现在课程讲解的项目进展到哪里了?

该系列文章是基于中华石杉老师的“[大数据架构]-亿级流量电商详情页系统的大型高并发与高可用缓存架构实战”课程整理的课堂笔记。

redis

要保证高并发,高可用,海量数据,备份,随时可以恢复,缓存架构如果要支撑这些要点,首先要redis支撑。
redis架构,每秒钟几十万的访问量QPS,99.99%的高可用性,TB级的海量的数据,备份和恢复,缓存架构就成功了一半了。

redis安装

  1. 安装单机版redis
wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
tar -xzvf tcl8.6.1-src.tar.gz
cd  /usr/local/tcl8.6.1/unix/
./configure  
make && make install
  1. redis的生产环境启动方案

redis作为一个系统的daemon进程去运行的,每次系统启动,redis进程一起启动

(1)redis utils目录下,有个redis_init_script脚本
(2)将redis_init_script脚本拷贝到linux的/etc/init.d目录中,将redis_init_script重命名为redis_6379,6379是我们希望这个redis实例监听的端口号
(3)修改redis_6379脚本的第6行的REDISPORT,设置为相同的端口号(默认就是6379)
(4)创建两个目录:/etc/redis(存放redis的配置文件),/var/redis/6379(存放redis的持久化文件)
(5)修改redis配置文件(默认在根目录下,redis.conf),拷贝到/etc/redis目录中,修改名称为6379.conf

(6)修改redis.conf中的部分配置为生产环境

daemonize	yes							# 让redis以daemon进程运行
pidfile		/var/run/redis_6379.pid 	# 设置redis的pid文件位置
port		6379						# 设置redis的监听端口号
dir 		/var/redis/6379				# 设置持久化文件的存储位置

(7)启动redis,执行cd /etc/init.d, chmod 777 redis_6379,./redis_6379 start

(8)确认redis进程是否启动,ps -ef | grep redis

(9)让redis跟随系统启动自动启动

在redis_6379脚本中,最上面,加入两行注释

# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database

chkconfig redis_6379 on
  1. redis cli的使用
redis-cli SHUTDOWN,连接本机的6379端口停止redis进程
redis-cli -h 127.0.0.1 -p 6379 SHUTDOWN,制定要连接的ip和端口号
redis-cli PING,ping redis的端口,看是否正常
redis-cli,进入交互式命令行
SET k1 v1
GET k1

Redis持久化

(二)redis高阶知识/redis在高并发缓存架构中的地位_第1张图片
企业级redis集群架构:海量数据、高并发、高可用
比如你redis整个挂了,重启redis,尽快让它对外提供服务,但是就像上一讲说,如果你没做数据备份,这个时候redis启动了,也不可用啊,数据都没了。很可能说,大量的请求过来,缓存全部无法命中,在redis里根本找不到数据,这个时候就死定了,缓存雪崩问题,所有请求,没有在redis命中,就会去mysql数据库这种数据源头中去找,一下子mysql承接高并发,然后就挂了。

RDB和AOF两只持久化机制

RDB

RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。

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

在我们安装了redis之后,所有的配置都是在redis.conf文件中,里面保存了RDB和AOF两种持久化机制的各种配置。

既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,是实现这个过程。

对于RDB来说,提供了三种机制:save、bgsave、自动化。我们分别来看一下

  1. save触发方式

该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。

执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

  1. bgsave触发方式

执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令

  1. 自动触发

自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置,我们可以去设置:

①save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。

默认如下配置:

#表示900 秒内如果至少有 1 个 key 的值变化,则保存save 900 1
#表示300 秒内如果至少有 10 个 key 的值变化,则保存save 300 10
#表示60 秒内如果至少有 10000 个 key 的值变化,则保存save 60 10000

不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。

  • stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了

  • rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。

  • rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

  • dbfilename :设置快照的文件名,默认是 dump.rdb

  • dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。

我们可以修改这些配置来实现我们想要的效果。因为第三种方式是配置的,所以我们对前两种进行一个对比:

(二)redis高阶知识/redis在高并发缓存架构中的地位_第2张图片
4. RDB 的优势和劣势

①、优势

(1)RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。

(2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。

(3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快

②、劣势

RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据

AOF机制

全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。

1、持久化原理
每当有一个写命令过来时,就直接保存在我们的AOF文件中。

2、文件重写原理

AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。
(二)redis高阶知识/redis在高并发缓存架构中的地位_第3张图片
重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

3、AOF也有三种触发机制

(1)每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好

(2)每秒同步everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失

(3)不同no:从不同步

4、优点

(1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。(2)AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。

(3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。

(4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

5、缺点

(1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

(3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。

选择的话,两者加一起才更好。
如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整

RDB持久化配置和数据恢复

如何配置RDB持久化机制

redis.conf文件,也就是/etc/redis/6379.conf,去配置持久化。

save 60 1000

每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting,快照。

也可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成

save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件

RDB持久化机制的工作流程

  1. redis根据配置自己尝试去生成rdb快照文件
  2. fork一个子进程出来
  3. 子进程尝试将数据dump到临时的rdb快照文件中
  4. 完成rdb快照文件的生成之后,就替换之前的旧的快照文件

dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照

基于RDB持久化机制的数据恢复实验

  1. 在redis中保存几条数据,立即停掉redis进程,然后重启redis,看看刚才插入的数据还在不在
    数据还在,为什么?

带出来一个知识点,通过redis-cli SHUTDOWN这种方式去停掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据立即生成一份完整的rdb快照

/var/redis/6379/dump.rdb

  1. 在redis中再保存几条新的数据,用kill -9粗暴杀死redis进程,模拟redis故障异常退出,导致内存数据丢失的场景。
    这次就发现,redis进程异常被杀掉,数据没有进dump文件,几条最新的数据就丢失了

  2. 手动设置一个save检查点,save 5 1

  3. 写入几条数据,等待5秒钟,会发现自动进行了一次dump rdb快照,在dump.rdb中发现了数据

  4. 异常停掉redis进程,再重新启动redis,看刚才插入的数据还在。

AOF持久化配置和数据恢复

  1. AOF持久化的配置
  2. AOF持久化的数据恢复实验
  3. AOF rewrite
  4. AOF破损文件的修复
  5. AOF和RDB同时工作

AOF持久化的配置

AOF持久化,默认是关闭的,默认是打开RDB持久化

appendonly yes,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,除非你说随便丢个几分钟的数据也无所谓。

打开AOF持久化机制之后,redis每次接收到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下。

而且即使AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的,因为aof数据比较完整

可以配置AOF的fsync策略,有三种策略可以选择:

  • always: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,那就只能这样了。
  • everysec: 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的。
  • no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控。

mysql和Redis:
mysql -> 内存策略,大量磁盘,QPS到多少,一两k。QPS,每秒钟的请求数量
redis -> 内存,磁盘持久化,QPS到多少,单机,一般来说,上万QPS没问题

AOF持久化的数据恢复实验

  1. 先仅仅打开RDB,写入一些数据,然后kill -9杀掉redis进程,接着重启redis,发现数据没了,因为RDB快照还没生成。
  2. 打开AOF的开关,启用AOF持久化
  3. 写入一些数据,观察AOF文件中的日志内容

其实你在appendonly.aof文件中,可以看到刚写的日志,它们其实就是先写入os cache的,然后1秒后才fsync到磁盘中,只有fsync到磁盘中了,才是安全的,要不然光是在os cache中,机器只要重启,就什么都没了。

  1. kill -9杀掉redis进程,重新启动redis进程,发现数据被恢复回来了,就是从AOF文件中恢复回来的。
    redis进程启动的时候,直接就会从appendonly.aof中加载所有的日志,把内存中的数据恢复回来。

AOF rewrite

redis中的数据其实有限的,很多数据可能会自动过期,可能会被用户删除,可能会被redis用缓存清除的算法清理掉。redis中的数据会不断淘汰掉旧的,就一部分常用的数据会被自动保留在redis内存中。所以可能很多之前的已经被清理掉的数据,对应的写日志还停留在AOF中,AOF日志文件就一个,会不断的膨胀,到很大很大。

所以AOF会自动在后台每隔一定时间做rewrite操作,比如日志里已经存放了针对100w数据的写日志了; redis内存只剩下10万; 基于内存中当前的10万数据构建一套最新的日志,到AOF中; 覆盖之前的老日志; 确保AOF日志文件不会过大,保持跟redis内存数据量一致。

redis 2.4之前,还需要手动,开发一些脚本,crontab,通过BGREWRITEAOF命令去执行AOF rewrite,但是redis 2.4之后,会自动进行rewrite操作。

在redis.conf中,可以配置rewrite策略:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

比如说上一次AOF rewrite之后,是128mb。

然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite。

但是此时还要去跟min-size,64mb去比较,256mb > 64mb,才会去触发rewrite。

  1. redis fork一个子进程
  2. 子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志
  3. redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件
  4. 子进程写完新的日志文件之后,redis主进程将内存中的新日志再次追加到新的AOF文件中
  5. 用新的日志文件替换掉旧的日志文件
    (二)redis高阶知识/redis在高并发缓存架构中的地位_第4张图片

AOF破损文件的修复

如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损。

用redis-check-aof --fix命令来修复破损的AOF文件。

AOF和RDB同时工作

  1. 如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting
  2. 如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite
  3. 同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整

6、最后一个小实验,让大家对redis的数据恢复有更加深刻的体会

(1)在有rdb的dump和aof的appendonly的同时,rdb里也有部分数据,aof里也有部分数据,这个时候其实会发现,rdb的数据不会恢复到内存中
(2)我们模拟让aof破损,然后fix,有一条数据会被fix删除
(3)再次用fix得aof文件去重启redis,发现数据只剩下一条了

Redis在项目中的应用

到这里为止,其实还是停留在简单学习知识的程度,学会了redis的持久化的原理和操作,但是在企业中,持久化到底是怎么去用得呢?

企业级的数据备份和各种灾难下的数据恢复,是怎么做得呢?

企业级的持久化的配置策略

  1. 在企业中,RDB的生成策略,用默认的也差不多。

save 60 10000:如果你希望尽可能确保说,RDB最多丢1分钟的数据,那么尽量就是每隔1分钟都生成一个快照,低峰期,数据量很少,也没必要。
10000->生成RDB,1000->RDB,这个根据你自己的应用和业务的数据量,你自己去决定

  1. AOF一定要打开,fsync,everysec

auto-aof-rewrite-percentage 100: 就是当前AOF大小膨胀到超过上次100%,上次的两倍
auto-aof-rewrite-min-size 64mb: 根据你的数据量来定,16mb,32mb

企业级的数据备份方案

RDB非常适合做冷备,每次生成之后,就不会再有修改了

数据备份方案(比如:防止程序bug,跑了很久发现数据处理错了,此时就要恢复)

  1. 写crontab定时调度脚本去做数据备份
  2. 每小时都copy一份rdb的备份,到一个目录中去,仅仅保留最近48小时的备份
  3. 每天都保留一份当日的rdb的备份,到一个目录中去,仅仅保留最近1个月的备份
  4. 每次copy备份的时候,都把太旧的备份给删了
  5. 每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去
/usr/local/redis
每小时copy一次备份,删除48小时前的数据
crontab -e
0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.sh
redis_rdb_copy_hourly.sh
#!/bin/sh 
cur_date=`date +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -48hour +%Y%m%d%k`
rm -rf /usr/local/redis/snapshotting/$del_date
每天copy一次备份

crontab -e

0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.sh

redis_rdb_copy_daily.sh

#!/bin/sh 

cur_date=`date +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$cur_date
mkdir /usr/local/redis/snapshotting/$cur_date
cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date

del_date=`date -d -1month +%Y%m%d`
rm -rf /usr/local/redis/snapshotting/$del_date

每天一次将所有数据上传一次到远程的云服务器上去

数据恢复方案

  1. 如果是redis进程挂掉,那么重启redis进程即可,直接基于AOF日志文件恢复数据

不演示了,在AOF数据恢复那一块,演示了,fsync everysec,最多就丢一秒的数

  1. 如果是redis进程所在机器挂掉,那么重启机器后,尝试重启redis进程,尝试直接基于AOF日志文件进行数据恢复。

AOF没有破损,也是可以直接基于AOF恢复的

AOF append-only,顺序写入,如果AOF文件破损,那么用redis-check-aof fix

  1. 如果redis当前最新的AOF和RDB文件出现了丢失/损坏,那么可以尝试基于该机器上当前的某个最新的RDB数据副本进行数据恢复。

当前最新的AOF和RDB文件都出现了丢失/损坏到无法恢复,一般不是机器的故障,人为

/var/redis/6379下的文件给删除了。

找到RDB最新的一份备份,小时级的备份可以了,小时级的肯定是最新的,copy到redis里面去,就可以恢复到某一个小时的数据。

容灾演练

坑:使用rdb备份进行数据恢复的时候失败:

**原因:**appendonly.aof + dump.rdb,优先用appendonly.aof去恢复数据,如果appendonly.aof文件不存在,就会创建一个新的空的appendonly.aof;恢复的时候就读取了空的aof数据,没有读取dump.rdb里面的数据,导致恢复失败。

**解决方案:**停止redis,暂时在配置中关闭aof,然后拷贝一份rdb过来,再重启redis,数据能不能恢复过来,可以恢复过来

  1. 如果当前机器上的所有RDB文件全部损坏,那么从远程的云服务上拉取最新的RDB快照回来恢复数据

  2. 如果是发现有重大的数据错误,比如某个小时上线的程序一下子将数据全部污染了,数据全错了,那么可以选择某个更早的时间点,对数据进行恢复

举个例子,12点上线了代码,发现代码有bug,导致代码生成的所有的缓存数据,写入redis,全部错了

找到一份11点的rdb的冷备,然后按照上面的步骤,去恢复到11点的数据,不就可以了吗

redis读写分离

  1. redis高并发跟整个系统的高并发之间的关系

redis,你要搞高并发的话,不可避免,要把底层的缓存搞得很好

mysql,高并发,做到了,那么也是通过一系列复杂的分库分表,订单系统,事务要求的,QPS到几万,比较高了

要做一些电商的商品详情页,真正的超高并发,QPS上十万,甚至是百万,一秒钟百万的请求量

光是redis是不够的,但是redis是整个大型的缓存架构中,支撑高并发的架构里面,非常重要的一个环节

首先,你的底层的缓存中间件,缓存系统,必须能够支撑的起我们说的那种高并发,其次,再经过良好的整体的缓存架构的设计(多级缓存架构、热点缓存),支撑真正的上十万,甚至上百万的高并发

  1. redis不能支撑高并发的瓶颈在哪里?

单机

  1. 如果redis要支撑超过10万+的并发,那应该怎么做?

单机的redis几乎不太可能说QPS超过10万+,除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂,单机在几万。

读写分离,一般来说,对缓存,一般都是用来支撑读高并发的,写的请求是比较少的,可能写请求也就一秒钟几千,一两千。大量的请求都是读,一秒钟二十万次读。

主从架构 -> 读写分离 -> 支撑10万+读QPS的架构

  1. 接下来要讲解的一个topic

redis replication
redis主从架构 -> 读写分离架构 -> 可支持水平扩展的读高并发架构

redis replication

redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发

  1. redis replication的核心机制

(1)redis采用异步方式复制数据到slave节点,不过redis 2.8开始,slave node会周期性地确认自己每次复制的数据量
(2)一个master node是可以配置多个slave node的
(3)slave node也可以连接其他的slave node
(4)slave node做复制的时候,是不会block master node的正常工作的
(5)slave node在做复制的时候,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了
(6)slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量

  1. master持久化对于主从架构的安全保障的意义

如果采用了主从架构,那么建议必须开启master node的持久化

不建议用slave node作为master node的数据热备,因为那样的话,如果你关掉master的持久化,可能在master宕机重启的时候数据是空的,然后可能一经过复制,salve node数据也丢了。

master -> RDB和AOF都关闭了 -> 全部在内存中。
master宕机,重启,是没有本地数据可以恢复的,然后就会直接认为自己IDE数据是空的。master就会将空的数据集同步到slave上去,所有slave的数据全部清空。100%的数据丢失。所以master节点,必须要使用持久化机制。

第二个,master的各种备份方案,要不要做,万一说本地的所有文件丢失了; 从备份中挑选一份rdb去恢复master; 这样才能确保master启动的时候,是有数据的。

即使采用了后续讲解的高可用机制,slave node可以自动接管master node,但是也可能sentinal还没有检测到master failure,master node就自动重启了,还是可能导致上面的所有slave node数据清空故障。

主从架构的核心原理

当启动一个slave node的时候,它会发送一个PSYNC命令给master node

如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization

开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。

slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。

主从复制的断点续传

从redis 2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份

master node会在内存中常见一个backlog,master和slave都会保存一个replica offset还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制

但是如果没有找到对应的offset,那么就会执行一次resynchronization

无磁盘化复制

master在内存中直接创建rdb,然后发送给slave,不会在自己本地落地磁盘了

repl-diskless-sync
repl-diskless-sync-delay,等待一定时长再开始复制,因为要等更多slave重新连接过来

过期key处理

slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。

replication流程深入讲解

复制的完整流程

(二)redis高阶知识/redis在高并发缓存架构中的地位_第5张图片

  1. slave node启动,仅仅保存master node的信息,包括master node的host和ip,但是复制流程没开始。master host和ip是从哪儿来的,redis.conf里面的slaveof配置的。

  2. slave node内部有个定时任务,每秒检查是否有新的master node要连接和复制,如果发现,就跟master node建立socket网络连接。

  3. slave node发送ping命令给master node。

  4. 口令认证,如果master设置了requirepass,那么salve node必须发送masterauth的口令过去进行认证。

  5. master node第一次执行全量复制,将所有数据发给slave node。

  6. master node后续持续将写命令,异步复制给slave node。

数据同步相关的核心机制

指的就是第一次slave连接msater的时候,执行的全量复制,那个过程里面的一些细节的机制:

  1. master和slave都会维护一个offset。
    master会在自身不断累加offset,slave也会在自身不断累加offset。
    slave每秒都会上报自己的offset给master,同时master也会保存每个slave的offset。

这个倒不是说特定就用在全量复制的,主要是master和slave都要知道各自的数据的offset,才能知道互相之间的数据不一致的情况

  1. backlog

master node有一个backlog,默认是1MB大小
master node给slave node复制数据时,也会将数据在backlog中同步写一份
backlog主要是用来做全量复制中断候的增量复制的

  1. master run id
    (二)redis高阶知识/redis在高并发缓存架构中的地位_第6张图片
    info server,可以看到master run id

如果根据host+ip定位master node,是不靠谱的,如果master node重启或者数据出现了变化,那么slave node应该根据不同的run id区分,run id不同就做全量复制
如果需要不更改run id重启redis,可以使用redis-cli debug reload命令

  1. psync

从节点使用psync从master node进行复制,psync runid offset
master node会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,可能是CONTINUE触发增量复制

全量复制

  1. master执行bgsave,在本地生成一份rdb快照文件
  2. master node将rdb快照文件发送给salve node,如果rdb复制时间超过60秒(repl-timeout),那么slave node就会认为复制失败,可以适当调节大这个参数
  3. 对于千兆网卡的机器,一般每秒传输100MB,6G文件,很可能超过60s
  4. master node在生成rdb时,会将所有新的写命令缓存在内存中,在salve node保存了rdb之后,再将新的写命令复制给salve node
  5. client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么停止复制,复制失败
  6. slave node接收到rdb之后,清空自己的旧数据,然后重新加载rdb到自己的内存中,同时基于旧的数据版本对外提供服务
  7. 如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF

rdb生成、rdb通过网络拷贝、slave旧数据的清理、slave aof rewrite,很耗费时间
如果复制的数据量在4G~6G之间,那么很可能全量复制时间消耗到1分半到2分钟

增量复制

(1)如果全量复制过程中,master-slave网络连接断掉,那么salve重新连接master时,会触发增量复制
(2)master直接从自己的backlog中获取部分丢失的数据,发送给slave node,默认backlog就是1MB
(3)msater就是根据slave发送的psync中的offset来从backlog中获取数据的

heartbeat

主从节点互相都会发送heartbeat信息

master默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat

异步复制

master每次接收到写命令之后,现在内部写入数据,然后异步发送给slave node

redis主从架构的搭建

一主一从,往主节点去写,在从节点去读,可以读到,主从架构就搭建成功了

启用复制,部署slave node

wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
tar -xzvf tcl8.6.1-src.tar.gz
cd  /usr/local/tcl8.6.1/unix/
./configure  
make && make install

使用redis-3.2.8.tar.gz(截止2017年4月的最新稳定版)

tar -zxvf redis-3.2.8.tar.gz
cd redis-3.2.8
make && make test && make install
  1. redis utils目录下,有个redis_init_script脚本

  2. 将redis_init_script脚本拷贝到linux的/etc/init.d目录中,将redis_init_script重命名为redis_6379,6379是我们希望这个redis实例监听的端口号

  3. 修改redis_6379脚本的第6行的REDISPORT,设置为相同的端口号(默认就是6379)

  4. 创建两个目录:/etc/redis(存放redis的配置文件),/var/redis/6379(存放redis的持久化文件)

  5. 修改redis配置文件(默认在根目录下,redis.conf),拷贝到/etc/redis目录中,修改名称为6379.conf

  6. 修改redis.conf中的部分配置为生产环境

daemonize	yes							让redis以daemon进程运行
pidfile		/var/run/redis_6379.pid 	设置redis的pid文件位置
port		6379						设置redis的监听端口号
dir 		/var/redis/6379				设置持久化文件的存储位置
  1. 让redis跟随系统启动自动启动

在redis_6379脚本中,最上面,加入两行注释

# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database
系统默认启动:
chkconfig redis_6379 on

在slave node上配置:slaveof 192.168.1.1 6379,即可
也可以使用slaveof命令

强制读写分离

基于主从复制架构,实现读写分离

redis slave node只读,默认开启,slave-read-only

开启了只读的redis slave node,会拒绝所有的写操作,这样可以强制搭建成读写分离的架构

集群安全认证

master上启用安全认证,requirepass
master连接口令,masterauth

读写分离架构的测试

先启动主节点,eshop-cache01上的redis实例
再启动从节点,eshop-cache02上的redis实例

刚才我调试了一下,redis slave node一直说没法连接到主节点的6379的端口

在搭建生产环境的集群的时候,不要忘记修改一个配置,bind

bind 127.0.0.1 -> 本地的开发调试的模式,就只能127.0.0.1本地才能访问到6379的端口

每个redis.conf中的bind 127.0.0.1 -> bind自己的ip地址
在每个节点上都: iptables -A INPUT -ptcp --dport 6379 -j ACCEPT

redis-cli -h ipaddr
info replication

主从架构QPS压测和水平扩容

QPS压测,redis-benchmark

你如果要对自己刚刚搭建好的redis做一个基准的压测,测一下你的redis的性能和QPS(query per second)

redis自己提供的redis-benchmark压测工具,是最快捷最方便的,当然啦,这个工具比较简单,用一些简单的操作和场景去压测

  1. 对redis读写分离架构进行压测,单实例写QPS+单实例读QPS
redis-3.2.8/src

./redis-benchmark -h 192.168.31.187

-c        Number of parallel connections (default 50)
-n       Total number of requests (default 100000)
-d           Data size of SET/GET value in bytes (default 2)

根据你自己的高峰期的访问量,在高峰期,瞬时最大用户量会达到10万+,-c 100000,-n 10000000,-d 50

各种基准测试,直接出来

1核1G,虚拟机

====== PING_INLINE ======
  100000 requests completed in 1.28 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.78% <= 1 milliseconds
99.93% <= 2 milliseconds
99.97% <= 3 milliseconds
100.00% <= 3 milliseconds
78308.54 requests per second

====== PING_BULK ======
  100000 requests completed in 1.30 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.87% <= 1 milliseconds
100.00% <= 1 milliseconds
76804.91 requests per second

====== SET ======
  100000 requests completed in 2.50 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

5.95% <= 1 milliseconds
99.63% <= 2 milliseconds
99.93% <= 3 milliseconds
99.99% <= 4 milliseconds
100.00% <= 4 milliseconds
40032.03 requests per second

====== GET ======
  100000 requests completed in 1.30 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.73% <= 1 milliseconds
100.00% <= 2 milliseconds
100.00% <= 2 milliseconds
76628.35 requests per second

====== INCR ======
  100000 requests completed in 1.90 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

80.92% <= 1 milliseconds
99.81% <= 2 milliseconds
99.95% <= 3 milliseconds
99.96% <= 4 milliseconds
99.97% <= 5 milliseconds
100.00% <= 6 milliseconds
52548.61 requests per second

====== LPUSH ======
  100000 requests completed in 2.58 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

3.76% <= 1 milliseconds
99.61% <= 2 milliseconds
99.93% <= 3 milliseconds
100.00% <= 3 milliseconds
38684.72 requests per second

====== RPUSH ======
  100000 requests completed in 2.47 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

6.87% <= 1 milliseconds
99.69% <= 2 milliseconds
99.87% <= 3 milliseconds
99.99% <= 4 milliseconds
100.00% <= 4 milliseconds
40469.45 requests per second

====== LPOP ======
  100000 requests completed in 2.26 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

28.39% <= 1 milliseconds
99.83% <= 2 milliseconds
100.00% <= 2 milliseconds
44306.60 requests per second

====== RPOP ======
  100000 requests completed in 2.18 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

36.08% <= 1 milliseconds
99.75% <= 2 milliseconds
100.00% <= 2 milliseconds
45871.56 requests per second

====== SADD ======
  100000 requests completed in 1.23 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.94% <= 1 milliseconds
100.00% <= 2 milliseconds
100.00% <= 2 milliseconds
81168.83 requests per second

====== SPOP ======
  100000 requests completed in 1.28 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.80% <= 1 milliseconds
99.96% <= 2 milliseconds
99.96% <= 3 milliseconds
99.97% <= 5 milliseconds
100.00% <= 5 milliseconds
78369.91 requests per second

====== LPUSH (needed to benchmark LRANGE) ======
  100000 requests completed in 2.47 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

15.29% <= 1 milliseconds
99.64% <= 2 milliseconds
99.94% <= 3 milliseconds
100.00% <= 3 milliseconds
40420.37 requests per second

====== LRANGE_100 (first 100 elements) ======
  100000 requests completed in 3.69 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

30.86% <= 1 milliseconds
96.99% <= 2 milliseconds
99.94% <= 3 milliseconds
99.99% <= 4 milliseconds
100.00% <= 4 milliseconds
27085.59 requests per second

====== LRANGE_300 (first 300 elements) ======
  100000 requests completed in 10.22 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.03% <= 1 milliseconds
5.90% <= 2 milliseconds
90.68% <= 3 milliseconds
95.46% <= 4 milliseconds
97.67% <= 5 milliseconds
99.12% <= 6 milliseconds
99.98% <= 7 milliseconds
100.00% <= 7 milliseconds
9784.74 requests per second

====== LRANGE_500 (first 450 elements) ======
  100000 requests completed in 14.71 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.00% <= 1 milliseconds
0.07% <= 2 milliseconds
1.59% <= 3 milliseconds
89.26% <= 4 milliseconds
97.90% <= 5 milliseconds
99.24% <= 6 milliseconds
99.73% <= 7 milliseconds
99.89% <= 8 milliseconds
99.96% <= 9 milliseconds
99.99% <= 10 milliseconds
100.00% <= 10 milliseconds
6799.48 requests per second

====== LRANGE_600 (first 600 elements) ======
  100000 requests completed in 18.56 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.00% <= 2 milliseconds
0.23% <= 3 milliseconds
1.75% <= 4 milliseconds
91.17% <= 5 milliseconds
98.16% <= 6 milliseconds
99.04% <= 7 milliseconds
99.83% <= 8 milliseconds
99.95% <= 9 milliseconds
99.98% <= 10 milliseconds
100.00% <= 10 milliseconds
5387.35 requests per second

====== MSET (10 keys) ======
  100000 requests completed in 4.02 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

0.01% <= 1 milliseconds
53.22% <= 2 milliseconds
99.12% <= 3 milliseconds
99.55% <= 4 milliseconds
99.70% <= 5 milliseconds
99.90% <= 6 milliseconds
99.95% <= 7 milliseconds
100.00% <= 8 milliseconds
24869.44 requests per second

我们这个读写分离这一块的第一讲

大部分情况下来说,看你的服务器的机器性能和配置,机器越牛逼,配置越高。单机上十几万,单机上二十万。

很多公司里,给一些低配置的服务器,操作复杂度。大公司里,都是公司会提供统一的云平台,比如京东、腾讯、BAT、其他的一些、小米、美团。

虚拟机,低配,搭建一些集群,专门为某个项目,搭建的专用集群,4核4G内存,比较复杂的操作,数据比较大。几万,单机做到,差不多了

redis提供的高并发,至少到上万,没问题

几万~十几万/二十万不等

QPS,自己不同公司,不同服务器,自己去测试,跟生产环境还有区别

生产环境,大量的网络请求的调用,网络本身就有开销,你的redis的吞吐量就不一定那么高了

QPS的两个杀手:一个是复杂操作,lrange,挺多的; value很大,2 byte,我之前用redis做大规模的缓存

做商品详情页的cache,可能是需要把大串数据,拼接在一起,作为一个json串,大小可能都几k。

水平扩容redis读节点,提升度吞吐量

就按照上一节课讲解的,再在其他服务器上搭建redis从节点,单个从节点读请QPS在5万左右,两个redis从节点,所有的读请求打到两台机器上去,承载整个集群读QPS在10万+

redis主从架构的高可用

什么是99.99%高可用?

架构上,高可用性,99.99%的高可用性

365天,在365天 * 99.99%的时间内,你的系统都是可以哗哗对外提供服务的,那就是高可用性,99.99%

redis不可用是什么?单实例不可用?主从架构不可用?不可用的后果是什么?

(二)redis高阶知识/redis在高并发缓存架构中的地位_第7张图片

redis怎么才能做到高可用?

主备切换:在master node故障时,自动检测,并且将某个salve node自动切换成master node的过程。这个过程,实现了redis的主从架构下的高可用。

一旦master node不可用,在很短的时间内,就会切换到另一个master上去。
这个时间内redis是不可用的。

哨兵(sentinal node)节点,负责监听master是否可用。
(二)redis高阶知识/redis在高并发缓存架构中的地位_第8张图片

redis哨兵架构的相关基础知识

哨兵的介绍

sentinal,中文名是哨兵

哨兵是redis集群架构中非常重要的一个组件,主要功能如下

  1. 集群监控,负责监控redis master和slave进程是否正常工作
  2. 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
  3. 故障转移,如果master node挂掉了,会自动转移到slave node上
  4. 配置中心,如果故障转移发生了,通知client客户端新的master地址

哨兵本身也是分布式的,作为一个哨兵集群去运行,互相协同工作

(1)故障转移时,判断一个master node是宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题
(2)即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了

目前采用的是sentinal 2版本,sentinal 2相对于sentinal 1来说,重写了很多代码,主要是让故障转移的机制和算法变得更加健壮和简单

哨兵的核心知识

(1)哨兵至少需要3个实例,来保证自己的健壮性
(2)哨兵 + redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性
(3)对于哨兵 + redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练

为什么redis哨兵集群只有2个节点无法正常工作?

哨兵集群必须部署2个以上节点

如果哨兵集群仅仅部署了个2个哨兵实例,quorum=1

M1/S1
R1/S2

Configuration: quorum = 1

master宕机,s1和s2中只要有1个哨兵认为master宕机就可以还行切换,同时s1和s2中会选举出一个哨兵来执行故障转移

同时这个时候,需要majority,也就是大多数哨兵都是运行的,2个哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2个哨兵都运行着,就可以允许执行故障转移

但是如果整个M1和S1运行的机器宕机了,那么哨兵只有1个了,此时就没有majority来允许执行故障转移,虽然另外一台机器还有一个R1,但是故障转移不会执行

4、经典的3节点哨兵集群

       	+----+
       	| M1 |
       	| S1 |
       	+----+
          |
+----+    |    +----+
| R2 |----+----| R3 |
| S2 |         | S3 |
+----+         +----+

Configuration: quorum = 2,majority

如果M1所在机器宕机了,那么三个哨兵还剩下2个,S2和S3可以一致认为master宕机,然后选举出一个来执行故障转移

同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移

redis准备切换数据的丢失:异步复制和集群脑裂

1、两种数据丢失的情况
2、解决异步复制和脑裂导致的数据丢失

两种数据丢失的情况

  1. 异步复制导致的数据丢失

因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了。

  1. 脑裂导致的数据丢失
    (二)redis高阶知识/redis在高并发缓存架构中的地位_第9张图片
    脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着。

此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master。

这个时候,集群里就会有两个master,也就是所谓的脑裂。

此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了。

因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。

解决异步复制和脑裂导致的数据丢失

min-slaves-to-write 1
min-slaves-max-lag 10

要求至少有1个slave,数据复制和同步的延迟不能超过10秒。

如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了。

上面两个配置可以减少异步复制和脑裂导致的数据丢失。

  1. 减少异步复制的数据丢失
    (二)redis高阶知识/redis在高并发缓存架构中的地位_第10张图片

有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内

  1. 减少脑裂的数据丢失

如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求。

这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失。

上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求。

因此在脑裂场景下,最多就丢失10秒的数据。

redis哨兵的核心底层原理

sdown和odown转换机制

sdown和odown两种失败状态

sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机

odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机

sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机

sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机

哨兵集群的自动发现机制

哨兵互相之间的发现,是通过redis的pub/sub系统实现的,每个哨兵都会往__sentinel__:hello这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在

每隔两秒钟,每个哨兵都会往自己监控的某个master+slaves对应的__sentinel__:hello channel里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置

每个哨兵也会去监听自己监控的每个master+slaves对应的__sentinel__:hello channel,然后去感知到同样在监听这个master+slaves的其他哨兵的存在

每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步

slave配置的自动纠正

哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据; 如果slave连接到了一个错误的master上,比如故障转移之后,那么哨兵会确保它们连接到正确的master上

slave->master选举算法

如果一个master被认为odown了,而且majority哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来

会考虑slave的一些信息

  1. 跟master断开连接的时长
  2. slave优先级
  3. 复制offset
  4. run id

如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就被认为不适合选举为master

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

接下来会对slave进行排序

(1)按照slave优先级进行排序,slave priority越低,优先级就越高
(2)如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
(3)如果上面两个条件都相同,那么选择一个run id比较小的那个slave

quorum和majority

每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换

如果quorum < majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换

但是如果quorum >= majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换

configuration epoch

哨兵会对一套redis master+slave进行监控,有相应的监控的配置

执行切换的那个哨兵,会从要切换到的新master(salve->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的

如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号

configuraiton传播

哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub消息机制

这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的

其他的哨兵都是根据版本号的大小来更新自己的master配置的

动手实操,练习如何操作部署哨兵集群,如何基于哨兵进行故障转移,还有一些企业级的配置方案

三节点主从企业级配置

1、哨兵的配置文件

sentinel.conf

最小的配置

每一个哨兵都可以去监控多个maser-slaves的主从架构

因为可能你的公司里,为不同的项目,部署了多个master-slaves的redis主从集群

相同的一套哨兵集群,就可以去监控不同的多个redis主从集群

你自己给每个redis主从集群分配一个逻辑的名称

sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5

sentinel monitor mymaster 127.0.0.1 6379 
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1

类似这种配置,来指定对一个master的监控,给监控的master指定的一个名称,因为后面分布式集群架构里会讲解,可以配置多个master做数据拆分

上面的三个配置,都是针对某个监控的master配置的,给其指定上面分配的名称即可

上面这段配置,就监控了两个master node

这是最小的哨兵配置,如果发生了master-slave故障转移,或者新的哨兵进程加入哨兵集群,那么哨兵会自动更新自己的配置文件

sentinel monitor master-group-name hostname port quorum
quorum的解释如下:
1. 至少多少个哨兵要一致同意,master进程挂掉了,或者slave进程挂掉了,或者要启动一个故障转移操作
2. quorum是用来识别故障的,真正执行故障转移的时候,还是要在哨兵集群执行选举,选举一个哨兵进程出来执行故障转移操作
3. 假设有5个哨兵,quorum设置了2,那么如果5个哨兵中的2个都认为master挂掉了; 2个哨兵中的一个就会做一个选举,选举一个哨兵出来,执行故障转移; 如果5个哨兵中有3个哨兵都是运行的,那么故障转移就会被允许执行

down-after-milliseconds,超过多少毫秒跟一个redis实例断了连接,哨兵就可能认为这个redis实例挂了

parallel-syncs,新的master别切换之后,同时有多少个slave被切换到去连接新master,重新做同步,数字越低,花费的时间越多

假设你的redis是1个master,4个slave

然后master宕机了,4个slave中有1个切换成了master,剩下3个slave就要挂到新的master上面去

这个时候,如果parallel-syncs是1,那么3个slave,一个一个地挂接到新的master上面去,1个挂接完,而且从新的master sync完数据之后,再挂接下一个

如果parallel-syncs是3,那么一次性就会把所有slave挂接到新的master上去

failover-timeout,执行故障转移的timeout超时时长

2、在eshop-cache03上再部署一个redis

只要安装redis就可以了,不需要去部署redis实例的启动

wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
tar -xzvf tcl8.6.1-src.tar.gz
cd /usr/local/tcl8.6.1/unix/
./configure
make && make install

使用redis-3.2.8.tar.gz(截止2017年4月的最新稳定版)
tar -zxvf redis-3.2.8.tar.gz
cd redis-3.2.8
make && make test
make install

正式的配置

哨兵默认用26379端口,默认不能跟其他机器在指定端口连通,只能在本地访问

mkdir /etc/sentinal
mkdir -p /var/sentinal/5000
分别修改三台机器上的:
/etc/sentinel/5000.conf

port 5000
bind 192.168.31.187
dir /var/sentinal/5000
sentinel monitor mymaster 192.168.31.187 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

port 5000
bind 192.168.31.19
dir /var/sentinal/5000
sentinel monitor mymaster 192.168.31.187 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

port 5000
bind 192.168.31.227
dir /var/sentinal/5000
sentinel monitor mymaster 192.168.31.187 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1

启动哨兵进程

在eshop-cache01、eshop-cache02、eshop-cache03三台机器上,分别启动三个哨兵进程,组成一个集群,观察一下日志的输出

redis-sentinel /etc/sentinal/5000.conf
redis-server /etc/sentinal/5000.conf --sentinel

日志里会显示出来,每个哨兵都能去监控到对应的redis master,并能够自动发现对应的slave

哨兵之间,互相会自动进行发现,用的就是之前说的pub/sub,消息发布和订阅channel消息系统和机制

检查哨兵状态

redis-cli -h 192.168.31.187 -p 5000

sentinel master mymaster
SENTINEL slaves mymaster
SENTINEL sentinels mymaster

// 根据集群的逻辑名词查询mater node的ip
SENTINEL get-master-addr-by-name mymaster

哨兵节点的增加和删除

增加sentinal,会自动发现

删除sentinal的步骤

(1)停止sentinal进程
(2)SENTINEL RESET *,在所有sentinal上执行,清理所有的master状态
(3)SENTINEL MASTER mastername,在所有sentinal上执行,查看所有sentinal对数量是否达成了一致

slave的永久下线

让master摘除某个已经下线的slave:SENTINEL RESET mastername,在所有的哨兵上面执行

slave切换为Master的优先级

slave->master选举优先级:slave-priority,值越小优先级越高

基于哨兵集群架构下的安全认证

每个slave都有可能切换成master,所以每个实例都要配置两个指令

master上启用安全认证,requirepass
master连接口令,masterauth

sentinal,sentinel auth-pass

容灾演练

通过哨兵看一下当前的master:SENTINEL get-master-addr-by-name mymaster

把master节点kill -9掉,pid文件也删除掉

查看sentinal的日志,是否出现+sdown字样,识别出了master的宕机问题; 然后出现+odown字样,就是指定的quorum哨兵数量,都认为master宕机了

(1)三个哨兵进程都认为master是sdown了
(2)超过quorum指定的哨兵进程都认为sdown之后,就变为odown
(3)哨兵1是被选举为要执行后续的主备切换的那个哨兵
(4)哨兵1去新的master(slave)获取了一个新的config version
(5)尝试执行failover
(6)投票选举出一个slave区切换成master,每隔哨兵都会执行一次投票
(7)让salve,slaveof noone,不让它去做任何节点的slave了; 把slave提拔成master; 旧的master认为不再是master了
(8)哨兵就自动认为之前的187:6379变成了slave了,19:6379变成了master了
(9)哨兵去探查了一下187:6379这个salve的状态,认为它sdown了

所有哨兵选举出了一个,来执行主备切换操作

如果哨兵的majority都存活着,那么就会执行主备切换操作

再通过哨兵看一下master:SENTINEL get-master-addr-by-name mymaster

尝试连接一下新的master

故障恢复,再将旧的master重新启动,查看是否被哨兵自动切换成slave节点

(1)手动杀掉master
(2)哨兵能否执行主备切换,将slave切换为master
(3)哨兵完成主备切换后,新的master能否使用
(4)故障恢复,将旧的master重新启动
(5)哨兵能否自动将旧的master变为slave,挂接到新的master上面去,而且也是可以使用的

哨兵的生产环境部署

修改哨兵配置文件5000.conf

// 后台进程启动哨兵:
daemonize yes
// 日志存放路径
logfile /var/log/sentinel/5000/sentinel.log

创建日志存放的文件夹

mkdir -p /var/log/sentinel/5000

redis如何在保持读写分离+高可用的架构下,还能横向扩容master支撑1T+海量数据

单机master在海量数据面前的瓶颈?

master节点和slave节点的数据是一样的,master最大容量就是slave的最大容量也是整个集群的最大容量。
(二)redis高阶知识/redis在高并发缓存架构中的地位_第11张图片

怎么才能够突破单机瓶颈,让redis支撑海量数据?

增加集群的master node。

使用redis cluster(redis cluster主要是能够实现扩容,slave node主要实现的是读写分离,sentinel实现的是高可用
搭建支撑N个redis master node,每个master node都可以挂载多个slave node。

读写分离的架构,对于每个master来说,写就写到master,然后读就从mater对应的slave去读。

高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master

redis cluster =(多master + 读写分离 + 高可用)

我们只要基于redis cluster去搭建redis集群即可,不需要手工去搭建replication复制+主从架构+读写分离+哨兵集群+高可用

redis cluster vs. replication + sentinal

如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,单机足够了

replication,一个mater,多个slave,要几个slave跟你的要求的读吞吐量有关系,然后自己搭建一个sentinal集群,去保证redis主从架构的高可用性,就可以了

redis cluster,主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,那么建议就用redis cluster

redis cluster的分布式数据存储算法

为了解决多个master分布式数据存储的问题:

hash算法 -> 一致性hash算法(memcached) -> redis cluster,hash slot算法

用不同的算法,就决定了在多个master节点的时候,数据如何分布到这些节点上去

redis cluster介绍

redis cluster

  1. 自动将数据进行分片,每个master上放一部分数据
  2. 提供内置的高可用支持,部分master不可用时,还是可以继续工作的

在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加10000的端口号,比如16379端口号是用来进行节点间通信的,也就是cluster bus的东西,集群总线。cluster bus的通信,用来进行故障检测,配置更新,故障转移授权

cluster bus用了另外一种二进制的协议,主要用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间

几个常见的分布式算法
1、最老土的hash算法和弊端(大量缓存重建)
(二)redis高阶知识/redis在高并发缓存架构中的地位_第12张图片
2、一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡)
(二)redis高阶知识/redis在高并发缓存架构中的地位_第13张图片
(二)redis高阶知识/redis在高并发缓存架构中的地位_第14张图片
4、redis cluster的hash slot算法

(二)redis高阶知识/redis在高并发缓存架构中的地位_第15张图片
redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot。

redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot。

hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去

,减少一个master,就将它的hash slot移动到其他master上去。移动hash slot的成本是非常低的。

客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现。

多master node的集群环境搭建

redis cluster最最基础的一些知识
redis cluster: 自动,master+slave复制和读写分离,master+slave高可用和主备切换,支持多个master的hash slot支持数据分布式存储。

停止之前所有的实例,包括redis主从和哨兵集群

redis cluster的重要配置

cluster-enabled 

cluster-config-file :这是指定一个文件,供cluster模式下的redis实例将集群状态保存在那里,包括集群中其他机器的信息,比如节点的上线和下限,故障转移,不是我们去维护的,给它指定一个文件,让redis自己去维护的

cluster-node-timeout :节点存活超时时长,超过一定时长,认为节点宕机,master宕机的话就会触发主备切换,slave宕机就不会提供服务

在三台机器上启动6个redis实例

  1. 在eshop-cache03上部署目录

/etc/redis(存放redis的配置文件),/var/redis/6379(存放redis的持久化文件)

  1. 编写配置文件

redis cluster集群,要求至少3个master,去组成一个高可用,健壮的分布式的集群,每个master都建议至少给一个slave,3个master,3个slave,最少的要求

正式环境下,建议都是说在6台机器上去搭建,至少3台机器

保证,每个master都跟自己的slave不在同一台机器上,如果是6台自然更好,一个master+一个slave就死了

3台机器去搭建6个redis实例的redis cluster

mkdir -p /etc/redis-cluster
mkdir -p /var/log/redis
mkdir -p /var/redis/7001

port 7001
cluster-enabled yes
cluster-config-file /etc/redis-cluster/node-7001.conf
cluster-node-timeout 15000
daemonize	yes							
pidfile		/var/run/redis_7001.pid 						
dir 		/var/redis/7001		
logfile /var/log/redis/7001.log
bind 192.168.31.187		
appendonly yes

至少要用3个master节点启动,每个master加一个slave节点,先选择6个节点,启动6个实例

将上面的配置文件,在/etc/redis下放6个,分别为: 7001.conf,7002.conf,7003.conf,7004.conf,7005.conf,7006.conf

  1. 准备生产环境的启动脚本

在/etc/init.d下,放6个启动脚本,分别为: redis_7001, redis_7002, redis_7003, redis_7004, redis_7005, redis_7006

每个启动脚本内,都修改对应的端口号

  1. 分别在3台机器上,启动6个redis实例

将每个配置文件中的slaveof给删除

创建集群

  1. 在一台机器上安装ruby和rubygems
yum install -y ruby
yum install -y rubygems
gem install redis

cp /usr/local/redis-3.2.8/src/redis-trib.rb /usr/local/bin
  1. 配置集群环境:
redis-trib.rb create --replicas 1 192.168.31.187:7001 192.168.31.187:7002 192.168.31.19:7003 192.168.31.19:7004 192.168.31.227:7005 192.168.31.227:7006
  • replicas: 每个master有几个slave

6台机器,3个master,3个slave,尽量自己让master和slave不在一台机器上

  1. 检查集群环境
    redis-trib.rb check 192.168.31.187:7001

读写分离+高可用+多master

读写分离:每个master都有一个slave。
高可用:master宕机,slave自动被切换过去。
多master:横向扩容支持更大数据量。

redis cluster搭建的多master集群环境检验

redis cluster,提供了多个master,数据可以分布式存储在多个master上; 每个master都带着slave,自动就做读写分离; 每个master如果故障,那么久会自动将slave切换成master,高可用

redis cluster的基本功能,来测试一下

实验多master写入 -> 海量数据的分布式存储

你在redis cluster写入数据的时候,其实是你可以将请求发送到任意一个master上去执行。

但是,每个master都会计算这个key对应的CRC16值,然后对16384个hashslot取模,找到key对应的hashslot,找到hashslot对应的master。

如果对应的master就在自己本地的话,set mykey1 v1,mykey1这个key对应的hashslot就在自己本地,那么自己就处理掉了。

但是如果计算出来的hashslot在其他master上,那么就会给客户端返回一个moved error,告诉你,你得到哪个master上去执行这条写入的命令。

什么叫做多master的写入,就是每条数据只能存在于一个master上,不同的master负责存储不同的数据,分布式的数据存储。100w条数据,5个master,每个master就负责存储20w条数据,分布式数据存储。

大型的java系统架构,还专注在大数据系统架构,分布式,分布式存储,hadoop hdfs,分布式资源调度,hadoop yarn,分布式计算,hadoop mapreduce/hive。分布式的nosql数据库,hbase,分布式的协调,zookeeper,分布式通用计算引擎,spark,分布式的实时计算引擎,storm。
如果你要处理海量数据,就涉及到了一个名词,叫做大数据,只要涉及到大数据,那么其实就会涉及到分布式。
大数据相关的系统,也涉及很多的java系统架构,高并发、高可用、高性能、可扩展、分布式系统。
会给大家稍微拓展一下知识面,从不同的角度去讲解一块知识。
redis,高并发、高性能、每日上亿流量的大型电商网站的商品详情页系统的缓存架构,来讲解的,redis是作为大规模缓存架构中的底层的核心存储的支持。
高并发、高性能、每日上亿流量,redis持久化 -> 灾难的时候,做数据恢复,复制 -> 读写分离,扩容slave,支撑更高的读吞吐,redis怎么支撑读QPS超过10万,几十万; 哨兵,在redis主从,一主多从,怎么保证99.99%可用性; redis cluster,海量数据。
java架构课,架构思路和设计是很重要的,但是另外一点,我希望能够带着大家用真正java架构师的角度去看待一些技术,而不是仅仅停留在技术的一些细节的点。
给大家从一些大数据的角度,去分析一下我们java架构领域中的一些技术。
天下武功,都出自一脉,研究过各种大数据的系统,redis cluster讲解了很多原理,跟elasticsearch,很多底层的分布式原理,都是类似的。
redis AOF,fsync和elasticsearch建立索引的时候类似,先写内存缓存,每秒钟把数据刷入os cache,接下来再每隔一定时间fsync到磁盘上去。
redis cluster,写可以到任意master,任意master计算key的hashslot以后,告诉client,重定向,路由到其他mater去执行,分布式存储的一个经典的做法。
elasticsearch,建立索引的时候,也会根据doc id/routing value,做路由,路由到某个其他节点,重定向到其他节点去执行。
分布式的一些,hadoop,spark,storm里面很多核心的思想都是类似的。

2、实验不同master各自的slave读取 -> 读写分离

在这个redis cluster中,如果你要在slave读取数据,那么需要带上readonly指令,get mykey1。
redis-cli -c启动,就会自动进行各种底层的重定向的操作。

实验redis cluster的读写分离的时候,会发现有一定的限制性,默认情况下,redis cluster的核心的理念,主要是用slave做高可用的,每个master挂一两个slave,主要是做数据的热备,还有master故障时的主备切换,实现高可用的。redis cluster默认是不支持slave节点读或者写的,跟我们手动基于replication搭建的主从架构不一样的。

slave node,readonly,get,这个时候才能在slave node进行读取。

redis cluster,主从架构是出来,读写分离,复杂了点,也可以做,jedis客户端,对redis cluster的读写分离支持不太好的。默认的话就是读和写都到master上去执行的。

如果你要让最流行的jedis做redis cluster的读写分离的访问,那可能还得自己修改一点jedis的源码,成本比较高。
要不然你就是自己基于jedis,封装一下,自己做一个redis cluster的读写分离的访问api。

核心的思路,就是说,redis cluster的时候,就没有所谓的读写分离的概念了。

读写分离,是为了什么,主要是因为要建立一主多从的架构,才能横向任意扩展slave node去支撑更大的读吞吐量。redis cluster的架构下,实际上本身master就是可以任意扩展的,你如果要支撑更大的读吞吐量,或者写吞吐量,或者数据量,都可以直接对master进行横向扩展就可以了。也可以实现支撑更高的读吞吐的效果。

redis cluster,主从架构,读写分离,没说错,没有撒谎

redis cluster实现master和slave读写分离支持的不太好,无论是server层面,还是jedis client层面;但是在对master做扩容的同时,跟之前扩容slave,效果是一样的。

实验自动故障切换 -> 高可用性

redis-trib.rb check 192.168.31.187:7001

比如把master1,187:7001,杀掉,看看它对应的19:7004能不能自动切换成master,可以自动切换。

切换成master后的19:7004,可以直接读取数据。

再试着把187:7001给重新启动,恢复过来,自动作为slave挂载到了19:7004上面去。

redis cluster下的水平扩容

redis cluster模式下,不建议做物理的读写分离了。

我们建议通过master的水平扩容,来横向扩展读写吞吐量,还有支撑更多的海量数据。

redis单机,读吞吐是5w/s,写吞吐2w/s。扩展redis更多master,那么如果有5台master,不就读吞吐可以达到总量25/s QPS,写可以达到10w/s QPS。

redis单机,内存,6G,8G,fork类操作的时候很耗时,会导致请求延时的问题

扩容到5台master,能支撑的总的缓存数据量就是30G,40G -> 100台,600G,800G,甚至1T+,海量数据

redis是怎么扩容的

加入新master

mkdir -p /var/redis/7007

port 7007
cluster-enabled yes
cluster-config-file /etc/redis-cluster/node-7007.conf
cluster-node-timeout 15000
daemonize	yes							
pidfile		/var/run/redis_7007.pid 						
dir 		/var/redis/7007		
logfile /var/log/redis/7007.log
bind 192.168.31.227		
appendonly yes

搞一个7007.conf,再搞一个redis_7007启动脚本

手动启动一个新的redis实例,在7007端口上

redis-trib.rb add-node 192.168.31.227:7007 192.168.31.187:7001

redis-trib.rb check 192.168.31.187:7001

连接到新的redis实例上,cluster nodes,确认自己是否加入了集群,作为了一个新的master。

reshard一些数据过去

resharding的意思就是把一部分hash slot从一些node上迁移到另外一些node上

redis-trib.rb reshard 192.168.31.187:7001

要把之前3个master上,总共4096个hashslot迁移到新的第四个master上去

How many slots do you want to move (from 1 to 16384)?

1000

添加node作为slave

eshop-cache03

mkdir -p /var/redis/7008

port 7008
cluster-enabled yes
cluster-config-file /etc/redis-cluster/node-7008.conf
cluster-node-timeout 15000
daemonize yes
pidfile /var/run/redis_7008.pid
dir /var/redis/7008
logfile /var/log/redis/7008.log
bind 192.168.31.227
appendonly yes

redis-trib.rb add-node --slave --master-id 28927912ea0d59f6b790a50cf606602a5ee48108 192.168.31.227:7008 192.168.31.187:7001

删除node

先用resharding将数据都移除到其他节点,确保node为空之后,才能执行remove操作

redis-trib.rb del-node 192.168.31.187:7001 bd5a40a6ddccbd46a0f4a2208eb25d2453c2a8db

2个是1365,1个是1366。当你清空了一个master的hashslot时,redis cluster就会自动将其slave挂载到其他master上去。这个时候就只要删除掉master就可以了。

redis cluster自动化slave迁移

slave的自动迁移

比如现在有10个master,每个有1个slave,然后新增了3个slave作为冗余,有的master就有2个slave了,有的master出现了salve冗余。如果某个master的slave挂了,那么redis cluster会自动迁移一个冗余的slave给那个master。只要多加一些冗余的slave就可以了。
如果你每个master只有一个slave,万一说一个slave死了,然后很快,master也死了,那可用性还是降低了。但是如果你给整个集群挂载了一些冗余slave,那么某个master的slave死了,冗余的slave会被自动迁移过去,作为master的新slave,此时即使那个master也死了,还是有一个slave会切换成master的

实验:
之前有一个master是有冗余slave的,直接让其他master其中的一个slave死掉,然后看有冗余slave会不会自动挂载到那个master。

redis cluster的核心原理分析

节点间的内部通信机制

基础通信原理

  1. redis cluster节点间采取gossip协议进行通信

跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的。

维护集群的元数据方式两种:集中式和gossip。

集中式
(二)redis高阶知识/redis在高并发缓存架构中的地位_第16张图片
好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力

gossip
(二)redis高阶知识/redis在高并发缓存架构中的地位_第17张图片
好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后

我们刚才做reshard,去做另外一个操作,会发现说,configuration error,达成一致

  1. 10000端口

每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口

每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong

  1. 交换的信息
    故障信息,节点的增加和移除,hash slot信息,等等

gossip协议

gossip协议包含多种消息,包括ping,pong,meet,fail,等等。

meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信。

redis-trib.rb add-node

其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群。

ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据。

每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新。

pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新。

fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。

ping消息深入

ping很频繁,而且要携带一些元数据,所以可能会加重网络负担。

每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点。

当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了。

比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题,所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率。

每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换

至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息。


面向集群的jedis内部实现原理

开发,jedis,redis的java client客户端,redis cluster,jedis cluster api

jedis cluster api与redis cluster集群交互的一些基本原理

基于重定向的客户端

redis-cli -c,自动重定向

  1. 请求重定向

客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会计算key对应的hash slot,如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向。

cluster keyslot mykey,可以查看一个key对应的hash slot是什么。

用redis-cli的时候,可以加入-c参数,支持自动的请求重定向,redis-cli接收到moved之后,会自动重定向到对应的节点执行命令。

  1. 计算hash slot

计算hash slot的算法,就是根据key计算CRC16值,然后对16384取模,拿到对应的hash slot。

hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100}和set mykey2:{100}

  1. hash slot查找

节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上

smart jedis

  1. 什么是smart jedis

基于重定向的客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点。所以大部分的客户端,比如java redis客户端,就是jedis,都是smart的。

本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向。

  1. JedisCluster的工作原理

在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池。

每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点。如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved

如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存。

重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException

jedis老版本,可能会出现在集群某个节点故障还没完成自动切换恢复时,频繁更新hash slot,频繁ping节点检查活跃,导致大量网络IO开销。

jedis最新版本,对于这些过度的hash slot更新和ping,都进行了优化,避免了类似问题。

  1. hashslot迁移和ask重定向

如果hash slot正在迁移,那么会返回ask重定向给jedis

jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存。

已经可以确定说,hashslot已经迁移完了,moved是会更新本地hashslot->node映射表缓存的

高可用性与主备切换原理

redis cluster的高可用的原理,几乎跟哨兵是类似的

  1. 判断节点宕机

如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机

如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown

在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail

如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail

  1. 从节点过滤

对宕机的master node,从其所有的slave node中,选择一个切换成master node

检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master

这个也是跟哨兵是一样的,从节点超时过滤的步骤

  1. 从节点选举

哨兵:对所有从节点进行排序,slave priority,offset,run id

每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举

所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master

从节点执行主备切换,从节点切换为主节点

  1. 与哨兵比较

整个流程跟哨兵相比,非常类似,所以说,redis cluster功能强大,直接集成了replication和sentinal的功能

没有办法去给大家深入讲解redis底层的设计的细节,核心原理和设计的细节,那个除非单独开一门课,redis底层原理深度剖析,redis源码

对于咱们这个架构课来说,主要关注的是架构,不是底层的细节,对于架构来说,核心的原理的基本思路,是要梳理清晰的

redis实践中的问题和优化

fork耗时导致高并发请求延时

RDB和AOF的时候,其实会有生成RDB快照,AOF rewrite,耗费磁盘IO的过程,主进程fork子进程

fork的时候,子进程是需要拷贝父进程的空间内存页表的,也是会耗费一定的时间的

一般来说,如果父进程内存有1个G的数据,那么fork可能会耗费在20ms左右,如果是10G~30G,那么就会耗费20 * 10,甚至20 * 30,也就是几百毫秒的时间

info stats中的latest_fork_usec,可以看到最近一次form的时长

redis单机QPS一般在几万,fork可能一下子就会拖慢几万条操作的请求时长,从几毫秒变成1秒

优化思路

fork耗时跟redis主进程的内存有关系,一般控制redis的内存在10GB以内,slave -> master,全量复制

AOF的阻塞问题

redis将数据写入AOF缓冲区,单独开一个现场做fsync操作,每秒一次

但是redis主线程会检查两次fsync的时间,如果距离上次fsync时间超过了2秒,那么写请求就会阻塞

everysec,最多丢失2秒的数据

一旦fsync超过2秒的延时,整个redis就被拖慢

优化思路

优化硬盘写入速度,建议采用SSD,不要用普通的机械硬盘,SSD,大幅度提升磁盘读写的速度

主从复制延迟问题

主从复制可能会超时严重,这个时候需要良好的监控和报警机制

在info replication中,可以看到master和slave复制的offset,做一个差值就可以看到对应的延迟量

如果延迟过多,那么就进行报警

主从复制风暴问题

如果一下子让多个slave从master去执行全量复制,一份大的rdb同时发送到多个slave,会导致网络带宽被严重占用

如果一个master真的要挂载多个slave,那尽量用树状结构,不要用星型结构

vm.overcommit_memory

0: 检查有没有足够内存,没有的话申请内存失败
1: 允许使用内存直到用完为止
2: 内存地址空间不能超过swap + 50%

如果是0的话,可能导致类似fork等操作执行失败,申请不到足够的内存空间

cat /proc/sys/vm/overcommit_memory
echo “vm.overcommit_memory=1” >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1

swapiness

cat /proc/version,查看linux内核版本

如果linux内核版本<3.5,那么swapiness设置为0,这样系统宁愿swap也不会oom killer(杀掉进程)
如果linux内核版本>=3.5,那么swapiness设置为1,这样系统宁愿swap也不会oom killer

保证redis不会被杀掉

echo 0 > /proc/sys/vm/swappiness
echo vm.swapiness=0 >> /etc/sysctl.conf

最大打开文件句柄

ulimit -n 10032 10032

自己去上网搜一下,不同的操作系统,版本,设置的方式都不太一样

tcp backlog

cat /proc/sys/net/core/somaxconn
echo 511 > /proc/sys/net/core/somaxconn

redis启动日志中有这些优化建议。

redis阶段总结

讲解redis是为了什么?

topic:高并发、亿级流量、高性能、海量数据的场景,电商网站的商品详情页系统的缓存架构

商品详情页系统,大型电商网站,会有很多部分组成,但是支撑高并发、亿级流量的,主要就是其中的大型的缓存架构。在这个大型的缓存架构中,redis是最最基础的一层。高并发,缓存架构中除了redis,还有其他的组成部分,但是redis至关重要。

大量的离散请求,随机请求,各种你未知的用户过来的请求,上千万用户过来访问,每个用户访问10次; 集中式的请求,1个用户过来,一天访问1亿次

支撑商品展示的最重要的,就是redis cluster,去抗住每天上亿的请求流量,支撑高并发的访问。

redis cluster在整个缓存架构中,如何跟其他几个部分搭配起来组成一个大型的缓存系统,后面再讲。

讲解的redis可以实现什么效果?

redis:持久化、复制(主从架构)、哨兵(集群。高可用,主备切换)、redis cluster(分布式+集群。海量数据+横向扩容+高可用/主备切换)

持久化:高可用的一部分,在发生redis集群灾难的情况下(比如说部分master+slave全部死掉了),如何快速进行数据恢复,快速实现服务可用,才能实现整个系统的高可用

复制:主从架构,master -> slave 复制,读写分离的架构,写master,读slave,横向扩容slave支撑更高的读吞吐,读高并发,10万,20万,30万,上百万,QPS,横向扩容

哨兵:高可用,主从架构,在master故障的时候,快速将slave切换成master,实现快速的灾难恢复,实现高可用性

redis cluster:多master读写,数据分布式的存储,横向扩容,水平扩容,快速支撑高达的数据量+更高的读写QPS,自动进行master -> slave的主备切换,高可用

让底层的缓存系统,redis,实现能够任意水平扩容,支撑海量数据(1T+,几十T,10G * 600 redis = 6T),支撑很高的读写QPS(redis单机在几万QPS,10台,几十万QPS),高可用性(给我们每个redis实例都做好AOF+RDB的备份策略+容灾策略,slave -> master主备切换)

1T+海量数据、10万+读写QPS、99.99%高可用性

redis的第一套企业级的架构

如果你的数据量不大,单master就可以容纳,一般来说你的缓存的总量在10G以内就可以,那么建议按照以下架构去部署redis

redis持久化+备份方案+容灾方案+replication(主从+读写分离)+sentinal(哨兵集群,3个节点,高可用性)

可以支撑的数据量在10G以内,可以支撑的写QPS在几万左右,可以支撑的读QPS可以上10万以上(随你的需求,水平扩容slave节点就可以),可用性在99.99%

redis的第二套企业级架构

如果你的数据量很大,比如我们课程的topic,大型电商网站的商品详情页的架构(对标那些国内排名前三的大电商网站,*宝,*东,*宁易购),数据量是很大的

海量数据

redis cluster

多master分布式存储数据,水平扩容

支撑更多的数据量,1T+以上没问题,只要扩容master即可

读写QPS分别都达到几十万都没问题,只要扩容master即可,redis cluster,读写分离,支持不太好,readonly才能去slave上读

支撑99.99%可用性,也没问题,slave -> master的主备切换,冗余slave去进一步提升可用性的方案(每个master挂一个slave,但是整个集群再加个3个slave冗余一下)

我们课程里,两套架构都讲解了,后续的业务系统的开发,主要是基于redis cluster去做

我们现在课程讲解的项目进展到哪里了?

我们要做后续的业务系统的开发,redis的架构部署好,是第一件事情,也是非常重要的,也是你作为一个架构师而言,在对系统进行设计的时候,你必须要考虑到底层的redis的并发、性能、能支撑的数据量、可用性

redis:水平扩容,海量数据,上10万的读写QPS,99.99%高可用性

从架构的角度,我们的redis是可以做到的,水平扩容,只要机器足够,到1T数据量,50万读写QPS,99.99%

正式开始做大型电商网站的商品详情页系统,大规模的缓存架构设计

你可能感兴趣的:(高并发缓存架构设计,redis,redis高阶知识,redis)