一、现象:

我们的redis私有云,对外提供了redis-standalone, redis-sentinel, redis-cluster三种类型的redis服务。

其中redis-cluster, 使用的版本是 Redis Cluster 3.0.2, 客户端是jedis 2.7.2。

有人在使用时候,业务的日志中发现了一些异常(Too many Cluster redirections)。

二、jedis源码分析:

先从jedis源码中找到这个异常,这段异常是在JedisClusterCommand类中

Java代码

  1. if (redirections <= 0) {
  2. throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections? key=" + key);
  3. }
 
  1. if (redirections <= 0) {

  2. throw new JedisClusterMaxRedirectionsException("Too many Cluster redirections? key=" + key);

  3. }

在jedis中调用redis-cluster使用的JedisCluster类,所有api的调用方式类似如下:

Java代码

  1. public String set(final String key, final String value) {
  2. return new JedisClusterCommand<String>(connectionHandler, maxRedirections) {
  3. @Override
  4. public String execute(Jedis connection) {
  5. return connection.set(key, value);
  6. }
  7. }.run(key);
  8. }
 
  1. public String set(final String key, final String value) {

  2. return new JedisClusterCommand<String>(connectionHandler, maxRedirections) {

  3. @Override

  4. public String execute(Jedis connection) {

  5. return connection.set(key, value);

  6. }

  7. }.run(key);

  8. }

所以重点代码在JedisClusterCommand这个类里,重要代码如下:

Java代码

  1. public T run(int keyCount, String... keys) {
  2. if (keys == null || keys.length == 0) {
  3. throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");
  4. }
  5. if (keys.length > 1) {
  6. int slot = JedisClusterCRC16.getSlot(keys[0]);
  7. for (int i = 1; i < keyCount; i++) {
  8. int nextSlot = JedisClusterCRC16.getSlot(keys[i]);
  9. if (slot != nextSlot) {
  10. throw new JedisClusterException("No way to dispatch this command to Redis Cluster "
  11. + "because keys have different slots.");
  12. }
  13. }
  14. }
  15. return runWithRetries(SafeEncoder.encode(keys[0]), this.redirections, false, false);
  16. }
  17. private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {
  18. if (redirections <= 0) {
  19. JedisClusterMaxRedirectionsException exception = new JedisClusterMaxRedirectionsException(
  20. "Too many Cluster redirections? key=" + SafeEncoder.encode(key));
  21. throw exception;
  22. }
  23. Jedis connection = null;
  24. try {
  25. if (asking) {
  26. // TODO: Pipeline asking with the original command to make it
  27. // faster....
  28. connection = askConnection.get();
  29. connection.asking();
  30. // if asking success, reset asking flag
  31. asking = false;
  32. } else {
  33. if (tryRandomNode) {
  34. connection = connectionHandler.getConnection();
  35. } else {
  36. connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));
  37. }
  38. }
  39. return execute(connection);
  40. } catch (JedisConnectionException jce) {
  41. if (tryRandomNode) {
  42. // maybe all connection is down
  43. throw jce;
  44. }
  45. // release current connection before recursion
  46. releaseConnection(connection);
  47. connection = null;
  48. // retry with random connection
  49. return runWithRetries(key, redirections - 1, true, asking);
  50. } catch (JedisRedirectionException jre) {
  51. // if MOVED redirection occurred,
  52. if (jre instanceof JedisMovedDataException) {
  53. // it rebuilds cluster's slot cache
  54. // recommended by Redis cluster specification
  55. this.connectionHandler.renewSlotCache(connection);
  56. }
  57. // release current connection before recursion or renewing
  58. releaseConnection(connection);
  59. connection = null;
  60. if (jre instanceof JedisAskDataException) {
  61. asking = true;
  62. askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));
  63. } else if (jre instanceof JedisMovedDataException) {
  64. } else {
  65. throw new JedisClusterException(jre);
  66. }
  67. return runWithRetries(key, redirections - 1, false, asking);
  68. } finally {
  69. releaseConnection(connection);
  70. }
  71. }
 
  1. public T run(int keyCount, String... keys) {

  2. if (keys == null || keys.length == 0) {

  3. throw new JedisClusterException("No way to dispatch this command to Redis Cluster.");

  4. }

  5. if (keys.length > 1) {

  6. int slot = JedisClusterCRC16.getSlot(keys[0]);

  7. for (int i = 1; i < keyCount; i++) {

  8. int nextSlot = JedisClusterCRC16.getSlot(keys[i]);

  9. if (slot != nextSlot) {

  10. throw new JedisClusterException("No way to dispatch this command to Redis Cluster "

  11. + "because keys have different slots.");

  12. }

  13. }

  14. }

  15. return runWithRetries(SafeEncoder.encode(keys[0]), this.redirections, false, false);

  16. }

  17. private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {

  18. if (redirections <= 0) {

  19. JedisClusterMaxRedirectionsException exception = new JedisClusterMaxRedirectionsException(

  20. "Too many Cluster redirections? key=" + SafeEncoder.encode(key));

  21. throw exception;

  22. }

  23. Jedis connection = null;

  24. try {

  25. if (asking) {

  26. // TODO: Pipeline asking with the original command to make it

  27. // faster....

  28. connection = askConnection.get();

  29. connection.asking();

  30. // if asking success, reset asking flag

  31. asking = false;

  32. } else {

  33. if (tryRandomNode) {

  34. connection = connectionHandler.getConnection();

  35. } else {

  36. connection = connectionHandler.getConnectionFromSlot(JedisClusterCRC16.getSlot(key));

  37. }

  38. }

  39. return execute(connection);

  40. } catch (JedisConnectionException jce) {

  41. if (tryRandomNode) {

  42. // maybe all connection is down

  43. throw jce;

  44. }

  45. // release current connection before recursion

  46. releaseConnection(connection);

  47. connection = null;

  48. // retry with random connection

  49. return runWithRetries(key, redirections - 1, true, asking);

  50. } catch (JedisRedirectionException jre) {

  51. // if MOVED redirection occurred,

  52. if (jre instanceof JedisMovedDataException) {

  53. // it rebuilds cluster's slot cache

  54. // recommended by Redis cluster specification

  55. this.connectionHandler.renewSlotCache(connection);

  56. }

  57. // release current connection before recursion or renewing

  58. releaseConnection(connection);

  59. connection = null;

  60. if (jre instanceof JedisAskDataException) {

  61. asking = true;

  62. askConnection.set(this.connectionHandler.getConnectionFromNode(jre.getTargetNode()));

  63. } else if (jre instanceof JedisMovedDataException) {

  64. } else {

  65. throw new JedisClusterException(jre);

  66. }

  67. return runWithRetries(key, redirections - 1, false, asking);

  68. } finally {

  69. releaseConnection(connection);

  70. }

  71. }

代码解释:

1. 所有jedis.set这样的调用,都用JedisClusterCommand包装起来(模板方法)

2. 如果操作的是多个不同的key, 会抛出如下异常,因为redis-cluster不支持key的批量操作(可以通过其他方法解决,以后会介绍):

Java代码

  1. throw new JedisClusterException("No way to dispatch this command to Redis Cluster because keys have different slots.");
   throw new JedisClusterException("No way to dispatch this command to Redis Cluster because keys have different slots.");

3. 参数解释

Java代码

  1. private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {
    private T runWithRetries(byte[] key, int redirections, boolean tryRandomNode, boolean asking) {

(1) key: 不多说了

(2) redirections: 节点调转次数(实际可以看做是重试次数)

(3) tryRandomNode: 是否从redis cluster随机选一个节点进行操作

(4) asking: 是否发生了asking问题

4. 逻辑说明:

正常逻辑:

(1) asking = true: 获取asking对应的jedis, 然后用这个jedis操作。

(2) tryRandomNode= true: 从jedis连接池随机获取一个可用的jedis, 然后用这个jedis操作。

(3) 都不是:直接用key->slot->jedis,直接找到key对应的jedis, 然后用这个jedis操作。

异常逻辑:

(1) JedisConnectionException: 连接出了问题,连接断了、超时等等,tryRandomNode= true,递归调用本函数

(2) JedisRedirectionException分两种:

---JedisMovedDataException: 节点迁移了,重新renew本地slot对redis节点的对应Map

---JedisAskDataException: 数据迁移,发生asking问题, 获取asking的Jedis

此过程最多循环redirections次。

异常含义:试了redirections次(上述),仍然没有完成redis操作。

三、原因猜测:

1. 超时比较多,默认超时时间是2秒。

(1). 网络原因:比如是否存在跨机房、网络割接等等。

(2). 慢查询,因为redis是单线程,如果有慢查询的话,会阻塞住之后的操作。

(3). value值过大?

(4). aof 重写/rdb fork发生?

2. 节点之间关系发生变化,会发生JedisMovedDataException

(1)在linux上的redis的配置文件中的  bind 参数所绑定的ip如果是 127.0.0.1 但测试时却是使用该主机的局域网地址会导致 redirection异常 ,

所以bind参数应该绑定主机的实际ip。

3. 数据在节点之间迁移,会发生JedisAskDataException

四、定位方法:

看了一下redis的日志第三节中的2,3并未发生,应该是超时的情况。

异常发生在2015-10-19 04:34:30左右,给出如下异常key值

Java代码

  1. key=v11Pay|huid|wlunm99_561555097
  2. key=play_anchorroom_info_529460
  3. key=v11Pay|huid|qq-qhncnxujax
  4. key=play_anchor_info_qq-luzvfcftnf
  5. key=play_anchor_info_qq-luzvfcftnf
  6. key=play_anchorroom_info_550649
  7. key=play_anchor_info_qq-cfrkukhdsd
  8. key=play_anchor_info_qq-rbufgcqbvk
 
  1. key=v11Pay|huid|wlunm99_561555097

  2. key=play_anchorroom_info_529460

  3. key=v11Pay|huid|qq-qhncnxujax

  4. key=play_anchor_info_qq-luzvfcftnf

  5. key=play_anchor_info_qq-luzvfcftnf

  6. key=play_anchorroom_info_550649

  7. key=play_anchor_info_qq-cfrkukhdsd

  8. key=play_anchor_info_qq-rbufgcqbvk

经过查询,这些key都同时定位在一个redis实例上,于是看了一下这个redis实例的日志(与异常时间点对应),发现如下:AOF fsync发生了异常,以经验看是本地IO使用较大造成的。

Java代码

  1. 17932:M 19 Oct 04:35:30.010 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
  2. 17932:M 19 Oct 04:35:41.087 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
  3. 17932:M 19 Oct 04:35:47.044 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
  4. 17932:M 19 Oct 10:15:51.463 * Starting automatic rewriting of AOF on 1795% growth
 
  1. 17932:M 19 Oct 04:35:30.010 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

  2. 17932:M 19 Oct 04:35:41.087 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

  3. 17932:M 19 Oct 04:35:47.044 * Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

  4. 17932:M 19 Oct 10:15:51.463 * Starting automatic rewriting of AOF on 1795% growth

看了一下tsar的历史记录:tsar --io -n 2 | head -200

Java代码

  1. Time            rrqms   wrqms      rs      ws   rsecs   wsecs  rqsize  qusize   await   svctm    util
  2. 19/10/15-04:00   0.00  164.08    0.01   34.52    0.04  745.15   21.58    0.00   25.30    4.13   14.26
  3. 19/10/15-04:05  40.38    1.1K  218.49   78.39   13.9K    4.9K   64.55    7.00   24.63    2.80   83.19
  4. 19/10/15-04:10  37.15    1.0K  360.58   71.91   13.4K    4.3K   42.04    6.00   14.67    1.70   73.34
  5. 19/10/15-04:15   1.99    1.5K   21.98  115.38  588.69    6.6K   53.12    5.00   39.86    1.98   27.14
  6. 19/10/15-04:20  40.17    1.0K  278.00   76.79   10.4K    4.2K   42.32    4.00   11.48    1.60   56.85
  7. 19/10/15-04:25  78.28  861.13  381.34   62.33   14.3K    3.6K   41.40    4.00    9.85    1.51   66.78
  8. 19/10/15-04:30  81.64  913.85  402.37   55.35   15.1K    3.8K   42.18    4.00    9.47    1.41   64.71
  9. 19/10/15-04:35  21.92  888.72  145.97   58.00   16.2K    3.7K   99.71    4.00   20.57    3.63   74.04
  10. 19/10/15-04:40  39.72  474.01  169.01   48.26   14.3K    2.0K   77.09    3.00   17.83    4.14   89.89
  11. 19/10/15-04:45  47.02  537.60  149.41   41.50   16.7K    2.3K  101.55    3.00   18.27    4.21   80.35
 
  1. Time rrqms wrqms rs ws rsecs wsecs rqsize qusize await svctm util

  2. 19/10/15-04:00 0.00 164.08 0.01 34.52 0.04 745.15 21.58 0.00 25.30 4.13 14.26

  3. 19/10/15-04:05 40.38 1.1K 218.49 78.39 13.9K 4.9K 64.55 7.00 24.63 2.80 83.19

  4. 19/10/15-04:10 37.15 1.0K 360.58 71.91 13.4K 4.3K 42.04 6.00 14.67 1.70 73.34

  5. 19/10/15-04:15 1.99 1.5K 21.98 115.38 588.69 6.6K 53.12 5.00 39.86 1.98 27.14

  6. 19/10/15-04:20 40.17 1.0K 278.00 76.79 10.4K 4.2K 42.32 4.00 11.48 1.60 56.85

  7. 19/10/15-04:25 78.28 861.13 381.34 62.33 14.3K 3.6K 41.40 4.00 9.85 1.51 66.78

  8. 19/10/15-04:30 81.64 913.85 402.37 55.35 15.1K 3.8K 42.18 4.00 9.47 1.41 64.71

  9. 19/10/15-04:35 21.92 888.72 145.97 58.00 16.2K 3.7K 99.71 4.00 20.57 3.63 74.04

  10. 19/10/15-04:40 39.72 474.01 169.01 48.26 14.3K 2.0K 77.09 3.00 17.83 4.14 89.89

  11. 19/10/15-04:45 47.02 537.60 149.41 41.50 16.7K 2.3K 101.55 3.00 18.27 4.21 80.35

于是发现从4点开始IO开销一直增大,以经验看应该是有定时任务(都是托管的机器,上面还有别人的应用),于是发现了如下,是一个nginx合并的脚本,本地IO开销较大。

Java代码

  1. 00 04 * * * sh /opt/script/logcron.sh
  2. 00 04 * * * sh /opt/script/logremove.sh
 
  1. 00 04 * * * sh /opt/script/logcron.sh

  2. 00 04 * * * sh /opt/script/logremove.sh

五、解决方法:

(1) 和使用方沟通一下,他们完全把redis当做memcache用,也就是允许断电后数据丢失,重新从数据源获取数据写到缓存,因此关闭了aof配置(此方法不治本)

(2) 定时脚本下线或者优化。(最终采用此方法)

Redis Cluster:Too many Cluster redirections异常相关推荐

  1. 聊一聊Redis的Sentinel与Cluster

    文章目录 哨兵模式 什么是哨兵模式? 哨兵模式的小细节 哨兵是如何去监控这些节点的 哨兵模式的下线模式 哨兵模式的选举 消息丢失 Cluster模式 什么是cluster模式? cluster模式的缺 ...

  2. redis:cluster nodes、cluster slaves node-id

    作用 cluster nodes cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息 集群中的每个节点都有当前集群配置的一个视图(快照),视图的信息由该节点 ...

  3. 玩转Redis集群之Cluster

    前面我们介绍了国人自己开发的Redis集群方案--Codis,Codis友好的管理界面以及强大的自动平衡槽位的功能深受广大开发者的喜爱.今天我们一起来聊一聊Redis作者自己提供的集群方案--Clus ...

  4. Redis集群配置和常见异常解决

    Redis集群配置和常见异常解决 参考文章: (1)Redis集群配置和常见异常解决 (2)https://www.cnblogs.com/hzb462606/p/11121281.html 备忘一下 ...

  5. 集成redis,删除key报“srem“异常

    Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: ERR wrong number of arguments ...

  6. 外部连接Redis时候,出现Time Out异常

    外部连接Redis时候,出现Time Out异常 当我们使用java连接Redis服务器得时候,出现下面这个异常 redis.clients.jedis.exceptions.JedisConnect ...

  7. 深入理解Redis Cluster和Jedis Cluster

    本文转载自:https://zhuanlan.zhihu.com/p/69800024 Redis Cluster是Redis官方提供的集群解决方案.由于业务的飞速增长,单机模式总会遇到内存.性能等各 ...

  8. 两台服务器安装redis集群_Redis Cluster搭建高可用Redis服务器集群

    一.Redis Cluster集群简介 Redis Cluster是Redis官方提供的分布式解决方案,在3.0版本后推出的,有效地解决了Redis分布式的需求,当一个节点挂了可以快速的切换到另一个节 ...

  9. Redis:Redis集群模式(Cluster)原理

    1.前言 由于Redis主从复制模式和Redis哨兵模式采用的都是复制Master节点的数据,实现读写分离.但是这种设计存在一个严重的问题,它没有真正意义上实现数据分片.两个模式都有一个问题,不能水平 ...

最新文章

  1. JS实现转动随机数抽奖的特效代码
  2. hdu-5834 Magic boy Bi Luo with his excited tree(树形dp)
  3. php 时间戳存储 原因,将php文件中的unix时间戳存储到mysql中(store unix timestamp from php file into mysql)...
  4. Mysql中show命令详解
  5. Java的JAR包, EAR包 ,WAR包内部结构
  6. PLSQL 连接不上
  7. 结对项目:电梯调度算法的实现和测试
  8. [Algo] Print Matrix Diagonal 对角打印
  9. Java:HttpClient篇,HttpClient4.2在Java中的几则应用:Get、Post参数、Session(会话)保持、Proxy(代理服务器)设置,多线程设置......
  10. 攻略:简易病毒制作(Windows)
  11. Visual Studio新特性:串口监视器和Zephyr支持
  12. STC12LE5612AD芯片使用心得(一)芯片介绍
  13. WPF中监听剪贴板存在的Bug:OpenClipboard HRESULT:0x800401D0 (CLIPBRD_E_CANT_OPEN))错误
  14. 虚拟机实现远程桌面连接
  15. 嵌入式测试 模拟共享单车
  16. 上传gitlab ! [remote rejected] dev - dev (pre-receive hook declined)
  17. 为什么大学计算机老师不去大公司当程序员说出来你都很难敢相信
  18. 百度地图开发技术方案及解决办法
  19. bmp180气压传感器工作原理_37张传感器工作原理动图,张张经典
  20. 中职计算机应用专业(云计算方向)建设实践

热门文章

  1. c语言 打印十字图案,打印十字图-蓝桥杯历届试题
  2. SQLite下载与安装
  3. Spring Boot整合elasticsearch实现全文检索
  4. 电影自助售票系统业务逻辑分析
  5. 快速排序之JavaScript版
  6. Observability:应用程序性能监控/管理(APM)实践
  7. python:scrapy 一个网站爬虫库
  8. python out of memory_详解Pycharm出现out of memory的终极解决方法
  9. vm虚拟机中的虚拟网络编辑器出现 不能更改网络为桥接:已经没有桥接的主机网络适配器的 解决方案
  10. 分享一个进销存项目(多层架构)