美文网首页
非阻塞微服务网关的设计和思考

非阻塞微服务网关的设计和思考

作者: doaj93 | 来源:发表于2018-03-22 14:54 被阅读0次

       前段时间的一个实习项目让我收获了很多,从high level分享一下自己的心得。

    微服务

        Monolithic和微服务的区别大家都知道。在企业早期的时候,因为人少代码少沟通方便,快速迭代很开心,而当规模开始超过Dunbar's number,员工之间的沟通开始成为问题。这个时候就要将团队分组,分业务,分职能,重新规划。我一直觉得微服务架构的最主要的好处不是易于线性扩展,而是减少了团队的沟通和管理成本。规模不是很大,没有必要采用微服务。

    RPC

        rpc本来是一个很老的概念。在微服务架构中,因为每个服务可能调用其他服务,是一个大型的分布式系统,存在大量network communication。为了降低整体系统的communication cost,在较大的系统中都不会采用http协议而是使用一些自定义的高效的rpc协议来通信。相比于http协议(不包括http2.0),自定义的rpc协议可以将报文压缩得很小,对于成规模的系统来说,减少了网络延迟也减少了经济支出。

        Facebook开源了thrift,google开源了grpc。apache thrift是一个从序列化到C/S的大框架,根据网上大家做的benchmark,性能比grpc要高出不少,有很多企业都是基于thrift,实习公司也是基于thrift的后台服务,所以在项目中也运用了thrift。grpc的优势在于层次清晰,序列化层用protobuf,协议层基于http2.0,并没有过分强调性能,而把眼光放长远强调对http的兼容性,很聪明。(听说ngingx开始支持grpc了)

        扯远了。后台服务采用rpc协议,但是javascript程序员并不愿意使用除了http以外的协议[滑稽],这就意味着客户端请求到达服务端后要做的一件事就是协议的转换。在网络的入口层,解析http请求,并对下游服务发送对应的rpc请求。

    负载均衡

        微服务还有一个特性那就是scalability。一个逻辑概念上的服务,可能有多个服务节点。为了均衡这些节点的work load,就需要有一个中间人将客户端的请求按照某种策略分发到不同的服务节点上。这不是一个新的概念。在比较传统的架构中,这样一个负载均衡的功能一般使用nginx之类的反向代理来实现。但是同样这个做法并不完全适合微服务架构。分布式系统讲究availability,而availability的核心就是不能有任何意义上的“单点”存在。Nginx之类的反向代理就是一个解决不掉的单点问题。单点一旦挂了,所有必须通过这个单点的操作就都挂了。

        在微服务中一般采用client side load balancing来解决这个问题,也就是说每一个客户端都有一个内置的负载均衡器。而在微服务架构中本身就存在很多的客户端,这样就自然而然的解决了单点问题。

    基于“EventLoop”的网关

        API网关作为整个微服务架构的网络入口,首先需要转换http协议成后台的rpc协议,同时他也是一个客户端(tier1服务的客户端)。所以协议转换和服务发现/负载均衡必然是少不了的功能。 同时很重要的一点是,API网关是一个网络IO密集型的应用。它本身并没有太多复杂的计算逻辑,网络是他的主要瓶颈。 

        高吞吐量是一个网关所必须的,为了解决网络IO的问题,项目选择基于Netty开发。Netty是一个java网络传输层的异步处理框架,很多知名的项目比如:Apache Spark, Couchbase, Elastic Search, Zookeeper等的网络通信层都使用netty实现(基本原理就是可以使用一个线程同时监听多个file descriptor)。在Netty当中,一个线程叫做一个“EventLoop”,可以同时处理多个TCP连接(Channel)上的消息,从而达到提高CPU利用率的目的。传统的J2EEspring框架里,每个请求对应一个线程,在高并发的时候会占用很多内存还有很多上下文切换导致性能并不理想。这也是公司想做这个项目的初衷。

        顺带一提,facebook有开源的nifty项目,提供基于netty的java thrift客户端和服务器。之所以没有采用而是自己实现了一个“类似thrift客户端的东西”是因为:nifty的核心是thrift,netty只是thrift客户端/服务器所采用的transport layer。而在Api Gateway当中,非阻塞(netty)是一切的核心,开始设计的时候还考虑了以后支持其他rpc协议的可能,thrift只是其支持的一种序列化方式。这会导致设计上略有不同。

        除了这些需求之外,在网络的入口层,一般还需要很多其他的功能。从业务上来说,会有类似于:连续登录失败三次锁定账号1天,每5分钟一个ip不得连续发出超过10次请求,某些用户对于某些服务没有权限访问等奇怪的需求。这些需求种类繁多而且可能时刻都会发生变化,所以不能把业务逻辑hardcode在网关核心代码当中,而是要像插件那样,可以在运行时动态更改插拔。这就引入了AOP的概念。

        。。装什么逼,AOP在这里不过就是个过滤器而已。然而过滤器可以实现的功能真的非常多,核心代码提供统一的过滤器接口,由开发者自行实现,同时对客户端暴露一个api,使得这些过滤器能在运行时被增删改查。在一个请求-响应的生命周期中,有三个位置可以插入过滤器:1)pre routing服务器接收请求后,2)routing 路由给下游服务前,3)post routing收到响应准备返还给客户端前。 比如要实现一个断路器的功能,只要实现一个prerouting过滤器和一个postrouting过滤器统计请求个数和下游服务响应状态,再再添加一个routing过滤器判断是否超时/失败响应在过去5分钟达到了50%,如果是就不发送请求直接返回就行了,非常简单。(举个例子)

        除此之外,当然还有服务发现和负载均衡。每个网关会和zookeeper集群保持一个连接,实时接收zookeeper推送过来的服务metadata的变更,并在本地缓存所有服务的metadata。负载均衡只需要在缓存中所有的服务节点中选择一个即可。

    压测下的bug

        我要说的其实也不是一个正确性上的bug,而是因为自己经验不足所犯的错误。在网关最早的实现中,网关对下游服务的每一次请求都是先创建一个连接,发送请求,接收响应,然后再关闭连接。错是没有错。然而在我做压力测试的时候出现了bug: cannot assign requested address

        。。。 没有截图,好像是报的这个错。后来经过调研,发现是因为不断创建并关闭连接导致大量连接处于Time_Wait状态。(一个TCP连接,客户端和服务器中主动关闭连接的一方,连接会变成Time_Wait状态,保持一小段时间确保没有后续数据抵达)这导致本地大量的端口被占用。。端口是一个非常宝贵的资源,一个机器只有65536个,多了没有。。所以在并发量达到一定程度之后,我的应用直接炸了。

        解决方案也很简单,就是采用连接池。在gatway里,对每个下游服务节点保持一个连接池,可以让请求复用其中的连接,这样就不会有这么多的Time_wait出现了。而且在真正的分布式环境中,频繁的tcp三次握手也是很大的性能损耗。后来差了很多资料发现这个原因也是http1.0升级到http1.1(支持keep_alive)的主要原因之一。做人果然还是要多看历史。

    可改进的地方

        1.项目使用的thrift版本是apache thrift0.10.0,一开始我单纯的以为thrift server 可以simultaneous requests on single channel,而且应该支持不按顺序的响应返回。结果发现并不行。。Facebook2014年重新开源的fbthrift里的ThriftServer,是支持以上功能的。

        2.第二个才是我想主要说的。实习的公司的整体架构是这样的(pc端,微信端这里指的是pc,weixin对应的后台接口服务,聚合后台服务的响应,统一返回给真正的web,mobile端):

        从原则上来说,这样设计的问题是,仍然没有摆脱单点的问题。(当然很多事情还要根据公司的具体情况看),即使Api网关是stateless,可以方便水瓶扩展,那么load balancing的代码就又要被加入到客户端当中,只是徒增复杂度。一个更好的设计应该是:将Api网关本身设计成一个client library,不再需要http的部分直接内嵌到客户端中。一是解决了单点,二还少了一次请求转发降低整体延迟。

        这么做也不简单,client library需要支持多语言,也就意味着这个gateway本身最好作为thriftRPC框架的一部分,可以直接code generation。

        3. 当前Api网关中,对下游服务的请求没有retry的机制。retry还是必须要有的。。

    代码地址:https://github.com/hongjic/sgw

    正式开始工作了,代码里还有一些待完善的本来想整理好可能也未必有时间了。

    相关文章

      网友评论

          本文标题:非阻塞微服务网关的设计和思考

          本文链接:https://www.haomeiwen.com/subject/kscaqftx.html