微服务网关解决方案调研和使用总结 专题

一.什么是网关

1.1 什么是网关

API Gateway(APIGW / API 网关),顾名思义,是出如今系统边界上的一个面向API的、串行集中式的强管控服务,这里的边界是企业IT系统的边界,能够理解为企业级应用防火墙,主要起到隔离外部访问与内部系统的做用。在微服务概念的流行以前,API网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。前端

API网关的流行,源于近几年来,移动应用与企业间互联需求的兴起。移动应用、企业互联,使得后台服务支持的对象,从之前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。这不只增长了后台服务的响应量,还增长了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件java

1.2 网关应该具备的功能

如上图所示:网关该具有的最基本的四大功能:统一接入,流量管控,协议适配转发,安全防御。mysql

 

网关的技术选型nginx

SpringCloud-Zuul :git

社区活跃,基于 SrpingCloud 完整生态, 是构建微服务体系前置网关服务的最佳选型.github

Kong : 基于OpenResty的 API 网关服务和网关服务管理层.web

自建网关服务: 如 谈谈基于 OpenResty 的接口网关设计[https://www.zybuluo.com/yishuailuo/note/844059?utm_source=tool.lu]redis

网关的设计要素算法

系统级别spring

高可用性
均衡负载: 容错,防止雪崩.
并发控制 : 错峰流控
动态路由制定和修改
应用级别

监控统计
版本控制
认证 鉴权
数据安全: 防篡改,参数脱敏…
协议转换: 如 HTTP => RPC协议.
其余(我的 YY)

基于机器学习, 预测流量高峰.

 

网关(API Gateway)技术选型

zuul
kong
nginx+lua
网关(API Gateway)的设计要素

限流:实现微服务访问流量计算,基于流量计算分析进行限流,能够定义多种限流规则。
缓存:数据缓存。
日志:日志记录。
监控:记录请求响应数据,api耗时分析,性能监控。
鉴权:权限身份认证。
灰度:线上灰度部署,能够减少风险。
路由:路由是API网关很核心的模块功能,此模块实现根据请求,锁定目标微服务并将请求进行转发。
简单介绍下你的网关实施方案

开发语言:java + groovy,groovy的好处是网关服务不须要重启就能够动态的添加filter来实现一些功能;
微服务基础框架:springboot;
网关基础组件:netflix zuul;
服务注册中心:consul;
权限校验:jwt;
API监控:prometheus + grafana;
API统一日志收集:logback + ELK;
压力测试:Jmeter;


你好,请教一下: 1.为何网关须要数据缓存,是由于须要鉴权,因此须要缓存用户数据?还有其余数据须要缓存么? 2.网关路由方面,只须要作路由的转发,仍是须要作服务的聚合? 谢谢
好比限流 你须要缓存一些限流的策略,主要是缓存网关功能用到的一些数据,不涉及业务数据。 路由主要是作转发

 

目前,咱们业务代码是多语言的环境,网关则是用go写的,目前主要是作到了对于HTTP和Thrift的业务服务的转发(HTTP利用了fasthttp,Thrift用的网关启动客户端调用业务服务端的形式)过滤器是环绕的,系通通一的过滤和针对API级别的过滤。虽然用了go比较轻巧,可是目前功能还很值得完善

go语言体积小,性能高。大家这个设计方案不错,又支持多语言,对技术栈没有限制,很值得借鉴。

 

设计要素:
#1,高可用很是重要;
#2,网关须要支持动态修改路由规则;
#3,与服务注册中心整合,经过注册中心实现路由转发;
#4,过滤器链适配不一样的路由。
以上是我的愚见

 

选型

所使用的网关架构必须灵活,由于咱们可能须要不少与咱们业务相关的定制话的东西
有平台背书,获取有足够的证据证实他是一个能抗的住咱们需求的并发的性能
根据需求选择最好的方案
设计要素

结构必须灵活,方便扩展
基础的功能应该由框架提供或者抽象,好比动态路由,权限校验,限流
个人

咱们使用zuul做为网关并对他进行了必定定制化的开发,由于咱们使用springcloud技术栈,同时zuul基于filter来处理一切的结构也是很是灵活的,而且由netflix背书。咱们在网关利用filter加入权限校验,统一访问日志记录,访问异常请求记录,聚合请求处理器等相关功能

负载均衡能够经过在以前加入一个nginx或者dns解析来作,高可用能够经过keepalived加虚拟ip与nginx结合或者直接与zuul结合来作


1.能处理一些公共的逻辑,好比获取token
2.能支持动态的修改路由规则
3.对各服务结果和异常进行统一处理后返给调用方
目前实施了几套方案,本身封装的gateway层,准备用zuul进行替代


首先是稳定且性能好,能支持海量请求,这次是安全,最后是功能完善且易于扩展。
网关智能路由,请求过滤,日志记录,流量控制,权限审查,负载均衡,实时监控,多协议支持等是一个好的网关方案应具有的。
目前公司采用zuul做为网关的,作了路由转发,权限控制,再加上自定义日志记录功能跟踪后端服务执行状况。
zuul多开,前端再加上nginx作端口转发和静态缓存,待解决高可用和偶尔超时问题。

在跟踪后端执行状况上增长了请求执行时间,后端服务地址,当前用户,请求参数,错误截取等内容,以便后续跟踪问题。
而后经过logback发给elk进行记录分析监控


网关的技术选型

1. SpringCloud-Zuul :社区活跃,基于 SrpingCloud 完整生态, 是构建微服务体系前置网关服务的最佳选型.
2. Kong : 基于OpenResty的 API 网关服务和网关服务管理层.
3. Nginx+Lua:成熟度也算能够
4. 自建网关:成本较高
网关(API Gateway)的设计要素(高可用,安全)

* 性能:API高可用,负载均衡,容错机制。
* 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
* 日志:日志记录(spainid,traceid)一旦涉及分布式,全链路跟踪必不可少。
* 缓存:数据缓存。
* 监控:记录请求响应数据,api耗时分析,性能监控。
* 限流:流量控制,错峰流控,目前有漏桶算法、令牌桶算法也能够定制限流规则。
* 灰度:线上灰度部署,能够减少风险。
* 路由:动态路由规则。
* 静态:代理
简单介绍下你的网关实施方案

* 微服务基础框架:springboot;
* 网关基础组件:zuul;
* 服务注册中心:consul;
* API监控:prometheus + grafana or 自建;
* API统一日志收集:时序db + ELK;
* 压力测试:Jmeter,AB,阿里压测;


网关(API Gateway)技术选型

zuul
nginx
网关(API Gateway)的设计要素

高可用
灰度
负载
限流
反向代理
简单介绍下你的网关实施方案

Spring Cloud Zuul
开发环境使用Spring cloud 技术栈 使用zuul方便
在网关上作限流 负载 限流。网关做为整个系统的入口不该该作不少事 身份认证 权限认证等都放在下面的聚合层
(Low逼的见解 大佬别喷)


首先,网关做为微服务的入口,其并发压力都在网关,当网关的并发能力没法支撑用户量的时候咱们就须要部署多个网关,而后在网关的前面加一个负载均衡服务器,可是在负载均衡服务器的选择上不能简单的选择nginx,由于若是选择nginx的话,客户端链接nginx,nginx再链接网关,网关再链接后端的微服务,这样下来光是TCP链接的三次握手就比较耗时,因此咱们应该在网关的前面选用支持四层负载均衡的服务器,譬如Haproxy或者lvs,使用他们的在tcp第一次握手的时候修改源IP地址和目的地址的模式,这样客户端至关于直连网关,能够作到性能最大化。


网关(API Gateway)技术选型
咱们本身写了一个,利用反射技术,链接同一个zk
网关(API Gateway)的设计要素
在库里边配置相应的信息便可
group varchar(255) NOT NULL,
service varchar(255) NOT NULL,
method varchar(255) DEFAULT NULL,
parameter varchar(255) NOT NULL,
value varchar(255) NOT NULL,
is_deleted tinyint(4) NOT NULL DEFAULT ‘0’,
简单介绍下你的网关实施方案
自有的devops 部署

1.首先部署多台gateway的网关 固然都是zuul的,好比部署3台,端口分别为 90,91,92.
2.使用ngnix代理80端口,而后配置权重把请求分发到90,91,92端口的网关,api网关本身再路由

首先服务网关 = 路由转发 + 过滤器
一、路由转发:接收一切外界请求,转发到后端的微服务上去;
二、过滤器:在服务网关中能够完成一系列的横切功能,例如权限校验、限流以及监控等,这些均可以经过过滤器完成(其实路由转发也是经过过滤器实现的)。
三、增长了网关,多了一层转发(本来用户请求直接访问open-service便可),性能会降低一些(可是降低不大,一般,网关机器性能会很好,并且网关与open-service的访问一般是内网访问,速度很快);
四、 网关的单点问题:在整个网络调用过程当中,必定会有一个单点,多是网关、nginx、dns服务器等。防止网关单点,能够在网关层前边再挂一台nginx,nginx的性能极高,基本不会挂,这样以后,网关服务就能够不断的添加机器。可是这样一个请求就转发了两次,因此最好的方式是网关单点服务部署在一台牛逼的机器上),并且nginx与zuul的性能比较其实相差不大,zuul是netflix开源的一个用来作网关的开源框架;
五、网关要尽可能轻。

http://www.spring4all.com/question/62

 

二.目前网关解决方案

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
http://www.javashuo.com/article/p-ntexbnsy-gg.html

 

http://xujin.org/janus/gw-solution/?from=timeline

相关文章
相关标签/搜索