zoukankan      html  css  js  c++  java
  • redis使用基础(六) ——Redis集群

    redis使用基础(六)

    ——Redis集群

     

    (转载请附上本文链接——linhxx)

    一、单台服务器

             单台redis服务器,会出现单点故障,且需要承受所有的负载。另外,所有的内容都存在单个服务器上,该服务器会成为瓶颈。

             使用多台服务器作为redis服务器,需要考虑集群管理,如数据一致性、增加节点、故障恢复等问题。redis对处理这些问题有一套方案。

    二、复制

             redis的持久化功能保证了数据的持久性,但是如果服务器故障,数据还是可能会丢失,因此需要将数据备份到其他服务器。当一台服务器内容更新,会通知其他服务器进行备份。

             多台服务器使用redis时,有主数据库、从数据库的概念。通常主数据库是读写(或只有写操作),从数据库都是只读。主数据库数据的变化会通知从库,让从库进行更新。

    1、命令

             1)redis配置从数据库,只需要在从数据库配置文件中加入命令:slave of 主库ip 主库端口。该命令可以在开启数据库的时候使用,也可以在运行期间使用。

             2)主数据库无需任何配置。

             3)查看数据库的从库/主库命令:INFO replication,该命令返回当前库的角色(master/slave),对于主库还可以看到从库的数量、每个从库的ip与端口、当前连接开启的从库数量,对于从库可以看到主库的ip和端口。

    2、注意事项

             如果当前数据库已经是某个数据库的从库,再输入slave of 新主库,则会断开和当前主库的连接,并成为新主库的从库,且同步新主库的数据(如果现有数据新的主库也有,会被覆盖)。

             运行期间输入slave of no one命令,使当前数据库与主库断开连接,并且自己成为主库。

    3、原理

    a. 复制初始化:

             1)从数据库启动后,给主库发送sync命令。

             2)主库接到命令后,会开始对当前数据保存快照(RDB持久化),且对保存期间客户端发送的写命令进行缓存。保存完毕后,将保存文件和缓存的写命令一起发给从数据库。

             3)从数据库收到后,会还原快照,并且执行缓存命令。

             复制初始化后,主数据库每当收到写命令,都会发送给从数据库。

             断开连接后重连时,2.8之前的redis版本会重新进行一遍复制初始化;2.8开始的版本可以进行增量初始化,加快初始化速度。

    b. 协议角度分析复制初始化:

             1)从库使用命令连接主库,如果需要输入密码则还要和主库发送auth验证。

             2)从库使用replconf listening-port 端口号告诉主库当前监听的端口号。

             3)向主库发送sync命令开始同步。主库发送回快照文件和缓存命令,从库收到后写入硬盘的临时文件中。

             4)写入完成后,从库会使用该rdb文件替换当前rdb文件。

             5)在同步过程中,从库不会阻塞,可以接收客户端的命令。默认情况下,同步还没完成时,会用同步前的数据响应客户端。也可以通过配置文件slave-server-stable-data=no,强制要求同步完成后才可以提供服务,则此时同步期间收到客户端的请求会报错。

             6)复制完成后,主数据库的任何写操作,从数据库都会收到异步的命令,并且去执行。

    c. 乐观复制

             redis采用乐观复制的策略,容忍一定时间内的数据差异,最终数据是一致的。

             当主库收到写命令,会立即写完并反馈给客户端,再异步地向从数据库发送命令告知同步,因此存在差异的时间端口。可以配置从库至少连接几个时,主库才可写,通过配置文件的命令min-slaves-to-write。

    4、图结构

             从数据库不仅可以作为主库的从库,也可以作为其他从数据库的主库。

     

             上图中A变化会同步给B和C,B变化会同步给D和E,但不会同步给A和C。

    5、读写分离优势

             正常的业务场景中,都是大量的读操作(远大于写操作),甚至带上计算(如sort)等场景,单个服务器很难应付,这是多个数据库的优势,保证主库仅进行写操作和同步数据,从库进行读操作,可以把工作量平均分配给从数据库。

    6、从库持久化

             持久化比较耗时,当有主从结构时,通常设置主库禁止持久化,由从库进行持久化操作。当主库奔溃时,使用以下方式进行还原:

             1)将进行持久化的某台从库服务器,执行slave of no one,使其变成主库。

             2)将重新恢复正常后的原主库,执行slave of 步骤1的从库,使其变成原从库的从库。

             3)注意事项

             主库设置关闭持久化后,一定要同时关闭自动重启功能。因为其没有持久化数据,关闭(无论正常还是异常关闭)后数据全部清空,如果此时自动重启,则所有的从库会同步数据,所有的数据都被清空。

    7、无硬盘复制

             使用rdb复制,简化逻辑,代码易复用,但是有以下缺点:

             1)无法确定主库恢复数据的时间,因为快照的复制时间不确定。

             2)复制初始化要在硬盘创建文件,如果硬盘性能差则会很慢(如网络硬盘)。

             因此,redis2.8开始的版本,允许主库通过配置文件开启无硬盘复制,即对从库进行复制初始化时,直接通过网络传输给从库,而不是在主库的本地先生成rdb文件,再传输rdb。命令是 repl-diskless-sync yes

    8、增量复制

    a. 当从库断开重连,如果要全量复制,速度较慢。从redis2.8开始,支持增量复制。增量复制如下步骤:

             1)从库存储主库的运行id(run id),每个redis实例有唯一运行id,重启后id会变。

             2)复制过程中,主库把命令传给从库,并把命令存到挤压队列(backlog)中,记录当前挤压队列存放命令的偏移量。

             3)从库接收到主库的命令后,会记录偏移量。

             4)从库准备就绪后,从redis2.8开始,不再发送sync,而是发送psync 主库运行id断开前最新偏移量。

    b. 主库收到psync后,会进行以下判断确定是否进行增量复制;

             1)首先判断运行id是否正确,例如主库重启过则id会是新的。如果运行id不正确,则进行全量复制。

             2)判断从库最后同步成功的命令是否在挤压队列中,在的话则可以进行增量复制,否则全量复制。

    c. 挤压队列:

             挤压队列本质是固定长度序列,大小为1MB,挤压队列越大,则允许断开的时间越长。通过repl-backlog-size设置挤压队列大小,通过repl-backlog-ttl设置全部从库断开连接后挤压队列释放的时间。

    三、哨兵

             redis2.8开始,提供稳定的哨兵,对redis的主从数据库进行自动化的系统监控和状态恢复。

    1、概念

             哨兵用于监控redis运行情况,监控主从数据库是否正常运行,并且在主库故障时将从库转换为主库。可以设置单个哨兵,也可以设置多个哨兵。

             单个哨兵:

     

    多个哨兵:

     

    2、哨兵使用方式

             现假设有一主二从数据库。哨兵建立方式如下:

             1)建立配置文件,文件名可以任意,如sentinel.conf,里面的内容为:

             sentinel monitor 主库名字(任意,只能是字母、数字、-、.、_) 主库ip 主库端口 主库投票数。

             由于同一个主从系统可以多个哨兵检查,则投票数的目的在于当该哨兵认为主库主观下线,至少还要有 投票数-1 (因为包括当前哨兵)的哨兵认为是客观下线,才会换主库。

             其中只需要配置主库,哨兵会自动获取从库。该文件可以有多行,每一行表示一个主库。设置多行表示检测多个主从系统。

             除了monitor,还可以配置其他监控内容,通过主库名字区分监控的是哪个主从系统。

             2)执行 sentinel进程,将配置文件传给哨兵:

             redis-sentinel  /sentinel.conf的路径/sentinel.conf

            3)启动哨兵,可以看到哨兵的run id,输出+monitor表示监测到主库,+slave表示检测到从库。

             哨兵执行过程如下:

             1)主库关闭

             如果将主库关闭(可以手动关闭或杀死进程),等待时间(默认30秒,可配置),会输出+sdown 主库ip 主库端口,表示哨兵主观认为主库停止服务。接着是+odown,表示哨兵客观认为主库停止服务。

             当输出+odown后,哨兵会自动开始故障恢复,+try-failover开始修复,+failover-end表示修复完毕。修复完毕后,会重新设置一个主库,+switch-master 原主库名 原主库ip 原主库端口 新主库ip 新主库端口。接着,又会有+slave输出,表示检测到新主库的从库,原主库恢复连接后,也会被设置成新主库的从库。

             2)从库关闭

             从库如果被关闭,也会检测到+sdown,当从库再次开启,会检测到-sdown,并且会输出+convert-to-slave将重启后的从库再次设置为从库。

             从库只有主观下线,没有客观下线。因为客观下线后需要重新选出新主库,这对于从库来说没有必要。

    3、哨兵原理

             1)哨兵进程启动时,通过读取上述提到的配置哨兵配置文件,确定主库的信息。当配置文件中有多行数据,表示是多个主从系统,哨兵也可以同时检测。

             2)哨兵启动后,与要检测的主库建立两个连接,一个订阅来自主数据__sentine1__:hello频道,以获取其他监控该主库的哨兵信息;另一个用来给主库建立连接定期发送INFO等命令(这是因为考虑到主库可能会进入订阅模式,而无法执行其他命令,所有才用连接)。

             3)连接建立后,进行如下操作:

             a. 每10秒向主库发送INFO命令。

             目的是获取当前主从系统的相关信息,如从库信息,比较上次发送的信息与当前的信息,确认主从库是否有故障。

             b. 每2秒向主库与从库的__sentine1__:hello发送哨兵自身的信息。

             目的是为了使各哨兵之间互相确认当前的检测范围,形成监测网。发送的内容包括哨兵地址、端口、运行id、配置版本,主库名字、地址、端口、配置版本。

    当发现主库配置版本比较落后,还会更新主库的信息。

             c. 每1秒向主从数据库以及其他哨兵发送ping命令。

             目的是确认各库、各哨兵是否正常服务。1秒这个时间可以通过配置文件的字段进行配置,down-after-milliseconds,单位是毫秒。如果大于1秒则采用1秒,小于1秒则采用配置的数据。因此开启哨兵后,最少每秒会查看一次各库的情况。

             4)判断各库是否下线

             a. 当超过down-after-milliseconds时间,库没有回复,则认为其主观下线,即上述的检测到+sdown。

             当下线的是主库,哨兵还会给其他哨兵发送命令,确认其他哨兵是否也认为主库主观下线,当达到指定数量(哨兵配置文件中主库的投票数,投票数含自身),则认为其客户下线(检测到+odown),此时重新选主库。

             5)选举领头哨兵

             多个哨兵确认主库客观下线,会选举出一个领头哨兵进行故障恢复,避免多个哨兵重复恢复。

             选举领头哨兵,采用Raft算法,如下:

             a. 发现主库客观下线的哨兵A,向其他哨兵发送命令,要求选举自己为领头哨兵。

             b. 如果接到命令的哨兵,没有选过其他人,则选A。

             c. 当A的票数超过哨兵的半数,且超过哨兵配置文件对该主库的投票数,则A称为领头哨兵。

             由于要超过半数才能当领头哨兵,因此确保每次选举都只会选出一个领头哨兵。

             d. 当多个节点参选领头哨兵,如果出现没有任何哨兵当选的情况,则每个哨兵随机等待一个时间,再重新发起请求。

             6)故障恢复

             领头哨兵挑选一个从库作为新的主库,挑选依据如下:

             a. 所有在线从库,选择优先级最高的从库,优先级通过配置文件的slave-priority来设置。

             b. 当多个从库优先级都一样且最高,则复制的偏移量越大,即增量复制时保存的数据相对约完整的,越优先。

             c. 以上条件都一样,则选择运行id最小的从库作为新的主库。

             7)后续步骤

             a. 选出新主库,领头哨兵会向其发送slave of no one,让其成为主库,再向其他从库发送slave of命令,让其他从库称为该新主库的从库。

             b. 将旧主库设置成为新主库的从库,使其重启后可以直接投入工作。

    4、哨兵部署

             哨兵以独立进程的方式进行监控,如果系统哨兵较少,可靠度则较低。因为哨兵本身也可能发生故障。因此,相对稳妥的方案:

             1)为每个数据库(无论主从)都配置一个哨兵。

             2)使每个哨兵与其对应节点网络环境相似。

             但是,redis不支持连接复用,配置过多哨兵会有太多的冗余连接;另外redis负载高时会影响其对哨兵的回复以及哨兵和其他哨兵的通信。

             因此要根据实际情况设置哨兵。

    四、集群

    1、分片方式

             当主从结构的每个库都存储全量数据,则导致该主从系统的最大存储量被最小存储的redis服务器限定,形成木桶效应。

    因此可以对redis进行水平扩容。由于redis轻量级,因此可以预先分足够多数量的片,并在存储的时候客户端采用某一算法将数据平均分配到不同的redis中。当数据量小的时候,每个片占用的内存都不多;当数据量很大时,也只需要将部分redis迁移到其他服务器即可。

    2、集群概述

             分片方式维护成本高,从redis3.0开始,集群是更好的解决方案。集群拥有和单机一样的性能,网络分区后提供可访问性,提供主库故障恢复支持。

    集群支持所有单机执行的命令,对于多键命令(如MGET),如果多键都在同一个集群节点则正常返回,否则报错。另外,集群只支持0号数据库,如果使用select选择数据库也会保存。

    3、配置集群

    1)将每个节点设置成可集群

             将每个数据库配置文件的cluster-enabled 开启(设置成yes)即可。每个集群至少要三个数据库才可能正常运行。

             集群会将当前节点记录的集群状态持久化存储在指定文件中,默认是当前工作目录下的nodes.conf文件。但是,要求每个节点文件不同,否则会报错。因此,需要在开启节点前,设置节点的持久化路径,通过配置文件的命令cluster-config-file  /路径/nodes.conf。

             连接上任一集群,通过INFO cluster命令,可以判断该集群是否可用。返回值中如果cluster_enabled:1,则表示可用。

             该配置生效后,表示该节点是一个独立的节点,还未加入集群。

    2)redis-trib.rb

             redis源码提供的一个文件,用于协助节点加入集群,由于是ruby写的,因此需要节点环境安装配置ruby,另外还需要gem包redis,通过gem install redis进行安装。

    3)初始化集群

             /redis-trib.rb的路径/redis-trib.rb create --replicas 1 ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6

             其中--replicas 1表示集群中每个主库一个从库,该命令会将ip1、2、3的库设置为主库,4、5、6设置为从库。

             也可以同一个ip的不同port加入集群。

    4)集群创建的详细过程

             a. redis-trib.rb以客户端形式连接所有节点,发送ping命令,如果有任何节点无法正常服务,则集群建立失败。接着,发送INFO命令,确定每个节点的运行id以及是否开启集群。

             b. 向每个节点发送cluster meet命令,格式cluster meet ip port,告诉当前节点指定ip:port的节点也是集群的一个节点。从而使各ip归入一个集群。

             c. 分配主从数据库节点,原则是尽量让每个主库运行在不同ip,确保系统容灾能力。

             d. 为每个主库分配插槽,实质就是分配哪些键归哪些节点负责。

             e. 为每个将要成为从库的库,发送命令CLUSTER REPLICATE 主库运行ID,将当前节点转换成从库,并复制数据。

             上述步骤后,集群创建完成,连接上任意节点,输入命令CLUSTER NODES,可以查看集群的情况,包括每个节点运行id、地址、端口、角色、状态、插槽等。

    4、节点增加

             向加为节点的redis(下面称为A),发送命令CLUSTER MEET ip port,其中ip和端口是目前集群中的任一节点(下面称为B)。

             A接到命令后,会与B进行握手,使B认为A是集群的一员。接着,B会使用Gossip协议,将A的信息通知集群的其他节点。

    5、插槽(Slot)分配

             新节点可以执行cluster replicate成为某个主节点的从节点,也可以申请一个插槽作为主节点运行。

    1)键与插槽的关系

             redis将每个键的键名有效部分用CRC16算法,计算散列值,再取对16384的余数。这样使每个键都可以分配到16384个插槽中。进而分配指定节点处理。因此,集群最大节点数量是16384,通常建议在1000左右。

             有效键名指:

             a. 如果键名包含{,且后面有},且{}之间至少1个字符,则有效键名是{}之间的内容。

             b. 如果不满足上述条件,则键名是整个数据。

             这样做的好处在于,可以把某个系统都设置成一样的,例如{order}:id,{order}:user:orderid,这两个键由于都有{},且里面的内容不止1个字符,因此有效键名都是order,则会被分配到1个节点进行存储,也就可以使用MGET等批量操作进行查询。

    2)插槽分配指定节点

             a. 插槽没有分配过

             在要分配的节点,执行命令cluster add slot1 [slot2 slot3….]进行分配,如果某个插槽已经分配节点,会报错。

             可以通过cluster slots查看当前插槽的情况。

             b. 分配过的插槽重新分配

             可以使用redis-trib.rb reshard 待分片ip:端口,然后跟着命令提示输入迁移插槽数量、迁移至的运行id、迁移前的运行id,完成迁移。(推荐使用此方法)

    也可以直接执行命令:cluster set slots 插槽号 node 新的运行id。但是由于迁移不会把键也迁移过去,因此如果插槽里面有键会丢失,所以要按下列方式进行迁移。

    a. 获取插槽存在的所有键:cluster getkeysinslot 插槽号 要返回键的数量

    b. 对每个键迁移:migrate          目标节点地址 目标节点端口 键名 数据库号码 超时时间 [copy] [replace]。[copy]则表示不删除当前键,是在新地址建立副本;[replace]表示目标地址如果有同名的键,则覆盖目标地址的同名键。

    c. 重新分配插槽:cluster setslots 插槽号 node 新的运行id。

    3)迁移过程的数据问题

             迁移过程中,如果时间较久,可能会存在数据不一致问题。redis提供解决方案,实现集群在线情况下的插槽迁移。

             命令:

             cluster setslot 插槽号 migrating 新节点的运行id

    cluster setslot 插槽号 importing 新节点的运行id

    如果要把0号插槽从A移到B,需要如下操作(redis-trib.rb的方案):

    a. B执行 cluster setslot 0 importing A

    b. A执行 cluster setslot 0 migrating B

    c. 执行cluster getkeysinslot 0获取0号插槽的键列表

    d. 对第三步的每个键执行migrate,迁移键

    e. cluster setslots 0 node B 完成迁移

    即多了a、b两个步骤,执行完前两步时,客户端A请求插槽0中的键时,如果键存在(未迁移),则正常处理;如果不存在,则返回ASK跳转请求,告诉客户端键在B。客户端接到ASK后,向B发送ASKING,再发送请求数据的命令。

     

    相反,如果客户端向B发送请求,如果有ASKING,则返回数据,否则B返回MOVED跳转,要求客户端去A请求数据。

     

    6、获取键对应的插槽

             客户端发送请求键的操作,如果某个节点有该键,则返回;否则返回重定向请求。这个重定向请求,如果开发语言已经封装,则对于开发者是透明的,相当于单机。如果没有封装,则需要手工实现重定向。

             重定向的返回:MOVED 插槽号 IP:port,客户端收到重定向后,需要重定向到IP:port的地方,去进行键的相关操作。

             redis-cli模式下,在对节点进行初始化连接的时候,加上-c参数,如果后续客户端发送过来该阶段不存在的键,会自动重定向。

             优化:

             重定向使得原本一次连接可以完成的操作,需要两次连接。反复这样会影响性能,通常需要客户端缓存插槽信息,后面就直接往相应的插槽发送数据。

    7、故障恢复

             集群内的每个节点,每秒都会随机选5个节点,再选这5个节点中最久没有响应的节点发送ping命令。如果一定时间内没有回复,会被认为疑似下线(PFAIL),与哨兵的主观下线相类似,如果要真正确认节点下线,还需要一定数量的节点认为该阶段疑似下线。

             过程如下:

             1)节点A认为节点B疑似下线,会和集群的其他节点广播。

             2)当集群中的某一节点C收到集群中半数以上的节点认为B疑似下线,则确认B下线,并进行广播。

             当集群中的一个主库下线,至少需要1个从库顶上,因此,集群中每个主库至少要有一个从库。

    选择哪个从库作为新主库,和哨兵的方式一样,Raft算法,如下:

             1)发现主库下线的从库A,向集群其他节点发送请求,要求称为主库。

             2)集群的其他节点如果没有选其他的从库,则选A作为主库。

    3)A发现集群半数以上节点选A做主库,则A就成为主库。

    4)当多个库参选,出现没有库可以当选主库的情况,则这些库都随机等待一个时间再发送请求。

    当某个从库变成主库后,会通过命令Slave of no one将自己变成主库,并把旧主库的插槽转给自己负责。

    当某个主库负责1个以上的插槽,且该主库下线,且没有可用的从库时,默认情况认为该集群已经不可用,集群下线。如果希望集群不下线继续工作,可以通过配置文件cluster-require-full-coverage no(默认是yes)。

    ——written by linhxx

    更多最新文章,欢迎关注微信公众号“决胜机器学习”,或扫描右边二维码。

    博客园这边,我会定期批量发布文章,如果想要实时看到最新的文章,欢迎关注微信公众号"决胜机器学习",里面有我最新的文章。linhxx
  • 相关阅读:
    HAproxy 1.5 dev14 发布
    IBM/DW 使用 Java 测试网络连通性的几种方法
    Skype 4.1 Linux 发布,支持微软帐号登录
    Dorado 7.1.20 发布,Ajax的Web开发平台
    Aspose.Slides for Java 3.0 发布
    开发版本 Wine 1.5.18 发布
    BitNami Rubystack 开始支持 Ruby 2.0
    XWiki 4.3 正式版发布
    Silverlight实例教程 Out of Browser的Debug和Notifications窗口
    Silverlight实例教程 Out of Browser与Office的互操作
  • 原文地址:https://www.cnblogs.com/linhxx/p/8412898.html
Copyright © 2011-2022 走看看