转载:springcloud 第六章 服务消费者(ZUUL)
本章节,我们讲解springcloud重要组件:微服务网关Zuul。如果有同学从第一章看到本章的,会发现我们已经讲解了大部分微服务常用的基本组件。
已经讲解过的:
一起来学Spring Cloud | 第一章 :如何搭建一个多模块的springcloud项目
一起来学Spring Cloud | 第二章:服务注册和发现组件 (Eureka)
一起来学Spring Cloud | 第三章:服务消费者 (负载均衡Ribbon)
一起来学Spring Cloud | 第四章:服务消费者 ( Feign )
一起来学Spring Cloud | 第五章:熔断器 ( Hystrix)
本章正在讲解的:一起来学Spring Cloud | 第六章:服务网关 ( Zuul)
下章即将讲解的: 一起来学Spring Cloud | 第七章:分布式配置中心(Spring Cloud Config)
刚入门的同学,如果把前面这七章都理解清楚,并且自己搭建一遍,在工作中,我们已经可以搭建一个最简单的微服务项目了,我曾经看过一个创业公司,他们使用微服务框架时,就用以上的组件在生产上运行着简单的后台业务系统。
一、Zuul简介:
Zuul是Netflix开源的微服务网关,它可以和Eureka、Feign、hystrix等组件配合使用,Zuul的核心是一系列过滤器,它主要功能是路由转发和过滤器。
在实际项目中,一个复杂的业务系统后台,少则几十个服务模块,多则成百上千,随着业务场景的不断变更,我们的系统也会不断在演变,就会遇到如下的几个问题:
1. 如果存在跨域请求,多个微服务在一定的场景下处理相对复杂。
2. 客户端多次请求不同的微服务,增加了客户端的复杂性。
3. 认证复杂,每个微服务都需要独立认证。
4. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个微服务合并成一个或者将一个微服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很能实施。
5. 某些微服务可能使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。
Zuul提供的作用:
1. 提供统一服务入口,微服务对前台透明
2. 聚合后台服务,节省流量,提升性能
3. 安全,过滤,流控等API管理功能
4. 提供统一服务出口,解耦
二、Zuul实现路由功能:
1. 在前面2章讲解的两个服务模块上,新增两个方法,模拟前端请求,做为本次zuul的测试接口
springcloud-ribbon-client模块的RibbonController类,增加/testzuul接口,具体模块信息参考:一起来学Spring Cloud | 第三章:服务消费者 (负载均衡Ribbon)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.haly.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.haly.service.RibbonService; @RestController public class RibbonController { @Autowired RibbonService ribbonService; @GetMapping (value = "/getHello" ) public String getHello( @RequestParam String name) { return ribbonService.getHello(name); } @GetMapping (value = "/testzuul" ) public String testzuul( @RequestParam String name) { return name + "这是springcloud-ribbon-clientd的服务接口" ; } } |
springcloud-feign-client模块的FeignController类,增加/testzuul接口,具体模块信息参考:一起来学Spring Cloud | 第四章:服务消费者 ( Feign )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package com.haly.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.haly.romote.FeignRemoteService; @RestController public class FeignController { @Autowired FeignRemoteService feignRemoteService; @GetMapping (value = "/getHello" ) public String getHello( @RequestParam String name) { return feignRemoteService.hello(name); } @GetMapping (value = "/testzuul" ) public String testzuul( @RequestParam String name) { return name + ",这是springcloud-feign-client的服务接口" ; } } |
2. 新建一个新的zuul服务工程,名称为:springcloud-zuul-server
①:修改pom.xml文件,parent标签引用的是父文件,具体父文件配置,参考:一起来学Spring Cloud | 第一章 :如何搭建一个多模块的springcloud项目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<?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>com.haly</groupId> <artifactId>springcloud</artifactId> <version> 0.0 . 1 -SNAPSHOT</version> </parent> <groupId>com.haly</groupId> <artifactId>springcloud-zuul-server</artifactId> <version> 0.0 . 1 -SNAPSHOT</version> <name>springcloud-zuul-server</name> <description>新建一个zuuld项目</description> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
②:新增模块启动类SpringcloudZuulServerApplication
注解@EnableZuulProxy,表示开启zuul的功能,它默认也具有@EnableCircuitBreaker和@EnableDiscoveryClient两个注解的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package com.haly; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableZuulProxy @SpringBootApplication public class SpringcloudZuulServerApplication { public static void main(String[] args) { SpringApplication.run(SpringcloudZuulServerApplication. class , args); } } |
③:application.properties加上以下的配置代码:
1
2
3
4
5
6
7
8
9
10
11
|
server.port= 9700 spring.application.name=springcloud-zuul-server eureka.client.serviceUrl.defaultZone=http: //localhost:8761/eureka/ zuul.ignored-services: "*" zuul.routes.a.path = /api/a/** zuul.routes.a.serviceId = springcloud-feign-client zuul.routes.b.path = /api/b/** zuul.routes.b.serviceId = springcloud-feign-client |
先解释下配置含义
zuul.ignored-services: "*" : 之前我们说过可以用服务名直接访问接口,如果我们不想向外界暴露除了application.properties配置映射的服务接口,配置这个属性,只能通过zuul映射的路径访问。
zuul.routes.a.path = /api/a/**
zuul.routes.a.serviceId = springcloud-feign-client
当我们访问zuul服务模块时,只要包含 /api/a/ 路径的服务请求,默认请求到springcloud-ribbon-client模块上的接口
zuul.routes.b.path = /api/b/**
zuul.routes.b.serviceId = springcloud-feign-client
同理,当我们访问zuul服务模块时,只要包含 /api/b/ 路径的服务请求,默认请求到springcloud-feign-client模块上的接口
3. 运行项目
启动 注册中心 springcloud-eureka-server,启动springcloud-ribbon-client服务模块,启动springcloud-feign-client服务模块,启动springcloud-zuul-server模块
在这里首先我要表达歉意,在第一章搭建多模块的微服务项目时,我使用的springcloud和springboot的版本会有问题,所以本章节启动springcloud-zuul-server模块时报错,具体报错如下:
原因是springboot与springcloud的版本不一致导致的,以后有同学遇到同样问题,记得将对应的版本号改成一致
在实际开发过程中,我们详细的版本对应关系:
现在我们将父pom中springcloud的版本号修改为:Greenwich.SR1 ,再启动springcloud-zuul-server服务模块,可以启动成功了,eureka上服务信息如下:
打开浏览器访问访问zuul服务的端口9700:http://localhost:9700/api/a/testzuul?name=young码农,我们发现/api/b/*的请求路由到 springcloud-ribbon-client模块
打开浏览器访问zuul服务的端口9700:http://localhost:9700/api/b/testzuul?name=young码农,我们发现/api/b/*的请求路由到 springcloud-feign-client模块
三、Zuul实现服务过滤:
zuul不仅只是路由,并且还能过滤,可以用来做一些安全验证和日志记录,我写一个简单的接口执行时间记录的功能
新建一个类:BaseZuulFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package com.haly.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; @Component public class BaseZuulFilter extends ZuulFilter {<br> protected final Logger logger = LoggerFactory.getLogger(getClass());<br> // 单例多线程 开始时间绑定在线程上 private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>(); <br> @Override public String filterType() { // 在请求被处理之后,会进入该过滤器 return "post" ; } <br> @Override public int filterOrder() { return 0 ; } @Override public boolean shouldFilter() { // 请求开始计时 long startTime = System.currentTimeMillis(); startTimeThreadLocal.set(startTime); return true ; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); String requestURI = String.valueOf(context.get( "requestURI" )); // 请求结束时间 Long startTime = startTimeThreadLocal.get(); Long endTime = System.currentTimeMillis(); logger.info( "[进入zuul日志记录功能] RequestURI:{}, {}:ms" , requestURI, endTime - startTime); return null ; } } |
routing:路由之时
post: 路由之后
error:发送错误调用
shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
package com.yy.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * token检验,请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示 */ @Component public class TokenFilter extends ZuulFilter { private final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { // //定义filter的类型,有pre、route、post、error四种 return "pre"; } @Override public int filterOrder() { return 0; //定义filter的顺序,数字越小表示顺序越高,越先执行 } @Override public boolean shouldFilter() { return true; // 是否执行该过滤器,此处为true,说明需要过滤 } @Override //filter需要执行的具体操作 public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); logger.info("------>>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString()); String token = request.getParameter("token"); // 请求中含有Token便让请求继续往下走, if(StringUtils.isNotBlank(token)){ ctx.setSendZuulResponse(true); // 对请求进行路由 ctx.setResponseStatusCode(200); ctx.set("isSuccess", true); } else { // 如果请求不带Token就直接返回并给出提示 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(400); ctx.setResponseBody("token is empty"); ctx.set("isSuccess", false); } return null; } }
四、总结:
当前为止,项目结构: