一.什么是网关
1.1 什么是网关
API Gateway(APIGW / API 网关),顾名思义,是出现在系统边界上的一个面向API的、串行集中式的强管控服务,这里的边界是企业IT系统的边界,可以理解为企业级应用防火墙
,主要起到隔离外部访问与内部系统的作用
。在微服务概念的流行之前,API网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。
API网关的流行,源于近几年来,移动应用与企业间互联需求的兴起。移动应用、企业互联,使得后台服务支持的对象,从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件
。
1.2 网关应该具有的功能
如上图所示:网关该具备的最基本的四大功能:统一接入,流量管控,协议适配转发,安全防护。
二.目前网关解决方案
2.1 Nginx+ Lua
Nginx是由IgorSysoev为俄罗斯访问量第二的Rambler.ru站点开发的,一个高性能的HTTP和反向代理服务器。Ngnix一方面可以做反向代理,另外一方面做可以做静态资源服务器。
但是准确的来说,在我看来,这种方案不是真正意义上的网关,而且即使自研网关的目标也是干掉Ngnix。
2.2 Kong
Kong是Mashape提供的一款API管理软件,它本身是基于Ngnix+lua的,但比nginx提供了更简单的配置方式,数据采用了 ApacheCassandra/PostgreSQL存储,并且提供了一些优秀的插件,比如验证,日志,调用频次限制等。
Kong的一个非常诱人的地方就是提供了大量的插件来扩展应用,通过设置不同的插件可以为服务提供各种增强的功能。Kong默认插件插件包括:
- 身份认证:Kong提供了Basic Authentication、Key authentication、OAuth2.0authentication、HMAC authentication、JWT、LDAP authentication认证实现。
- 安全:ACL(访问控制)、CORS(跨域资源共享)、动态SSL、IP限制、爬虫检测实现。
- 流量控制:请求限流(基于请求计数限流)、上游响应限流(根据upstream响应计数限流)、请求大小限制。限流支持本地、Redis和集群限流模式。
- 分析监控:Galileo(记录请求和响应数据,实现API分析)、Datadog(记录API Metric如请求次数、请求大小、响应状态和延迟,可视化API Metric)、Runscope(记录请求和响应数据,实现API性能测试和监控)。
- 转换:请求转换、响应转换
优点:Kong本身也是基于Nginx的,所以在性能和稳定性上都没有问题。Kong作为一款商业软件,在Nginx上做了很扩展工作,而且还有很多付费的商业插件。Kong本身也有付费的企业版,其中包括技术支持、使用培训服务以及API 分析插件。
缺点:Kong的缺点就是,如果你使用Spring Cloud,Kong如何结合目前已有的服务治理体系?
2.3 Spring Cloud Zuul
Zuul 是Netflix公司开源的一个API网关组件,Spring Cloud对其进行二次基于Spring Boot的注解式封装做到开箱即用。目前来说,结合Sring Cloud提供的服务治理体系,可以做到请求转发,根据配置的或者默认的路由规则进行路由和Load Balance,集成Hystrix。详细可以参考Spring Cloud Zuul的URL转发和路由规则。
Spring Cloud Zuul处理每个请求的方式是针对每个请求是用一个线程来处理。PS,根据统计数据目前Zuul最多能达到(1000-2000)QPS。使用过Netty的都知道,一般都会使用Boos组和work组,通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。
Spring Cloud Zuul需要做一些灰度,降级,标签路由,限流,WAF封禁,需要自定义Filter去或者做一些定制化实现。详细文章可以参考在Spring Cloud中实现降级之权重路由和标签路由
虽然可以通过自定义Filter实现,我们想要的功能,但是由于Zuul本身的设计和基于
单线程的接收请求和转发处理
,在我看来目前来看Zuul 就显得很鸡肋,随着Zuul2一直跳票,Spring Cloud推出自己的Spring Cloud Gateway.
The API Gateway is Dead! Long Live the API Gateway!
大意:Zuul已死,Spring Cloud Gateway永生。
2.4 Spring Cloud Gateway
A Gateway built on Spring Framework 5.0 and Spring Boot 2.0 providing routing and more。
Spring Cloud Gateway是基于Spring 框架5.0版本和Spring Boot 2.0的版本构建,提供路由等功能。
Spring Cloud GateWay具有以下特征
- Java 8/Spring 5/Boot 2
- WebFlux/Reactor
- HTTP/2 and Websockets
- Finchley Release Train (Q4 2017)
由于Spring 5.0支持Netty,Http2,而Spring Boot 2.0支持Spring 5.0,因此Spring Cloud Gateway支持Netty和Http2顺理成章。至于2017年Q4季度是否发布完整的Spring Cloud Gateway我们拭目以待,
但是至于最终落地看最终使用情况
。
详细信息可以参考:Spring Cloud Gateway离开孵化器的变化
2.5 Kong+Zuul的网关方案
如上图所示:Kong+Zuul实现的网关方案,在加上阿里云的SLB,整个调用链路多了好几层,为什么要这么做呢?发挥Kong+Spring Cloud Zuul各自的优点形成“聚合网关”。个人不建议这样使用网关,因此自研网关中间件,显得尤其重要。
三.基于Spring Cloud Zuul构建网关
用Spring Cloud Zuul构建网关其实相当鸡肋,比如动态Filter,比如标签路由,降级,比如动态Filter,比如带管控审计流程,易操作的UI界面等。
zuul是netfix的api 网关,主要特色有:filter的PRPE(pre,route,post,error)模型、groovy的fitler机制,其中spring cloud对其有比较好的扩展,但是spring cloud对其的扩展感觉不是很完美,存在路由规则无法只能是通过配置文件来存储,而无法动态配置的目的,其中有一个人写了一个starter插件来解决路由规则配置到Cassandra的问题,详细请看:将路由规则配置到KV分布式存储系统Cassandra
3.1 定义自己的Filter机制
这里主要是做了流控及协议转化的工作,这里主要是http->grpc的转换;
LimitAccessFilter:利用redis令牌桶算法进行流控
GrpcRemoteRouteFilter:http转化为grpc的协议转换
3.2 路由数据变更基于事件通知路由规则刷新
实现动态路由有两种实现方式:
1.第一是DiscoveryClientRouteLocator的重新覆盖,推荐是,Spring Cloud整合GRPC,REST协议适配转发为内部GRPC服务时采用此种方法扩展修改。
2.第二是实现了RefreshableRouteLocator接口,能够实现动态刷新,可以参考 spring cloud Zuul动态路由
3.2.1 基于事件更新源码分析
为什么要基于事件更新,原理如下所示:
在org.springframework.cloud.netflix.zuul.ZuulConfiguration.java中228-250行
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulConfiguration { //zuul的配置信息,对应了application.properties或yml中的配置信息 @Autowired protected ZuulProperties zuulProperties; @Autowired protected ServerProperties server; @Autowired(required = false) private ErrorController errorController; @Bean public HasFeatures zuulFeature() { return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class); } @Bean @ConditionalOnMissingBean(RouteLocator.class) public RouteLocator routeLocator() { //默认配置的实现是SimpleRouteLocator.class return new SimpleRouteLocator(this.server.getServletPrefix(), this.zuulProperties); } @Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; } //注册了一个路由刷新监听器,默认实现是ZuulRefreshListener.class @Bean public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() { return new ZuulRefreshListener(); } @Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); // The whole point of exposing this servlet is to provide a route that doesn't // buffer requests. servlet.addInitParameter("buffer-requests", "false"); return servlet; } // pre filters @Bean public ServletDetectionFilter servletDetectionFilter() { return new ServletDetectionFilter(); } @Bean public FormBodyWrapperFilter formBodyWrapperFilter() { return new FormBodyWrapperFilter(); } @Bean public DebugFilter debugFilter() { return new DebugFilter(); } @Bean public Servlet30WrapperFilter servlet30WrapperFilter() { return new Servlet30WrapperFilter(); } // post filters @Bean public SendResponseFilter sendResponseFilter() { return new SendResponseFilter(); } @Bean public SendErrorFilter sendErrorFilter() { return new SendErrorFilter(); } @Bean public SendForwardFilter sendForwardFilter() { return new SendForwardFilter(); } @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer() { return new ZuulFilterInitializer(this.filters); } } private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { @Autowired private ZuulHandlerMapping zuulHandlerMapping; private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent) { this.zuulHandlerMapping.setDirty(true); } else if (event instanceof HeartbeatEvent) { if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) { this.zuulHandlerMapping.setDirty(true); } } } } }
如上所示,当使用ApplicationEventPublisher发送的Event为ContextRefreshedEvent,RefreshScopeRefreshedEvent,RoutesRefreshedEvent才会通知Zuul去刷新路由。
3.3 基于事件更新实现方式处理方式-DiscoveryClientRouteLocator
3.3.1 处理思路
此插件针对的spring cloud zuul版本比较老,因此需要对其进行改进,将路由配置可以配置到mysql这样的关系型数据库中,详细请看Zuul的改动点。
3.3.2 对DiscoveryClientRouteLocator的重新覆盖
对DiscoveryClientRouteLocator的重新覆盖,该类的作用就是从yml或属性文件中读取路由规则;
具体参看源码org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator,主要方法如下,浅显易懂,就不做多余解释。
@Override protected LinkedHashMap<String, ZuulRoute> locateRoutes() { LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>(); routesMap.putAll(super.locateRoutes()); if (this.discovery != null) { Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>(); for (ZuulRoute route : routesMap.values()) { String serviceId = route.getServiceId(); if (serviceId == null) { serviceId = route.getId(); } if (serviceId != null) { staticServices.put(serviceId, route); } } // Add routes for discovery services by default List<String> services = this.discovery.getServices(); String[] ignored = this.properties.getIgnoredServices() .toArray(new String[0]); for (String serviceId : services) { // Ignore specifically ignored services and those that were manually // configured String key = "/" + mapRouteToService(serviceId) + "/**"; if (staticServices.containsKey(serviceId) && staticServices.get(serviceId).getUrl() == null) { // Explicitly configured with no URL, cannot be ignored // all static routes are already in routesMap // Update location using serviceId if location is null ZuulRoute staticRoute = staticServices.get(serviceId); if (!StringUtils.hasText(staticRoute.getLocation())) { staticRoute.setLocation(serviceId); } } if (!PatternMatchUtils.simpleMatch(ignored, serviceId) && !routesMap.containsKey(key)) { // Not ignored routesMap.put(key, new ZuulRoute(key, serviceId)); } } } if (routesMap.get(DEFAULT_ROUTE) != null) { ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE); // Move the defaultServiceId to the end routesMap.remove(DEFAULT_ROUTE); routesMap.put(DEFAULT_ROUTE, defaultRoute); } LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>(); for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) { String path = entry.getKey(); // Prepend with slash if not already present. if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.properties.getPrefix())) { path = this.properties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, entry.getValue()); } return values; }
3.3.3 生产者产生事件通知
数据变更对网关的稳定性来说,也是一个很大的挑战。当对路由信息进行CRUD操作之后,需要Spring Cloud Zuul重新刷新路由规则,实现方式通过spring的event来实现。
1.实现基于ApplicationEventPublisherAware的事件生产者的代码片段
private ApplicationEventPublisher publisher; publisher.publishEvent(new InstanceRegisteredEvent<>(this, this.environment));
2.Spring Cloud netflix内部的事件消费者
org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent
@SuppressWarnings("serial") public class RoutesRefreshedEvent extends ApplicationEvent { private RouteLocator locator; public RoutesRefreshedEvent(RouteLocator locator) { super(locator); this.locator = locator; } public RouteLocator getLocator() { return this.locator; } }
四.基于Spring Cloud Gateway构建网关
由于Spring Cloud Gateway未完全成熟,而且性能,稳定性等,现在无从考证,没有使用案例
,基于Spring Cloud Gateway方案构建自己的网关风险比较大
,而且PS不知道到年底是否成熟可用
。故在这里不做过多说明。
五.基于Netty自研网关中间件
5.1 架构图
可以参考架构图如下:
5.2 设计原则
- 1.每个Filter基于责任链,只做专一的一件事
- 2.每个Filter有各自独立的数据
- 3.损耗性能的Filter顺序往后放
- 4.启动读取配置顺序,先远端,若远端失败,则读取本地。
- 5.集群网关,要注意数据的diff和灰度
- 6.尽量做到和服务治理框架解耦,易于接入,易于升级
六.参考文章
企业级API网关的设计
微服务与API 网关(上): 为什么需要API网关?
http://blog.csdn.net/u013815546/article/details/68944039
https://segmentfault.com/a/1190000009191419
http://xujin.org/janus/gw-solution/?from=timeline
相关推荐
研究目标包括:1) 使用微服务架构设计后台系统,实现服务的独立部署和管理;2) 前端使用VUE+ElementUi实现交互界面;3) 数据库使用MySQL存储数据;4) 用户能够通过前端页面与系统进行有效交互,实现风险管理和隐患...
1. **需求调研与分析**:明确移动网关建设的目标和预期效果,梳理现有系统架构及存在的问题点。 2. **技术选型与方案设计**:根据业务特性选择合适的技术栈,并完成整体架构设计。 3. **开发与测试**:组织团队进行...
服务发现系统是现代分布式系统中的...选择合适的服务发现解决方案、通信协议以及容器技术,对于构建健壮、可扩展的微服务应用至关重要。在实际应用中,需要根据项目的需求、团队的技术栈和运维能力来综合考虑和决策。
例如,早期调研Istio的Ingress时,可能由于功能不完善无法满足业务需求,因此需要不断优化和调整解决方案。 7. **服务网关架构演进**:随着业务从单一到微服务的演变,服务网关的角色也在不断升级,从最初的API代理...
1. **项目立项**:首先,项目需要获得正式的立项批准,这意味着项目已经过充分的市场调研和技术评估,得到了公司高层的认可和支持。 2. **需求分析会议**:项目经理、研发部和产品部共同参加需求分析会议,确定项目...
- **Element UI**(或其他UI框架):基于Vue.js的桌面端组件库,提供了一套完整的组件解决方案,帮助快速搭建美观、实用的前端界面。 3. **数据库** - **MySQL**:选择MySQL作为项目的数据库,因其稳定、可靠且...
Spring Cloud Netflix是一个强大的微服务工具集,它是Spring Cloud生态的一部分,专门为基于Spring Boot的应用提供了一套完整的服务治理方案。在微服务架构中,Spring Cloud Netflix提供了多种组件来解决分布式系统...