10.5、请求路由

请求路由

目前我们已经搭建好Redis集群并且理解了通信和伸缩细节,但还没有使用客户端去操作集群。Redis集群对客户端通信协议做了比较大的修改,为了追求性能最大化,并没有采用代理的方式而是采用客户端直连节点的方式。因此对于希望从单机切换到集群环境的应用需要修改客户端代码。本节我们关注集群请求路由的细节,以及客户端如何高效的操作集群。

  1. 请求重定向

    在集群模式下,Redis接收任何键相关命令时首先计算对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。

    redis-cli自动帮我们连接到正确的节点执行命令,这个过程是在redis-cli内部维护,实质上是client端接到MOVED信息之后再次发起请求,并不在Redis节点中完成转发。节点对于不属于它的键命令只回复重定向响应,并不负责转发。熟悉Cassandra的哟农户希望在这里做好区分,不要混淆。正因为集群模式下把解析发起重定向的过程放到客户端完成,所以集群客户端协议相对于单机有了很大的变化。

    键命令执行步骤主要分为两步:计算槽,查找槽对应的节点。节点对于判定键命令是执行还是MOVED重定向,都是借助slots [CLUSTER_SLOTS]数组实现。根据MOVED重定向机制,客户端可以随机连接集群内任意Redis获取键所在节点,这种客户端又叫Dummy(傀儡)客户端,它优点是代码实现简单,对客户端协议影响较小,只需要根据重定向信息再次发送请求即可。但是它的弊端很明显,每次执行键命令前都要到Redis上进行重定向才能找到要执行命令的节点,额外增加了IO开销,这不是Redis集群高效的使用方式。正因为如此通常集群客户端都采用另一种实现:Smart(智能)客户端。

  2. Smart客户端

    1. 客户端原理

      大多数开发语言的Redis客户端都采用Smart客户端支持集群协议,从中找出符合自己要求的客户端类库。Smart客户端通过在内部维护slot->node的映射关系,本地就可实现键到节点的查找,从而保证IO效率的最大化,而MOVED重定向负责协助Smart客户端更新slot->node映射。Smart客户端操作集群的流程如下:

      1)首先在JedisCluster初始化是会选择一个运行节点,初始化槽和节点映射关系,使用cluster slots命令完成。

      2)Jedis Cluster解析cluster slots结果缓存到本地,并为每个节点创建唯一的JedisPool连接池。

      3)JedisCluster执行键命令的过程有些复杂,但是理解这个过程对于开发人员分析定位问题非常有帮助。键命令执行流程:

      • 计算slot并根据slots缓存获取目标节点连接,发送命令。

      • 如果出现连接错误,使用随机连接重新执行键命令,每次命令重试对redi-rections参数减1.

      • 捕获到MOVED重定向错误,使用cluster slots命令更新slots缓存(renewSlotCache方法)。

      • 重复执行第一步和第三步,知道命令执行成功,或者当redirections<=0时抛出JedsiClusterMaxRedirectionsException异常。

      从上面流程中发现,客户端需要结合异常和重试机制时刻保证跟Redis集群的slots同步,因此Smart客户端相比单机客户端有了很大的变化和实现难度。了解命令执行流程后,下面我们对Smart客户端成本和可能存在的问题进行分析:

      1)客户端内部维护slots缓存表,并且针对每个节点维护连接池,当集群规模非常大时,客户端会维护非常多的连接并消耗更多的内存。

      2)使用Jedis凑走集群是最常见的错误是:

      throw new JedisClusterMaxRedirectionsExceptions("Too many Cluster redirections?");
      

      这经常会引起开发人员的疑惑,它隐藏了内部错误细节,原因是节点宕机或请求超时都会抛出JedisConnectionException,导致触发了随机重试,当重试次数耗尽抛出这个错误。

      3)当出现JedisConnectionException时,Jedis认为可能是集群节点故障需要随机重试来更新slots缓存,因此了解哪些异常将抛出JedisConnectionException变得非常重要,有如下几种情况会抛出JedisConnectionException:

      • Jedis连接节点发生socket错误时抛出。

      • 所有命令/Lua囧爱本读写超时抛出。

      • JedisPool连接池获取可用Jedis对象超时抛出。

      前两点都可能是节点故障需要通过JedisConnectionException来更新slots缓存,但是第三点没有必要,因此Jedis2.8.1版本之后对于连接池的超时抛出JedisException,从而避免触发随机重试机制。

      4)Redis集群支持自动故障转移,但是从故障发现到完成转移需要一定的时间,节点宕机期间所有指向这个节点的名都会触发随机重试,每次收到MOVED重定向后会调用JedisClusterInfoCache类的renewSlotCache方法。获得写锁后再执行cluster slots命令初始化缓存,由于集群所有的键命令都会执行getSlotPool方法方法计算槽对应节点,它内部要求读锁。ReentrantReadWriteLock是读锁共享且读写锁互斥,从而导致所有的请求都会造成阻塞。对于并发量高的场景将极大地影响集群吞吐。这个现象称为cluster slots风暴,有如下现象:

      • 重试机制导致IO通信放大问题。比如默认重试5次的情况,当抛出JedisClusterMaxRedirectionsException异常时,内部最少需要9次IO通信:5次发送命令+2次ping命令保证随机节点正常+2次cluster slots命令初始化slots缓存。导致异常判定时间变长。

      • 个别节点操作异常导致频繁的更新slots缓存,多次调用cluster slots命令,高并发是将过度消耗Redis节点资源,如果集群slot<->映射庞大则cluster slots返回信息越多,问题越严重。

      • 频繁触发更新本地slots缓存操作,内部使用了写锁,阻塞对集群所有的键命令调用。

      针对以上问题在Jedis2.8.2版本做了改进:

      • 当接收到JedisConnectionException时不再轻易初始化slots缓存,大幅降低内部IO次数。逻辑为只有当重试次数到最后一次或者出现MovedDataException时才更新slots操作,降低了cluster slots命令代用次数。

      • 当更新slots缓存时,不再使用ping命令检测节点活跃度,并且使用redis covering变量保证同一时刻只有一个线程更新slots缓存,其他线程忽略,优化了写锁阻塞和cluster slots调用次数。

      综上所述,当出现JedisConnectionException时,命令发送次数变为5次:4次重试命令+1次cluster slots命令,同时避免了cluster slots不必要的并发调用。

      开发提示:

      • 执行cluster slots的过程不需要加入任何读写锁,因为cluster slots命令执行不需要做并发控制,值由修改本地slots时才需要控制并发,这样降低了写锁持有时间。

      • 当获取新的slots映射后使用读锁跟老slots比对,只有新老slots不一致时再加入写锁进行更新。防止集群slots映射没有变化时进行不必要的加写锁行为。

    2. Smart客户端——JedisCluster

      (1)JedisCluster的定义

      Jedis为Redis Cluster提供了Smart客户端,对应的类是JedisCluster,它的初始化方法如下:

      public JedisCluster (Set<HostAndPort> jedisClusterNode, int connectionTiemout, int soTimeout, int maxAttempts, final GenericObjectPoolConfig poolConfig) {
          ...
      }
      

      其中包含了5个参数:

      • Set<HostAndPort> jedisClusterNode:所有Redis Cluster节点信息(也可以是一部分,因为客户端可以通过cluster slots自动发现)。

      • int connectionTimeout:连接超时。

      • int soTimeout:读写超时。

      • int maxAttempts:重试次数。

      • GenericObjectPoolConfig poolConfig:连接池参数,JedisCluster会为Redis Cluster的每个节点创建连接池。对于JedisCluster的使用需要注意以下几点:

      • JedisCluster包含了所有节点的连接池(JedisPool),所以建议JedisCluster使用单例。

      • JedisCluster每次操作完成后,不需要管理连接池的借还,它在内部已经完成。

      • JedisCluster一般不要执行close(),它会将所有JedisPool执行destroy操作。

      (2)多节点命令和操作。

      Redis Cluster虽然提供了分布式的特性,但是有些命令或者操作,诸如keys、flushall、删除 指定模式的键,需要遍历所有节点才可以完成。具体分为如下几个步骤:

      • 通过jedisCluster.getClusterNodes()获取所有节点的连接池。

      • 使用info replication筛选上一步中的主节点。

      • 比那里主节点,使用scan命令找到指定模式的key,使用Pipeline机制删除。

      (3)批量操作的方法

      Redis Cluster中,由于key分布到各个节点上,会造成无法实现mget、mset等功能。但是可以利用CRC16算法计算出key对应的slot,以及Smart客户端保存了slot和节点对应关系的特性,将属于同一个Redis节点的key进行归档,然后分别对每个节点对应的子key列表执行mget或者pipeline操作。

      (4)使用Lua、事务等特性的方法

      Lua和事务需要所操作的key,必须在一个节点上,不过Redis Cluster提供了hashtag,如果开发人员确实需要使用Lua或者事务,可以将所要操作的key使用一个hashtag。具体操作步骤如下:

      • 将事务中所有的key添加hashtag。

      • 使用CRC16计算hashtag对应的slot。

      • 获取指定slot对应的节点连接池JedisPool。

      • 在JedisPool上执行事务。

  3. ASK重定向

    1. 客户端ASK重定向流程

      Redis集群支持在线迁移槽(slot)和数据来完成水平伸缩,当slot对应的数据从源节点到目标节点迁移过程中,客户端需要做到只能识别,保证键命令可正常执行。例如当一个slot数据从源节点迁移到目标节点时,期间可能出现一部分数据在源节点,而另一部分在目标节点。

      当出现上述情况时,客户端键命令执行流程将发生变化:

      • 客户端根据本地slots缓存发送命令道源节点,如果存在键对象则直接执行并返回结果给客户端。

      • 如果键对象不存在,则可能存在于目标节点,这时源节点会恢复ASK重定向异常。格式如下:(error) ASK {slot} {targetIP} : {targetPort}.

      • 客户端从ASK重定向异常提取出目标节点信息,发送asking命令道目标节点打开客户端连接标识,再执行键命令。如果存在则执行,不存在则返回不存在信息。

      ASK与MOVED虽然都是对客户端的重定向控制,但是有着本质区别。ASK重定向说明集群正在进行slot数据迁移,客户端无法知道什么时候迁移完成,因此只能是临时性的重定向,客户端不会更新slots缓存。但是MOVED重定向说明键对应的槽已经明确指定到新的节点,因此需要更新slots缓存。

    2. 节点内部处理

      为了支持ASK重定向,源节点和目标节点在内部的clusterState结构中维护当前正在迁移的槽信息,用于识别槽迁移情况。节点每次接收到键命令是,都会根据clusterState内的迁移属性进行命令处理,如下所示:

      • 如果键所在的槽由当前节点负责,但键不存在则查找migrating_slots_to数组查看槽是否正在迁出,如果是返回ASK重定向。

      • 如果客户端发送asking命令打开了CLIENT_ASKING标识,则该客户端下次发送键命令时查找importing_slots_from数组获取clusterNode,如果指向自身则执行命令。需要注意的是,asking命令时一次性命令,每次执行完后客户端标识都会修改回原状态,因此每次客户端接收ASK重定向后都需要发送asking命令。

      • 批量操作。ASK重定向对单键命令支持的很完善,但是,在开发送我们经常使用批量操作,如mget或pipeline。当槽处于迁移状态是,批量操作会受到影响。

      使用smart客户端批量操作集群时,需要评估mget/mset、Pipeline等方式在slot迁移场景下的容错性,防止集群迁移造成大量错误和数据丢失的情况。

      开发提示:集群环境下对于使用批量操作的场景,建议优先使用Pipeline方式,在客户端实现对ASK重定向的正确处理,这样既可以受益于批量操作的IO优化,又可以兼容slot迁移场景。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • NOSQL类型简介键值对:会使用到一个哈希表,表中有一个特定的键和一个指针指向特定的数据,如redis,volde...
    MicoCube阅读 3,958评论 2 27
  • 一、redis cluster简介 Redis在3.0版正式引入了集群这个特性。 Redis集群是一个提供在多个R...
    张伟科阅读 1,615评论 0 4
  • 基本目标与设计基本思想 Redis cluster 目标 高性能,并且能线性扩展到1000个节点。不需要代理,使用...
    tafeng阅读 2,756评论 0 0
  • 每一个当下是万物和那一刻时空交织的产物。目标,也是一个当下,而且是你在那个当下看到的、想要的那一部分,还不是全部。...
    晓颖_ed68阅读 466评论 0 3
  • 汽车视野盲区”,相信开车的朋友们都知道,也是非常害怕自己遇到盲区,因为盲区经常会发生交通事故,从而被称为“死亡地带...
    BAIOS宝视阅读 448评论 0 0