API网关可以看做系统与外界联通的入口,我们可以在网关进行处理一些非业务逻辑的逻辑,比如权限验证,监控,缓存,请求路由等等。由于在内部开发中我们都是以RPC协议 (thrift or dubbo)去做开发,暴露给内部服务,当外部服务需要使用这个接口的时候往往需要将RPC协议转换成HTTP协议。在我们的系统中由于同一个接口新老两套系统都在使用,我们需要根据请求上下文将请求路由到对应的接口。对于鉴权操作不涉及到业务逻辑,那么可以在网关层进行处理,不用下层到业务逻辑。由于网关是外部服务的入口,所以我们可以在这里监控我们想要的数据,比如入参出参,链路时间。
对于统一API网关的设计,如异步化请求、链式处理、业务隔离(信号量、线程池、集群隔离)、请求限流、熔断降级以及泛化调用等都是关键点。
您好!您的问题是关于Tomcat/Jetty+NIO+servlet3和Netty+NIO的比较。这两种方案都比较适合HTTP请求场景,但是在处理高并发时,Netty的性能更好。Netty为高并发而生,目前唯品会的网关使用这个策略,在唯品会的技术文章中在相同的情况下Netty是每秒30w+的吞吐量,Tomcat是13w+,可以看出是有一定的差距的,但是Netty需要自己处理HTTP协议,这一块比较麻烦。
对于全链路异步和链式处理,您可以参考以下内容:
- 全链路异步:对于来的请求我们已经使用异步了,为了达到全链路异步所以我们需要对去的请求也进行异步处理,对于去的请求我们可以利用我们rpc的异步支持进行异步请求所以基本可以达到下图:由在web容器中开启servlet异步,然后进入到网关的业务线程池中进行业务处理,然后进行rpc的异步调用并注册需要回调的业务,最后在回调线程池中进行回调处理。
- 链式处理:在设计模式中有一个模式叫责任链模式,他的作用是避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。通过这种模式将请求的发送者和请求的处理者解耦了。在我们的各个框架中对此模式都有实现,比如servlet里面的filter,springmvc里面的Interceptor。在Netflix Zuul中也应用了这种模式。
在全链路异步的情况下,不同业务之间的影响很小,但是如果在提供的自定义FiIlter中进行了某些同步调用,一旦超时频繁那么就会对其他业务产生影响。因此,需要采用隔离之术,降低业务之间的互相影响。以下是一些常见的隔离方式:
2.3.1 信号量隔离:信号量隔离只是限制了总的并发数,服务还是主线程进行同步调用。如果远程调用超时依然会影响主线程,从而会影响其他业务。因此,如果只是想限制某个服务的总并发调用量或者调用的服务不涉及远程调用的话,可以使用轻量级的信号量来实现。有赞的网关由于没有自定义filter所以选取的是信号量隔离。
2.3.2 线程池隔离:最简单的就是不同业务之间通过不同的线程池进行隔离,就算业务接口出现了问题由于线程池已经进行了隔离那么也不会影响其他业务。在京东的网关实现之中就是采用的线程池隔离,比较重要的业务比如商品或者订单 都是单独的通过线程池去处理。但是由于是统一网关平台,如果业务线众多,大家都觉得自己的业务比较重要需要单独的线程池隔离,如果使用的是Java语言开发的话那么,在Java中线程是比较重的资源比较受限,如果需要隔离的线程池过多不是很适用。如果使用一些其他语言比如Golang进行开发网关的话,线程是比较轻的资源,所以比较适合使用线程池隔离。
2.3.3 集群隔离:如果有某些业务就需要使用隔离但是统一网关又没有线程池隔离那么应该怎么办呢?那么可以使用集群隔离,如果你的某些业务真的很重要那么可以为这一系列业务单独申请一个集群或者多个集群,通过机器之间进行隔离。
流量控制可以采用很多开源实现:阿里最近开源了Sentinel和比较成熟的Hystrix。一般限流分为集群限流和单机限流:利用统一存储保存当前流量的情况,一般可以采用Redis;单机限流:限流每台机器我们可以直接利用Guava令牌桶去做,由于没有远程调用性能消耗较小。
熔断降级也可以参照开源实现Sentinel和Hystrix进行实现。
泛化调用是指将不同通信协议转换为统一的格式,例如将HTTP转换为Thrift。在一些开源网关中,如Zuul,并未实现此功能,因为各个公司的内部服务通信协议各不相同。以唯品会为例,它支持HTTP1、HTTP2以及二进制协议,并将其转换为内部协议;而淘宝则支持HTTPS、HTTP1和HTTP2等协议,这些协议都可以转换为HTTP、HSF和Dubbo等协议。
要实现泛化调用,首先需要为每个协议提供相应的映射接口。简单来说,就是将两个协议都转换成一种共同的语言,从而实现互相转换。通常有三种方式来指定共同语言:
1. JSON:JSON数据格式简单,解析速度快,且轻量级。在Dubbo生态中有一个将HTTP转换为JsonRpc的项目,然后再将JsonRpc转换为Dubbo。例如,可以将一个请求`www.baidu.com/id=1 GET`映射为JSON格式:
```json
{
"method": "getBaidu",
"param": {
"id": 1
}
}
```
2. XML:XML数据较重,解析困难,这里不再详细讨论。
3. 自定义描述语言:这种方式成本较高,需要自定义语言进行描述和解析。但其扩展性和个性化程度最高。例如,Spring框架自定义了一套自己的SPEL表达式语言。对于泛化调用,如果需要自定义较多的话,可以考虑使用JSON作为基本配置。
除了技术关键之外,还需要一个管理平台来对上述技术关键进行配置。管理平台需要包括以下配置:限流、熔断、缓存、日志、自定义过滤器以及泛化调用等。
最后,一个合理的标准网关应该按照以下方式实现: