redis cluster知识

2023-11-19

一 架构

Redis Cluster使用 Slot 的概念:作为一个KV系统,它把每个key的值hash成0 ~ 16383之间的一个数。这个hash值被用来确定对应的数据存储在哪个节点中。集群中的每个节点都存储了一份类似路由表的东西,描述每个节点所拥有的 Slots;当用户请求一个不在本机的key的时候,它可以根据这个路由表找到正确的服务节点,然后回复给用户一个moved,告知用户正确的服务节点。

  • slot = CRC16(key) % 16383;

  • 是集群内数据管理和迁移的最小单位,保证数据管理的粒度易于管理;

  • 每个节点都知道slot在集群中的分布,并能把对应信息回复给无法服务的请求。

  • 节点之间保持Gossip通信
    在这里插入图片描述
    在这里插入图片描述
    gossip 协议包含多种消息,包括ping,pong,meet,fail等等。

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

  • ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等)。

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

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

  • gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。

二 连接与部署

2.1客户端查找数据

在这里插入图片描述

比如:MOVED 10086 127.0.0.1:7002 表示,客户端请求的键值对所在的哈希槽 10086,实际是在 127.0.0.1:7002 这个实例上。

通过返回的 MOVED 命令,就相当于把哈希槽所在的新实例的信息告诉给客户端了。

这样一来,客户端就可以直接和 7002 连接,并发送操作请求了。

同时,客户端还会更新本地缓存,将该槽与 Redis 实例对应关系更新正确

MOVED 错误表示客户端请求的 key 不在当前实例的槽位上,客户端需要重定向到 MOVED 错误指向的实例

而 ASK 错误只是两个节点迁移槽过程中的一种临时措施,客户端请求的 key 正在迁移而且迁移到新实例上去时,就会返回一个 ASK 错误,客户端就会将请求的 key 重定向到 ASK 错误指向的新实例上

如果客户端再次请求相同的 key,它还是会向原来负责该槽位的实例发送请求

ASK 命令只是让客户端给新实例发送一起请求,不像 MOVED命令一样会更改本地缓存的哈希槽分配信息,让后续所有请求都发往新实例
在这里插入图片描述

单个集群节点不建议设置过多,过多的节点间的通信会带来较大的网络开销。按照服务来区分部署集群
1.集群能在挂一台机器的情况下不影响服务,要满足这个要求,主从节点就不能在一台机器上,也不能有过半数(包括半数)主节点在同一台机器上。
2.集群在挂一台机器的情况下,压力应尽可能平均分流到其他机器。
3.主节点和从节点在各台机器上的分布应当平均。
4.一个分片只配置一个slave
在这里插入图片描述
为了简化管理, 我们规定了集群的规格. 具体做法是每个主节点有且只有一个从节点. 并且以4个节点为最小的管理单位, 我们称为chunk. 一个chunk有两主两从, 分布在两台机器上面, 每台机器两个节点, 且4个节点内互相组成主从关系, 要求负责一个分片的主从分布在不同的机器上面.

一个chunk:
machine A machine B
master 1 / master 2
slave 2 /\ slave 1

三 故障检测与切换

  • slave发现自己的master变为FAIL

  • 将自己记录的集群currentEpoch加1,并广播Failover Request信息

  • 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack

  • 尝试failover的slave收集FAILOVER_AUTH_ACK

  • 超过半数后变成新Master

  • 广播Pong通知其他集群节点

在这里插入图片描述

1.SlaveMigration

/* Step 1: Don't migrate if the cluster state is not ok. */
    if (server.cluster->state != CLUSTER_OK) return;

    /* Step 2: Don't migrate if my master will not be left with at least
     *         'migration-barrier' slaves after my migration. */
    if (mymaster == NULL) return;
    for (j = 0; j < mymaster->numslaves; j++)
        if (!nodeFailed(mymaster->slaves[j]) &&
            !nodeTimedOut(mymaster->slaves[j])) okslaves++;
    if (okslaves <= server.cluster_migration_barrier) return;
candidate = myself;
    di = dictGetSafeIterator(server.cluster->nodes);
    while((de = dictNext(di)) != NULL) {
        clusterNode *node = dictGetVal(de);
        int okslaves = 0, is_orphaned = 1;

        /* We want to migrate only if this master is working, orphaned, and
         * used to have slaves or if failed over a master that had slaves
         * (MIGRATE_TO flag). This way we only migrate to instances that were
         * supposed to have replicas. */
        if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0;
        if (!(node->flags & CLUSTER_NODE_MIGRATE_TO)) is_orphaned = 0;

        /* Check number of working slaves. */
        if (nodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node);
        if (okslaves > 0) is_orphaned = 0;

        if (is_orphaned) {
            if (!target && node->numslots > 0) target = node;

            /* Track the starting time of the orphaned condition for this
             * master. */
            if (!node->orphaned_time) node->orphaned_time = mstime();
        } else {
            node->orphaned_time = 0;
        }

        /* Check if I'm the slave candidate for the migration: attached
         * to a master with the maximum number of slaves and with the smallest
         * node ID. */
        if (okslaves == max_slaves) {
            for (j = 0; j < node->numslaves; j++) {
                if (memcmp(node->slaves[j]->name,
                           candidate->name,
                           CLUSTER_NAMELEN) < 0)
                {
                    candidate = node->slaves[j];
                }
            }
        }
    }
    dictReleaseIterator(di);

/* Step 4: perform the migration if there is a target, and if I'm the
     * candidate, but only if the master is continuously orphaned for a
     * couple of seconds, so that during failovers, we give some time to
     * the natural slaves of this instance to advertise their switch from
     * the old master to the new one. */
    if (target && candidate == myself &&
        (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY &&
       !(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
    {
        serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
            target->name);
        clusterSetMaster(target);
    }

四 需要注意的坑

1.压力分布不均:

宕机后slave会选举为master 这台机器上会出现较多的master节点,在创建集群时需要预估该机器挂了后机器能否承受上面对应的服务产生的压力
保存初始主从节点的分布情况并及时更新,宕机后可以尽快的给集群补充机器

Redis Cluster 不建议使用 pipeline 和 multi-keys 操作(如 mset/mget. multi-key 操作),减少 max redirect 的产生;

五 常见问题排查

1.OOM

redis内存分布:
在这里插入图片描述

  • 客户端内存 :不受maxmemory限制
  • 对象内存: 主要占用内存的部分
  • 复制积压缓冲: 所有从库客户端共享、保存固定大小的写入命令用于从库失连后数据补偿
  • redis自身内存

oom导致:无法写入;大量key过期 影响读取

检查内存使用情况:

redis-cli -p 6383 memory stats|egrep -A 1 '(total.allocated|overhead.total|dataset.bytes|clients.normal)'

overhead.total:复制缓冲区、客户端输入输出缓冲区等,另外还包括⼀些元数据如 overhead.hashtable
dataset.bytes:数据对象使用内存
数据达到⼀定规模后,因需消耗额外的元数据、缓存内存,Redis 最终将超过 maxmemory 而 OOM
占用最多内存的连接数

# 1. 快速查看Redis内存是否够用
redis-cli -p 9999 info memory |egrep
'(used_memory_human|maxmemory_human|maxmemory_policy)'
# 2. 检查复制积压缓冲区使用情况
redis-cli -p 9999 memory stats|egrep -A 1
'(total.allocated|replication.backlog)'
# 3. 检查客户端输入缓冲区内存使用总量
redis-cli -p 9999 client list| awk 'BEGIN{sum=0}
{sum+=substr($12,6);sum+=substr($13,11)}END{print sum}'
# 4. 检查客户端输入缓冲区各客户端连接的内存情况
redis-cli -p 6383 client list|awk '{print substr($12,6),$1,$12,$18,$20}'|sort -nrk1,1|head -3| cut -f1 -d " " --complement
# 5. 检查客户端输出缓冲区内存使用总量
redis-cli -p 9999 client list| awk 'BEGIN{sum=0} {sum+=substr($16,6)}END{print
sum}'
# 6. 检查客户端输出缓冲区各客户端连接的内存使用排序
redis-cli -p 9999 client list|awk '{print substr($16,6),$1,$16,$18}'|sort -
nrk1,1 | cut -f1 -d" " --complement |head -n10
# 7. 检查数据对象使用内存总量
redis-cli -p 9999 memory stats|grep -A 1 'dataset.bytes'

如果定位到有连接异常,可以使用如下命令杀掉

CLIENT KILL ID

压测常用命令:

# 1. 持续给Redis灌数据
redis-benchmark -p 9999 -t set -r 100000000 -l
# 2. 模拟输入缓冲区过大
redis-benchmark -p 9999 -q -c 10 -d 102400000 -n 10000000 -r 50000 -t set
# 3. 模拟输出缓冲区过大
redis-benchmark -p 9999 -t get -r 5000000 -n 10000000 -d 100 -c 1000 -P 500 -l

2.常用运维命令

2.1慢日志

#获取5条慢日志
slowlog get 5
#设置阈值
config set slowlog-log-slower-than 2000

2.2 rename高危操作

#在配置文件中添加:
rename-command flushdb flushddbb
rename-command flushall flushallall
rename-command keys keysys

2.3 monitor

#输出keys命令的执行情况
redis-cli -p 6380 monitor | grep keys

2.4 config

#将config热修改的参数刷到redis配置文件中持久化
config rewrite

2.5 redis 查看cluster各节点ip

redis-cli -p 6380 cluster nodes |awk '{print $2}' |awk -F'@' '{print "- "$1}'

2.6 关闭&打开 rdb持久化

redis-cli --cluster call 127.0.0.1:6380 config set save ""
redis-cli --cluster call 127.0.0.1:6380 config rewrite
redis-cli --cluster call 127.0.0.1:6380 config  get save*

redis-cli --cluster call 127.0.0.1:6380 config set save "900 1 300 10 60 10000"
redis-cli --cluster call 127.0.0.1:6380 config rewrite

2.7 下线节点

redis-cli --cluster del-node host:port node_id 
#删除节点 需要通过reshard命令把slot分配到其他节点,然后再执行删除命令

#或者
cluster forget <node_id> :从集群中移除 node_id 指定的节点(需在所有节点执行)

#echo "usage: host port"
nodes_addrs=$(redis-cli -h $1 -p $2 cluster nodes|grep -v handshake| awk '{print $2}')
echo $nodes_addrs
for addr in ${nodes_addrs[@]}; do
    host=${addr%:*}
    port=${addr#*:}
    del_nodeids=$(redis-cli -h $host -p $port cluster nodes|grep -E 'handshake|fail'| awk '{print $1}')
    for nodeid in ${del_nodeids[@]}; do
        echo $host $port $nodeid
        redis-cli -h $host -p $port cluster forget $nodeid
    done
done
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis cluster知识 的相关文章

  • 何时/为何使用/定义接口[重复]

    这个问题在这里已经有答案了 可能的重复 何时最好使用 java 中的接口 https stackoverflow com questions 2586389 when best to use an interface in java Hi
  • 使用 AbstractTableModel 获取 JTable 中选定的行

    我有一个JTable using AbstractTableModel我在哪里有一个JCheckBox在第一列中用于选择行 现在 我需要从已检查的表中获取选定的行 现在 我按顺序从第一行遍历到最后一行并获取所有选择的行 如下所示 List
  • 在Java中清空数组/处理

    除了循环遍历数组中的每个元素并将每个元素设置为 null 之外 Java 处理中是否有一个本机函数可以简单地清空数组 或销毁它 以便能够将其重新声明为新数组 There s Arrays fill myArray null 并不是说它执行的
  • 静态方法的 Java 内存模型

    我来自操作系统和 C 语言背景 在代码编译时 世界很简单 需要处理和理解堆栈 堆文本部分等 当我开始学习 Java 时 我确实了解 JVM 和垃圾收集器 我对静态方法感到很有趣 根据我的理解 类的所有实例都会在堆中创建 然后被清理 但是 对
  • 使用 jpql 和 jpa 从日期字段中提取年份

    我想从数据库中的一行中提取年份部分 以便将其与值进行比较 这是我的功能 public List
  • 如何测试调用父类的受保护(不需要的)方法的方法?

    我陷入了一个非常奇怪的情况 我有一些需要测试的特定代码 这里是 public class A The real method of real class is so big that I just don t want to test it
  • Spring-boot中将redis-cache反序列化为对象的问题

    我在 Client 类中使用 JsonNode 来处理 MySQL 8 数据库中 JSON 类型的字段 即使对于 API 请求 它也能很好地工作 但是当我使用 Redis 启用缓存 我确实需要它 时 我注意到 Redis 无法序列化 Jso
  • 如何识别 Java 中的不可变对象

    在我的代码中 我正在创建一个对象集合 这些对象将由各种线程以只有在对象不可变的情况下才安全的方式访问 当尝试将新对象插入到我的集合中时 我想测试它是否是不可变的 如果不是 我将抛出异常 我能做的一件事是检查一些众所周知的不可变类型 priv
  • firebase推送通知错误Spring Boot服务器端

    我正在尝试从 Spring Boot 服务器端发送通知到客户端 android 服务器运行良好 一切都很好 2020 09 01 08 13 07 691 INFO 18941 restartedMain e DevToolsPropert
  • 是否可以从另一个方法传递 args[] 来调用 main 方法?

    我试图从另一个传递参数的方法调用类的主要方法 就像从命令行运行该类时一样 有没有办法做到这一点 您可以致电main方法就像您调用任何其他 静态 方法一样 MyClass main new String arg1 arg2 arg3 Exam
  • 正确使用 JDBC 连接池 (Glassfish)

    我需要在 Java Web 服务中作为会话 bean 实现数据库连接 但我不确定我这样做是否正确 我创建了一个类 public final class SQLUtils private static DataSource m ds null
  • BlackBerry SQLite:将一个 SQLite 数据库连接到另一个

    我正在尝试使用 SQLite 将一个 SQLite 数据库附加到 BlackBerry 上的另一个数据库附加数据库 http www sqlite org lang attach html命令 Database d1 d2 Statemen
  • C# 中的协变和逆变

    首先我要说的是 我是一名正在学习 C 编程的 Java 开发人员 因此 我会将我所知道的与我正在学习的进行比较 我已经使用 C 泛型几个小时了 我已经能够在 C 中重现我在 Java 中知道的相同内容 除了几个使用协变和逆变的示例 我正在读
  • Java:使用 Java.util.concurrent 线程访问读取线程串行端口

    我正在尝试编写一个 Java 串行设备驱动程序并想使用 对我来说是新的 java util concurrent包裹 我有一种发送数据包然后等待 ACK 的方法 我打算有炭 接收在不同的线程中运行 如果接收线程收到 ACK 它应该使用发送数
  • 如何使用 Guava 连接字符串?

    我写了一些代码来连接字符串 String inputFile for String inputLine list inputFile inputLine trim 但我不能使用 连接 所以我决定使用 Guava 所以我需要使用Joiner
  • 抽象类或接口。哪种方式是正确的?

    有两种方法可以选择抽象类或接口 微软解决方案和Oracle解决方案 微软 设计指南 请使用抽象 在 Visual Basic 中为 MustInherit 类而不是接口来将协定与实现分离 http msdn microsoft com en
  • HTTP PUT 在 Java 中上传文件

    Edit 我想我已经弄清楚如何执行二进制数据部分 仔细检查代码 但我很确定我做对了 现在 当我尝试按照中所述完成上传时遇到新错误Vimeo API 文档 http vimeo com api docs upload streaming Ed
  • 为什么现在()? (客观化)

    为什么我想要异步加载 Objectify 实体 异步加载到底意味着什么 根据客观化有关加载的文档 https code google com p objectify appengine wiki BasicOperations Loadin
  • 找不到符号assertEquals

    我正在尝试为计算器编写第一个单元测试 但 NetBeans 说它找不到该符号assertEquals和注释 Test 我应该包括一些东西吗 我正在使用 NetBeans 7 3 1 和 W7 package calculator impor
  • 将带有 webapp 的 WAR 部署到 Maven 中央存储库是否有意义?

    这样做有意义吗 如果是 我在哪里可以找到使用简单的 Web Hello World 执行此操作的示例 当人们从 Maven 执行 Web 应用程序时 他们会使用 Jetty 来运行它吗 我想 tomcat 太重了 任何帮助将不胜感激 谢谢

随机推荐

  • Maven常见问题、异常、错误整理【持续更新】

    前言 之前也写过一篇与Maven相关的文章 这是第二篇对于Maven常见问题 常见异常的整理 开一篇新的文章一个原因是自己更换了新的IDE 之前使用myEclipse 现在换成IDEA 更重要的原因是新的文章对于处理Maven这类错误的方案
  • 如何调用同文件夹的py文件

    你可以使用 import 语句来调用同文件夹中的另一个 Python 文件 举个例子 假设你有一个文件夹 里面有两个 Python 文件 main py 和 helper py 你想在 main py 中调用 helper py 中的函数
  • 用pycharm+flask 建立项目以后运行出现ImportError: No module named flask-login问题

    出现此问题 一般情况下 打开CMD输入 pip install flask login 然后 在cmd中输入命令 pip list 查看目前已安装的的模板 在此时 如果你继续运行项目 有可能会发现问题已经解决 但是也有可能会发现问题依然存在
  • Rust中的iter(), into_iter(), iter_mut()

    在Rust中 iter into iter iter mut 都是用于在集合类型上创建迭代器的方法 这三个方法各有不同 下面一一进行介绍 iter iter 方法创建一个不可变的引用迭代器 当你只想读取集合中的元素 而不想改变它们或消耗集合
  • C++57个入门知识点_27 继承的概念(类的组合关系:将一个类的对象作为另外一个类的成员;类的组合关系的访问;继承的概念:A类是B的儿子;A被称为子类,B被称为父类或者A被称为派生类,B被称为基类)

    我们知道类有三大特性 封装 继承和多态 封装在前面已经介绍完 本篇开始将会介绍继承 继承是在封装基础上的扩展 也是多态的一个承接 总结 类的组合关系 将一个类的对象作为另外一个类的成员 Class CStudent public priva
  • maven常用命令大全(附详细解释)

    1 常用打包命令 mvn clean package Dmaven test skip true 跳过单测打包 mvn clean install Dmaven test skip true 跳过单测打包 并把打好的包上传到本地仓库 mvn
  • MCU震荡电路的晶振边的22pf电容的作用

    振荡电路用于实时时钟RTC 对于这种振荡电路只能用32 768KHZ 的晶体 晶体被连接在OSC3 与OSC4 之间而且为了获得稳定的频率必须外加两个带外部电阻的电容以构成振荡电路 32 768KHZ的时钟晶振产生的振荡信号经过石英钟内部分
  • (3)numpy数组的索引和切片操作

    在开始之前 先导入numpy第三方包 import numpy as np 索引 谈到索引 我们就应该想到 在python语言中 是如何对列表进行索引的 接下来将会对python列表索引和numpy数组索引进行比较 先定义一个numpy数组
  • 查看linux中的TCP连接数

    一 查看哪些IP连接本机 netstat an 二 查看TCP连接数 1 统计80端口连接数 netstat nat grep i 80 wc l 2 统计httpd协议连接数 ps ef grep httpd wc l 3 统计已连接上的
  • 一招秒开GitHub,永久解决!

    步骤 目录 步骤 1 打开电脑 进入如下的路径 2 将hosts 复制到桌面 3 把hosts文件移动到桌面后etc文件夹下的hosts文件不存在 已经被移动到桌面了 4 在移到桌面的hosts文件里面进行修改 5 再把修改好的hosts文
  • mysql tomcat 自动重连_Java开发网 - tomcat连接池支持重新连接数据库吗?

    Posted by pigengler Posted on 2006 04 20 15 23 我做了一个测试 程序使用tomcat的连接池 factory org apache commons dbcp BasicDataSourceFac
  • 微信api ----统一下单

    应用场景 除被扫支付场景以外 商户系统先调用该接口在微信支付服务后台生成预支付交易单 返回正确的预支付交易回话标识后再按扫码 JSAPI APP等不同场景生成交易串调起支付 状态机 支付状态转变如下 接口链接 URL地址 https api
  • pandas中的data.corr()函数方法说明及使用

    数据相关性分析中 经常用到data corr 函数 data corr 表示了data中的两个变量之间的相关性 取值范围为 1 1 取值接近 1 表示反相关 类似反比例函数 取值接近1 表正相关 DataFrame corr 函数使用说明如
  • 国际版阿里云/腾讯云:阿里云流量包是用来做什么

    阿里云流量包是用来做什么 阿里云同享流量包是一种通用流量套餐 同享流量包具有多地域和多产品流量抵扣的优势 同享流量包不仅能够抵扣云服务器ECS发生的流量 还能够抵扣弹性公网IP和负载均衡SLB发生的流量 同享流量包掩盖产品规模广 同享流量包
  • Qt 设置:两个窗口位置重合

    目录 Qt 设置 两个窗口位置重合 QT向界面中嵌套新的界面 QT向界面中嵌套新的界面 https www cnblogs com bob jianfeng p 11609012 html 第一步 先进入ui编辑界面 加入一个水平或者垂直的
  • 数据预处理与特征工程—10.图像切割与特征提取

    文章目录 引言 一 图像切割 二 特征提取 1 各阶颜色矩的计算公式 三 python实现 水质图像数据 百度网盘链接提取码 1234 引言 本文以水质图像为例 进行图像切割与特征提取 一 图像切割 一般情况下 采集到的水样图片包含盛水容器
  • 深入研究C++多态(虚函数和虚继承)

    文章目录 多态的引入 虚函数表 几种常见继承关系中的类内存分布 单继承 多继承 菱形继承 总结 作者 狗子孙 链接 https www jianshu com p 02183498a2c2 来源 简书 简书著作权归作者所有 任何形式的转载都
  • 如何彻底删除JetBrains系列软件教程

    mac安装JetBrains系列软件后 如果JetBrains系列软件出了问题需要重新安装 有时候软件删除重装后 JetBrains系列软件仍然会打不开的问题 很是困扰 接下来为您带来了mac上如何彻底删除IntelliJ IDEA等软件的
  • Char.IsDigit与Char.IsNumber的区别

    需要判断Char是否为数字 查看了下MSDN 发现有三种方法 Char IsDigit aChar 指示指定字符串中位于指定位置处的字符是否属于十进制数字类别 Char IsNumber aChar 指示指定字符串中位于指定位置的字符是否属
  • redis cluster知识

    一 架构 Redis Cluster使用 Slot 的概念 作为一个KV系统 它把每个key的值hash成0 16383之间的一个数 这个hash值被用来确定对应的数据存储在哪个节点中 集群中的每个节点都存储了一份类似路由表的东西 描述每个