• redis05


    redis cluster

    数据节点

    顺序分区

    哈希分区
    hash(key) % node_count

    哈希分区

    1.节点取余分区

    hash(key) % node_count
    优点:hash+取余的方式计算节点的分区很简单
    缺点:当节点伸缩时候,数据节点关系发生变化,导致数据迁移
    扩容的时候建议翻倍扩容,可以降低数据的迁移量。

    2.一致性哈希分区

    哈希+顺时针(优化取余)
    约定长度2的32次方 位的哈希环,在其中分布若干个hash点。
    第一步对每个key做哈希处理得到hashVal
    第二步将hashVal顺时针偏移,得到的第一个hash点,即为分区的落点
    优点:节点伸缩的时候,只会影响邻近节点,但是还是会有数据迁移
    翻倍的伸缩,可以保证最小的迁移数据且达到数据的负载均衡

    3.虚拟槽分区

    预设虚拟槽,每个槽映射一个数据子集,一般比节点数大
    采用CRC16(key) & 16383来决定节点
    每个节点顺序地平均分布16384个槽,即当有5个节点时
    A 0 ~ 3276
    B 3277 ~ 6553
    C 6554 ~ 9830
    D 9831 ~ 13107
    E 13108 ~ 16383

    RedisCluster架构

    节点

    由多个master主节点组成,各个master都负责去读写,每个master都有各自的slave节点。

    每个node的cluster_enabled配置为yes

    Gossip协议

    多个master节点之间会使用Gossip协议进行通信

    1.meet消息

    用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。

    当A meet B以及A meet C之后,B就可以与C做交互了

    2.ping消息

    集群中交换最频繁的消息,集群内各个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。

    ping消息发送封装了自身节点和部分其他节点的状态数据。

    3.pong消息

    当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。

    pong消息内部封装了自身状态数据。

    节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新

    4.fail消息

    当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新诶下线状态


    指派槽

    需要为RedisCluster指派槽,指定各个master节点的槽范围,让它进行正常的读写


    复制

    每个master节点包含若干个slave节点,形成主从复制的形式,以提高高可用性

    通过各个节点之间相互监控来达到Sentinel的目的

    Redis Cluster的安装

    环境安装

     1 redis集群管理工具redis-trib.rb依赖ruby环境,首先需要安装ruby环境
     2 
     3 1.yum install ruby
     4   yum install rubygems
     5 
     6 2.上传工具包 redis-3.0.0.gem 
     7 
     8 3.安装ruby和redis的接口程序
     9 gem install /usr/local/redis-3.0.0.gem
    10 
    11 4.将Redis集群搭建脚本文件复制到/usr/local/redis/redis-cluster目录下
    12 #cd /root/redis-3.0.0/src/
    13 #ll *.rb
    14 # cp redis-trib.rb /usr/local/redis/rediscluster/ -r

    集群搭建
    搭建集群最少也得需要3台主机,如果每台主机再配置一台从机的话,则最少需要6台机器。
    端口设计如下:7001-7006

     1 cd /usr/local/redis
     2 
     3 1.复制出一个7001机器
     4 cp bin ./redis-cluster/7001 –r
     5 
     6 2.如果存在持久化文件,则删除
     7 cd  redis-cluster/7001
     8 rm -rf appendonly.aof dump.rdb
     9 
    10 3.设置集群参数
    11 cluster-enable yes
    12 
    13 4.修改端口
    14 port 7001
    15 
    16 5.复制出7002-7006机器
    17 cp 7001/ 7002 -r
    18 cp 7001/ 7003 -r
    19 cp 7001/ 7004 -r
    20 cp 7001/ 7005 -r
    21 cp 7001/ 7006 -r
    22 
    23 6.修改 7002 -7006 机器的端口
    24 
    25 
    26 7.创建startall.sh可执行文件
    27 
    28 cd 7001
    29 ./redis-server redis.conf
    30 cd ../7002
    31 ./redis-server redis.conf
    32 cd ../7003
    33 ./redis-server redis.conf
    34 cd ../7004
    35 ./redis-server redis.conf
    36 cd ../7005
    37 ./redis-server redis.conf
    38 cd ../7006
    39 ./redis-server redis.conf
    40 cd ..
    41 
    42 8.增大权限
    43 chomd u+x startall.sh
    44 ./startall.sh
    45 
    46 9.创建集群
    47 ./redis-trib.rb create --replicas 1 192.168.239.139:7001 192.168.239.139:7002 192.168.239.139:7003 192.168.239.139:7004 192.168.239.139:7005  192.168.239.139:7006

    集群连接与查看

    1 ./redis-cli –h 127.0.0.1 –p 7001 -c    c表示集群连接方式
    2 
    3                               
    4 查看集群 
    5 >cluster info
    6 
    7 查看集群节点
    8 >cluster nodes

    集群伸缩

    一. 伸缩原理

    集群伸缩 = 槽和数据在节点之间的移动

    二、扩容集群

    1.准备新节点(例如,加入6385,6386)

    需要是集群模式 cluster_enabled = yes
    配置和其他集群节点保持一致
    启动后是一个孤儿节点

    redis-server conf/redis-6385.conf
    redis-server conf/redis-6386.conf


    2.加入集群


    redis-trib.rb add-node newHost:newPort existsHost:existPort --slave --master-id

    redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
    reids-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379


    在没有图形化界面时,建议使用redis-trib.rb能够避免新节点已加入其它集群,造成故障
    新节点加入到集群中,有两类作用
    为它迁移槽和数据实现扩容
    作为从节点负责参与故障转移

    3.迁移槽和数据

    基于原生命令

    1. 对目标节点发送:cluster setslot ${slot} importing ${sourceNodeId},让目标节点准备导入槽的数据。
    2. 对源节点发送:cluster set ${slot} migrating ${targetNodeId},让源节点准备迁出槽的数据
    3. 源节点循环执行:cluster getkeysinslot ${slot} ${count},每次获取count个属于槽的键
    4. 在源节点上执行:migrate ${targetIp} ${targetPort} key 0 ${timeout},将指定的key迁移
    5. 重复执行步骤3~步骤4直到槽下所有的数据迁移至目标节点
    6. 向集群内所有主节点发送 cluster setslot ${slot} node ${targetNodeId},通知槽分配给目标节点


    基于redis-trib.rb

     1 #指定任意一个集群中的节点即可
     2 redis-trib.rb reshard ${everyHost}:${everyPort}
     3 #首先通过提示输入需要迁移多少个slot
     4 #然后提示输入接收这些槽的nodeId
     5 #接着提示输入源节点的节点ID集合,输入all则表示所有待迁移节点平均
     6 
     7 redis-trib.rb reshard host:port --from ${fromNodeId} --to ${toNodeId} 
     8     --slots ${slotCount} --yes --timeout --pipeline ${batchSize}
     9 #host:port     必传参数,集群内任意节点地址,用来获取整个集群信息
    10 #--from     源节点ID,当有多个源节点用逗号分隔,如果是all,则源节点为集合内除目标节点外的所有主节点
    11 #--to        目标节点ID,只能填写一个
    12 #--slots    需要迁移槽的总数量
    13 #--yes        迁移无需用户手动确认
    14 #--timeout    每次migrate操作的超时时间,默认为60000毫秒=60秒
    15 #--pipeline    控制每次批量迁移键的数量,默认是10

    三、缩容集群
    1.迁移槽到其他节点

    #将槽从当前节点迁出可以使用上文中的reshard进行完成
    redis-trib.rb reshard ${everyHost}:${everyPort}

    2.通知其他节点忘记下线节点

    #在‘其他节点’上依次执行如下命令,需要在60秒内执行
    cluster forget ${downNodeId}


    3.关闭节点

    客户端路由

    #计算指定key的对应的槽位
    cluster keyslot ${key}


    一、moved重定向

    1. 客户端给任一节点发送请求
    2. 节点计算该请求的槽位以及对应的节点
    3. 如果对应的节点是自身,则将执行命令并返回执行结果
    4. 如果对应的节点不是当前节点自身,将会返回一个moved异常:MOVED ${slot} ${targetHost} ${targetPort}
    5. 然后客户端再重新按照targetHost:targetPort连接另一redis节点,发送请求,跳转至第2步。

    二、ask重定向

    1. ask请求发生在slot迁移的过程中
    2. 客户端向source发送命令(不存在moved重定向的情况)
    3. source节点回复客户端一个ASK重定向:ASK ${slot} ${targetHost} ${targetPort}
    4. 客户端给target节点发送一个Asking请求
    5. 然后紧接着客户端发送命令给target节点
    6. target节点执行请求并响应结果

    move 和 ask 的比较

     

    两者都是客户端的重定向

    MOVED的场景下,可以确定槽不在当前节点

    ASK的场景下,槽还在迁移过程中

    三、smart客户端(JedisCluster)
    1.smart客户端原理:追求性能
    从集群中选一个可运行节点,使用clutser slots初始化槽和节点映射

    将cluster slots结果映射到本地,为每个节点创建JedisPool

    准备执行命令

    JedisCluster

     1 Set<HostAndPort> nodeList = new HashSet<>();
     2 nodeList.add(new HostAndPort("127.0.0.1" , 7000) );
     3 nodeList.add(new HostAndPort("127.0.0.1" , 7001) );
     4 nodeList.add(new HostAndPort("127.0.0.1" , 7002) );
     5 nodeList.add(new HostAndPort("127.0.0.1" , 7003) );
     6 nodeList.add(new HostAndPort("127.0.0.1" , 7004) );
     7 nodeList.add(new HostAndPort("127.0.0.1" , 7005) );
     8 JedisPoolConfig poolConfig = new JedisPoolConfig();
     9 int timeout = 30_000;
    10 JedisCluster jedisCluster = new JedisCluster(nodeList , timeout , poolConfig);
    11 jedisCluster.set("hello" , "world");
    12 System.out.println(jedisCluster.get("hello"));
    13 
    14 //获取所有节点的JedisPool
    15 Map<String,JedisPool> jedisPoolMap = jedisCluster.getClusterNodes();


    集群模式下批量操作的实现

    mget、mset需要一组key必须在同一个槽下

    1.串行GET/SET

    对每一个key在for循环中依次执行GET/SET
    优点:执行简单
    缺点:n个key就需要n次网络时间,效率低下

    2.根据CRC16&16383的结果做内聚后串行IO

    在本地通过CRC16&16383计算出各个key的槽,然后以各个槽做内聚,然后串行依次访问各个node

    3.根据CRC16&16383的结果做内聚后并行IO

    在本地通过CRC16&16383计算出各个key的槽,然后以各个槽做内聚,然后并行访问各个node

    4.使用hash_tag的方式

    当一个key包含 {} 的时候,就不对整个key做hash,而仅对 {} 包括的字符串做hash。
    此时对每个key拼接一个字符串{tag},就可以访问一个节点,完成O(1)的请求

    比较

    故障转移

    一、故障发现

    通过ping、pong消息实现故障发现:不需要sentinel

    1.主观下线(pfail消息)

    某个节点认为另一个节点不可用,“偏见”
    主观下线流程
    节点1每秒定时给节点2发送PING消息
    当节点2收到PING消息后返回的PONG消息被节点1收到时,节点1会更新与节点2的最后更新时间
    否则如果PING/PONG失败,会触发通信异常断开连接
    当与节点2最后的通信时间达到或大于***cluster-node-timeout***,则节点1主观认为节点2下线。

    2.客观下线(fail消息)

    当半数以上持有槽的主节点都标记某节点主观下线

    流程
    当每个节点收到其他节点发来的PING消息时,会解析其中是否包含其他节点的主观下线消息
    收到PING的结果会把收到的主观下线消息存入故障链表中
    在故障链表中,节点可以知道有多少主节点主观下线
    故障链表存在有效期,有效期为***cluster-node-timeout X 2***,防止很久之前的主观下线消息长久存在于故障链表中。
    计算有效的下线报告数量,当达到半数以上时,就将节点更新为客观下线
    并向集群广播下线节点的fail消息

    3.客观下线的作用

    通知集群内所有节点标记故障节点为客观下线
    通知故障节点的从节点触发故障转移流程

    二、故障恢复

    1.资格检查(对从节点进行资格检查)

    每个从节点检查与故障主节点的断线时间
    超过cluster-node-timeout * cluster-slave-validity-factor取消资格
    cluster-slave-validity-factor:默认是10

    2.准备选举时间

    对各个符合资格的从节点按照偏移量进行排序偏移量越大(与主节点数据越接近)的节点准备选举时间越小(越早被选举,后续投票阶段可获得更多票数)

    3.选举投票

    由可用的master节点给各个符合资格的slave节点投票
    准备选举时间越小的节点,越早被master节点投票,就更可能得到更多的票数
    当投票数达到 (可用master节点数)/2 + 1,可以晋升成为master节点

    4.替换主节点

    1. 当前从节点取消复制变成主节点(slaveof no one)
    2. 执行cluster del slot撤销故障主节点负责的槽,并执行cluster add slot把这些槽分配给自己
    3. 向集群广播自己的pong消息,表明已经替换了故障从节点

    Redis Clutser常见问题

    一、集群完整性

    cluster-require-full-converage 默认为yes
    完整性要求集群中所有节点都是在线状态并且16384个槽都在一个服务的状态***才对外提供服务
    大多数业务无法容忍,cluster-require-full-coverage建议设置为no

    二、带宽消耗

    官方建议:RedisCluster的节点不要超过1000个
    每秒的PING/PONG消息会引起大量的带宽消耗
    消息发送频率:节点发现与其他节点通信时间超过cluster-node-timeout / 2时会直接发PING消息
    消息数据量:slots槽数组(2KB空间)和整个集群1/10的状态数据(10个节点状态数据约1KB)
    节点部署的机器规模:集群分布的机器越多且每台机器划分的节点数越均匀,则集群内整体的可用带宽越高

    优化方法
    避免使用“大”集群:避免多业务使用一个集群,大业务可用多集群
    cluster-node-timeout:影响带宽和故障转移时间,需要权衡
    尽量均匀分配到多机器上:保证高可用和带宽

    三、Pub/Sub广播

    问题:publish在集群每个节点中广播:加重带宽压力
    解决方案1:需要使用Pub/Sub时,为了保证高可用,可以单独开启一套Redis Sentinel
    解决方案2:使用消息队列

    数据倾斜

    1.数据倾斜

    节点和槽的分配不均匀
    redis-trib.rb info ip:port 查看节点、槽、键值分布
    redis-trib.rb rebalance ip:port 进行槽的均衡(谨慎使用)
    不同槽对应的键值数差异较大
    CRC16正常情况下比较均匀
    可能存在hashKey
    cluster countkeysnslot ${slot} 获取槽对应的键值个数
    包含bigKey(大字符串、几百万元素的hash、set、zset、list)
    可以在从节点上执行:redis-cli --bigkeys
    优化数据结构
    内存相关配置不一致

    2.请求倾斜(热点key或者bigKey)

    通过优化数据结构避免bigKey
    热点Key不要使用hash_tag
    当一致性要求不高时,可以用本地缓存+MQ

    读写分离

    只读连接:集群模式的从节点不接受任何读写请求
    重定向到负责槽的主节点
    readonly命令可以读:连接级别的命令
    读写分离:更加复杂
    与主从复制有同样的问题:复制延迟、读取过期数据、从节点故障
    需要实现自己的客户端:cluster slave ${nodeId}
    思路与Redis Sentinel一致

    数据迁移

    官方迁移工具:redis-trib.rb import
    只能从单机迁移到集群
    不支持在线迁移:source需要停止写入
    不支持断点续传
    单线程迁移:影响速度
    对source数据进行SCAN,然后进行导入操作


    在线迁移
    唯品会:redis-migrate-tool
    豌豆荚:redis-port
    均支持在线迁移,会伪装成source节点的slave,利用这份更新数据再传送给target

    集群与单机比较

    集群限制
    key批量操作支持有限:例如mget、mset必须在一个slot
    Key事务和Lua支持有限,操作的Key必须在一个节点
    Key是数据分区的最小粒度,不支持bigKey分区
    不支持多数据库:集群模式下只有db 0
    复制只支持一层:不支持树形复制结构
    分布式Redis不一定好
    Redis Cluster:满足容量和性能的扩展性,很多业务不需要

    不多数客户端性能会降低
    命令无法跨节点使用:mget、mset、scan、flush、sinter
    Lua和事务无法跨节点使用
    客户端维护更复杂:SDK和应用本身消耗(例如更多的连接池)
    很多场景下:Redis Sentinel已经足够好

  • 相关阅读:
    MySQL视图——学习笔记及实验
    小学生四则运算自动刷题库优化升级
    软件工程小项目——小学生四则运算自动刷题库
    笔记--运算符、表达式和语句
    笔记--基本数据类型与数组
    笔记--java入门
    原因: java.lang.ClassNotFoundException: Hello
    使用gopm代替go get 解决go包卡慢的问题
    调用微信截图功能c# 截图带扩展名
    如何用golang搜索抓取淘宝商品
  • 原文地址:https://www.cnblogs.com/quyangyang/p/11372902.html
Copyright © 2020-2023  润新知