专栏
天翼云开发者社区

redis-分布式缓存详解

2022-06-30 18:57:09 16阅读

redis单节点问题

1.数据丢失问题,数据在内存中,重启就会丢失

  解决方案: 实现redis数据持久化

2.并发访问问题,性能不错但是高并发情况下还是会有问题

  解决方案: 搭建主从集群,实现读写分离

3.故障恢复问题,单节点部署,一旦出现问题,就出现整个微服务影响的范围非常大

  解决方案: 利用redis哨兵实现健康检测和自动恢复

4.存储能力问题,单节点内存难以满足海量数据的存储

  解决方案: 搭建分片集群,利用插槽机制实现动态扩容

 

问题一: redis持久化

    有两种解决方式RDB和AOF

    RDB: redis数据备份文件,也叫做redis数据快照,简单来说就是把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据

         快照文件称为RDB文件,默认是保存是保存在当前的运行目录,默认文件名dump.rdb

         执行save命令就会进行一次快照保存(save命令是主进程来执行,会影响效率)

         一般情况下执行bgsave开启子进程执行RDB,避免住进程收到影响

         主动关闭redis会默认自动执行一次RDB。

         除了主动执行和关机执行,RDB还会根据配置文件中的配置来执行。

         save 900 1       //900秒内,如果至少有1个key被修改,则执行bgsave,如果是save "" 则表示禁用RDB。以下两个同理

         save 300 10

         save 60  10000

         RDB的底层执行流程: bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取读取内存数据并写入RDB文件中

         RDB缺点: RDB执行间隔比较长,两次RDB之间写入的数据有丢失的风险。 fork主进程,压缩,写出RDB文件都比较耗时

    

    AOF: redis处理的每一个命令都会记录在AOF文件中,可以看做是命令日志文件(默认关闭)

         开启需要修改redis.config文件,将appendonly yes(默认为no),appendfilename "appendonly.aof"(修改aof文件名称)

         aof记录日志的频率有三种,通过redis.config文件进行修改

         第一种表示每执行一次写的命令,都记录在aof文件中   appendfsync always

         第二种表示写命令执行完先放入aof缓存区,然后表示每隔一秒将缓存区数据写到aof文件,是默认方案  appendfsync everysec

         第三种表示写命令执行完先放入aof缓存区,由操作系统决定何时将缓存区数据写会磁盘   appendfsync no

         

         因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。

         通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

         比如执行了三条语句 set name 123,  set name 456, set name 789  执行了bgrewriteaof命令之后会合并为 mset name 123 456 789

         

         Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:

         auto-aof-rewrite-percentage 100  //AOF文件比上次文件 增长超过多少百分比则触发重写

         auto-aof-rewrite-min-size 64mb   //AOF文件体积最小多大以上才触发重写

 

         

问题二: 并发访问问题

        单节点redis并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离     

        因为redis主要的业务是读的操作,写的操作比较少,一般都是master节点接收读的操作,slave节点接收写的操作,然后master节点将数据同步到各个slave节点上,实现数据一致

        

        集群搭建(一主两从):

        1. 这里我们会在同一台虚拟机中开启3个redis实例,模拟主从集群,信息如下:

           | 192.169.666.888 | 7001 | master |

           | 192.169.666.888 | 7002 | slave  |

           | 192.169.666.888 | 7003 | slave  |

        2. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。 

           我们创建三个文件夹,名字分别叫7001、7002、7003:

           # 进入/tmp目录

            cd /home

           # 创建目录

            mkdir 7001 7002 7003

           恢复原始配置

           修改redis-6.2.4/redis.conf文件,将其中的持久化模式改为默认的RDB模式,AOF保持关闭状态。

           # 开启RDB

           # save ""

           save 3600 1

           save 300 100

           save 60 10000

 

           # 关闭AOF

           appendonly no

           

           拷贝配置文件到每个实例目录

           然后将redis-6.2.4/redis.conf文件拷贝到三个目录中

           cp redis-6.2.4/redis.conf 7001

           cp redis-6.2.4/redis.conf 7002

           cp redis-6.2.4/redis.conf 7003

           

           修改每个实例的端口、工作目录

           修改每个文件夹内的配置文件,将端口分别修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在目录

           sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/home\/7001\//g' 7001/redis.conf

           sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/home\/7002\//g' 7002/redis.conf

           sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/home\/7003\//g' 7003/redis.conf

           

           虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下:

           # redis实例的声明 IP

           replica-announce-ip 123.57.87.143

           

           每个目录都要改,我们一键完成修改

           sed -i '1a replica-announce-ip 192.169.666.888' 7001/redis.conf

           sed -i '1a replica-announce-ip 192.169.666.888' 7002/redis.conf

           sed -i '1a replica-announce-ip 192.169.666.888' 7003/redis.conf

         

           为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:

           # 第1个

           redis-server 7001/redis.conf

           # 第2个

           redis-server 7002/redis.conf

           # 第3个

           redis-server 7003/redis.conf

           

           开启主从关系

           现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。

           有临时和永久两种模式:

           修改配置文件(永久生效)

                在redis.conf中添加一行配置: slaveof <masterip> <masterport>

           使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):

                slaveof <masterip> <masterport>

           注意: 在5.0以后新增命令replicaof,与salveof效果一致。

           

           这里我们为了演示方便,使用方式二。

 

           通过redis-cli命令连接7002,执行下面命令:

            # 连接 7002

            redis-cli -p 7002

            # 执行slaveof

            slaveof 192.169.666.888 7001

            

           通过redis-cli命令连接7003,执行下面命令:

            # 连接 7003

            redis-cli -p 7003

            # 执行slaveof

            slaveof 192.169.666.888 7001

 

           然后连接 7001节点,查看集群状态:

            # 连接 7001

            redis-cli -p 7001

            # 查看状态

            info replication

 

           测试:

            - 利用redis-cli连接7001,执行set num 123

 

            - 利用redis-cli连接7002,执行get num,再执行set num 666

 

            - 利用redis-cli连接7003,执行get num,再执行set num 888

 

        可以发现,只有在7001这个master节点上可以执行写操作,7002和7003这两个slave节点只能执行读操作(完成^_^)

 

        redis主从数据同步原理(全量同步):

        1. slave节点请求增量同步

        2. master节点判断replid,发现不一致,拒绝增量同步

        3. master将完整内存数据生成RDB,发送RDB到slave

        4. slave清空本地数据,加载master的RDB

        5. master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave

        6. slave执行接收到的命令,保持与master之间的同步

        

        redis主从数据同步原理(增量同步):

        slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

        什么时候执行全量同步?

        slave节点第一次连接master节点时

        slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

        什么时候执行增量同步?

        slave节点断开又恢复,并且在repl_baklog中能找到offset时

        

        

问题三:故障恢复问题

    slave节点宕机后重启可以找到master节点同步数据,那master节点宕机了怎么办?

    解决: redis提供了哨兵sentinel机制来实现主从集群的自动故障恢复。

    监控: sentinel会不断检查master和slave是否按照预期工作

    自动故障恢复: 如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主

    通知: sentinel充当redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给redis的客户端    

        

    Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

    主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

    客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

    

    一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

    1. 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点 (与master断开时间太长了,丢失的数据越多)

    2. 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举 

    3. 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 (offset代表当前节点与master数据同步进度)

    4. 最后是判断slave节点的运行id大小,越小优先级越高。

 

    选取master结束后,故障转义的步骤

    1. sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

    2. sentinel给所有其它slave发送slaveof 192.169.666.888 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。

    3. 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

    

    搭建哨兵集群

    集群状态(哨兵集群3个实例,redis集群3个实例)    

    三个sentinel实例信息如下:

    | s1   | 192.169.666.888 | 27001 |

    | s2   | 192.169.666.888 | 27002 |

    | s3   | 192.169.666.888 | 27003 |    

    1. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。

       我们创建三个文件夹,名字分别叫s1、s2、s3: 

        # 进入/home目录

        cd /home

        # 创建目录

        mkdir s1 s2 s3

    2. 然后我们在s1目录创建一个sentinel.conf文件,添加下面的内容:

       port 27001

       sentinel announce-ip 192.169.666.888

       sentinel monitor mymaster 192.169.666.888 7001 2

       sentinel down-after-milliseconds mymaster 5000

       sentinel failover-timeout mymaster 60000

       dir "/home/s1"

       

       配置解读:

       port 27001: 是当前sentinel实例的端口

       sentinel monitor mymaster 192.169.666.888 7001 2: 指定主节点信息

       mymaster: 主节点名称,自定义,任意写

       192.169.666.888 7001: 主节点的ip和端口

       2: 选举master时的quorum值

       

       然后将s1/sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp目录执行下列命令):

       cp s1/sentinel.conf s2

       cp s1/sentinel.conf s3

       

       修改s2、s3两个文件夹内的配置文件,将端口分别修改为27002、27003:

       sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf

       sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf

     

    3. 启动 

       为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:

       # 第1个

       redis-sentinel s1/sentinel.conf

       # 第2个

       redis-sentinel s2/sentinel.conf

       # 第3个

       redis-sentinel s3/sentinel.conf

    

       启动redis集群

       # 第1个

       redis-server 7001/redis.conf

       # 第2个

       redis-server 7002/redis.conf

       # 第3个

       redis-server 7003/redis.conf

 

    4. 测试

       将7001节点关机模拟宕机,sentinel会选取一个成为主节点,我这里测试,发现选取了7002为master。

       连接7002 info replication查看集群状态,发现7002为master集群,在7002上执行set操作可以成功

       连接7003 get 7002上set的值可以成功,7003 set数据提示失败。

       以上证明,当master宕机之后,sentinel选取了新的节点成为了master。

       将7001开启,再次连接到7002查看集群状态,发现7002还是master,7001和7003都是集群中的salve

 

 

    springboot整合RedisTemplate连接redis集群与整合redis-sentinel集群:

    1. 添加pom

       <dependency>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-data-redis</artifactId>

       </dependency>

    

    2.添加配置(注意: 这里配置的不是redis集群地址,是sentinel集群地址。因为在sentinel模式下主从的地址是有可能变化的,所以不能写死)

      spring:

          redis:

            sentinel:

              master: mymaster #指定集群名称

              nodes:           #指定redis-sentinel集群信息

                - 192.169.666.888:27001

                - 192.169.666.888:27002

                - 192.169.666.888:27003    

    

    3.配置读写分离

      @Bean

      public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){

        return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);

      }

      这个配置表示优先从slave节点读,slave阶段不可用时再从master节点读

 

 

问题四:存储能力问题,和如果写的操作也很多怎么办?

       这里主要用到了redis的分片集群来解决

       这里可以学习到 1.redis分片集群  2.散列插槽的原理  3.集群伸缩  4故障转移  5.RedisTemplate访问分片集群

       

       redis分片集群

       主从和哨兵模式可以解决高可用,高并发问题,但是依然有两个问题没有解决

       第一个问题海量数据存储问题

       第二个问题高并发写的问题

       

       使用分片集群可以解决以上的问题。

       分片集群的特征(不在需要哨兵模式了,但是具备了哨兵模式的功能)

       - 集群中有多个master,每个master保存不同的数据

       - 每个master都可以有多个slave节点

       - master之间通过ping来检测彼此健康状态

       - 客户端请求可以访问集群任意节点,最终都会被转发到正确的节点上

       

       redis分片集群的搭建

       步骤一: 集群结构

               分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:

               这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:

               | 192.169.666.888 | 7001 | master |

               | 192.169.666.888 | 7002 | master |

               | 192.169.666.888 | 7003 | master |

               | 192.169.666.888 | 8001 | slave  |

               | 192.169.666.888 | 8002 | slave  |

               | 192.169.666.888 | 8003 | slave  |

               

        步骤二: 准备实例和配置

                删除之前的7001、7002、7003这几个目录,重新创建出7001、7002、7003、8001、8002、8003目录:

                # 进入/tmp目录

                cd /home

                # 删除旧的,避免配置干扰

                rm -rf 7001 7002 7003

                # 创建目录

                mkdir 7001 7002 7003 8001 8002 8003

                

                在/home下准备一个新的redis.conf文件,内容如下:

                port 6379

                # 开启集群功能

                cluster-enabled yes

                # 集群的配置文件名称,不需要我们创建,由redis自己维护

                cluster-config-file /home/6379/nodes.conf

                # 节点心跳失败的超时时间

                cluster-node-timeout 5000

                # 持久化文件存放目录

                dir /home/6379

                # 绑定地址

                bind 0.0.0.0

                # 让redis后台运行

                daemonize yes

                # 注册的实例ip

                replica-announce-ip 192.169.666.888

                # 保护模式

                protected-mode no

                # 数据库数量

                databases 1

                # 日志

                logfile /home/6379/run.log

                

                将这个文件拷贝到每个目录下:

                # 进入/home目录

                cd /home

                # 执行拷贝

                echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

                

                修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:

                # 进入/home目录

                cd /home

                # 修改配置文件

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

                

        步骤三: 启动

                因为已经配置了后台启动模式,所以可以直接启动服务:

                # 进入/home目录

                cd /home

                # 一键启动所有服务

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

                

                通过ps查看状态:

                ps aux|grep redis 或 ps -ef | grep redis

                

                如果要关闭所有进程,可以执行命令:

                ps -ef | grep redis | awk '{print $2}' | xargs kill

                或者(推荐这种方式):

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

                

        步骤四: 创建集群

                虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。        

                我们需要执行命令来创建集群,在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中。

                

                Redis5.0之前:

                Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。

                # 安装依赖

                yum -y install zlib ruby rubygems

                gem install redis

                然后通过命令来管理集群:

                # 进入redis的src目录

                cd /tmp/redis-6.2.4/src

                # 创建集群

                ./redis-trib.rb create --replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003

                

                Redis5.0以后:

                我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:

                redis-cli --cluster create --cluster-replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003

                命令说明:

                    redis-cli --cluster 或者 ./redis-trib.rb: 代表集群操作命令

                    create: 代表是创建集群

                    --replicas 1 或者 --cluster-replicas 1: 指定集群中每个master的副本(就是slave节点)个数为1,此时 节点总数 ÷ (replicas + 1)  得到的就是master的数量。

                    因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

                

                注意: 这里如果创建集群的时候遇到Waiting for the cluster to join ....................................... 然后无尽的等待。请参照下面这篇文章修改

                https://www.jianshu.com/p/250f5da36b49/

                

                通过命令可以查看集群状态: 

                redis-cli -p 7001 cluster nodes

                

                

        步骤五: 测试

                尝试连接7001节点,存储一个数据:        

                # 连接

                redis-cli -p 7001

                # 存储数据

                set num 123

                # 读取数据

                get num

                # 再次存储

                set a 1

                结果悲剧了: 会提示失败

                集群操作时,需要给redis-cli 加上 -c 参数才可以: redis-cli -c -p 7001

                

        散列插槽的原理

        Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:

        数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:

        key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分

        key中不包含“{}”,整个key都是有效部分

        例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

        

        问题:

            Redis如何判断某个key应该在哪个实例?

            将16384个插槽分配到不同的实例

            根据key的有效部分计算哈希值,对16384取余

            余数作为插槽,寻找插槽所在实例即可

            如何将同一类数据固定的保存在同一个Redis实例?

            这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

        

                

        集群伸缩问题

        redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:

            redis-cli --cluster help

        比如,添加节点的命令:

            add-node       new_host:new_port existing_host:existing_port

                 --cluster-slave

                 --cluster-master-id <arg>

        

        案例演示: 

        需求:

        启动一个新的redis实例,端口为7004

        添加7004到之前的集群,并作为一个master节点

        给7004节点分配插槽,使得num这个key可以存储到7004实例

        1. 新建一个文件7004

        2. 拷贝redis.conf配置文件到7004中

        3. 修改配置文件中的端口号

        4. redis-server 7004/redis.conf 运行7004

        5. 将7004加入到集群中   redis-cli --cluster add-node 192.169.666.888:7004 192.169.666.888:7001

        6. 分配插槽,不然新增的节点没有任何意义 执行重新分配插槽的命令

           redis-cli --cluster reshard 192.169.666.888:7001

           然后选择移动的插槽数量输入: 3000

           然后选择谁接收这部分插槽: 把7004的id复制过来,就代表7004来接收这部分插槽

           然后选择从哪里作为数据源来拷贝: 把7001的id复制过来

           然后输入done  表示结束

        

        需求:

        删除7004这个实例

        1.将插槽移动到7001中(移动步骤同上)

        2.然后删除7004  redis-cli --cluster del-node 192.169.666.888:7004 2e9ee91b50676fe00bd8471e91ada1d36a337264

        

                

        故障转移(自动,发生意外宕机)

        分片集群虽然没有哨兵,但是它也具备故障转移的功能,我们一起验证一下这个功能

        当集群中有一个master宕机会发生什么呢?

        首先是该实例与其它实例失去连接

        然后是疑似宕机:

        最后是确定下线,自动提升一个slave为新的master:

        验证:

        1. 通过命令监控集群: watch redis-cli -p 7001 cluster nodes

        2. 使用命令redis-cli -p 7002 shutdown

        3. 发现8001变成了master

        4. 重启7002之后,7002会加入到集群中,但是还是slave节点

        

        

        (手动故障转移, 机器升级,替换机器时使用)

        利用cluster failover命令可以手动让集群中的某个master宕机,切换到新的redis机器上,连接redis执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:

        手动的Failover命令支持三种不同模式(参数):

            缺省:默认的流程,如图1~6歩

            force:省略了对offset的一致性校验

            takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

 

        

        使用RedisTemplate访问分片集群

        RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

        步骤:

        1.引入redis的starter依赖

          <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

          </dependency>

        2.配置分片集群地址

        3.配置读写分离

        4.与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

          spring:  

            redis:

                 cluster:

                 nodes: # 指定分片集群的每一个节点信息        

                  - 192.168.150.101:7001

                  - 192.168.150.101:7002

                  - 192.168.150.101:7003

                  - 192.168.150.101:8001

                  - 192.168.150.101:8002

                  - 192.168.150.101:8003

 

        (完结散花)

————————————————

版权声明:本文为CSDN博主「学生云剑锋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yunjianfeng123/article/details/122037909

  • 0
  • 0
  • 0
0 评论
0/1000
评论(0) 发表评论
天翼云文档找茬小助手

天翼云文档找茬小助手

41 篇文章 7 粉丝
关注

redis-分布式缓存详解

2022-06-30 18:57:09 16阅读

redis单节点问题

1.数据丢失问题,数据在内存中,重启就会丢失

  解决方案: 实现redis数据持久化

2.并发访问问题,性能不错但是高并发情况下还是会有问题

  解决方案: 搭建主从集群,实现读写分离

3.故障恢复问题,单节点部署,一旦出现问题,就出现整个微服务影响的范围非常大

  解决方案: 利用redis哨兵实现健康检测和自动恢复

4.存储能力问题,单节点内存难以满足海量数据的存储

  解决方案: 搭建分片集群,利用插槽机制实现动态扩容

 

问题一: redis持久化

    有两种解决方式RDB和AOF

    RDB: redis数据备份文件,也叫做redis数据快照,简单来说就是把内存中的所有数据都记录到磁盘中,当redis实例故障重启后,从磁盘读取快照文件,恢复数据

         快照文件称为RDB文件,默认是保存是保存在当前的运行目录,默认文件名dump.rdb

         执行save命令就会进行一次快照保存(save命令是主进程来执行,会影响效率)

         一般情况下执行bgsave开启子进程执行RDB,避免住进程收到影响

         主动关闭redis会默认自动执行一次RDB。

         除了主动执行和关机执行,RDB还会根据配置文件中的配置来执行。

         save 900 1       //900秒内,如果至少有1个key被修改,则执行bgsave,如果是save "" 则表示禁用RDB。以下两个同理

         save 300 10

         save 60  10000

         RDB的底层执行流程: bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取读取内存数据并写入RDB文件中

         RDB缺点: RDB执行间隔比较长,两次RDB之间写入的数据有丢失的风险。 fork主进程,压缩,写出RDB文件都比较耗时

    

    AOF: redis处理的每一个命令都会记录在AOF文件中,可以看做是命令日志文件(默认关闭)

         开启需要修改redis.config文件,将appendonly yes(默认为no),appendfilename "appendonly.aof"(修改aof文件名称)

         aof记录日志的频率有三种,通过redis.config文件进行修改

         第一种表示每执行一次写的命令,都记录在aof文件中   appendfsync always

         第二种表示写命令执行完先放入aof缓存区,然后表示每隔一秒将缓存区数据写到aof文件,是默认方案  appendfsync everysec

         第三种表示写命令执行完先放入aof缓存区,由操作系统决定何时将缓存区数据写会磁盘   appendfsync no

         

         因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。

         通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

         比如执行了三条语句 set name 123,  set name 456, set name 789  执行了bgrewriteaof命令之后会合并为 mset name 123 456 789

         

         Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置:

         auto-aof-rewrite-percentage 100  //AOF文件比上次文件 增长超过多少百分比则触发重写

         auto-aof-rewrite-min-size 64mb   //AOF文件体积最小多大以上才触发重写

 

         

问题二: 并发访问问题

        单节点redis并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离     

        因为redis主要的业务是读的操作,写的操作比较少,一般都是master节点接收读的操作,slave节点接收写的操作,然后master节点将数据同步到各个slave节点上,实现数据一致

        

        集群搭建(一主两从):

        1. 这里我们会在同一台虚拟机中开启3个redis实例,模拟主从集群,信息如下:

           | 192.169.666.888 | 7001 | master |

           | 192.169.666.888 | 7002 | slave  |

           | 192.169.666.888 | 7003 | slave  |

        2. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。 

           我们创建三个文件夹,名字分别叫7001、7002、7003:

           # 进入/tmp目录

            cd /home

           # 创建目录

            mkdir 7001 7002 7003

           恢复原始配置

           修改redis-6.2.4/redis.conf文件,将其中的持久化模式改为默认的RDB模式,AOF保持关闭状态。

           # 开启RDB

           # save ""

           save 3600 1

           save 300 100

           save 60 10000

 

           # 关闭AOF

           appendonly no

           

           拷贝配置文件到每个实例目录

           然后将redis-6.2.4/redis.conf文件拷贝到三个目录中

           cp redis-6.2.4/redis.conf 7001

           cp redis-6.2.4/redis.conf 7002

           cp redis-6.2.4/redis.conf 7003

           

           修改每个实例的端口、工作目录

           修改每个文件夹内的配置文件,将端口分别修改为7001、7002、7003,将rdb文件保存位置都修改为自己所在目录

           sed -i -e 's/6379/7001/g' -e 's/dir .\//dir \/home\/7001\//g' 7001/redis.conf

           sed -i -e 's/6379/7002/g' -e 's/dir .\//dir \/home\/7002\//g' 7002/redis.conf

           sed -i -e 's/6379/7003/g' -e 's/dir .\//dir \/home\/7003\//g' 7003/redis.conf

           

           虚拟机本身有多个IP,为了避免将来混乱,我们需要在redis.conf文件中指定每一个实例的绑定ip信息,格式如下:

           # redis实例的声明 IP

           replica-announce-ip 123.57.87.143

           

           每个目录都要改,我们一键完成修改

           sed -i '1a replica-announce-ip 192.169.666.888' 7001/redis.conf

           sed -i '1a replica-announce-ip 192.169.666.888' 7002/redis.conf

           sed -i '1a replica-announce-ip 192.169.666.888' 7003/redis.conf

         

           为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:

           # 第1个

           redis-server 7001/redis.conf

           # 第2个

           redis-server 7002/redis.conf

           # 第3个

           redis-server 7003/redis.conf

           

           开启主从关系

           现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。

           有临时和永久两种模式:

           修改配置文件(永久生效)

                在redis.conf中添加一行配置: slaveof <masterip> <masterport>

           使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):

                slaveof <masterip> <masterport>

           注意: 在5.0以后新增命令replicaof,与salveof效果一致。

           

           这里我们为了演示方便,使用方式二。

 

           通过redis-cli命令连接7002,执行下面命令:

            # 连接 7002

            redis-cli -p 7002

            # 执行slaveof

            slaveof 192.169.666.888 7001

            

           通过redis-cli命令连接7003,执行下面命令:

            # 连接 7003

            redis-cli -p 7003

            # 执行slaveof

            slaveof 192.169.666.888 7001

 

           然后连接 7001节点,查看集群状态:

            # 连接 7001

            redis-cli -p 7001

            # 查看状态

            info replication

 

           测试:

            - 利用redis-cli连接7001,执行set num 123

 

            - 利用redis-cli连接7002,执行get num,再执行set num 666

 

            - 利用redis-cli连接7003,执行get num,再执行set num 888

 

        可以发现,只有在7001这个master节点上可以执行写操作,7002和7003这两个slave节点只能执行读操作(完成^_^)

 

        redis主从数据同步原理(全量同步):

        1. slave节点请求增量同步

        2. master节点判断replid,发现不一致,拒绝增量同步

        3. master将完整内存数据生成RDB,发送RDB到slave

        4. slave清空本地数据,加载master的RDB

        5. master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave

        6. slave执行接收到的命令,保持与master之间的同步

        

        redis主从数据同步原理(增量同步):

        slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

        什么时候执行全量同步?

        slave节点第一次连接master节点时

        slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

        什么时候执行增量同步?

        slave节点断开又恢复,并且在repl_baklog中能找到offset时

        

        

问题三:故障恢复问题

    slave节点宕机后重启可以找到master节点同步数据,那master节点宕机了怎么办?

    解决: redis提供了哨兵sentinel机制来实现主从集群的自动故障恢复。

    监控: sentinel会不断检查master和slave是否按照预期工作

    自动故障恢复: 如果master故障,sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主

    通知: sentinel充当redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给redis的客户端    

        

    Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

    主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

    客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

    

    一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

    1. 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点 (与master断开时间太长了,丢失的数据越多)

    2. 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举 

    3. 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 (offset代表当前节点与master数据同步进度)

    4. 最后是判断slave节点的运行id大小,越小优先级越高。

 

    选取master结束后,故障转义的步骤

    1. sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master

    2. sentinel给所有其它slave发送slaveof 192.169.666.888 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。

    3. 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点

    

    搭建哨兵集群

    集群状态(哨兵集群3个实例,redis集群3个实例)    

    三个sentinel实例信息如下:

    | s1   | 192.169.666.888 | 27001 |

    | s2   | 192.169.666.888 | 27002 |

    | s3   | 192.169.666.888 | 27003 |    

    1. 要在同一台虚拟机开启3个实例,必须准备三份不同的配置文件和目录,配置文件所在目录也就是工作目录。

       我们创建三个文件夹,名字分别叫s1、s2、s3: 

        # 进入/home目录

        cd /home

        # 创建目录

        mkdir s1 s2 s3

    2. 然后我们在s1目录创建一个sentinel.conf文件,添加下面的内容:

       port 27001

       sentinel announce-ip 192.169.666.888

       sentinel monitor mymaster 192.169.666.888 7001 2

       sentinel down-after-milliseconds mymaster 5000

       sentinel failover-timeout mymaster 60000

       dir "/home/s1"

       

       配置解读:

       port 27001: 是当前sentinel实例的端口

       sentinel monitor mymaster 192.169.666.888 7001 2: 指定主节点信息

       mymaster: 主节点名称,自定义,任意写

       192.169.666.888 7001: 主节点的ip和端口

       2: 选举master时的quorum值

       

       然后将s1/sentinel.conf文件拷贝到s2、s3两个目录中(在/tmp目录执行下列命令):

       cp s1/sentinel.conf s2

       cp s1/sentinel.conf s3

       

       修改s2、s3两个文件夹内的配置文件,将端口分别修改为27002、27003:

       sed -i -e 's/27001/27002/g' -e 's/s1/s2/g' s2/sentinel.conf

       sed -i -e 's/27001/27003/g' -e 's/s1/s3/g' s3/sentinel.conf

     

    3. 启动 

       为了方便查看日志,我们打开3个ssh窗口,分别启动3个redis实例,启动命令:

       # 第1个

       redis-sentinel s1/sentinel.conf

       # 第2个

       redis-sentinel s2/sentinel.conf

       # 第3个

       redis-sentinel s3/sentinel.conf

    

       启动redis集群

       # 第1个

       redis-server 7001/redis.conf

       # 第2个

       redis-server 7002/redis.conf

       # 第3个

       redis-server 7003/redis.conf

 

    4. 测试

       将7001节点关机模拟宕机,sentinel会选取一个成为主节点,我这里测试,发现选取了7002为master。

       连接7002 info replication查看集群状态,发现7002为master集群,在7002上执行set操作可以成功

       连接7003 get 7002上set的值可以成功,7003 set数据提示失败。

       以上证明,当master宕机之后,sentinel选取了新的节点成为了master。

       将7001开启,再次连接到7002查看集群状态,发现7002还是master,7001和7003都是集群中的salve

 

 

    springboot整合RedisTemplate连接redis集群与整合redis-sentinel集群:

    1. 添加pom

       <dependency>

           <groupId>org.springframework.boot</groupId>

           <artifactId>spring-boot-starter-data-redis</artifactId>

       </dependency>

    

    2.添加配置(注意: 这里配置的不是redis集群地址,是sentinel集群地址。因为在sentinel模式下主从的地址是有可能变化的,所以不能写死)

      spring:

          redis:

            sentinel:

              master: mymaster #指定集群名称

              nodes:           #指定redis-sentinel集群信息

                - 192.169.666.888:27001

                - 192.169.666.888:27002

                - 192.169.666.888:27003    

    

    3.配置读写分离

      @Bean

      public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){

        return configBuilder -> configBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);

      }

      这个配置表示优先从slave节点读,slave阶段不可用时再从master节点读

 

 

问题四:存储能力问题,和如果写的操作也很多怎么办?

       这里主要用到了redis的分片集群来解决

       这里可以学习到 1.redis分片集群  2.散列插槽的原理  3.集群伸缩  4故障转移  5.RedisTemplate访问分片集群

       

       redis分片集群

       主从和哨兵模式可以解决高可用,高并发问题,但是依然有两个问题没有解决

       第一个问题海量数据存储问题

       第二个问题高并发写的问题

       

       使用分片集群可以解决以上的问题。

       分片集群的特征(不在需要哨兵模式了,但是具备了哨兵模式的功能)

       - 集群中有多个master,每个master保存不同的数据

       - 每个master都可以有多个slave节点

       - master之间通过ping来检测彼此健康状态

       - 客户端请求可以访问集群任意节点,最终都会被转发到正确的节点上

       

       redis分片集群的搭建

       步骤一: 集群结构

               分片集群需要的节点数量较多,这里我们搭建一个最小的分片集群,包含3个master节点,每个master包含一个slave节点,结构如下:

               这里我们会在同一台虚拟机中开启6个redis实例,模拟分片集群,信息如下:

               | 192.169.666.888 | 7001 | master |

               | 192.169.666.888 | 7002 | master |

               | 192.169.666.888 | 7003 | master |

               | 192.169.666.888 | 8001 | slave  |

               | 192.169.666.888 | 8002 | slave  |

               | 192.169.666.888 | 8003 | slave  |

               

        步骤二: 准备实例和配置

                删除之前的7001、7002、7003这几个目录,重新创建出7001、7002、7003、8001、8002、8003目录:

                # 进入/tmp目录

                cd /home

                # 删除旧的,避免配置干扰

                rm -rf 7001 7002 7003

                # 创建目录

                mkdir 7001 7002 7003 8001 8002 8003

                

                在/home下准备一个新的redis.conf文件,内容如下:

                port 6379

                # 开启集群功能

                cluster-enabled yes

                # 集群的配置文件名称,不需要我们创建,由redis自己维护

                cluster-config-file /home/6379/nodes.conf

                # 节点心跳失败的超时时间

                cluster-node-timeout 5000

                # 持久化文件存放目录

                dir /home/6379

                # 绑定地址

                bind 0.0.0.0

                # 让redis后台运行

                daemonize yes

                # 注册的实例ip

                replica-announce-ip 192.169.666.888

                # 保护模式

                protected-mode no

                # 数据库数量

                databases 1

                # 日志

                logfile /home/6379/run.log

                

                将这个文件拷贝到每个目录下:

                # 进入/home目录

                cd /home

                # 执行拷贝

                echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

                

                修改每个目录下的redis.conf,将其中的6379修改为与所在目录一致:

                # 进入/home目录

                cd /home

                # 修改配置文件

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

                

        步骤三: 启动

                因为已经配置了后台启动模式,所以可以直接启动服务:

                # 进入/home目录

                cd /home

                # 一键启动所有服务

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

                

                通过ps查看状态:

                ps aux|grep redis 或 ps -ef | grep redis

                

                如果要关闭所有进程,可以执行命令:

                ps -ef | grep redis | awk '{print $2}' | xargs kill

                或者(推荐这种方式):

                printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

                

        步骤四: 创建集群

                虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。        

                我们需要执行命令来创建集群,在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中。

                

                Redis5.0之前:

                Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。

                # 安装依赖

                yum -y install zlib ruby rubygems

                gem install redis

                然后通过命令来管理集群:

                # 进入redis的src目录

                cd /tmp/redis-6.2.4/src

                # 创建集群

                ./redis-trib.rb create --replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003

                

                Redis5.0以后:

                我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:

                redis-cli --cluster create --cluster-replicas 1 192.169.666.888:7001 192.169.666.888:7002 192.169.666.888:7003 192.169.666.888:8001 192.169.666.888:8002 192.169.666.888:8003

                命令说明:

                    redis-cli --cluster 或者 ./redis-trib.rb: 代表集群操作命令

                    create: 代表是创建集群

                    --replicas 1 或者 --cluster-replicas 1: 指定集群中每个master的副本(就是slave节点)个数为1,此时 节点总数 ÷ (replicas + 1)  得到的就是master的数量。

                    因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

                

                注意: 这里如果创建集群的时候遇到Waiting for the cluster to join ....................................... 然后无尽的等待。请参照下面这篇文章修改

                https://www.jianshu.com/p/250f5da36b49/

                

                通过命令可以查看集群状态: 

                redis-cli -p 7001 cluster nodes

                

                

        步骤五: 测试

                尝试连接7001节点,存储一个数据:        

                # 连接

                redis-cli -p 7001

                # 存储数据

                set num 123

                # 读取数据

                get num

                # 再次存储

                set a 1

                结果悲剧了: 会提示失败

                集群操作时,需要给redis-cli 加上 -c 参数才可以: redis-cli -c -p 7001

                

        散列插槽的原理

        Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:

        数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:

        key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分

        key中不包含“{}”,整个key都是有效部分

        例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

        

        问题:

            Redis如何判断某个key应该在哪个实例?

            将16384个插槽分配到不同的实例

            根据key的有效部分计算哈希值,对16384取余

            余数作为插槽,寻找插槽所在实例即可

            如何将同一类数据固定的保存在同一个Redis实例?

            这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

        

                

        集群伸缩问题

        redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:

            redis-cli --cluster help

        比如,添加节点的命令:

            add-node       new_host:new_port existing_host:existing_port

                 --cluster-slave

                 --cluster-master-id <arg>

        

        案例演示: 

        需求:

        启动一个新的redis实例,端口为7004

        添加7004到之前的集群,并作为一个master节点

        给7004节点分配插槽,使得num这个key可以存储到7004实例

        1. 新建一个文件7004

        2. 拷贝redis.conf配置文件到7004中

        3. 修改配置文件中的端口号

        4. redis-server 7004/redis.conf 运行7004

        5. 将7004加入到集群中   redis-cli --cluster add-node 192.169.666.888:7004 192.169.666.888:7001

        6. 分配插槽,不然新增的节点没有任何意义 执行重新分配插槽的命令

           redis-cli --cluster reshard 192.169.666.888:7001

           然后选择移动的插槽数量输入: 3000

           然后选择谁接收这部分插槽: 把7004的id复制过来,就代表7004来接收这部分插槽

           然后选择从哪里作为数据源来拷贝: 把7001的id复制过来

           然后输入done  表示结束

        

        需求:

        删除7004这个实例

        1.将插槽移动到7001中(移动步骤同上)

        2.然后删除7004  redis-cli --cluster del-node 192.169.666.888:7004 2e9ee91b50676fe00bd8471e91ada1d36a337264

        

                

        故障转移(自动,发生意外宕机)

        分片集群虽然没有哨兵,但是它也具备故障转移的功能,我们一起验证一下这个功能

        当集群中有一个master宕机会发生什么呢?

        首先是该实例与其它实例失去连接

        然后是疑似宕机:

        最后是确定下线,自动提升一个slave为新的master:

        验证:

        1. 通过命令监控集群: watch redis-cli -p 7001 cluster nodes

        2. 使用命令redis-cli -p 7002 shutdown

        3. 发现8001变成了master

        4. 重启7002之后,7002会加入到集群中,但是还是slave节点

        

        

        (手动故障转移, 机器升级,替换机器时使用)

        利用cluster failover命令可以手动让集群中的某个master宕机,切换到新的redis机器上,连接redis执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:

        手动的Failover命令支持三种不同模式(参数):

            缺省:默认的流程,如图1~6歩

            force:省略了对offset的一致性校验

            takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

 

        

        使用RedisTemplate访问分片集群

        RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

        步骤:

        1.引入redis的starter依赖

          <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

          </dependency>

        2.配置分片集群地址

        3.配置读写分离

        4.与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

          spring:  

            redis:

                 cluster:

                 nodes: # 指定分片集群的每一个节点信息        

                  - 192.168.150.101:7001

                  - 192.168.150.101:7002

                  - 192.168.150.101:7003

                  - 192.168.150.101:8001

                  - 192.168.150.101:8002

                  - 192.168.150.101:8003

 

        (完结散花)

————————————————

版权声明:本文为CSDN博主「学生云剑锋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yunjianfeng123/article/details/122037909

文章来自专栏

云知识的搬运工

224 篇文章 7 订阅
0 评论
0/1000
评论(0) 发表评论
  • 0
    点赞
  • 0
    收藏
  • 0
    评论