redis集群方案

设计集群方案时,至少要考虑如下因素:
(1)高可用要求:根据故障转移的原理,至少须要3个主节点才能完成故障转移,且3个主节点不该在同一台物理机上;每一个主节点至少须要1个从节点,且主从节点不该在一台物理机上;所以高可用集群至少包含6个节点。
(2)数据量和访问量:估算应用须要的数据量和总访问量(考虑业务发展,留有冗余),结合每一个主节点的容量和能承受的访问量(能够经过benchmark获得较准确估计),计算须要的主节点数量。
(3)节点数量限制:Redis官方给出的节点数量限制为1000,主要是考虑节点间通讯带来的消耗。在实际应用中应尽可能避免大集群;若是节点数量不足以知足应用对Redis数据量和访问量的要求,能够考虑:node

  a.业务分割,大集群分为多个小集群;redis

  b.减小没必要要的数据;算法

  c.调整数据过时策略等。
(4)适度冗余:Redis能够在不影响集群服务的状况下增长节点,所以节点数量适当冗余便可,不用太大。数据库

集群的原理:缓存

集群最核心的功能是数据分区,所以首先介绍数据的分区规则;而后介绍集群实现的细节:通讯机制和数据结构;最后以cluster meet(节点握手)、cluster addslots(槽分配)为例,说明节点是如何利用上述数据结构和通讯机制实现集群命令的。ruby

数据分区方案:
  数据分区有顺序分区、哈希分区等,其中哈希分区因为其自然的随机性,使用普遍;集群的分区方案即是哈希分区的一种。
  哈希分区的基本思路是:对数据的特征值(如key)进行哈希,而后根据哈希值决定数据落在哪一个节点。常见的哈希分区包括:哈希取余分区、一致性哈希分区、带虚拟节点的一致性哈希分区等。
  (1)哈希取余分区
    哈希取余分区思路很是简单:计算key的hash值,而后对节点数量进行取余,从而决定数据映射到哪一个节点上。该方案最大的问题是,当新增或删减节点时,节点数量发生变化,系统中全部的数据都须要从新计算映射关系,引起大规模数据迁移。
  (2)一致性哈希分区
    一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,范围为0-2^32-1;对于每一个数据,根据key计算hash值,肯定数据在环上的位置,而后今后位置沿环顺时针行走,找到的第一台服务器就是其应该映射到的服务器。服务器

 

   与哈希取余分区相比,一致性哈希分区将增减节点的影响限制在相邻节点。若是在node1和node2之间增长node5,则只有node2中的一部分数据会迁移到node5;若是去掉node2,则原node2中的数据只会迁移到node4中,只有node4会受影响。
   一致性哈希分区的主要问题在于,当节点数量较少时,增长或删减节点,对单个节点的影响可能很大,形成数据的严重不平衡。仍是以上图为例,若是去掉node2,node4中的数据由总数据的1/4左右变为1/2左右,与其余节点相比负载太高。
  (3)带虚拟节点的一致性哈希分区
    该方案在一致性哈希分区的基础上,引入了虚拟节点的概念。Redis集群使用的即是该方案,其中的虚拟节点称为槽(slot)。槽是介于数据和实际节点之间的虚拟概念;每一个实际节点包含必定数量的槽,每一个槽包含哈希值在必定范围内的数据。引入槽之后,网络

   数据的映射关系由数据hash->实际节点,变成了数据hash->槽->实际节点。数据结构

    在使用了槽的一致性哈希分区中,槽是数据管理和迁移的基本单位。槽解耦了数据和实际节点之间的关系,增长或删除节点对系统的影响很小。仍以上图为例,系统中有4个实际节点,假设为其分配16个槽(0-15); 槽0-3位于node1,4-7位于node2,并发

     以此类推。若是此时删除node2,只须要将槽4-7从新分配便可,例如槽4-5分配给node1,槽6分配给node3,槽7分配给node4;能够看出删除node2后,数据在其余节点的分布仍然较为均衡。槽的数量通常远小于2^32,远大于实际节点的数量;

    在Redis集群中,槽的数量为16384

  下面这张图很好的总结了Redis集群将数据映射到实际节点的过程:

        

  (1)Redis对数据的特征值(通常是key)计算哈希值,使用的算法是CRC16。 Crc16(key) = hash
  (2)根据哈希值,计算数据属于哪一个槽。 Hash % 16384
  (3)根据槽与节点的映射关系,计算数据属于哪一个节点。

 

Redis集群搭建:

1、主/从(master/slave)(缺点: 数据冗余,浪费内存 (ping - pong)

  1.主节点读写,从节点只能读,不能写
  2.当主节点读写数据变化时,会直接同步到从节点
  3.一个master能够拥有多个slave,可是一个slave只能对应一个master

主从复制工做机制:
  当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,而后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。

主从配置:
  redis默认是主数据,因此master无需配置,咱们只须要修改slave的配置便可。
  a.设置须要链接的master的ip端口:
    slaveof 192.168.0.107 6379
  b.若是master设置了密码。须要配置:
    masterauth
  c.链接成功进入命令行后,能够经过如下命令行查看链接该数据库的其余库信息:
    info replication

 

2、哨兵模式(sentinel)

  哨兵的命令,哨兵是一个独立的进程,做为进程,它会独立运行。其原理是哨兵经过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。(ping - pong)

  

  

这里的哨兵有两个做用:
  • 经过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,而后经过发布订阅模式通知其余的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,咱们可使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就造成了多哨兵模式。
故障切换(failover)的过程:
  假设主服务器宕机,哨兵1先检测到这个结果,系统并不会立刻进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,而且数量达到必定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操做。切换成功后,就会经过发布订阅模式,让各个哨兵把本身监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的

配置:

  配置Redis的主从服务器,修改redis.conf文件以下
  # 使得Redis服务器能够跨网络访问
  bind 0.0.0.0
  # 设置密码
  requirepass "123456"
  # 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不须要配置
  slaveof 192.168.11.128 6379
  # 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不须要配置
  masterauth 123456

  配置哨兵,在Redis安装目录下有一个sentinel.conf文件,copy一份进行修改
  # 禁止保护模式
  protected-mode no
  #配置监听的主服务器,这里sentinel monitor表明监控,mymaster表明服务器的名称,能够自定义,192.168.11.128表明监控的主服务器,6379表明端口,2表明只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操做。
  sentinel monitor mymaster 192.168.11.128 6379 2
  # sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
  # sentinel auth-pass <master-name> <password>
  sentinel auth-pass mymaster 123456

启动:
  # 启动Redis服务器进程
  ./redis-server ../redis.conf
  # 启动哨兵进程
  ./redis-sentinel ../sentinel.conf

须要特别注意的是:

  客观下线是主节点才有的概念;若是从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操做。选举领导者哨兵节点:当主节点被判断客观下线之后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操做。监视该主节点的全部哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,若是B没有赞成过其余哨兵,则会赞成A成为领导者。选举的具体过程这里不作详细描述,通常来讲,哨兵选择的过程很快,谁先完成客观下线,通常就能成为领导者。

  故障转移:选举出的领导者哨兵,开始进行故障转移操做,该操做大致能够分为3个步骤:

    • 在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;而后选择优先级最高的从节点(由slave-priority指定);若是优先级没法区分,则选择复制偏移量最大的从节点;若是仍没法区分,则选择runid最小的从节点。

    • 更新主从状态:经过slaveof no one命令,让选出来的从节点成为主节点;并经过slaveof命令让其余节点成为其从节点。

    • 将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379从新上线后,它会成为新的主节点的从节点。

3、集群

集群,即Redis Cluster,是Redis 3.0开始引入的分布式存储方案。

集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。

集群的做用,能够概括为两点:

  一、数据分区:数据分区(或称数据分片)是集群最核心的功能。

集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增长;另外一方面每一个主节点均可以对外提供读服务和写服务,大大提升了集群的响应能力。

Redis单机内存大小受限问题,在介绍持久化和主从复制时都有说起;例如,若是单机内存太大,bgsave和bgrewriteaof的fork操做可能致使主进程阻塞,主从环境下主机切换时可能致使从节点长时间没法提供服务,全量复制阶段主节点的复制缓冲区可能溢出……。

  二、高可用:集群支持主从复制和主节点的自动故障转移(与哨兵相似);当任一节点发生故障时,集群仍然能够对外提供服务。

集群的搭建:
  这一部分咱们将搭建一个简单的集群:共6个节点,3主3从。方便起见:全部节点在同一台服务器上,以端口号进行区分;配置从简。3个主节点端口号:7000/7001/7002,对应的从节点端口号:8000/8001/8002。


集群的搭建有两种方式:

  (1)手动执行Redis命令,一步步完成搭建;

  (2)使用Ruby脚本搭建。两者搭建的原理是同样的,只是Ruby脚本将Redis命令进行了打包封装;在实际应用中推荐使用脚本方式,简单快捷不容易出错。下面分别介绍这两种方式。
集群的搭建能够分为四步:(1)启动节点:将节点以集群模式启动,此时节点是独立的,并无创建联系;(2)节点握手:让独立的节点连成一个网络;(3)分配槽:将16384个槽分配给主节点;(4)指定主从关系:为从节点指定主节点。

第一种搭建: 手动
(1)启动节点
  集群节点的启动仍然是使用redis-server命令,但须要使用集群模式启动。下面是7000节点的配置文件(只列出了节点正常工做关键配置,其余配置(如开启AOF)能够参照单机节点进行):

  #redis-7000.conf
  port 7000
  cluster-enabled yes
  cluster-config-file "node-7000.conf"
  logfile "log-7000.log"
  dbfilename "dump-7000.rdb"
  daemonize yes
  其中的cluster-enabled和cluster-config-file是与集群相关的配置。

  cluster-enabled yes:Redis实例能够分为单机模式(standalone)和集群模式(cluster);cluster-enabled yes能够启动集群模式。在单机模式下启动的Redis实例,若是执行info server命令,能够发现redis_mode一项为standalone
  cluster-config-file:该参数指定了集群配置文件的位置。每一个节点在运行过程当中,会维护一份集群配置文件;每当集群信息发生变化时(如增减节点),集群内全部节点会将最新信息更新到该配置文件;当节点重启后,会从新读取该配置文件,获取集群信息,能够方便的从新加入到集群中。也就是说,当Redis节点以集群模式启动时,会首先寻找是否有集群配置文件,若是有则使用文件中的配置启动,若是没有,则初始化配置并将配置保存到文件中。集群配置文件由Redis节点维护,不须要人工修改。
编辑好配置文件后

使用redis-server命令启动该节点:
  redis-server redis-7000.conf
(2)节点握手
  节点启动之后是相互独立的,并不知道其余节点存在;须要进行节点握手,将独立的节点组成一个网络。
  节点握手使用cluster meet {ip} {port}命令实现
  例如在7000节点中执行cluster meet 192.168.72.128 7001,能够完成7000节点和7001节点的握手;注意ip使用的是局域网ip而不是localhost或127.0.0.1,
是为了其余机器上的节点或客户端也能够访问
同理,在7000节点中使用cluster meet命令,能够将全部节点加入到集群,完成节点握手:
  1 cluster meet 192.168.72.128 7002
  2 cluster meet 192.168.72.128 8000
  3 cluster meet 192.168.72.128 8001
  4 cluster meet 192.168.72.128 8002
(3)分配槽
  在Redis集群中,借助槽实现数据分区,具体原理后文会介绍。集群有16384个槽,槽是数据管理和迁移的基本单位。当数据库中的16384个槽都分配了节点时,集群处于上线状态(ok);若是有任意一个槽没有分配节点,则集群处于下线状态(fail)。
分配槽使用cluster addslots命令,执行下面的命令将槽(编号0-16383)所有分配完毕:
  1 redis-cli -p 7000 cluster addslots {0..5461}
  2 redis-cli -p 7001 cluster addslots {5462..10922}
  3 redis-cli -p 7002 cluster addslots {10923..16383}
(4)指定主从关系
集群中指定主从关系再也不使用slaveof命令,而是使用cluster replicate命令;参数使用节点id。
经过cluster nodes得到几个主节点的节点id后,执行下面的命令为每一个从节点指定主节点:
  1 redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae
  2 redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4
  3 redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1

第二种搭建: 脚本
  使用Ruby脚本搭建集群
  在{REDIS_HOME}/src目录下能够看到redis-trib.rb文件,这是一个Ruby脚本,能够实现自动化的集群搭建。
  (1)安装Ruby环境
  以Ubuntu为例,以下操做便可安装Ruby环境:
  1 apt-get install ruby #安装ruby环境
  2 gem install redis #gem是ruby的包管理工具,该命令能够安装ruby-redis依赖
  (2)启动节点
  与第一种方法中的“启动节点”彻底相同。
  (3)搭建集群
  redis-trib.rb脚本提供了众多命令,其中create用于搭建集群,使用方法以下

  ./redis-trib.rb create --replicas 1 192.168.72.128:7000 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:8000 192.168.72.128:8001 192.168.72.128:8002

 

  其中:--replicas=1表示每一个主节点有1个从节点;后面的多个{ip:port}表示节点地址,前面的作主节点,后面的作从节点。使用redis-trib.rb搭建集群时,要求节点不能包含任何槽和数据。

  执行建立命令后,脚本会给出建立集群的计划;计划包括哪些是主节点,哪些是从节点,以及如何分配槽

 

集群扩展:

1. 集群伸缩
  实践中经常须要对集群进行伸缩,如访问量增大时的扩容操做。Redis集群能够在不影响对外服务的状况下实现伸缩;伸缩的核心是槽迁移:修改槽与节点的对应关系,实现槽(即数据)在节点之间的移动。例如,若是槽均匀分布在集群的3个节点中,此时增长一个节点,则须要从3个节点中分别拿出一部分槽给新节点,从而实现槽在4个节点中的均匀分布。
  增长节点、
  假设要增长7003和8003节点,其中8003是7003的从节点;步骤以下:
  (1)启动节点:方法参见集群搭建
  (2)节点握手:可使用cluster meet命令,但在生产环境中建议使用redis-trib.rb的add-node工具,其原理也是cluster meet,但它会先检查新节点是否已加入其它集群或者存在数据,避免加入到集群后带来混乱。
  1 redis-trib.rb add-node 192.168.72.128:7003 192.168.72.128 7000
  2 redis-trib.rb add-node 192.168.72.128:8003 192.168.72.128 7000
  (3)迁移槽:推荐使用redis-trib.rb的reshard工具实现。reshard自动化程度很高,只须要输入redis-trib.rb reshard ip:port (ip和port能够是集群中的任一节点),而后按照提示输入如下信息,槽迁移会自动完成:
    ○ 待迁移的槽数量:16384个槽均分给4个节点,每一个节点4096个槽,所以待迁移槽数量为4096
    ○ 目标节点id:7003节点的id
    ○ 源节点的id:7000/7001/7002节点的id
  (4)指定主从关系:方法参见集群搭建
  减小节点、
  假设要下线7000/8000节点,能够分为两步:
  (1)迁移槽:使用reshard将7000节点中的槽均匀迁移到7001/7002/7003节点
  (2)下线节点:使用redis-trib.rb del-node工具;应先下线从节点再下线主节点,由于若主节点先下线,从节点会被指向其余主节点,形成没必要要的全量复制。
  1 redis-trib.rb del-node 192.168.72.128:7001 {节点8000的id}
  2 redis-trib.rb del-node 192.168.72.128:7001 {节点7000的id}


2. 故障转移
  集群只实现了主节点的故障转移;从节点故障时只会被下线,不会进行故障转移。所以,使用集群时,应谨慎使用读写分离技术,由于从节点故障会致使读服务不可用,可用性变差。
这里再也不详细介绍故障转移的细节,只对重要事项进行说明:
节点数量:在故障转移阶段,须要由主节点投票选出哪一个从节点成为新的主节点;从节点选举胜出须要的票数为N/2+1;其中N为主节点数量(包括故障主节点),但故障主节点实际上不能投票。所以为了可以在故障发生时顺利选出从节点,集群中至少须要3个主节点(且部署在不一样的物理机上)。
  故障转移时间:从主节点故障发生到完成转移,所须要的时间主要消耗在主观下线识别、主观下线传播、选举延迟等几个环节;具体时间与参数cluster-node-timeout有关,通常来讲:
故障转移时间(毫秒) ≤ 1.5 * cluster-node-timeout + 1000
  cluster-node-timeout的默认值为15000ms(15s),所以故障转移时间会在20s量级


3. 集群的限制及应对方法
  因为集群中的数据分布在不一样节点中,致使一些功能受限,包括:
  (1)key批量操做受限:例如mget、mset操做,只有当操做的key都位于一个槽时,才能进行。针对该问题,一种思路是在客户端记录槽与key的信息,每次针对特定槽执行mget/mset;另一种思路是使用Hash Tag,将在下一小节介绍。
  (2)keys/flushall等操做:keys/flushall等操做能够在任一节点执行,可是结果只针对当前节点,例如keys操做只返回当前节点的全部键。针对该问题,能够在客户端使用cluster nodes获取全部节点信息,并对其中的全部主节点执行keys/flushall等操做。
  (3)事务/Lua脚本:集群支持事务及Lua脚本,但前提条件是所涉及的key必须在同一个节点。Hash Tag能够解决该问题。
  (4)数据库:单机Redis节点能够支持16个数据库,集群模式下只支持一个,即db0。
  (5)复制结构:只支持一层复制结构,不支持嵌套。


4. Hash Tag
Hash Tag原理是:当一个key包含 {} 的时候,不对整个key作hash,而仅对 {} 包括的字符串作hash。
Hash Tag可让不一样的key拥有相同的hash值,从而分配在同一个槽里;这样针对不一样key的批量操做(mget/mset等),以及事务、Lua脚本等均可以支持。不过Hash Tag可能会带来数据分配不均的问题,这时须要:(1)调整不一样节点中槽的数量,使数据分布尽可能均匀;(2)避免对热点数据使用Hash Tag,致使请求分布不均。

下面是使用Hash Tag的一个例子;经过对product加Hash Tag,能够将全部产品信息放到同一个槽中,便于操做。

 

5. 参数优化
  cluster_node_timeout
  cluster_node_timeout参数在前面已经初步介绍;它的默认值是15s,影响包括:
  (1)影响PING消息接收节点的选择:值越大对延迟容忍度越高,选择的接收节点越少,能够下降带宽,但会下降收敛速度;应根据带宽状况和应用要求进行调整。
  (2)影响故障转移的断定和时间:值越大,越不容易误判,但完成转移消耗时间越长;应根据网络情况和应用要求进行调整。
  cluster-require-full-coverage
  前面提到,只有当16384个槽所有分配完毕时,集群才能上线。这样作是为了保证集群的完整性,但同时也带来了新的问题:当主节点发生故障而故障转移还没有完成,原主节点中的槽不在任何节点中,此时会集群处于下线状态,没法响应客户端的请求。
  cluster-require-full-coverage参数能够改变这一设定:若是设置为no,则当槽没有彻底分配时,集群仍能够上线。参数默认值为yes,若是应用对可用性要求较高,能够修改成no,但须要本身保证槽所有分配。


6. redis-trib.rb
  redis-trib.rb提供了众多实用工具:建立集群、增减节点、槽迁移、检查完整性、数据从新平衡等;经过help命令能够查看详细信息。在实践中若是能使用redis-trib.rb工具则尽可能使用,不但方便快捷,还能够大大下降出错几率。

 

缓存穿透
  缓存穿透,是指查询一个数据库必定不存在的数据。正常的使用缓存流程大体是,数据查询先进行缓存查询,若是key不存在或者key已通过期,再对数据库进行查询,并把查询到的对象,放进缓存。若是数据库查询对象为空,则不放进缓存。
  解决方案:若是从数据库查询的对象为空,也放入缓存,只是设定的缓存过时时间较短,好比设置为60秒, 最大不超过5min

缓存雪崩
  缓存雪崩,是指在某一个时间段,缓存集中过时失效。
  产生雪崩的缘由之一,好比在写本文的时候,立刻就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过时了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
  解决方案:通常是采起不一样分类商品,缓存不一样周期。在同一分类中的商品,加上一个随机因子。这样能尽量分散缓存过时时间,并且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源

缓存击穿  缓存击穿,是指一个key很是热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。小编在作电商项目的时候,把这货就成为“爆款”。  其实,大多数状况下这种爆款很难对数据库服务器形成压垮性的压力。达到这个级别的公司没有几家的。因此,务实主义的小编,对主打商品都是早早的作好了准备,让缓存永不过时。即使某些商品本身发酵成了爆款,也是直接设为永不过时就行了。mutex key互斥锁

相关文章
相关标签/搜索