演示地址:http://139.196.87.48:9002/kitty前端
用户名:admin 密码:adminjava
前面咱们经过Ribbon或Feign实现了微服务之间的调用和负载均衡,那咱们的各类微服务又要如何提供给外部应用调用呢。git
固然,由于是REST API接口,外部客户端直接调用各个微服务是没有问题的,但出于种种缘由,这并非一个好的选择。spring
让客户端直接与各个微服务通信,会有如下几个问题:apache
面对相似上面的问题,咱们要如何解决呢?答案就是:服务网关!后端
使用服务网关具备如下几个优势:跨域
服务网关是微服务架构中一个不可或缺的部分。经过服务网关统一贯外系统提供REST API的过程当中,除了具有服务路由、均衡负载功能以外,它还具有了权限控制等功能。浏览器
Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的做用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体可以具有更高的可复用性和可测试性。架构
在Spring Cloud体系中, Spring Cloud Zuul 封装了Zuul组件,做为一个API网关,负责提供负载均衡、反向代理和权限认证。app
Zuul的核心是一系列的filters, 其做用相似Servlet框架的Filter,Zuul把客户端请求路由到业务处理逻辑的过程当中,这些filter在路由的特定时期参与了一些过滤处理,好比实现鉴权、流量转发、请求统计等功能。Zuul的整个运行机制,能够用下图来描述。
Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期能够用下图来表示。
基于Zuul的这些过滤器,能够实现各类丰富的功能,而这些过滤器类型则对应于请求的典型生命周期。
PRE: 这种过滤器在请求被路由以前调用。咱们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
POST:这种过滤器在路由到微服务之后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其余阶段发生错误时执行该过滤器。
除了默认的过滤器类型,Zuul还容许咱们建立自定义的过滤器类型。例如,咱们能够定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
Zuul默认实现了不少Filter,这些Filter以下面表格所示。
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
route | 1 | DebugFilter | 标记调试标志 |
route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
能够在application.yml中配置须要禁用的filter,格式为zuul.<SimpleClassName>.<filterType>.disable=true
。
好比要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
就设置以下。
zuul: SendResponseFilter: post: disable: true
实现自定义滤器须要继承ZuulFilter,并实现ZuulFilter中的抽象方法。
public class MyFilter extends ZuulFilter { @Override String filterType() { return "pre"; // 定义filter的类型,有pre、route、post、error四种 } @Override int filterOrder() { return 5; // 定义filter的顺序,数字越小表示顺序越高,越先执行 } @Override boolean shouldFilter() { return true; // 表示是否须要执行该filter,true表示执行,false表示不执行 } @Override Object run() { return null; // filter须要执行的具体操做 } }
新建一个项目 kitty-zuul 做为服务网关,工程结构以下图。
添加 consul、zuul 相关依赖。
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"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.louis</groupId> <artifactId>kitty-zuul</artifactId> <version>${project.version}</version> <packaging>jar</packaging> <name>kitty-zuul</name> <description>kitty-zuul</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.version>1.0.0</project.version> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
启动类添加 @EnableZuulProxy 注解,开启服务网关支持。
package com.louis.kitty.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy @SpringBootApplication public class KittyZuulApplication { public static void main(String[] args) { SpringApplication.run(KittyZuulApplication.class, args); } }
配置启动端口为 8010, 注册服务到注册中心,配置zuul转发规则。
这里配置在访问 locathost:8010/feigin/call 和 ribbon/call时,调用消费者对应接口。
server: port: 8010 spring: application: name: kitty-zuul cloud: consul: host: localhost port: 8500 discovery: serviceName: ${spring.application.name} # 注册到consul的服务名称 zuul: routes: ribbon: path: /ribbon/** serviceId: kitty-consumer # 转发到消费者 /ribbon/ feign: path: /feign/** serviceId: kitty-consumer # 转发到消费者 /feign/
依次启动注册中心、监控、服务提供者、服务消费者、服务网关等项目。
访问 http://localhost:8010/ribbon/call, 效果以下图。
访问 http://localhost:8010/feign/call, 效果以下图。
说明Zuul已经成功转发请求,并成功调用后端微服务。
若是想给每一个服务的API接口加上一个前缀,可以使用zuul.prefix进行配置。
例如http://localhost:8010/v1/feign/call,即在全部的API接口上加一个v1做为版本号。
zuul: prefix: /v1 routes: ribbon: path: /ribbon/** serviceId: kitty-consumer # 转发到消费者 /ribbon/ feign: path: /feign/** serviceId: kitty-consumer # 转发到消费者 /feign/
上面咱们是经过添加路由配置进行请求转发的,内容以下。
zuul: routes: ribbon: path: /ribbon/** serviceId: kitty-consumer # 转发到消费者 /ribbon/ feign: path: /feign/** serviceId: kitty-consumer # 转发到消费者 /feign/
可是若是后端微服务服务很是多的时候,每个都这样配置仍是挺麻烦的,因此Spring Cloud Zuul已经帮咱们作了默认配置。默认状况下,Zuul会代理全部注册到注册中心的微服务,而且Zuul的默认路由规则以下:http://ZUUL_HOST:ZUUL_PORT/微服务在注册中心的serviceId/**
会被转发到serviceId对应的微服务,因此说若是遵循默认路由规则,基本上就没什么配置了。
咱们移除上面的配置,直接经过 serviceId/feign/call 的方式访问。
访问 http://localhost:8010/kitty-consumer/feign/call,结果以下。
结果也是可用访问的,说明ZUUL默认路由规则正在产生做用。
Zuul做为Netflix组件,能够与Ribbon、Eureka和Hystrix等组件相结合,实现负载均衡、熔断器的功能。默认状况下Zuul和Ribbon相结合,实现了负载均衡。实现熔断器功能须要实现FallbackProvider接口,实现该接口的两个方法,一个是getRoute(),用于指定熔断器功能应用于哪些路由的服务;另外一个方法fallbackResponse()为进入熔断器功能时执行的逻辑。
建立 MyFallbackProvider 类,getRoute()方法返回"kitty-consumer",只针对consumer服务进行熔断。若是须要全部的路由服务都加熔断功能,须要在getRoute()方法上返回”*“的匹配符。getBody()方法返回发送熔断时的反馈信息,这里在发送熔断时返回信息:"Sorry, the service is unavailable now." 。
MyFallbackProvider.java
package com.louis.kitty.zuul; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; 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; @Component public class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "kitty-consumer"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { System.out.println("route:"+route); System.out.println("exception:"+cause.getMessage()); return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "ok"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } }
从新启动,访问 http://localhost:8010/kitty-consumer/feign/call, 能够正常访问
停掉 kitty-consumer 服务, 再次访问 http://localhost:8010/kitty-consumer/feign/call, 效果以下。
说明咱们自定义的熔断器已经起做用了。
建立一个MyFilter, 继承ZuulFilter类,覆写run()方法逻辑,在转发请求前进行token认证,若是请求没有携带token,返回"there is no request token"提示。
package com.louis.kitty.zuul; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; @Component public class MyFilter extends ZuulFilter { private static Logger log=LoggerFactory.getLogger(MyFilter.class); @Override public String filterType() { return "pre"; // 定义filter的类型,有pre、route、post、error四种 } @Override public int filterOrder() { return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行 } @Override public boolean shouldFilter() { return true; // 表示是否须要执行该filter,true表示执行,false表示不执行 } @Override public Object run() throws ZuulException { // filter须要执行的具体操做 RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String token = request.getParameter("token"); System.out.println(token); if(token==null){ log.warn("there is no request token"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("there is no request token"); } catch (IOException e) { e.printStackTrace(); } return null; } log.info("ok"); return null; } }
OK,这样就好了,Zuul会自动加载Filter执行过滤的。
从新启动Zuul项目,访问 http://localhost:8010/kitty-consumer/feign/call,效果以下。
请求时带上token,访问 http://localhost:8010/kitty-consumer/feign/call?token=111,效果以下。
Zuul做为API服务网关,不一样的客户端使用不一样的负载将请求统一分发到后端的Zuul,再有Zuul转发到后端服务。所以,为了保证Zuul的高可用性,前端能够同时开启多个Zuul实例进行负载均衡,另外,在Zuul的前端还可使用Nginx或者F5再次进行负载转发,从而保证Zuul的高可用性。
后端:https://gitee.com/liuge1988/kitty
前端:https://gitee.com/liuge1988/kitty-ui.git
做者:朝雨忆轻尘
出处:https://www.cnblogs.com/xifengxiaoma/ 版权全部,欢迎转载,转载请注明原文做者及出处。