[TOCM]
[TOC]
源码地址:https://github.com/IsResultXaL/springcloud
通过上一节内容介绍与实践,我们已经搭建起微服务架构中的核心组件
服务注册中心
服务提供者
服务消费者
断路器Hystrix
Hystrix仪表板
Turbine集群监控
服务消费者Feign
SpringCloud(八):服务消费者Feign 下
###Spring Cloud Zuul(路由配置)
Spring Cloud Zuul 通过与 Spring Cloud Eureka 进行整合,将自身注册到 Eureka Server中,与Eureka,Ribbon,Hystrix等整合,同时从 Eureka 中获得了所有其它微服务的实例信息。这样的设计通过把网关和服务治理整合到一起,Spring Cloud Zuul可以获取到服务注册信息,结合Ribbon,Hystrix等更好的实现路由转发,负载均衡等功能。
创建一个model项目(service-zuul)
service-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> <groupId>com.caogen</groupId> <artifactId>service-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>service-zuul</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Edgware.RELEASE</spring-cloud.version> </properties> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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注解,开启Zuul的API网关服务。
package com.caogen.servicezuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy //开启Zuul的API网关服务 @EnableDiscoveryClient @SpringBootApplication public class ServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(ServiceZuulApplication.class, args); } }
配置application.yml
server: port: 5555 spring: application: name: service-zuul # 关闭安全限制 management: security: enabled: false # 路由配置 zuul: routes: api-a: path: /api-a/** serviceId: eureka-client api-b: path: /api-b/** serviceId: feign-consumer # 注册中心地址 eureka: client: serviceUrl: defaultZone: http://localhost:1111/eureka/ # 配置超时时间断路器才有效果(加入Zuul) ribbon: ReadTimeout: 30000 ConnectTimeout: 30000
配置文件的路由配置属于面向服务路由,有兴趣的同学可以去了解了解传统路由配置方式,因为传统路由配置不太友好,它需要运维人员花费大量的时间来维护各个路由path与url的关系,为了解决这个问题,Spring Cloud Zuul实现了与Spring Cloud Eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它映射到某个具体的服务,而具体的url则交给Eureka的服务发现机制去自动维护,我们称这类路由为面向服务的路由。
以上的路由配置定义了发往API网关服务的请求中,所有符合/api-a/**规则的URL都将被路由器转发到eureka-client服务中,也就是说,当我们访问 http://localhost:5555/api-a/hello 的时候,API网关服务会将该请求路由到 http://eureka-client/hello 提供的微服务接口上。
启动以下应用
服务注册中心 eureka-server 端口 1111
服务提供实例 eureka-client 端口 1112
服务消费实例 feign-consumer 端口 1115
服务网关 service-zuul 端口 5555
对了,服务网关有个URL可以查看网关映射 http://localhost:5555/routes
// 20171211140533 // http://localhost:5555/routes { "/api-a/**": "eureka-client", "/api-b/**": "feign-consumer", "/eureka-client/**": "eureka-client", "/feign-consumer/**": "feign-consumer" }
通过上面的搭建工作,我们可以通过服务网关来访问eureka-client和feign-consumer这两个服务。根据配置的映射关系,分别向网关发起下面的这些请求。
1: http://localhost:5555/api-a/hello 该url符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceId为eureka-client,所以最终/hello请求会被转发到eureka-client服务的某个实例上
Hello,World! port:1112
2: http://localhost:5555/api-b/feign-consumer2 该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer2请求会被转发到feign-consumer服务的某个实例上
Hello,World! port:1115 Hello caogen User{name='caogen', age=18} Hello caogen, age 18
面向服务的路由配置方式完全归功于Spring Cloud Eureka的服务发现机制,它使得API网关服务可以自动化完成服务实例清单的维护,完美解决了对路由映射实例的维护问题。
###Spring Cloud Zuul(请求过滤)
在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。
在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
一个简单的Zuul过滤器
package com.caogen.servicezuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class AccessFilter extends ZuulFilter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); logger.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if(accessToken == null) { logger.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); }catch (Exception e){} return null; } logger.info("ok"); return null; } }
在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写下面四个方法来实现自定义的过滤器。
filterType: 过滤器的类型。它决定过滤器在请求的哪个生命周期中执行,这里定义为pre,代码会在请求被路由之前执行。
fileterOrder: 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
shouldFilter: 判断该过滤器是否需要被执行。这里我们直接返回true,因此该过滤器对所有请求都会生效。
run: 过滤器的具体逻辑。
在添加过滤器之后,我们可以重新启动网关服务,并发起下面的请求
http://localhost:5555/api-a/hello : 页面打印 token is empty
http://localhost:5555/api-a/hello?token=1 : 正确路由到eureka-client服务的/hello接口,页面打印
Hello,World! port:1112