CodisLabs / jodis

A java client for codis based on Jedis and Curator
MIT License
217 stars 97 forks source link

这个方法经常会抛出异常,read timeout 或者Unexpected end of stream 怎么避免? #21

Closed chinaer closed 7 years ago

chinaer commented 8 years ago
  /**
   * This methods assumes there are required bytes to be read. If we cannot read anymore bytes an
   * exception is thrown to quickly ascertain that the stream was smaller than expected.
   */
  private void ensureFill() throws JedisConnectionException {
    if (count >= limit) {
      try {
        limit = in.read(buf);
        count = 0;
        if (limit == -1) {
          throw new JedisConnectionException("Unexpected end of stream.");
        }
      } catch (IOException e) {
        throw new JedisConnectionException(e);
      }
    }
  }
}

@Apache9

spinlock commented 8 years ago

出这种情况看可能是 proxy 关闭了连接,去看一下 proxy log,会给出每个 connection 退出原因的。

chinaer commented 8 years ago

2016/04/28 11:07:04 session.go:70: [INFO] session [0xc824470c00] closed: {"ops":5,"lastop":1461812824,"create":1461812824,"remote":"172.16.4.140:48008"}, error = EOF 这种算正常吗?

spinlock commented 8 years ago

@admln 这个不可能呀。

首先,proxy B 发生主从切换是什么意思?codis-proxy 里面的主从切换目前都是集群同时进行的。

然后,主从切换本质是 fillslot 操作,会等待未完成指令先返回,然后再更新连接,这种操作不会导致 client 连接被断开。但是如果切换前 master 挂了,这就另说了。所以先确认是 master 被 kill 在先还是 主从切换完成在先?

chinaer commented 8 years ago

proxy不是无状态的吗?而且这种出现不是由于主从切换导致的,而是我不停的set数据的过程中发生的少量的这种情况,但是把timeout时间设置大一些会少一些出现,但是还是会有出现,如果codis主从切换的过程发生震荡到还好理解,问题是没有进行这方面操作

spinlock commented 8 years ago

proxy 不会自己进行主从切换,所有切换操作都是外部通过 rpc 进行操作的(codis3),或者通过 zk 获取的(codis2)。

所以,@admln 主从切换这件事提到的主从切换是怎么回事儿应该和你 @chinaer 提到的不是一回事。

@chinaer 你提到通过 set 会出现这种错误,你是如何调用的?通过 pipeline 么?

admln commented 8 years ago

我描述错了。是其中一个group里面master死掉了,然后slave顶上去了。然后正在连接proxy写数据的客户端就报上面的错误了

chinaer commented 8 years ago

我没有通过pipeline,我就是用你的例子,不停循环set随机数,这个串不要太长,循环1w次,应该就能重现,你可以重现一下看看,我就追踪到jedis的这个方法出错,但是不知道怎么解决?@spinlock

spinlock commented 8 years ago

@admln 对的。

因为 master 死掉了。client 被 reset 是因为 client 请求已经被发给死掉的 master 了,如果 client 的请求在 proxy 收到主从切换指令之后,那么 client 不会受到影响。

spinlock commented 8 years ago

能提供完整的测试程序么?

admln commented 8 years ago

是的。我用的codis3.x,我模拟一个group里面发生主从切换。在主从切换时一直在持续连接某个proxy写数据。我想的是能否让几个group里面的一个在发生主从切换时不影响正在操作的客户端,而让写操作或者其他操作应用到其他没有正在发生主从切换的group里面。这样能降低意外情况下发生主从切换的影响

chinaer commented 8 years ago

这个除非自己客户端做修改吧,这个主从切换一般是偶然发生意外导致master死掉了,你才会去切换,这个时候hash到这个group的slot的时候,这部分连接应该是坏的,其他group这个时候正常工作,除非你代码里面能够捕获重新分到其他group,我是这样觉着的@admln

spinlock commented 8 years ago

@admln 你这个需求在 redis 是无解的。 因为底层连接已经出错了,所以根本无法判断数据是否已经写成功,不确定是 redis 未收到还是返回 response 时挂掉,也不能知道写请求是不是同步给了 slave。(除非所有数据都是 set/del 之类的幂等操作)

再加上 proxy 目前提升主从是通过外部进行同步的。所以目前这个似乎没有很好的解决办法。

而且客户端能够正确处理连接异常情况也应该是必须的吧?

admln commented 8 years ago

嗯。我现在在客户端加了异常捕捉,出现异常重新从pool中获取一个连接。这样就不会打断客户端,只是短暂停顿一下。谢谢你们的解答。 @spinlock @chinaer

chinaer commented 8 years ago

第一种---------------------------------------------------------------- JedisResourcePool jedisPool = RoundRobinJedisPool.create() .curatorClient("172.16.4.51:2181,172.16.4.52:2181,172.16.4.53:2181", 30000).zkProxyDir("/zk/codis/db_nonobank/proxy").build(); Jedis jedis = jedisPool.getResource(); for(int i=10000;i<100000;i++){ System.out.println(i); jedis.set("foo"+i, "bar"); jedis.get("foo"+i); } 这样会出现Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:201) at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) at redis.clients.jedis.Protocol.process(Protocol.java:141) at redis.clients.jedis.Protocol.read(Protocol.java:205) at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297) at redis.clients.jedis.Connection.getBinaryBulkReply(Connection.java:216) at redis.clients.jedis.Connection.getBulkReply(Connection.java:205) at redis.clients.jedis.Jedis.get(Jedis.java:101) at com.nonobank.arch.cacheclient.Test.main(Test.java:20) Caused by: java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.read(Unknown Source) at java.net.SocketInputStream.read(Unknown Source) at java.net.SocketInputStream.read(Unknown Source) at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:195) ... 8 more 第二种----------------------------------------------------------------------------------------------------- 然后JedisResourcePool jedisPool = RoundRobinJedisPool.create() .curatorClient("172.16.4.51:2181,172.16.4.52:2181,172.16.4.53:2181", 30000).zkProxyDir("/zk/codis/db_nonobank/proxy").build(); for(int i=10000;i<100000;i++){ Jedis jedis = jedisPool.getResource(); System.out.println(i); jedis.set("foo"+i, "bar"); jedis.get("foo"+i); } 这样会出现很多waiting状态的线程,这如何解决呢? @spinlock 线程在这个时候的dump发现是在borrowObject的时候出现了time waiting 状态

spinlock commented 8 years ago

https://github.com/CodisLabs/jodis/issues/22

这个 issue 你也回答了的呀。

你这个 sample code 里面没有把 jodis 显示的还回去,每次都是创建新连接。

spinlock commented 8 years ago

codis-proxy 目前没有限制 connection 数量,之后我会加上去,那样的话,你这个代码就会直接抛异常了。

spinlock commented 8 years ago

https://github.com/CodisLabs/jodis/issues/8

chinaer commented 8 years ago

我的例子里面有两个的,第一个是拿了一个连接,一直使用,第二个是每次操作拿一个连接,如果采用第一种会报错,采用第二种,应该会通过语法糖还回去,这个时候卡住了,这是为什么呢?@spinlock