Redis集群

对于小型项目,使用一台 Redis 服务器已经足够了,然而现实中的项目通常需要若干台 Redis 服务器的支持:

 

☆ 从结构上:单个 Redis 服务器会发生单点故障,同时一台服务器需要承受所有的请求负载。这就需要为数据生成多个副本并分配在不同的服务器上;

 

☆ 从容量上:单个 Redis 服务器的内存非常容易成为存储瓶颈,所以需要进行数据分片。

 

1、复制

 

为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此,Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。

 

1.1、配置

 

在复制的概念中,数据库(泛指 Redis 服务器)分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。

 

一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。从数据库也可以拥有从数据库。

 

在 Redis 中使用复制功能非常容易,只需要在从数据库的配置文件中加入“slaveof 主数据库地址 主数据库端口”即可,主数据库无需进行任何配置。

 

redis-server --port 6380 --slaveof 127.0.0.1 6379

 

redis-cli -p 6379

 

INFO replication 查看节点信息

 

可以通过设置从数据库的配置文件中的 slave-read-only 为 no 以使从数据库可写。但是对从数据库的任何更改都不会同步给任何其他数据库。通常场景下不应该设置从数据库可写。

 

除了通过配置文件或命令行参数设置 slaveof 参数,还可以在运行时使用 SLAVEOF 命令修改: SLAVEOF 127.0.0.1 6379

 

如果该数据库已经是其他主数据库的从数据库了, SLAVEOF 命令会停止和原来数据库的同步转而和新数据同步。

 

对于从数据库来说,还可以使用 SLAVEOF NO ONE 命令来使当前数据库停止接收其他数据库的同步并转换成为主数据库。

 

1.2、复制原理

 

复制初始化:当一个从数据库启动后,会向主数据库发送 SYNC 命令。同时主数据库接收到 SYNC 命令后会开始在后台保存快照(即 RDB 持久化的过程),并将保存快照期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从数据库。

 

从数据库收到后,会载入快照文件并执行收到的缓存命令。从数据库收到后,会载入快照文件并执行收到的缓存命令。

 

复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数据库,从而保证主从数据库数据一致。

 

当主从数据库之间的连接断开重连后,Redis 2.6 以及之前的版本会重新进行复制初始化,即使从数据库可以仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使得主从数据库断线重连后的数据恢复过程效率很低下。

 

Redis 2.8 版的一个重要改进就是断线重连能够支持有条件的增量数据传输,当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从数据库,从而大大提高 Redis 复制的实用性。

 

复制初始化阶段结束后,主数据库执行的任何会导致数据变化的命令都会异步地传送给从数据库,这一过程为复制同步阶段。复制同步阶段会贯穿整个主从同步过程的始终,直到主从关系终止为止。

 

乐观复制:Redis 采用了乐观复制(optimistic replication)的复制策略,容忍在一定时间内主从数据库的内容是不同的,但是两者的数据会最终同步。

 

Redis 提供了两个配置选项来限制只有当数据至少同步给指定数量的从数据库时,主数据库才是可写的:

 

min-slaves-to-write 3 表示只有当3个或3个以上的从数据库连接到主数据库时,主数据库才是可写的,否则会返回错误。

 

min-slaves-max-lag 10 表示允许从数据库最长失去连接的时间,如果从数据库最后于主数据库联系的时间小于这个值,则认为从数据库还在保持与主数据库的连接。

 

1.3、从数据库持久化

 

为了提高性能,可以通过复制功能建立一个(或若干个)从数据库,并在从数据库中启用持久化,同时在主数据库禁用持久化。当从数据库崩溃重启后主数据库会自动将数据同步过来,所以无需担心数据丢失。

 

当主数据库崩溃时,情况就稍显复杂了。手工通过从数据库数据恢复主数据库数据时,需要严格按照以下两步执行:

 

1、在从数据库中使用 SLAVEOF NO ONE 命令将从数据库提升成主数据库继续服务;

 

2、启动之前崩溃的主数据库,然后使用 SLAVEOF 命令将其设置成新的主数据库的从数据库,即可将数据同步回来。

 

当开启复制且主数据库关闭持久化功能时,一定不要使用工具另主数据库崩溃后自动重启。同样当主数据库所在的服务器因故关闭时,也要避免直接重新启动。

 

1.4、无硬盘复制

 

从2.8.18版本开始,Redis 引入了无硬盘复制选项,开启该选项时,Redis 在与从数据库进行复制初始化时将不会将快照内容存储在硬盘上,而是直接通过网络发送给从数据库,避免了硬盘的性能瓶颈。

 

目前无硬盘复制的功能还在试验阶段,可以在配置文件中使用如下配置来开启该功能: repl-diskless-sync yes

 

1.5、增量复制

 

增量复制是基于如下3点实现的:

 

1、从数据库会存储主数据库的运行ID(run id)。每个 Redis 运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID;

 

2、在复制同步阶段,主数据库每将一个命令传送给从数据库时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中存放的命令的偏移量范围;

 

3、同时,从数据库接收到主数据库传来的命令时,会记录下该命令的偏移量。

 

决定是否可以执行增量复制的条件:

 

1、首先主数据库会判断从数据库传送过来的运行ID是否和自己的运行ID相同。这一步骤的意义在于确保从数据库之前确实是和自己同步的,以免从数据库拿到错误的数据。

 

2、然后判断从数据库最后同步成功的命令偏移量是否在积压队列中,如果在则可以执行增量复制,并将积压队列中相应的命令发送给从数据库。

 

如果此次重连不满足增量复制的条件,主数据库会进行一次全部同步。

 

积压队列在本质上是一个固定长度的循环队列,默认情况下积压队列的大小为 1MB,可以通过配置文件的 repl-backlog-size 选项来调整。

 

与积压队列相关的另一个配置选项是 repl-backlog-ttl ,即当所有从数据库与主数据库断开连接后,经过多久时间可以释放积压队列的内存空间。默认时间是 1 小时。

 

2、哨兵

 

哨兵的作用就是监控 Redis 系统的运行情况。它的功能包括以下两个:

 

1、监控主数据库和从数据库是否正常;主数据库出现故障时自动将从数据库转换为主数据库。

 

2、在一个一主多从的 Redis 系统中,可以使用多个哨兵进行监控任务以保证系统足够稳健。哨兵之间也可以互相监控。

 

一个哨兵节点可以同时监控多个 Redis 主从系统,只需要提供多个 sentinel monitor 配置即可。同时多个哨兵节点也可以同时监控同一个Redis主从系统,从而形成网状结构。

 

2.1、配置哨兵

 

建立一个配置文件,如 sentinel.conf ,内容为: sentinel monitor mymaster 127.0.0.1 6379 1

 

其中 mymaster表示要监控的主数据库的名字,可以自己定义一个。这个名字必须仅由大小写字母、数字和“.-_”这三个字符组成。最后的 1 表示最低通过票数。

 

启动 哨兵 进程:redis-sentinel /path/to/sentinel.conf /path/to/sentinel.conf为建立的配置文件的路径。

 

配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。

 

2.2、实现原理

 

哨兵启动后,会与要监控的主数据库建立两条连接,这两个连接的建立方式与普通的 Redis 客户端无异。

 

其中一条连接咏来订阅该主数据库的 __sentinel__:hello 频道以获取其他同样监控该数据库的哨兵节点的信息;另一条连接用来供哨兵向主数据库发送 INFO 等命令。

 

和主数据库建立连接完成后,哨兵会定时执行下面3个操作:

 

1、每10秒哨兵会向主数据库和从数据库发送 INFO 命令;————获得当前数据库的相关信息从而实现新节点的自动发现。

 

2、每 2 秒哨兵会向主数据库和从数据库的 __sentinel__:hello 频道发送自己的信息;————与同样监控该数据库的哨兵分享自己的信息。

 

3、每1秒哨兵会向主数据库、从数据库和其他哨兵节点发送 PING 命令。

 

这3个操作贯穿哨兵进程的整个生命周期中。

 

当超过 down-after-milliseconds 选项指定时间后,如果被 PING 的数据库或节点仍然未进行回复,则哨兵认为其 主观下线(subjectively down)。主观下线表示从当前的哨兵进程看来,该节点已经下线。

 

如果该节点是主数据库,则哨兵会进一步判断是否需要对其进行故障恢复:哨兵发送 SENTINEL is-master-down-by-addr 命令询问其他哨兵节点以了解他们是否也认为该主数据库主观下线,如果达到指定数量时,

 

哨兵会认为其客观下线(objectively down),并选举领头的哨兵节点对主从系统发起故障恢复。这个指定数量即为配置的最低通过票数。

 

虽然当前哨兵节点发现了主数据库客观下线,需要故障恢复,但是故障恢复需要由领头的哨兵来完成,这样可以保证同一时间只有一个哨兵节点来执行故障恢复。选举领头哨兵的过程使用了 Raft 算法,具体过程如下:

 

1)、发现主数据库客观下线的哨兵节点(下面称作 A)向每个哨兵节点发送命令,要求对方选自己成为领头哨兵。

 

2)、如果目标哨兵节点没有选过其他人,则会同意将 A 设置成领头哨兵。

 

3)、如果A发现有超过半数且超过 quorum 参数值的哨兵节点同意选自己成为领头哨兵,则 A 成功成为领头哨兵。

 

4)、当有多个哨兵节点同时参选领头哨兵,则会出现没有任何节点当选的可能。此时每个参选节点将等待一个随机时间重新发起参选请求,进行下一轮选举,知道选举成功。

 

选出领头哨兵后,领头哨兵将会开始对数据库进行故障恢复。故障恢复的过程相对简单,具体如下:

 

首先领头哨兵将从停止服务的主数据库的从数据库中挑选一个来充当新的主数据库。挑选的依据如下:

 

1)、所有在线的从数据库中,选择优先级最高的从数据库。优先级可以通过 slave-priority 选项来配置。

 

2)、如果有多个最高优先级的从数据库,则复制的命令偏移量越大(即复制完整性)越优先。

 

3)、如果以上条件都一样,则选择运行 ID 较小的从数据库。

 

选出一个从数据库后,领头哨兵将向从数据库发送  SLAVEOF  NO  ONE 命令使其升格为主数据库。

 

而后领头哨兵向其他从数据库发送 SLAVEOF 命令来使其成为新主数据库的从数据库。

 

最后一步则是更新内部的记录,将已经停止服务的旧的主数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库的身份继续服务。

 

2.3、哨兵的部署

 

哨兵以独立进程的方式对一个主从系统进行监控,监控的效果好坏与否取决于哨兵的视角是否有代表性。如果一个主从系统中配置的哨兵较少,哨兵对整个系统的判断的可靠性就会降低。

 

极端情况下,当只有一个哨兵时,哨兵本身就可能会发生单点故障。整体来讲,相对稳妥的哨兵部署方案是使得哨兵的视角尽可能地与每个节点的视角一致,即

 

1)、为每个节点(无论是主数据库还是从数据库)部署一个哨兵;——每个哨兵都会和系统中的所有节点建立连接。

 

2)、使每个哨兵与其对应的节点的网络环境相同或相近。

 

这样的部署方案可以保证哨兵的视角拥有较高的代表性和可靠性。同时设置 quorum 的值为 N/2 + 1 (其中 N 为哨兵节点数量),这样使得只有当大部分哨兵节点同意后才会进行故障恢复。

 

3、集群

 

Redis 3.0 版的一大特性就是支持集群(Cluster)功能。集群的特点在于拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。

 

另外集群支持几乎所有所有的单机实例支持的命令,对于涉及多键的命令(如 MGET),如果每个键都位于同一个节点中,则可以正常支持,否则会提示错误。

 

除此之外集群还有一个限制是只能使用默认的 0 号数据库,如果执行 SELECT 切换数据库则会提示错误。

 

哨兵和集群是两个独立的功能,但从特性来看哨兵可以视为集群的自己,当不需要数据分片或者已经在客户端进行分片的场景下哨兵就足够用了,但如果需要进行水平扩容,则集群是一个非常好的选择。

 

3.1、配置集群

 

使用集群,只需要将每个数据看节点的 cluster-enabled 配置选项打开即可。每个集群中至少需要3个主数据库才能正常运行。 cluster-enabled yes

 

Redis源代码中提供了一个辅助工具 redis-trib.rb 可以非常方便地完成这一任务。因为 redis-trib.rb 是用 Ruby 语言编写的,所以运行前需要在服务器上安装 Ruby 程序。redis-trib.rb 依赖于 gem 包 redis,可执行 gem install redis来安装。

 

使用 redis-trib.rb 来初始化集群,只需要执行: redis-trib.rb create --replicas 1 127.0.0.1:6380  127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385   

 

其中 create 参数表示要初始化集群,--replicas 1 表示每个主数据库拥有的从数据库个数个1,所以整个集群共有3(6/2)个主数据库以及3个从数据库。

 

执行完上述命令后,redis-trib.rb 会输出集群具体的分配方案,如果觉得没问题则输入 yes 来开始创建。创建过程如下:

 

1、首先 redis-trib.rb 会以客户端的形式尝试连接所有的节点,并发送 PING 命令以确定节点能正常服务。如果有任何节点无法连接,则创建失败。同时发送 INFO 命令获取每个节点的运行 ID 以及是否开启了集群功能(即cluster-enabled 为1)。

 

准备就绪后集群会向每个节点发送 CLUSTER MEET 命令,格式为 CLUSTER MEET ip port,这个命令用来高速当前节点指定 ip 和 port 上在运行的节点也是集群的一部分。

 

2、然后 redis-trib.rb 会分配主从数据库节点,分配的原则是尽量保证每个主数据库运行在不同的 IP 地址上,同时没个从数据库和主数据库均不运行同一 IP 地址上,以保证系统的容灾能力。

 

分配完成后,会为每个主数据库分配插槽,分配插槽的过程其实就是分配哪些键归哪些节点负责。之后对每个要成为子数据库的节点发送 CLUSTER REPLICATE 主数据库的运行 ID 来将当前节点转换成从数据库并复制指定的ID的节点。

 

此时整个集群的过程即创建完成,使用 Redis 命令行客户端连接任意一个节点执行 CLUSTER NODES 可以获得集群中的所有节点的信息。

 

3.2、节点的增加

 

加入新节点只需要向新节点(以下记做A)发送如下命令即可:CLUSTER MEET ip port,ip 和 port 是集群中任意一个节点的地址和端口号。

 

3.3、插槽的分配

 

新的节点加入集群后有两种选择,要么使用 CLUSTER REPLICATE 命令复制每个主数据库来以从数据库的形式运行,要么向集群申请分配插槽(slot)来以主数据库的形式运行。

 

在一个集群中,所有的键会被分配给 16384 个插槽,而每个主数据库会负责处理其中的一部分插槽。

 

键与插槽的对应关系:Redis将每个键的键名的有效部分使用 CRC16 算法计算出散列值,然后去对 16384 的余数。这样使得每个键都可以分配到 16384 个插槽中,进而分配给指定的一个节点中处理。键名的有效部分是指:

 

1、如果键名包含 { 符号,且在 { 符号后面存在 } 符号,并且 { 和 } 之间有至少一个字符,则有效部分是指 { 和 } 之间的内容;

 

2、如果不满足上一条规则,那么整个键名为有效部分。

 

插槽的分配分为如下几种情况:

 

1、插槽之前没有被分配过,现在想分配给指定节点。——使用 CLUSTER ADDSLOTS 命令实现。命令用法为:CLUSTER ADDSLOTS slot1 [slot2] ... [slotN] 。可以通过 CLUSTER ADDSLOTS 来查看插槽的分配情况。

 

2、插槽之前被分配过,现在想移动到指定节点。——使用命令 CLUSTER SETSLOT 插槽号 NODE 新节点的运行 ID。使用此命令迁移插槽时并不会连同相应的键一起迁移。

 

4、集群配置过程中遇到的问题

 

1、使用命令 ./redis-server 启动节点,发现 redis.conf   中的配置未生效(端口配置、cluster-enabled 配置)

 

使用命令  ./redis-server ../redis.conf  启动节点即可。

 

2、执行 ./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 10.10.77.86:6379 10.10.77.86:6380 10.10.77.86:6381   出现  /usr/bin/env: ruby: 没有那个文件或目录

 

redis-trib.rb是用Ruby语言编写的,所以运行前需要在服务器上安装Ruby程序。

 

3、安装Ruby    

 

 

tar -xvzf ruby-2.4.1.tar.gz

 

cd ruby-2.4.1

 

  • 配置并编译源代码

 

$ ./configure

 

$ make

 

$ sudo make install

 

  • 安装后,通过在命令行中输入以下命令来确保一切工作正常

 

$ruby -v

 

ruby 2.4.1……

 

4、因redis-trib.rb依赖于gem包redis,因此需要执行命令:gem  install redis,报错:

 

ERROR:  Loading command: install (LoadError)   

 

no such file to load -- zlib

 

——重新安装ruby,问题解决。

 

5、执行命令gem  install  redis,再次报错:

 

ERROR:  While executing gem ... (Gem::Exception) 

 

Unable to require openssl, install OpenSSL and rebuild ruby (preferred) or use non-HTTPS sources 

 

解决方法:

 

 

Successfully installed redis-4.0.0

 

Parsing documentation for redis-4.0.0

 

Installing ri documentation for redis-4.0.0

 

Done installing documentation for redis after 1 seconds

 

1 gem installed

 

6、执行命令 ./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6379 10.10.38.46:6379 10.10.38.46:6380 10.10.38.46:6381 报错:

 

./redis-trib.rb:1573: warning: key "threshold" is duplicated and overwritten on line 1573

 

>>> Creating cluster

 

[ERR] Node 127.0.0.1:6380 is not configured as a cluster node.

 

原因:启动节点时使用了命令./redis-server --port 6380

 

解决方案:使用命令  ./redis-server ../redis.conf  启动节点

 

7.执行命令./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6379 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384报错:

 

./redis-trib.rb:1573: warning: key "threshold" is duplicated and overwritten on line 1573

 

>>> Creating cluster

 

[ERR] Node 127.0.0.1:6380 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

 

解决方案删除生成的配置文件nodes.conf,如果不行则说明现在创建的结点包括了旧集群的结点信息,需要删除redis的持久化文件后再重启redis,比如:appendonly.aof、dump.rdb

 

8、带密码的redis集群搭建

 

./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6379 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384报错:

 

./redis-trib.rb:1573: warning: key "threshold" is duplicated and overwritten on line 1573

 

>>> Creating cluster

 

[ERR] Sorry, can't connect to node 127.0.0.1:6380

 

错误原因:redis集群设置了集群,然而 redis-trib.rb 源码中是这样的 @r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)

 

解决方案:修改redis-trib.rb 源码  @r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60, :password => '666dc666)

 

9、执行127.0.0.1:6379> set dc_test_key helloworld 报错:

 

(error) MOVED 10829 127.0.0.1:6381

 

解决方案:连接客户端时添加-c参数,使用命令./redis-cli -c

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值