本篇文章来学习微服务当中非常重要的一个知识点-服务网关(API GateWay),什么叫服务网关呢?为了方便理解,先举个简单的例子,比如假如先在要实现业务功能A,而功能A可能需要调用服务1,服务2,,服务3...,那么如何调用呢?看下面的图
这样调用的方式是最常用,当然肯定也是能完成功能的,那么有没有问题呢?问题肯定是有的,因为既然是微服务,说明服务的拆分粒度肯定是很细的,可能一个功能需要调用几十个服务甚至上百个可能更多,如果是这种情况会存在哪些问题呢?
1 对于上面的功能A,它需要知道每个服务的IP和端口号,这对于它来说是个灾难(因为要完全通过硬编码写入,一旦一个变化,修改起来,不敢想象)
2 对于上的功能A,它可能还会做一些其他的非业务相关的功能,比如在调用某个服务的时候,需要做用户权限的验证,访问的流量监控,如果这些都是它来做,那也是一个不可想象的灾难
3 对于上面的功能A,可能先需要查询Service1和Servcie2获取到一个结果,然后再把Service3的结果进行聚合,从而达到最终的结果,也就是说要做服务的聚合,如果每个聚合都需要它自身完成,这个业务也太复杂了,维护起来也很困难
4 对于上面的功能A,可能需要在不同的载体中进行展示,比如有的需要在PC端,有的需要在移动端,还有的需要在APP端,如果都要它来区分的话,它就是太重了
上面随便分析了直接调用服务存在的一些弊端,既然知道了问题,那么肯定就需要解决方案?其实也很容易想出来,不管怎么解决,其实思想就是进行职责的拆分,尽量让功能A所做都是核心业务的事情,非核心业务的事情就不需要它做,让个其他人做不就可以了吗?
演变一下调用上面的调用方式图:
其实对比之下就是多了一个黄色标注的的载体,不错那就是我们所说的服务网关,把一些事情就分配给它来做,看一下它的定义
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。
其实在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端,下面简单总结一下它的好处:
服务网关的好处:
1 屏蔽了客户端调用服务端的复杂性
》客户端不需要具体指导每个服务的存在具体信息,比如服务的域名 ,端口号 是否可访问等,这些都由网关来做
2 数据的聚合和适当的剪枝
》网关自身来进行部分服务数据的部分聚合,或者根据适当的场景进行数据的剪枝
3 数据的多客户端或者多版本的支持
》网关可以根据不同的展示载体,比如上面说到的PC端,移动端,APP端定制不同的展示,当然也可以支持多版本等
4 实现用户权限的验证,流量限制,日志拦截等
》这些功能本身就并非是业务的核心,给网关来做非常的合适
OK,那么网关的作用很大,如果是自己开发一个这样的组件,那是很庞大的工程啊,放心开源的世界已经为我们提供了多种解决方案:
Tyk:Tyk是一个开放源码的API网关,它是快速、可扩展和现代的。Tyk提供了一个API管理平台,其中包括API网关、API分析、开发人员门户和API管理面板。Try 是一个基于Go实现的网关服务。
Kong:Kong是一个可扩展的开放源码API Layer(也称为API网关或API中间件)。Kong 在任何RESTful API的前面运行,通过插件扩展,它提供了超越核心平台的额外功能和服务。
Orange:和Kong类似也是基于OpenResty的一个API网关程序,是由国人开发的,学姐也是贡献者之一。
Netflix zuul:Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
apiaxle: Nodejs 实现的一个 API 网关。
api-umbrella: Ruby 实现的一个 API 网关。
方案很多种,目前业界最常用的就是nginx+lua的这种方式,如果使用springcloud推荐使用zuul,也是接下来要学习的组件,下面主要介绍zuul相关知识:
& zuul的各种配置使用举例
继续在之前的项目建立一个新模块叫server_zuul:,不要写多少代码,只需要写一个main方法的类,看一下项目结构,后续的功能都会这个上进行扩展
看一下pom.xml的文件内容:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-cloud</artifactId> <groupId>com.suning.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>server_zuul</artifactId> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--集成ZUUL网关API--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> </project>
看一下main方法的类
package com.server.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @Author 18011618 * @Description * @Date 14:17 2018/7/12 * @Modify By */ @SpringBootApplication @EnableZuulProxy public class ServerZuulApplication { public static void main(String[] args) { SpringApplication.run(ServerZuulApplication.class,args); } }
要使用zuul功能,只需要在应用类上加一个新的注解即可,OK 看一下配置文件
spring: application: name: service-gatway-zuul server: port: 9005 eureka: client: service-url: defaultZone: http://admin:admin@localhost:8080/eureka instance: prefer-ip-address: true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000
OK 启动应用类以及service_provider_user的应用,访问浏览器看看效果:http://localhost:9005/service-provider-user/findUser/1,意向不到的结果发生了:既然能真的访问到数据:
9005是zuul的端口号,而service-provider-user是服务提供者的名称,这就说明了zuul真的实现了服务转发调用的功能,但是貌似配置文件也没有配置啥?这是怎么实现的呢,原因很简单因为zuul提供了一个默认的配置规则:http://ZUUL_HOST:ZUUL_PORT/微服务在Eureka上的serviceId/**,也就是说zuul会自己去eureka上寻找注册服务的应用名称也就是serviceId(这个ID实际还可以自己指定,后面会讲到),上面因为使用的是默认配置,所以看起来比较长,能不能简化点呢,当然可以,实际使用中我们可能希望能根据业务类型来加访问区分字段,比如和用户有关的叫/user/**,和店铺有关的叫/shop/**,和商品有关的/product/**,和价格有关的叫/price/**,这个也是更加符合使用习惯,下面就修改配置文件实现这样的功能..,对于刚才的service-provider-user,希望通过/user/**这种方式来访问:在之前的配置文件中加一下这个配置即可,如下图所示
重启应用试试看:http://localhost:9005/user/findUser/2:也是可以正常访问的
,效果达到我们所想要的,当然可能有的一些服务,不需要使用这种自定义的访问方式,而是使用默认的话,这个也很简单只需要加一个这样的配置,比如我们希望service-provider-user2这个服务不能使用/user/**这种方式访问,这样配置
接下来再说中配置方法,就是为不同的微服务加上不同的path,看一下下面这个修改之后的配置:
这个配置的作用实现了,当要访问service-provider-user使用/user/**这种方式,而对于user-service-consumer-ribbon需要使用/user-ribbon/**这种方式,大家可以自行验证一下效果,接下来再说一种配置方式,假如一个服务没有注册到eureka上该怎么办,没有关系因为zuul也支持具体的url访问方式,如下所示
这样配置也是OK的。
,再看这样的一种配置,一个服务没有注册到eureka上,但是还要实现负载均衡,这种配置该怎么实现呢?看如下截图
一样的,可以看一下访问效果,如果要对访问前缀有需求的话,可以使用下面这样的配置
映射关系如下所示:http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list,如果为false的话
映射关系如下所示:http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/api/user/list,如果为指定的url进行设置,可以按照这样配置:
到这里有关和zuul常用的配置就讲解的差不多了...,下面给一份完整的配置例子
spring: application: name: service-gatway-zuul server: port: 9005 eureka: client: service-url: defaultZone: http://admin:admin@localhost:8080/eureka instance: prefer-ip-address: true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000 #为部分指定访问路径,其它服务使用默认访问路径 #zuul: # routes: # ignoredServices: service-provider-user2 # service-provider-user: /user/** 指定service-provider-user使用/user/**访问,而2服务不能使用 #为不同的服务指定不同的访问path #zuul: # routes: # provider: #ID 名称随便起 但是要保证不重复即唯一 # path: /user/** # serviceId: service-provider-user # consumer: #ID 名称随便起 但是要保证不重复即唯一 # path: /user-ribbon/** # serviceId: user-service-consumer-ribbon #服务没有注册到eureka上 #zuul: # routes: # provider: # path: /user-url/** # url: http://localhost:9002/ #一个服务没有注册到eureka上,但是还要实现负载均衡 #zuul: # routes: # user: # path: /user-url/** # service-id: service-provider-user #ribbon: # eureka: # enabled: false #禁用到eureka # #service-provider-user: # 这边是ribbon要请求的微服务的serviceId # ribbon: # listOfServers: http://localhost:7900,http://localhost:7901 #指定当前服务提供者的具体地址 #http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list #zuul: # prefix: /api # strip-prefix: true #http://127.0.0.1:8181/api/user/list -> http://192.168.1.100:8080/user/list #zuul: # prefix: /api # strip-prefix: false #为指定的映射路径 进行忽略 zuul: prefix: /findUser strip-prefix: false
在这里说一下通配符,上面我们都是使用/**,其实还有/?,/*,这种方式,这些方式有啥区别呢,看下面
》:/user/**可以匹配,/user/a/b,/user/a/b/c=多级目录
》:/user/*可以匹配,/user/aaa,/user/abcd=匹配多个字符
》:/user/?可以匹配,/user/a,/user/b=单一字符
多个服务先后顺序:
这里有个问题要注意,就是实际当中如果访问的服务是有顺序要求的话,切记不能使用properties来做配置文件,因为它不支持先后顺序,要使用yaml格式,它会按照你配置服务的先后顺序来保存。
& zuul的fallback机制
zuul已经集成了hystrix的功能,所以它也有回退,在调用失败的时候,可以获取到一些信息以及给客户端返回一些信息,比如下面要实现的一个功能就是,当服务者关闭的时候,向客户端显示调用信息,而非服务不可用,实现这个功能很简单自定一个类实现,看一下代码
package com.server.zuul.fallback; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; /** * @Author 18011618 * @Date 16:45 2018/7/12 * @Function zuul调用失败了的回退的功能 */ @Component public class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "service-provider-user"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.BAD_REQUEST; } @Override public int getRawStatusCode() throws IOException { return HttpStatus.BAD_REQUEST.value(); } @Override public String getStatusText() throws IOException { return HttpStatus.BAD_REQUEST.getReasonPhrase(); } @Override public void close() { } /** * @Author 18011618 * @Date 16:49 2018/7/12 * @Function 这里可以向客户端展示信息 */ @Override public InputStream getBody() throws IOException { String result ="sorry,you get provider service is faild:"; return new ByteArrayInputStream(("fallback:" + MyFallbackProvider.this.getRoute()).getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
直接看效果吧,重新启动应用,然后关闭服务提供者,看一下会显示啥
是不是很简单就实现了回退功能,实际环境中,这里可以写复杂的功能,这里只是为了演示,所以比较简单,当然它还支持过滤器的功能,下面就讲解这个
& zuul的filter各种场景分析
zuul可以实现各种与核心业务无关的一些功能,比如权限的验证,日志的拦截,流量限制,URL的改写,要实现这些功能,只需要定义自己的filter即可,下面举一个最简单的例子,在调用服务之前,打印对应的host信息。建立一个类继承即可,看一下主要代码
package com.server.zuul.filter; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; /** * @Author 18011618 * @Date 16:59 2018/7/12 * @Function 日志拦截功能 */ public class PreZuulFilter extends ZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(PreZuulFilter.class); @Override public boolean shouldFilter() { return true; } @Override public Object run() { HttpServletRequest request = RequestContext.getCurrentContext().getRequest(); String host = request.getRemoteHost(); PreZuulFilter.LOGGER.info("您当前请求的host:{}", host); return null; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } }
详细分下一下里面的方法:
shouldFilter():一定是为true,不能是false,否则执行不了
run():实现具体业务逻辑的方法,比如权限验证,日志拦截,url的改写等 需要在这个方法来实现
filterType():返回方法执行的时机,pre:路由之前,routing:路由之时,post:路由之后,error:路由错误的时候
filterOrder():方法过滤的顺序
好了,重启应用,看看红色标注的日志拦截功能是否能够打印出来?
& zuul的pattern机制
你可以使用regexmapper提供serviceId和routes之间的绑定. 它使用正则表达式组来从serviceId提取变量, 然后注入到路由表达式中,在main的主类里面加一下这个代码
@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
这表示serviceId “myusers-v1” 将会被映射到 “/v1/myusers/“.任何正则表达式都可以,但是所有的命名组都必须在servicePattern和routePattern中存在。如果servicePattern没有匹配到一个serviceId,默认的行为会被启用
&zuul的生成环境使用建议:
因为是网关,所以所有的请求都会优先进过zuul,那么也就是说在并发量很大情况下,有可能zuul就成为了瓶颈,所以可能也要考虑zuul的HA机制,还有可以根据自身业务需求对网关进行分类,不一定就一个网关,可以使用多重网关,比如有的网关是做权限验证以及日志拦截,还有的网关是做数据聚合的,这样对于zuul的压力也进行分摊,最后把上面的流程图再优化一下:
GateWay1:做权限验证,拦截,GateWay2:做数据聚合,剪枝等,至此和zuul相关的网关功能就介绍完了.
版权声明:本文为博主原创文章,未经博主允许不得转载:https://blog.csdn.net/qq_18603599/article/details/80941704