hello云胜

技术与生活

0%

Consumer源码分析

消费者消费消息有两种模式:推模式和拉模式。

所谓拉模式,就是由consumer主动从broker拉取消息进行消费。

推模式,就是由broker将一批消息推送给consumer消费。

rocketmq中这两种模式都可以使用,我们通常使用较多的是push模式,因为处理起来比较简单。如果使用pull模式需要我们再consume代码中自己处理消息的拉取。

但实际上,严格来说,RocketMq并没有实现push模式。RocketMQ消息推模式的实现基于拉模式,在拉模式上包装一层,在消费端开启一个线程 PullMessageService 循环向 Broke r拉取消息,一次拉取任务结束后马上又发起另一次拉取操作,实现准实时自动拉取。给我们的感觉好像是broker主动推过来的。

从start方法开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void start() throws MQClientException {
// 配置消费者组名
setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup));
// start方法。入口
this.defaultMQPushConsumerImpl.start();
if (null != traceDispatcher) {
try {
// 消息轨迹追踪
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}

我们现在重点看defaultMQPushConsumerImpl.start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public synchronized void start() throws MQClientException {
switch (this.serviceState) {
case CREATE_JUST:
log.info("the consumer [{}] start beginning. messageModel={}, isUnitMode={}", this.defaultMQPushConsumer.getConsumerGroup(),
this.defaultMQPushConsumer.getMessageModel(), this.defaultMQPushConsumer.isUnitMode());
this.serviceState = ServiceState.START_FAILED;
// 检查消费者配置
this.checkConfig();
//
this.copySubscription();

if (this.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) {
this.defaultMQPushConsumer.changeInstanceNameToPID();
}

this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPushConsumer, this.rpcHook);

this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup());
this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel());
this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy());
this.rebalanceImpl.setmQClientFactory(this.mQClientFactory);

this.pullAPIWrapper = new PullAPIWrapper(
mQClientFactory,
this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());
this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList);

if (this.defaultMQPushConsumer.getOffsetStore() != null) {
this.offsetStore = this.defaultMQPushConsumer.getOffsetStore();
} else {
switch (this.defaultMQPushConsumer.getMessageModel()) {
case BROADCASTING:
this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
case CLUSTERING:
this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup());
break;
default:
break;
}
this.defaultMQPushConsumer.setOffsetStore(this.offsetStore);
}
this.offsetStore.load();

if (this.getMessageListenerInner() instanceof MessageListenerOrderly) {
this.consumeOrderly = true;
this.consumeMessageService =
new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
} else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
this.consumeOrderly = false;
this.consumeMessageService =
new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
}

this.consumeMessageService.start();

boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);
if (!registerOK) {
this.serviceState = ServiceState.CREATE_JUST;
this.consumeMessageService.shutdown(defaultMQPushConsumer.getAwaitTerminationMillisWhenShutdown());
throw new MQClientException("The consumer group[" + this.defaultMQPushConsumer.getConsumerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}

mQClientFactory.start();
log.info("the consumer [{}] start OK.", this.defaultMQPushConsumer.getConsumerGroup());
this.serviceState = ServiceState.RUNNING;
break;
case RUNNING:
case START_FAILED:
case SHUTDOWN_ALREADY:
throw new MQClientException("The PushConsumer service state not OK, maybe started once, "
+ this.serviceState
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
null);
default:
break;
}

this.updateTopicSubscribeInfoWhenSubscriptionChanged();
this.mQClientFactory.checkClientInBroker();
this.mQClientFactory.sendHeartbeatToAllBrokerWithLock();
this.mQClientFactory.rebalanceImmediately();
}

Redis的持久化问题

aof

aof不会记录读请求。aof只有执行成功的命令才会记录。

看看 AOF 日志的内 容。其中,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着 具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例 如,“$3 set”表示这部分有 3 个字节,也就是“set”命令

image-20220113160550265

落盘策略

AOF 日志也是在主线程中执行的!!

Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;

Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲 区,每隔一秒把缓冲区中的内容写入磁盘;建议优先使用。

No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓 冲区,由操作系统决定何时将缓冲区内容写回磁盘。

一般而言,只要你采用的不是 always 的持久 化策略,就不会对性能造成太大影响。

aof重写

Redis 根据数据库的现状创建一个新的 AOF 文 件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写 入。不是对原aof文件进行什么命令合并操作!!

和 AOF 日志由主线程写回不同,重写过程是由后台线程 bgrewriteaof 来完成的,这也是 为了避免阻塞主线程

执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此 时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的 最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数 据写成操作,记入重写日志。

image-20220113160911282

fork子进程时,子进程是会拷贝父进程的页表,即虚 实映射关系,而不会拷贝物理内存。子进程复制了父进程页表,也能共享访问父进程的内存数据 了,此时,类似于有了父进程的所有内存数据。

开启:AOF 重写由两个参数共同控制,auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size,同时满足这两个条件,则触发 AOF 后台重写 BGREWRITEAOF。

1
2
3
4
// 当前AOF文件比上次重写后的AOF文件大小的增长比例超过100 
auto-aof-rewrite-percentage 100
// 当前AOF文件的文件大小大于64MB
auto-aof-rewrite-min-size 64mb

Huge page的问题

Huge page。这个特性大家在使用Redis也要注意。Huge page对提升TLB命 中率比较友好,因为在相同的内存容量下,使用huge page可以减少页表项,TLB就可以缓存更 多的页表项,能减少TLB miss的开销。 但是,这个机制对于Redis这种喜欢用fork的系统来说,的确不太友好,尤其是在Redis的写入请 求比较多的情况下。因为fork后,父进程修改数据采用写时复制,复制的粒度为一个内存页。如 果只是修改一个256B的数据,父进程需要读原来的内存页,然后再映射到新的物理地址写入。一 读一写会造成读写放大。如果内存页越大(例如2MB的大页),那么读写放大也就越严重,对Re dis性能造成影响。 Huge page在实际使用Redis时是建议关掉的。

rdb

用 AOF 方法进行故障恢复 的时候,需要逐一把操作日志都执行一遍。如果操作日志非常多,Redis 就会恢复得很缓 慢,影响到正常使用

Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。

save:在主线程中执行,会导致阻塞;

bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置

这里我就要说到一个常见的误区了,避免阻 塞和正常处理写操作并不是一回事。此时,主线程的确没有阻塞,可以正常接收请求,但 是,为了保证快照完整性,它只能处理读操作,因为不能修改正在执行快照的数据

为了快照而暂停写操作,肯定是不能接受的。所以这个时候,Redis 就会借助操作系统提 供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。

image-20220113161511886

如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C), 那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本 数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

fork 这个创建过程本身会阻塞主线程,而且主线程的内存越 大,阻塞时间越长。

混合持久化

执行快照的频率不好把握。

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一 定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

混合持久化只发生于 AOF 重写过程。使用了混合持久化,重写后的新 AOF 文件前半段是 RDB 格式的全量数据,后半段是 AOF 格式的增量数据。

开启:混合持久化的配置参数为 aof-use-rdb-preamble,配置为 yes 时开启混合持久化,在 redis 4 刚引入时,默认是关闭混合持久化的,但是在 redis 5 中默认已经打开了。

关闭:使用 aof-use-rdb-preamble no 配置即可关闭混合持久化。

混合持久化本质是通过 AOF 后台重写(bgrewriteaof 命令)完成的,不同的是当开启混合持久化时,fork 出的子进程先将当前全量数据以 RDB 方式写入新的 AOF 文件,然后再将 AOF 重写缓冲区(aof_rewrite_buf_blocks)的增量命令以 AOF 方式写入到文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

找到一个看起来不错的redis-operator来进行redis的部署,今天来测试一下效果如何。

关于k8s的operator是啥就不细讲了,内容太多。

快速体验

按照readme来快速部署下试试

1
2
3
4
helm repo add td-redis-operator https://tongdun.github.io/td-redis-operator/charts/td-redis-operator
helm repo update
kubectl create ns td-redis
helm install -n td-redis tongdun td-redis-operator/td-redis-operator

这回部署redis-operator,manager控制台,redis集群,redis哨兵模式全都部署一个。

等待片刻,都部署好了。

看看都有啥服务

image-20230625163231237

看到了熟悉的predixy,那就是说。redis集群对外暴露也是用的predixy做代理。

redis-manager是一个web端的控制台。

这个就要给个赞了。我喜欢,可以基于这个进行二次开发,能省不少事。

image-20230625163153839

redis-jerry是redis-cluster

redis-tom是redis单机模式

sentinel-tom是redis-tom的哨兵集群

看看全部的pod

image-20230625165931633

比较多,影响我们测试。

只部署一个redis-cluster

下面我清理掉,重新部署一个干净的redis-cluster模式。开始我们的测试。

1
helm -n td-redis uninstall tongdun

清理完毕之后,

1
helm install -n td-redis tongdun td-redis-operator/td-redis-operator --set type=cluster

只安装redis-cluster

image-20230626110849638

现在就比较清楚了。

使用deploy部署了predixy,所谓redis-cluster的代理。使用sts部署了3主3从的redis集群。

redis的默认密码是88c185e86f684251,可以通过.Values.secret修改

predixy的默认密码是123,目前还不能改,这个要优化

基于的redis版本是redis_version:5.0.8

进到容器里看看

redis的pod里有两个container

1
2
[root@paas-m-k8s-master-1 ~]# kc -n td-redis get pod redis-jerry-0-0 -o jsonpath={.spec.containers[*].name}
redis-jerry-0-prometheus-sidecar redis-jerry-0

进入redis的那个container

1
kc -n td-redis exec -it redis-jerry-0-0 -c redis-jerry-0 -- /bin/sh

redis的配置目录:

1
2
3
sh-4.2# cd /home/admin/redis/
sh-4.2# ls
data nodes.conf output redis.conf script

predixy的配置目录: /etc/predixy/

1
2
3
[root@paas-m-k8s-master-1 ~]# kc -n td-redis exec -it predixy-redis-jerry-8d56fd856-6zhb2 -- /bin/sh
# ls /etc/predixy
auth.conf cluster.conf predixy.conf

测试节点宕机

先随便set一个值

1
2
3
4
5
6
7
8
[root@paas-m-k8s-master-1 ~]# kc -n td-redis exec -it redis-jerry-0-0 -c redis-jerry-0 -- redis-cli -a 88c185e86f684251 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> set a 100
-> Redirected to slot [15495] located at 100.111.149.228:6379
OK
100.111.149.228:6379> get a
"100"
100.111.149.228:6379>

先记录一下当前的cluster nodes

1
2
3
4
5
6
7f747ee38177f5c211bd7505272a1cc75d0a24d0 100.111.149.194:6379@16379 master - 0 1687761037053 1 connected 0-5460
7e3bfda2e4d225004c977c1941f4a5054e3425d4 100.111.149.235:6379@16379 slave fb514a9f534097be588782b2210778cac026bbc8 0 1687761036553 2 connected
454db5bfed5880f430f9a60047e1780f29643d96 100.66.220.219:6379@16379 slave 7f747ee38177f5c211bd7505272a1cc75d0a24d0 0 1687761037354 1 connected
8e780f58621794552f22f4aaeee50d58d6d40658 100.66.54.125:6379@16379 slave f6b07a08be8605041fcec0eb0610783fc13fb8ef 0 1687761037053 3 connected
fb514a9f534097be588782b2210778cac026bbc8 100.66.54.70:6379@16379 master - 0 1687761037000 2 connected 5461-10922
f6b07a08be8605041fcec0eb0610783fc13fb8ef 100.111.149.228:6379@16379 myself,master - 0 1687761036000 3 connected 10923-16383

之前set的值move在100.111.149.228,那就干掉redis-jerry-2-0

1
2
[root@paas-m-k8s-master-1 ~]# kc -n td-redis delete pod redis-jerry-2-0
pod "redis-jerry-2-0" deleted

再看下pod

1
2
3
4
5
6
7
8
9
10
11
[root@paas-m-k8s-master-1 ~]# kc -n td-redis get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
predixy-redis-jerry-8d56fd856-55ds7 1/1 Running 0 3h27m 100.115.23.139 paas-m-k8s-node-5 <none> <none>
predixy-redis-jerry-8d56fd856-njbtp 1/1 Running 0 3h27m 100.105.152.53 paas-m-k8s-node-4 <none> <none>
redis-jerry-0-0 2/2 Running 0 3h28m 100.111.149.194 paas-m-k8s-node-6 <none> <none>
redis-jerry-0-1 2/2 Running 0 3h28m 100.66.220.219 paas-m-k8s-node-1 <none> <none>
redis-jerry-1-0 2/2 Running 0 3h28m 100.66.54.70 paas-m-k8s-node-3 <none> <none>
redis-jerry-1-1 2/2 Running 0 3h28m 100.111.149.235 paas-m-k8s-node-6 <none> <none>
redis-jerry-2-0 2/2 Running 0 19s 100.115.23.179 paas-m-k8s-node-5 <none> <none>
redis-jerry-2-1 2/2 Running 0 3h28m 100.66.54.125 paas-m-k8s-node-3 <none> <none>
td-redis-operator-6fd6959df9-fbwbm 1/1 Running 0 3h28m 100.108.161.170 paas-m-k8s-node-2 <none> <none>

redis-jerry-2-0的ip变为100.115.23.179

看一下redis-cluster的nodes

1
2
3
4
5
6
7
8
9
[root@paas-m-k8s-master-1 ~]# kc -n td-redis exec -it redis-jerry-0-0 -c redis-jerry-0 -- redis-cli -a 88c185e86f684251 -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> cluster nodes
454db5bfed5880f430f9a60047e1780f29643d96 100.66.220.219:6379@16379 slave 7f747ee38177f5c211bd7505272a1cc75d0a24d0 0 1687761311000 1 connected
8e780f58621794552f22f4aaeee50d58d6d40658 100.66.54.125:6379@16379 master - 0 1687761311849 4 connected 10923-16383
fb514a9f534097be588782b2210778cac026bbc8 100.66.54.70:6379@16379 master - 0 1687761310848 2 connected 5461-10922
ec0580e826a659f0280f699b82aa1daa79bf4403 100.115.23.179:6379@16379 slave 8e780f58621794552f22f4aaeee50d58d6d40658 0 1687761311047 4 connected
7e3bfda2e4d225004c977c1941f4a5054e3425d4 100.111.149.235:6379@16379 slave fb514a9f534097be588782b2210778cac026bbc8 0 1687761311548 2 connected
7f747ee38177f5c211bd7505272a1cc75d0a24d0 100.111.149.194:6379@16379 myself,master - 0 1687761311000 1 connected 0-5460

看到因为之前的redis-jerry-2-0宕机,现在redis-cluster的master已经变为redis-jerry-2-1。同时redis-jerry-2-0的ip也变为100.115.23.179

看起来没问题。

但是此时却发现一个问题,从我的电脑上predixy却连不上了

image-20230626145223025

image-20230626143932149

看看predixy出了什么问题

image-20230626144111448

当前信息打印一下

image-20230626144604769

看到,代理转发给service/redis-jerry。也没问题

但是看上面的endpoints是5个ip,正常应该是6个,describe一下看看

image-20230626144932987

NotReady的是100.115.23.179,是新创建的pod的ip,为什么会有问题呢

看predixy的日志

image-20230626150138251

有大量报错日志,还是在用删除前的pod的ip。

所以,要重启predixy

果然就好了

image-20230626150457009

这应该是predixy的配置问题。也可以说是operator需要优化,发生pod重启,需要重新部署predixy。

测试全部节点宕机

redis使用statefulset部署的。

1
2
3
4
5
[root@paas-m-k8s-master-1 ~]# kc -n td-redis get sts
NAME READY AGE
redis-jerry-0 2/2 5h2m
redis-jerry-1 2/2 5h2m
redis-jerry-2 2/2 5h2m

全部缩容到0

1
2
3
4
5
6
[root@paas-m-k8s-master-1 ~]# kc -n td-redis scale sts redis-jerry-0 --replicas=0
statefulset.apps/redis-jerry-0 scaled
[root@paas-m-k8s-master-1 ~]# kc -n td-redis scale sts redis-jerry-1 --replicas=0
statefulset.apps/redis-jerry-1 scaled
[root@paas-m-k8s-master-1 ~]# kc -n td-redis scale sts redis-jerry-2 --replicas=0
statefulset.apps/redis-jerry-2 scaled

再扩容回来

测试,

image-20230626161606367

不行了,集群无法自动恢复。

可靠性方面还需要加强。

备节点failover失败

今天redis集群遇到一个故障

1:S 18 Jul 2023 06:48:30.029 # Currently unable to failover: Disconnected from master for longer than allowed. Please check the ‘cluster-replica-validity-factor’ configuration option.

这个报错是说,备节点的数据太老了,没有办法 failover成主节点。

让我们检查cluster-replica-validity-factor参数。

cluster-replica-validity-factor

这个参数是控制什么样的从节点可以进行faiover(故障转移),变成master节点。

如果一个从节点的数据太旧了,就不能faiover成master

但是并没有一个简单的办法可以准确的判断数据的新旧程度。redis会进行下面两个判断逻辑

1、 选主从同步offset最大的

2、 比较同master最后交互的时间。发送ping或者命令都算交互

如果这个时间间隔太长了,这个从节点将不能进行failover

第2点是可以配置的。根据这个公式,得到的是最大的间隔时间

(cluster-node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period

比如,假设node-timeout是30秒,cluster-replica-validity-factor是10

repl-ping-replica-period是主备心跳间隔时间,默认是10秒

那么最后交互时间超过310秒的从节点就不能进行failover

这个从节点数据有效因子如果太大,可能导致比较旧的从节点failover成主节点。

如果太小,又可能导致没有从节点能failover成主节点。

为了保持最高的高可用性,可以将replica-validity-factor设置为0.

这样从节点总会尝试进行failover

我的错

我用的这几个配置都是默认值,按理说问题不大。

继续查看redis日志,发现是之前主从一直同步失败。

原因是之前的配置文件没有配置masterauth。所以一直无法同步数据,进而导致从节点的数据过时。

这里需要补充我之前文章里的一个错误

就是这个文档,

image-20230719144940893

configmap里的redis.conf里忘记配masterauth了。

使用Helm安装生产级别redis集群

快速安装

1,添加bitnami的仓库

1
helm repo add bitnami https://charts.bitnami.com/bitnami

2,查询redis资源

1
2
3
4
 helm search repo redis -l
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/redis 16.13.2 6.2.7 Redis(R) is an open source, advanced key-value ...
bitnami/redis-cluster 7.6.4 6.2.7 Redis(R) is an open source, scalable, distribut...

就选这个redis-cluster

加上 -l 显示所有版本的资源

CHART VERSION 是chart的版本

APP VERSION是redis的版本

3,安装

1
helm -n demo install redis-cluster bitnami/redis-cluster --version 7.6.4

-n 是指定k8s的命名空间,我这里的demo

NAME: redis-cluster
LAST DEPLOYED: Tue Jun 20 10:50:29 2023
NAMESPACE: demo
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: redis-cluster
CHART VERSION: 7.6.4
APP VERSION: 6.2.7** Please be patient while the chart is being deployed **

To get your password run:
export REDIS_PASSWORD=$(kubectl get secret –namespace “demo” redis-cluster -o jsonpath=”{.data.redis-password}” | base64 -d)

You have deployed a Redis® Cluster accessible only from within you Kubernetes Cluster.INFO: The Job to create the cluster will be created.To connect to your Redis® cluster:

  1. Run a Redis® pod that you can use as a client:
    kubectl run –namespace demo redis-cluster-client –rm –tty -i –restart=’Never’
    –env REDIS_PASSWORD=$REDIS_PASSWORD
    –image docker.io/bitnami/redis-cluster:6.2.7-debian-11-r9 – bash

  2. Connect using the Redis® CLI:

redis-cli -c -h redis-cluster -a $REDIS_PASSWORD

4,查看

可以使用helm list命令查看

1
2
3
4
[root@paas-m-k8s-master-1 7.6.4]# helm list -n demo
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
redis-cluster demo 1 2023-06-20 11:21:01.374707946 +0800 CST deployed redis-cluster-7.6.4 6.2.7

1
kc -n demo get svc,sts,pod,cm,secret,deploy

image-20230620112354167

可以看到,默认是启动了3主3从的cluster集群。使用的是statefulset。也创建了configmap和secret。

然后我们想进一步确认redis集群的状态,上面第三部的提示内容已经很清楚的告诉我们怎么连接上redis,

1,获取redis集群的密码
1
export REDIS_PASSWORD=$(kubectl get secret --namespace "demo" redis-service-redis-cluster -o jsonpath="{.data.redis-password}" | base64 --decode)

这是将redis的密码存到linux的环境变量REDIS_PASSWORD中

然后,我们可以使用

1
export | grep REDIS_PASSWORD

查看一下

2,启动一个pod做redis的client使用
1
2
3
kubectl run --namespace demo redis-cluster-client --rm --tty -i --restart='Never' \
--env REDIS_PASSWORD=$REDIS_PASSWORD \
--image docker.io/bitnami/redis-cluster:6.2.3-debian-10-r2 -- bash

这里就用上了环境变量REDIS_PASSWORD

进入redis

1
2
3
4
redis-cli -c -h redis-cluster -a $REDIS_PASSWORD
> cluster info
> cluster nodes
查看一下集群状态

image-20210518160347948

自定义安装

我们也可以通过修改values.yaml中的参数配置实现自定义安装

1,下载chart

1
helm pull bitnami/redis-cluster --version 7.6.4

下载下来的是一个压缩包redis-cluster-7.6.4.tgz

加上 –version 现在指定chart版本的资源

解压,

image-20210518154905597

里面的values.yaml就是我们需要的

2,修改配置

image-20210518155154288

redis的版本

image-20210518155044283

可以看到,这里默认是3主3从的6个节点。

3,安装自己的chart

cd到解压后的redis-cluster目录的上一级

1
helm install my-redis redis-cluster -n demo

helm卸载redis

1
helm uninstall redis-cluster -n demo

测试

image-20210609105657273

1,停掉一个redis主节点

image-20210609110153589

1
kubectl delete pod redis-service-redis-cluster-2 -n demo

image-20210609110454578

image-20210609110515485

k8s自动重启pod,使用新的ip。但是redis集群可以自动感知到,并更新了集群节点信息。

通过压测代码发现

1623209212219 2021-06-09 11:26:52

1623209213393 2021-06-09 11:26:53

有1秒钟的cluster is down

image-20210609113018410

进到容器里看看

1
kc -n demo exec -it redis-cluster-0 -- /bin/sh

找到其数据目录

1
2
3
4
$ pwd
/bitnami/redis/data
$ ls
appendonly.aof dump.rdb lost+found nodes.conf nodes.sh

发现有一个nodes.sh

1
declare -A host_2_ip_array=([redis-cluster-1.redis-cluster-headless]="100.66.54.98" [redis-cluster-2.redis-cluster-headless]="100.115.23.182" [redis-cluster-4.redis-cluster-headless]="100.111.149.249" [redis-cluster-3.redis-cluster-headless]="100.105.152.11" [redis-cluster-0.redis-cluster-headless]="100.108.161.145" [redis-cluster-5.redis-cluster-headless]="100.66.220.245" )

记录了域名和ip的对应关系

2,模拟全部pod宕机

通过将statefulset的pod数量降到0,模拟整个redis集群宕机

1
kc -n demo scale sts redis-cluster --replicas=0

然后把副本数再恢复到6

1
kc -n demo scale sts redis-cluster --replicas=6

image-20230620143617496

发现其pod会自动重启几次,完成对redis集群的恢复。

这个容灾能力还是可以的。

查看redis-cluster的statefulset的定义。

image-20230621141331982

猜测是根据pod的名字dns解析出新的ip,从而重新组织起集群。代码实现还是比较复杂的。

主从同步

第一次主从同步流程

image-20220113171749651

  1. 从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数 来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。

    runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实 例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设 为“?”。 offset,此时设为 -1,表示第一次复制

  2. 主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库 目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。

    FULLRESYNC 响应表示第一次复制采用的全量复制,

剩下的阶段比较简单。

所以建议:

一个 Redis 实例的数据库不要太大,一个实例大小在几 GB 级别 比较合适,这样可以减少 RDB 文件生成、传输和重新加载的开销。

如果有多个从库,可以采用“主 - 从 - 从”复制模式。

主从断连恢复

从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步

当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也 会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己 已经读到的位置。

image-20220113172522671

要强调一下,因为 repl_backlog_buffer 是一个环形缓冲区,所以在 缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速 度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库 间的数据不一致。

注意这个runID

runID

每个redis实例在启动时候,都会随机生成一个长度为40的唯一字符串来标识当前运行的redis节点,查看此id可通过命令info server查看。

如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会更具offset偏移量之后的数据判断是否执行部分复制,如果offset偏移量之后的数据仍然都在复制积压缓冲区里,则执行部分复制,否则执行全量复制;

如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的redis节点并不是当前的主节点,只能进行全量复制;

每次重启runID会变,所以如果master宕机,再重启.按着这个逻辑,是需要进行全量同步的.

redis4.0部分同步优化

redis4.0新增两个复制id,通过info replication查看

image-20220113174115491

1
2
master_replid:e921804a4f45d1a4a85f33bc6cded39df82885da
master_replid2:0000000000000000000000000000000000000000

去对用的从节点看一下

image-20220113174908585

第一组:master_replid和master_repl_offset:如果redis是主实例,则表示为自己的replid和复制偏移量; 如果redis是从实例,则表示为自己主实例的replid1和同步主实例的复制偏移量。

第二组:master_replid2和second_repl_offset:无论主从,都表示自己上次主实例repid1和复制偏移量;用于兄弟实例或级联复制,主库故障切换psync。

redis在关闭时,把当前实例的repl-id和repl-offset保存到RDB文件中,

redis加载RDB文件,会专门处理文件中辅助字段(AUX fields)信息,把其中repl_id和repl_offset加载到实例中,分别赋给master_replid和master_repl_offset两个变量值,特别注意当从库开启了AOF持久化,redis加载顺序发生变化优先加载AOF文件,但是由于aof文件中没有复制信息,所以导致重启后从实例依旧使用全量复制!

从节点上报复制请求时,传master_replid和master_repl_offset

主节点判断这两个信息,不再判断runid

判断是否使用部分复制条件:如果从库提供的master_replid与master的replid不同,且与master的replid2不同,或同步的offset快于master; 就必须进行全量复制,否则执行部分复制。

也就是说如果现在的主从,两者之前都是某一个master的从,并且进行了同步.当之前的这个master宕机以后,两个从的一个升级未主,另一个在这种情况下只需要进行部分复制.

img

但是如果进行了主从切换,之前的主再重新起来.如果能从rdb中恢复master_replid,理论上来说应该是会进行部分同步,但是如果用的aof,丢掉了master_replid应该会全量同步.(待实验!!)

高效的数据结构

都知道redis快,那为什么redis可以这么快?微秒级

一方面是因为纯内存访问。另一方面,也是更关键的点是redis采用的高效的数据接口。

这一块是很值得学习的。不是总说数据结构和算法没用吗?

redis主要有5中数据类型:string,list,hash,set,sorted set

每种类型都用自己的底层实现数据结构

image-20220113101143671

这是值的数据结构。

redis找到数据,首先要解决键找到值的问题。

redis使用哈希表来保存所有的键值对。

哈希表最大的好处就是可以用O(1)的时间复杂度来查找键值对。

但是当哈希表中数据太多,哈希冲突不可避免。哈希值相同的key放在同一个哈希桶中。

Redis解决哈希冲突的方式就是链式哈希。就是同一个哈希桶中的多个元素用链表来保存。

这样会有另一个问题,当这个桶里的数据越来越多,链越来越长。我们知道链的特性是插入快,查询慢。

所以Redis会对哈希表做rehash操作。rehash会增加桶的数量。

rehash

redis的具体做法,redsi默认使用两个哈希表,哈希表1和哈希表2。

最开始一直使用哈希表1。哈希表2还是空的,没有分配空间。当哈希表1中数据逐渐增多,达到一个阈值。开始进行rehash

  1. 给哈希表2分配更大的空间。比如2倍
  2. 把哈希表1中的数据拷贝到哈希表2中,同时重新进行hash映射
  3. 切换到哈希表2,释放哈希表1的空间

这里的难点在第二步,如果一次性全量copy,会造成redis线程阻塞。redis的方案时进行渐进式rehash

渐进式rehash

简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求 时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝 到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries

rehash是以bucket(桶)为基本单位进行渐进式的数据迁移的,每步完成一个bucket的迁移,直至所有数据迁移完毕

因为在进行渐进式 rehash 的过程中, 会同时使用两个哈希表, 所以在渐进式 rehash 进行期间, 删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 h哈希表1 里面进行查找, 如果没找到的话, 就会继续到 哈希表2 里面进行查找, 诸如此类。

另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到哈希表2 里面, 而 哈希表1 则不再进行任何添加操作: 这一措施保证了 哈希表1包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。

什么时候做 rehash

Redis 会使用装载因子(load factor)来判断是否需要做 rehash。

装载因子的计算方式 是,哈希表中所有 entry 的个数除以哈希表的哈希桶个数。Redis 会根据装载因子的两种 情况,来触发 rehash 操作:

装载因子≥1,同时,哈希表被允许进行 rehash;

装载因子≥5

在第一种情况下,如果装载因子等于 1,同时我们假设,所有键值对是平均分布在哈希表 的各个桶中的,那么,此时,哈希表可以不用链式哈希,因为一个哈希桶正好保存了一个 键值对。 但是,如果此时再有新的数据写入,哈希表就要使用链式哈希了,这会对查询性能产生影 响。在进行 RDB 生成和 AOF 重写时,哈希表的 rehash 是被禁止的,这是为了避免对 RDB 和 AOF 重写造成影响。如果此时,Redis 没有在生成 RDB 和重写 AOF,那么,就 可以进行 rehash。否则的话,再有数据写入时,哈希表就要开始使用查询较慢的链式哈希 了。

在第二种情况下,也就是装载因子大于等于 5 时,就表明当前保存的数据量已经远远大于 哈希桶的个数,哈希桶里会有大量的链式哈希存在,性能会受到严重影响,此时,就立马 开始做 rehash。 刚刚说的是触发 rehash 的情况,如果装载因子小于 1,或者装载因子大于 1 但是小于 5, 同时哈希表暂时不被允许进行 rehash(例如,实例正在生成 RDB 或者重写 AOF),此 时,哈希表是不会进行 rehash 操作的。

采用渐进式 hash 时,如果实例暂时没有收到新请求,是不是就不做 rehash 了?

其实不是的。Redis 会执行定时任务,定时任务中就包含了 rehash 操作。所谓的定时任 务,就是按照一定频率(例如每 100ms/ 次)执行的任务。 在 rehash 被触发后,即使没有收到新请求,Redis 也会定时执行一次 rehash 操作,而 且,每次执行时长不会超过 1ms,以免对其他任务造成影响。

渐进式rehash的问题

在rehash时,需要分配一个新的hash表,在rehash期间,同时有两个hash表在使用,会使得redis内存使用量瞬间突增,在Redis 满容状态下由于Rehash会导致大量Key驱逐。

通过key找到了value。如果value是string,那么取出来就行了。

如果是其他四种集合类型,对集合进行操作又涉及了不同的数据结构。

比如哈希表结构要比链表结构的访问效率更高。

从上图中,看到redis的集合有5中底层的数据结构:哈希表,数组,双向链表,压缩列表,跳表。

数据类型和底层数据结构

整数数组

整数数组和压缩列表的设计,充分体现了 Redis“又快又省”特点中的“省”,也就是节 省内存空间。整数数组和压缩列表都是在内存中分配一块地址连续的空间,然后把集合中 的元素一个接一个地放在这块空间内,非常紧凑。因为元素是挨个连续放置的,我们不用 再通过额外的指针把元素串接起来,这就避免了额外指针带来的空间开销。

压缩列表

压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同 的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的 偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。

压缩列表

在压缩列表中,如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段 的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查 找,此时的复杂度就是 O(N) 了。

image-20220114111518175

每个元素的元数据的内容

prev_len,表示前一个 entry 的长度。prev_len 有两种取值情况:1 字节或 5 字节。 取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数 值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上 一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。

len:表示自身长度,4 字节;

encoding:表示编码方式,1 字节;

content:保存实际数据。

这些 entry 会挨个儿放置在内存中,不需要再用额外的指针进行连接,这样就可以节省指 针所占用的空间。

跳表

有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。具体来说,跳 表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位,

image-20220113110243834

当数据量很大时,跳表的查找复杂度就是 O(logN)。

Hash 类型底层结构什么时候使用压缩列表,什么时候使用哈希表呢?

Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表 来保存数据了。

hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。

hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

如果我们往 Hash 集合中写入的元素个数超过了 hash-max-ziplist-entries,或者写入的 单个元素大小超过了 hash-max-ziplist-value,Redis 就会自动把 Hash 类型的实现结构 由压缩列表转为哈希表。

不同操作的复杂度

口诀:

1
2
3
4
单元素操作是基础;
范围操作非常耗时;
统计操作通常高效;
例外情况只有几个。

范围操作的复杂度一般是 O(N),比较耗时, 我们应该尽量避免

不过,Redis 从 2.8 版本开始提供了 SCAN 系列操作(包括 HSCAN,SSCAN 和 ZSCAN),这类操作实现了渐进式遍历,每次只返回有限数量的数据。这样一来,相比于 HGETALL、SMEMBERS 这类操作来说,就避免了一次性返回所有元素而导致的 Redis 阻 塞。

redis的单线程:

我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。 但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执 行的。

Redis的网络模式是单reactor模式。select/epoll 提供了基于事件的回调机制。IO多路复用。

和netty的主从Reactor的区别在于,netty单线程处理IO,线程池处理业务。redis是一个线程处理io和业务。

据说redis6.0推出了多线程。和netty很像了,搞一个单独的线程处理io。

再联想到了dubbo的dispacher,all模式下就是这样io和业务请求都交给业务线程池处理了。message模式下二者分离开。

运维手段

info命令

最基本的监控运维手段。

Prometheus

接入Prometheus监控,

Prometheus 提供了插件功能来实现对一个系统的监控,我们把插件称为 exporter, 每一个 exporter 实际是一个采集监控数据的组件。

Redis-exporter就是用来监控 Redis 的

慢查询日志

慢查询日志的两个参数:

  • slowlog-log-slower-than:这个参数表示,慢查询日志对执行时间大于多少微秒的命 令进行记录。
  • slowlog-max-len:这个参数表示,慢查询日志最多能记录多少条命令记录。默认是 128。建议设置为 1000 左右

命令:

1
SLOWLOG GET 1

除了慢查询日志以外,Redis 从 2.8.13 版本开始,还提供了 latency monitor 监控工具, 这个工具可以用来监控 Redis 运行过程中的峰值延迟情况

1
2
config set latency-monitor-threshold 1000
latency latest

查询bigkey

在执行 redis-cli 命令时带上–bigkeys 选项,进而对整个数据库中的键值对大小 情况进行统计分析。比如说,统计每种数据类型的键值对个数以及平均大小。

这个 命令执行后,会输出每种数据类型中最大的 bigkey 的信息

1
./redis-cli --bigkeys

不过,在使用–bigkeys 选项时,有一个地方需要注意一下。这个工具是通过扫描数据库来 查找 bigkey 的,所以,在执行的过程中,会对 Redis 实例的性能产生影响

如果你在使 用主从集群,我建议你在从节点上执行该命令。因为主节点上执行时,会阻塞主节点。

你两个小建议:

第一个建议是,在 Redis 实例业务压力的低峰 阶段进行扫描查询,以免影响到实例的正常运行;

第二个建议是,可以使用 -i 参数控制扫 描间隔,避免长时间扫描降低 Redis 实例的性能。

1
./redis-cli --bigkeys -i 0.1

对于集合类型来说,这个方法只统计集合元素个数的多少,而不是实际占用的内存量。 但是,一个集合中的元素个数多,并不一定占用的内存就多。

使用 MEMORY USAGE 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间

1
MEMORY USAGE user:info

查询主从复制

Redis 的 INFO replication 命令

master_repl_offset:主库接收写命令的进度信息

slave_repl_offset:从库复制写命令的进度信息

slot操作

查看slot

1
cluster slots

迁移slot

1
2
3
CLUSTER SETSLOT:使用不同的选项进行三种设置,分别是设置 Slot 要迁入的目标实例,Slot 要迁出的源实例,以及 Slot 所属的实例。
CLUSTER GETKEYSINSLOT:获取某个 Slot 中一定数量的 key。
MIGRATE:把一个 key 从源实例实际迁移到目标实例。

假设我们要把 Slot 300 从源实例(ID 为 3)迁移到目标实例(ID 为 5),那要怎么做 呢? 实际上,我们可以分成 5 步。

第 1 步,我们先在目标实例 5 上执行下面的命令,将 Slot 300 的源实例设置为实例 3,表 示要从实例 3 上迁入 Slot 300。

1
CLUSTER SETSLOT 300 IMPORTING 3

第 2 步,在源实例 3 上,我们把 Slot 300 的目标实例设置为 5,这表示,Slot 300 要迁 出到实例 5 上,如下所示:

1
CLUSTER SETSLOT 300 MIGRATING 5

第 3 步,从 Slot 300 中获取 100 个 key。因为 Slot 中的 key 数量可能很多,所以我们需 要在客户端上多次执行下面的这条命令,分批次获得并迁移 key。

1
CLUSTER GETKEYSINSLOT 300 100

第 4 步,我们把刚才获取的 100 个 key 中的 key1 迁移到目标实例 5 上(IP 为 192.168.10.5),同时把要迁入的数据库设置为 0 号数据库,把迁移的超时时间设置为timeout。我们重复执行 MIGRATE 命令,把 100 个 key 都迁移完。

1
MIGRATE 192.168.10.5 6379 key1 0 timeout

第5步,我们重复执行第 3 和第 4 步,直到 Slot 中的所有 key 都迁移完成。

从 Redis 3.0.6 开始,你也可以使用 KEYS 选项,一次迁移多个 key(key1、2、3),这 样可以提升迁移效率。

1
MIGRATE 192.168.10.5 6379 "" 0 timeout KEYS key1 key2 key3

命令重命名

高风险的命令,例如 KEYS、FLUSHDB、FLUSHALL 等

禁用命令,修改服务器的配置文件redis.conf,在SECURITY这一项中,我们新增以下命令:

1
2
3
4
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command CONFIG ""
rename-command KEYS ""

如果还想保留命令,只是想改名

1
2
3
4
rename-command FLUSHALL joYAPNXRPmcarcR4ZDgC81TbdkSmLAzRPmcarcR
rename-command FLUSHDB qf69aZbLAX3cf3ednHM3SOlbpH71yEXLAX3cf3e
rename-command CONFIG FRaqbC8wSA1XvpFVjCRGryWtIIZS2TRvpFVjCRG
rename-command KEYS eIiGXix4A2DreBBsQwY6YHkidcDjoYA2DreBBsQ

说明:对于FLUSHALL命令,需要设置配置文件中appendonly no,否则服务器无法启动哦~

数据迁移工具 Redis-shake

是阿里云 Redis 和 MongoDB 团队开发的一个用于 Redis 数据同步的 工具。

Redis-shake 的基本运行原理,是先启动 Redis-shake 进程,这个进程模拟了一个 Redis 实例。然后,Redis-shake 进程和数据迁出的源实例进行数据的全量同步。 这个过程和 Redis 主从实例的全量同步是类似的

源实例相当于主库,Redis-shake 相当于从库,源实例先把 RDB 文件传输给 Redis-shake,Redis-shake 会把 RDB 文件发送给目的实例。接着,源实例会再把增量命令发送 给 Redis-shake,Redis-shake 负责把这些增量命令再同步给目的实例。

集群管理工具 CacheCloud

sohu开源的Redis 运维管理的云平台

Redis5.0新特性

stream–一种新的数据类型

之前redis共有7中数据类型:

字符串(string),哈希(hash),列表(list),集合(set),有序集合(sorted set)

位图 ( Bitmaps ),基数统计 ( HyperLogLogs )

现在又加了一种stream

Redis stream本质上是个时序数据结构

支持多播的可持久化的消息队列,用于实现发布订阅功能

集群管理功能集成到redis-cli

在Redis4.x及之前版本,需要安装redis-trib模块管理集群,因为绝大部分集群功能需要依赖redis-trib模块实现。

Redis 5.x及以上版本彻底抛弃了ruby,将集群管理功能集成到了redis-cli工具中。

配置文件redis.conf

  1. salve名词全部替换为replica

    因为政治正确的原因,slave这个词不能用了,所有redis.conf中全部替换为replica

    image-20230215144235885

​ 所以,主从配置由以前的slaveof <masterip> <masterport> 改为 replicaof <masterip> <masterport>

​ 还有很多主从配置的命令也同样修改了

  1. 从5.0开始,备节点默认将忽略maxmemory这个参数

    过期数据的淘汰只会发生在主节点,然后同步del命令给备节点进行删除

  2. aof-use-rdb-preamble默认为打开了

    这个功能就是当aof文件进行重写时,用rdb格式保存当前的数据,新的数据用aof追加

    以前这个配置默认时关闭的,现在改为打开了

  3. 动态hz参数dynamic-hz

    hz参数用于指定Redis后台任务的执行频率,这些任务包括关闭超时的客户端连接、主动清除过期key等。

    5.0之前的redis版本,hz参数一旦设定之后就是固定的了。

    hz默认是10。这也是官方建议的配置。

    如果改大,表示在reids空闲时会用更多的cpu去执行这些任务。官方并不建议这样做。

    但是,如果连接数特别多,在这种情况下应该给与更多的cpu时间执行后台任务。

    所以有了这个dynamic-hz参数,默认就是打开。当连接数很多时,自动加倍hz,以便处理更多的连接。

  4. 增加了一个rdb持久化时优化

    rdb-save-incremental-fsync默认就是true

    rdb文件每增加32mb 就执行fsync一次(增量式,避免一次性大写入导致的延时)

    这个是针对rdb模式的优化,aof模式在4版本的时候已经有这个优化了aof-rewrite-incremental-fsync

  5. 增加了绑核配置
    可以将不同的redis的线程和进程绑定到特定的cpu上,以便最大化利用服务器的性能。
    这项能力在将redis的不同线程绑定到不同cpu时很有用,更有用的场景是在一台server上
    启动多个redis实例,可以将不同的redis绑定到指定的cpu上。
    语法和taskset命令(linux的绑核命令)一样

  6. 能够在arm上启动redis了
    在arm64架构下启动redis会报ARM64-COW-BUG警告,redis不能启动。可以开启这个忽略
    ignore-warnings ARM64-COW-BUG