• 全球领先的redis客户端:SFedis


    零、背景

      这个客户端起源于我们一个系统的生产问题。

    一、问题的发生

      在我们的生产环境上发生了两次redis服务端连接数达到上限(我们配置的单节点连接数上限为8000)导致无法创建连接的情况。由于这个系统生产环境的redis集群的tps达到百万级,所以发生了这个情况的后果是非常严重的,有的业务会发生缓存穿透的情况,有的业务会直接报错。

    二、问题分析

      在生产环境上每个redis节点的tps上限在50000左右,我们监控redis的slowlog的阀值设置为0.1ms,也就是说如果服务端慢到10000tps时就会触发报警,但在问题发生当时并没有报警。实际上这是我们的一个失误:如果redis一个服务节点是独享一个cpu核的,那么按照redis的机制是可以推测出slowlog是不可能会有“慢”的结果的。那么如果慢一定不是在redis本身的处理上,有可能是塞在epoll上或者网络上。但我们并没有发现有任何地方有异常(包括网络)。

      我们并没有查到故障发生在哪里,但故障的确就发生了,这是很离奇的。

      最后我们只能进行了推测:正常情况下整个集群的速度是非常快的。监控设置的0.1ms的阀值虽然看起来是非常快(万分之一秒),但和正常情况下的平均响应时长来说还是慢了5倍的差距。也就是说,我们检测每一个地方都没有看到问题,可能只是因为检测的标准以及检测工具的能力(精确度)的问题。比如说:平时单节点平均处理能力在0.02ms每个命令,但当慢(无论慢在哪里)到0.05ms的时候我们是没能监控出来的,而实际上这个时候问题已经发生了。假设网络因未知原因卡了一秒钟,那么就会有几十万到一百万个请求塞在网络上,客户端因请求还没有返回,新的请求就会向连接池申请新的连接,如果服务端没有保留足够的buffer来处理瞬间多出来的请求,那么很有可能在这个时候发生一个雪崩效应——连接数瞬间达到上限。

    三、临时解决

      当时在故障处理时,我们采取了比较粗爆但有效的办法:减少客户端的数量。我们停掉了相关服务的一半节点,使所有运行节点的线程池即使全部打满也不会达到redis服务端的上限,这样当业务消费一段时间后,请求降下来了,再启动被关掉的服务。

      当天晚上我们对redis集群进行了扩容,保留了更大的buffer,使应对异常冲击的能力提高一些。

      这些只是临时的解决方案,治标不治本的。所以还是需要更进一步研究更好的解决方案。

      这里需要说明一点:为什么服务可以停掉一半?如果服务停掉了一半,前端的请求会不会把服务的cpu打满,导致服务挂掉呢?

      这里是因为:

      1.服务端对所有的rest/http接口以及rpc接口都做了隔离限流,每任何一个接口超过一定的并发之后,后面的请求就会马上报错,保证服务的安全。

      2.用户端是移动App,在移动端我们对所有重要业务做了统一的重试机制,如果没有传上来的,可以在一定时间之后再次重试。

      所以这里服务端减少服务能力的情况下,并不会导致严重的业务问题,但是会使业务数据上传变慢一些。

    四、原理分析

      当时我们的客户端用的是jedis,连接的管理用的是jedis自带的。

      因为redis服务端的每个节点的数据是不同的,所以在长时间的调用下,每个客户端一定会访问到每个服务端节点。这样的话,服务端每个节点的连接数就并不取决于服务端集群的大小,而取决于客户端集群的大小。

      如下图所示:如果客户端有2个,每个客户端的连接池上限是40个连接,那么无论服务端是多少个节点,每个节点的连接数量的上限应该是40*2=80个。

      

      那么问题来了:服务端的每个节点处理能力是有限的,连接数过多是没有意义的,如果每个服务端的连接上限是10000个,每个客户端的连接池上限是100个,那么在理论上要保证连接安全,客户端的节点数上限是10000/100=100个。但如果我们需要更大的业务处理能力(业务应用集群的节点数需要超过100个)的情况下,怎么办呢?

    五、一个想法

      从理论上说,1个连接是可以达到一个网卡的带宽极限的,那么是否有可能做到每个redis客户端只有一个连接,却可以达到原来n多个连接一样的性能(甚至更好)呢?

    六、研究业界现有方案

      带着问题,我们用了两个月时间来研究测试各种业界公认的成熟方案(除了当时正在用的jedis客户端之外,还研究了twemproxy、Codis、redis 4.0 (cluster)、redisson),发现这些方案并没有让我们满意。下面说一下我们为什么不选择这些方案:

      twemproxy:代理并不能完全解决连接数的问题,它只能让连接数少一些,而且代理大约有20%的性能损耗。

      Codis:1.代理和twemproxy的差不多,也不能完全解决连接数的问题;2.Codis新版本没有节点失效的检测的能力;3.整个方案的部署比较麻烦。4.在增加节点时,集群会自动迁移数据(当然,这个不能说是缺点,但如果整个集群的内存达到几个T的情况下,内存的数据迁移会有什么后果不好预料(迁移数据导致网络塞住怎么办?迁移数据时服务会中段多长时间?))。

      Redis cluster:1.必须做主备,当主备都挂了的情况下,不能自动摘除节点;2.在增加节点时,集群会自动迁移数据——这一点和Codis一样——我们宁可缓存穿透,也不希望他迁移数据(如果实现了一致性hash,那么会穿透的数据还是很少的——比如:如果我们服务已经有了100个节点,再加一个节点最多只会导致1%的数据失效);

      redisson:这个客户端用了nio机制,在异步操作的情况下的确会大大减少连接数,并且异步的性能非常好(极端的情况下,有可能是jedis的十倍)。但在同步的情况下就没那么乐观,还是需要多个连接才能勉强追得上jedis的速度。如果我们改用redisson的异步形式,则需要改业务代码,这是很难接受的——不过这里我认为是redisson的开发者们对代码的优化没有做到极致,因为在基础原理上nio可以达到的程度绝对可以比现在的redisson更好。

      另外,如果采用短连接的形式的话,对性能的影响比较大,所以我们也不想牺牲长连接的优势。

      既然找不到已经实现好的成熟方案,那么我们是否可以自己实现一个呢?

    七、自己开发

      目标很清晰:一个“新的jedis”,但每个客户端在连接每个服务节点时只连一个连接,最重要的是性能绝不可以比jedis差。

      虽然目标很清晰,并且在基础原理上是可以达到的,但具体的技术细节确并不容易。目标是我定的,但我给不出在技术细节上的实现方案,后来我们部门内的一个码神想到了一个很好的实现方案。

      具体原理是这样的:

      1.redis的通信协议是tcp,这就提供了异步请求的基础——如果是同步的网络请求,客户端就需要等待服务端的响应,那么在等待这段时间里,带宽是空着的,这样要打满带宽就必然需要多个连接,所以,如果我们需要用一个连接打满带宽就必然需要用异步。

      2.redis的命令协议上是没有在发送与接收之间建立对应关系的(没有msg_id之类的属性),这如果不停的发送与接收命令,应该如何告诉业务哪个接收到的数据属于应用事例的哪个线程呢?这里我们找到了一个很巧妙的对应关系:顺序。redis服务端是单线程的,那么服务端先接收到的命令必然先返回,同时,tcp协议又是保证顺序的,这就决定了我们可以用“顺序”做为“发”与“收”之间的对应协议。

      3.为了不修改业务,我们必须用“新的原理”来实现“老的接口”,老接口都是同步操作的,那么这里的阻塞动作就一定要在客户端框架中来实现了。这里就要用到Future了。

      

      最终的实现结果是:我们自己实现的新redis客户端框架SFedis访问每个服务节点只用1个连接,却比业界广泛使用的Jedis用多个连接还要快一点。

      我们现在还没有实现异步接口,如果我们真的实现了异步接口,那么估计比redisson还要快。

      另:在十一月的新书《决战618》我看到书中有写到京东也有用nio实现自己的redis客户端来解决连接数的问题,不过书中只有一句话讲这个,完全没有任何细节。

    八、结果展示

      我们有两个服务共用一个redis集群,下图是其中一个服务上线后的连接数监控图:

      

      这里可以看到:一个服务上线之后的几天比上线前的几天,redis连接数直接腰斩了。

      下图是另一个服务也上线之后的连接数监控图:

      

      可以看到在第二个服务上线之后,连接数已经完全不再波动了(这里千万别误会:后面三天的线是平的,不代表没有服务。服务是正常运行的,而且运行得很健康),这里连接数停留在应用实例的个数上(58个)。

      这里声明一下:这个系统是有做灰度的,在生产上有多个环境在跑不同的版本,上面的两个截图是一个小环境上线前后的监控情况,所以节点数比较少,只有58个。而且这个小环境在性能上留的buffer是比较充足的,所以平时的redis连接数也不高。在大的生产环境上这个图会显得更猛一些。

    九、开源计划

      目前这个客户端还没有开源,但开源已在计划之中。后续开源之后会公布出来。

    十、人员招募

      我们团队正在招人,岗位有:Android开发、Java后台开发、架构师、测试。欢迎大家推荐或自荐!

      简历请发我邮箱:zhouyou@sf-express.com

    十一、导流

      请扫码关注我的微信公众号:

      

  • 相关阅读:
    系统程序员成长计划并发(二)(下)
    Web开发必知的八种隔离级别
    国产Android视频,Broncho A1
    Android中的BatteryService及相关组件
    Struts2输出XML格式的Result
    系统程序员成长计划并发(三)(上)
    入选”第一期中国最受欢迎50大技术博客”
    Broncho团队招聘应届毕业生(包括大四学生) 2名
    系统程序员成长计划并发(三)(下)
    WordPress MailUp插件‘Ajax’函数安全绕过漏洞
  • 原文地址:https://www.cnblogs.com/naturemickey/p/7820252.html
Copyright © 2020-2023  润新知