通过前几篇教程的介绍,我们基本上认识了构建一个微服务系统所需要的组件,通过这些组件的结合,我们已经可以搭建一个较为简略的微服务系统了,比如下面的架构图:
https://img-blog.csdn.net/20180803175652889?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lleWF6aGlzaGFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70
可以看出,该系统架构图包含了服务注册中心eureka-server作为服务注册中心,config-server作为配置中心获取远程Git地址的配置文件信息,在服务的请求处还做了负载均衡,服务之间做了集群分布,实现了高可用,服务之间还可以实现相互调用,由此可见,一个简易的微服务系统就搭建成了。
引出Zuul
上面的架构已经包含了微服务系统的基础功能,但是仍有不足之处:
首先是服务路由的不确定性,在之前的学习中,我们通过restTemplate+ribbon 以及 feign客户端的方式实现了负载均衡,可以实现对高可用服务的消费,但那种直接基于服务名来调用的负载处理并不完全,我们需要有一个更强大的网关控制工具。
其次是缺乏校验功能,当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,这样跟服务之间就存在耦合了,不符合开发的宗旨。
针对上述的不足之处,spring cloud 提供了 Zuul组件来加以完善。
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能,加入了Zuul后的微服务架构将变得更加完善,架构图如下:
https://img-blog.csdn.net/20180803175708387?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lleWF6aGlzaGFuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70
下面用实例带大家领略Zuul的魅力。
创建工程
在创建Zuul工程之前,我们需要先引用之前的工程,读者有疑问可以参考https://blog.csdn.net/yeyazhishang/article/details/81392085
在原有的工程创建新新工程zuul-service,其pom文件如下:
<?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.yeya</groupId> <artifactId>zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>zuul</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.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>Finchley.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</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>
在程序的启动类上加入注解 @EnableDiscoveryClient 和 @EnableZuulProxy,开启服务注册和服务网关的功能,代码如下:
br/>@EnableDiscoveryClient
@EnableZuulProxypublic class ZuulApplication {
public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); }
}
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
server:
port: 1116
spring:
application:
name: zuul-service
zuul:
routes:
api-a:
serviceId: eureka-client
api-b:
serviceId: eureka-ribbon
其中,eureka.client.serviceUrl.defaultZone是服务注册中心的地址,服务的名称为zuul-service,以/clientapi/ 开头的请求都转发给eureka-client服务;以/ribbonapi/开头的请求都转发给eureka-ribbon服务; 配置好信息后,依次启动这几个工程,记得eureka-client要改端口 (1112,1113) 启动两次,然后打开浏览器,多次访问 http://localhost:1116/clientapi/hello,会发现浏览器反复显示
hello : 1112
hello : 1113
同样,多次访问 http://localhost:1116/ribbonapi/consumer ,浏览器会显示相同的结果,说明zuul的确起到了路由转发的作用。
服务过滤
zuul除了能做路由转发之外,还能实现过滤作用,做一些安全的验证功能,下面我们来改造一下zuul-service工程。在工程中创建一个过滤器继承ZuulFilter ,并实现类中的方法,具体实现如下功能; 如果请求地址中有带token,那么过滤器就不做拦截,否则拦截并输出 token is empty,下面是具体代码:
@Component
br/>@Override
public String filterType() {return "pre";
}
@Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object accessToken = request.getParameter("token"); if(accessToken == null) { ctx.setSendZuulResponse(false); try { ctx.getResponse().getWriter().write("token is empty"); }catch (Exception e){} return null; } return null; }
}
filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
pre:路由之前
routing:路由之时
post: 路由之后
error:发送错误调用
filterOrder:过滤的顺序
shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
写好代码后,重启服务,访问 http://localhost:1116/ribbonapi/consumer ,发现浏览器返回
token is empty
加上参数token,重新访问 http://localhost:1116/ribbonapi/consumer?token=11,发现浏览器正常输出
hello : 1112
hello : 1113
至此,zuul的过滤工程成功实现。
zuul作为微服务系统的服务网关,起到了至关重要的作用,所以,一般来说,在生产上的微服务系统中,zuul服务需要做集群分布,避免服务挂了网关失效的情况。