前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis进阶学习07--分布式缓存--下

Redis进阶学习07--分布式缓存--下

作者头像
大忽悠爱学习
发布2022-05-09 14:22:54
4400
发布2022-05-09 14:22:54
举报
文章被收录于专栏:c++与qt学习

Redis进阶学习07--分布式缓存--下

  • Redis分片集群
    • 搭建分片集群
      • 集群结构
      • 准备实例和配置
      • 启动
      • 创建集群
    • docker-compose快速搭建分片集群
      • 注意
    • 散列插槽
      • 插槽原理
      • 小结
    • 集群伸缩
      • 需求分析
        • 创建新的redis实例
        • 添加新节点到redis
        • 转移插槽
    • 故障转移
      • 自动故障转移
      • 手动故障转移
    • RedisTemplate访问分片集群

Redis分片集群

搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写的问题

使用分片集群可以解决上述问题,如图:

在这里插入图片描述
在这里插入图片描述

分片集群特征:

  • 集群中有多个master,每个master保存不同数据
  • 每个master都可以有多个slave节点
  • master之间通过ping监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

集群结构

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

在这里插入图片描述
在这里插入图片描述

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

IP

PORT

角色

192.168.150.101

7001

master

192.168.150.101

7002

master

192.168.150.101

7003

master

192.168.150.101

8001

slave

192.168.150.101

8002

slave

192.168.150.101

8003

slave


准备实例和配置

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

代码语言:javascript
复制
# 进入/tmp目录
cd /tmp
# 删除旧的,避免配置干扰
rm -rf 7001 7002 7003
# 创建目录
mkdir 7001 7002 7003 8001 8002 8003

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

代码语言:javascript
复制
port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.150.101
# 保护模式--不开启意味着不需要密码验证
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log

Protected-mode 是为了禁止公网访问redis cache,加强redis安全的。

它启用的条件,有两个: 1) 没有bind IP 2) 没有设置访问密码

如果启用了,则只能够通过lookback ip(127.0.0.1)访问Redis cache,如果从外网访问,则会返回相应的错误信息:

代码语言:javascript
复制
(error) DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the lookback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the --portected-mode no option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

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

代码语言:javascript
复制
# 进入/tmp目录
cd /tmp
# 执行拷贝
echo 7001 7002 7003 8001 8002 8003 | xargs -t -n 1 cp redis.conf

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

代码语言:javascript
复制
# 进入/tmp目录
cd /tmp
# 修改配置文件
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t sed -i 's/6379/{}/g' {}/redis.conf

启动

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

代码语言:javascript
复制
# 进入/tmp目录
cd /tmp
# 一键启动所有服务
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-server {}/redis.conf

通过ps查看状态:

代码语言:javascript
复制
ps -ef | grep redis

发现服务都已经正常启动:

在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
ps -ef | grep redis | awk '{print $2}' | xargs kill

或者(推荐这种方式):

代码语言:javascript
复制
printf '%s\n' 7001 7002 7003 8001 8002 8003 | xargs -I{} -t redis-cli -p {} shutdown

创建集群

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

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

1)Redis5.0之前

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

代码语言:javascript
复制
# 安装依赖
yum -y install zlib ruby rubygems
gem install redis

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

代码语言:javascript
复制
# 进入redis的src目录
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 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

2)Redis5.0以后

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

代码语言:javascript
复制
redis-cli --cluster create --cluster-replicas 1 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

命令说明:

  • redis-cli --cluster或者./redis-trib.rb:代表集群操作命令
  • create:代表是创建集群
  • --replicas 1或者--cluster-replicas 1 :指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1) 得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

运行后的样子:

在这里插入图片描述
在这里插入图片描述

这里输入yes,则集群开始创建:

在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
redis-cli -p 7001 cluster nodes
在这里插入图片描述
在这里插入图片描述

docker-compose快速搭建分片集群

这里为了不和之前的哨兵集群端口冲突,我们改为9001-9006

  • 准备一个配置文件
代码语言:javascript
复制
port 9001
#redis 访问密码
requirepass 123456
#redis 访问Master节点密码
masterauth 123456
#关闭保护模式
protected-mode no
#开启集群 
cluster-enabled yes
#集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file nodes.conf
#节点心跳失败的超时时间
cluster-node-timeout 5000
# 集群节点IP host模式为宿主机IP
cluster-announce-ip 192.168.25.129
# 集群节点端口 9001
cluster-announce-port 9001
#bus-port是redis集群总线端口为redis客户端端口加上10000
cluster-announce-bus-port 19001
# 持久化文件存放目录---工作目录
#dir /tmp
# 绑定地址
bind 0.0.0.0
# 让redis后台运行--docker这里必须设置为no
daemonize no
# 开启 appendonly 备份模式
appendonly yes
# 数据库数量
databases 1
# 日志配置
# debug:会打印生成大量信息,适用于开发/测试阶段
# verbose:包含很多不太有用的信息,但是不像debug级别那么混乱
# notice:适度冗长,适用于生产环境
# warning:仅记录非常重要、关键的警告消息
loglevel notice
# 日志文件路径
logfile "/data/redis.log"

检查配置文件是否设置了daemonize yes,如果是,就要改为daemonize no

因为该选项让redis成为在后台运行的守护进程而docker容器必须要有一个前台进程才能留存否则容器会自动退出

在这里插入图片描述
在这里插入图片描述
  • docker-compose文件准备
代码语言:javascript
复制
version: "3.3"
services:
     '9001':
       image: "${REDIS_VERSION}"
       container_name: '9001'
       ports:
         - "9001:9001"
         - "19001:19001"  
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9001/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9001/data:/data"
                                                                                          
                                                                                          
     '9002':
       image: "${REDIS_VERSION}"
       container_name: '9002'
       ports:
         - "9002:9002"
         - "19002:19002" 
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9002/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9002/data:/data"
                                                                                          
                                                                                          
     '9003':
       image: "${REDIS_VERSION}"
       container_name: '9003'
       ports:
         - "9003:9003"
         - "19003:19003" 
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9003/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9003/data:/data"
        

     '9004':
       image: "${REDIS_VERSION}"
       container_name: '9004'
       ports:
         - "9004:9004"
         - "19004:19004" 
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9004/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9004/data:/data"


     '9005':
       image: "${REDIS_VERSION}"
       container_name: '9005'
       ports:
         - "9005:9005"
         - "19005:19005"  
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9005/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9005/data:/data"


     '9006':
       image: "${REDIS_VERSION}"
       container_name: '9006'
       ports:
         - "9006:9006"
         - "19006:19006" 
       command: redis-server /etc/redis/redis.conf
       volumes:
         - "/dhy/redis/cluster/9006/redis.conf:/etc/redis/redis.conf"
         - "/dhy/redis/cluster/9006/data:/data"

还需要在当前目录准备一个.env文件,里面写入REDIS_VERSION=redis:5.0.5

或者将REDIS_VERSION加入环境变量

注意

在部署集群时,一直卡在 Waiting for the cluster to join …

由于没有打开集群总线的端口,集群总线是在集群端口+10000

首先需要理清一个概念,就是redis集群总线:

redis集群总线端口为redis客户端端口加上10000,比如说你的redis 6379端口为客户端通讯端口,那么16379端口为集群总线端口。若集群总线端口7001,7002,7003…,则应开启的端口是17001,17002,17003…

若搭建的 redis 集群端口为:7001~7006,其中 7001、7002、7003为主节点,7004、7005、7006为从节点,故应该打开的总线端口为:17001、17002、17003

在这里插入图片描述
在这里插入图片描述

使用云服务器需要开放上面这些端口号

  • 进入其中任意一个redis实例,执行集群创建命令
代码语言:javascript
复制
docker-compose exec 9001 bash
代码语言:javascript
复制
redis-cli --cluster create --cluster-replicas 1 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
 -a pwd

-a指定密码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
redis-cli -p 9001 -a pwd cluster nodes
在这里插入图片描述
在这里插入图片描述

散列插槽

插槽原理

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

在这里插入图片描述
在这里插入图片描述

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

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

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

在这里插入图片描述
在这里插入图片描述

如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到103节点。

到了7003后,执行get num时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点

小结

连接某个集群实例节点时: redis-cli后面必须加上-c参数,表示是集群模式连接,-p指定连接端口,如果cluster集群中每个节点都设置了密码,那么切换的时候,会因为需要密码验证,而导致某个key设置失败,怎么办呢?

redis-cli连接的时候通过-a指定密码即可,这样切换到其他节点时,也会使用这个密码,这也说明,集群中每个节点的密码要相同才可以

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

  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384取余
  • 余数作为插槽,寻找插槽所在实例即可

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

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

集群伸缩

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

在这里插入图片描述
在这里插入图片描述

比如,添加节点的命令:

在这里插入图片描述
在这里插入图片描述

需求分析

需求:向集群中添加一个新的master节点,并向其中存储 num = 10

  • 启动一个新的redis实例,端口为9007
  • 添加9007到之前的集群,并作为一个master节点
  • 给9007节点分配插槽,使得num这个key可以存储到9007实例

这里需要两个新的功能:

  • 添加一个节点到集群中
  • 将部分插槽分配到新插槽
创建新的redis实例
在这里插入图片描述
在这里插入图片描述

记得开放9007和19007端口

代码语言:javascript
复制
docker run -p 9007:9007 -p 19007:19007 -v $PWD/data:/data -v $PWD/redis.conf:/etc/redis/redis.conf --privileged=true --name 9007 -d redis:5.0.5 redis-server /etc/redis/redis.conf
添加新节点到redis

添加节点的语法如下:

在这里插入图片描述
在这里插入图片描述

执行命令:

代码语言:javascript
复制
redis-cli --cluster add-node  192.168.150.101:9007 192.168.150.101:9001
-a 密码

可以通过–cluster-slave直接让该节点成为某个–cluster-master-id 节点id对应的从节点

通过命令查看集群状态:

代码语言:javascript
复制
redis-cli -p 9007 cluster nodes

如图,9007加入了集群,并且默认是一个master节点:

在这里插入图片描述
在这里插入图片描述

但是,可以看到7007节点的插槽数量为0,因此没有任何数据可以存储到7007上

转移插槽

我们要将num存储到9007节点,因此需要先看看num的插槽是多少:

在这里插入图片描述
在这里插入图片描述

如上图所示,num的插槽为2765.

我们可以将0~3000的插槽从7001转移到7004,命令格式如下:

在这里插入图片描述
在这里插入图片描述

具体命令如下:

建立连接:

在这里插入图片描述
在这里插入图片描述

得到下面的反馈:

在这里插入图片描述
在这里插入图片描述

询问要移动多少个插槽,我们计划是3000个:

新的问题来了:

在这里插入图片描述
在这里插入图片描述

哪个node来接收这些插槽??

显然是9007,那么9007节点的id是多少呢?

在这里插入图片描述
在这里插入图片描述

复制这个id,然后拷贝到刚才的控制台后:

在这里插入图片描述
在这里插入图片描述

这里询问,你的插槽是从哪里移动过来的?

  • all:代表全部,也就是三个节点各转移一部分
  • 具体的id:目标节点的id
  • done:没有了

这里我们要从7001获取,因此填写7001的id:

在这里插入图片描述
在这里插入图片描述

填完后,点击done,这样插槽转移就准备好了:

在这里插入图片描述
在这里插入图片描述

确认要转移吗?输入yes:

然后,通过命令查看结果:

代码语言:javascript
复制
redis-cli -p 9007 -a 126433zdh cluster nodes
在这里插入图片描述
在这里插入图片描述

目的达成。


故障转移

集群初识状态是这样的:

在这里插入图片描述
在这里插入图片描述

其中9001、9002、9003都是master,我们计划让9002宕机。

自动故障转移

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

直接停止一个redis实例,例如9002:

代码语言:javascript
复制
docker stop 9002

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

2)然后是疑似宕机:

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

4)当9002再次启动,就会变为一个slave节点了:

在这里插入图片描述
在这里插入图片描述

手动故障转移

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

在这里插入图片描述
在这里插入图片描述

这种failover命令可以指定三种模式:

  • 缺省:默认的流程,如图1~6歩
  • force:省略了对offset的一致性校验
  • takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

案例需求:在9002这个slave节点执行手动故障转移,重新夺回master地位

步骤如下:

1)利用redis-cli连接9002这个节点

2)执行cluster failover命令

如图:

在这里插入图片描述
在这里插入图片描述

效果:

在这里插入图片描述
在这里插入图片描述

RedisTemplate访问分片集群

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

1)引入redis的starter依赖

2)配置分片集群地址

3)配置读写分离

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

代码语言:javascript
复制
spring:
  redis:
    cluster:
      nodes:
        - 192.168.150.101:9001
        - 192.168.150.101:9002
        - 192.168.150.101:9003
        - 192.168.150.101:9004
        - 192.168.150.101:9005
        - 192.168.150.101:9006
  password: 密码      

配置读写分离:

代码语言:javascript
复制
    @Bean
    public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
        return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
    }

正常使用即可:

代码语言:javascript
复制
@RestController
public class HelloController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/get/{key}")
    public String hi(@PathVariable String key) {
        String val = redisTemplate.opsForValue().get(key);
        return val;
    }

    @GetMapping("/set/{key}/{value}")
    public String hi(@PathVariable String key, @PathVariable String value) {
        redisTemplate.opsForValue().set(key, value);
        return "success";
    }
}

通过测试发现,读是优先走从节点,写走对应的主节点

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis进阶学习07--分布式缓存--下
  • Redis分片集群
    • 搭建分片集群
      • 集群结构
      • 准备实例和配置
      • 启动
      • 创建集群
    • docker-compose快速搭建分片集群
      • 注意
    • 散列插槽
      • 插槽原理
      • 小结
    • 集群伸缩
      • 需求分析
    • 故障转移
      • 自动故障转移
      • 手动故障转移
    • RedisTemplate访问分片集群
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档