本章节, 我们讲解 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)
- 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 )
- 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
1: 修改 pom.xml 文件, parent 标签引用的是父文件, 具体父文件配置, 参考: 一起来学 Spring Cloud | 第一章 : 如何搭建一个多模块的 springcloud 项目
- <?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>
2: 新增模块启动类 SpringcloudZuulServerApplication
注解 @EnableZuulProxy, 表示开启 zuul 的功能, 它默认也具有 @EnableCircuitBreaker 和 @EnableDiscoveryClient 两个注解的功能
- 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);
- }
- }
3:application.properties 加上以下的配置代码:
- 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
- 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 {
- protected final Logger logger = LoggerFactory.getLogger(getClass());
- // 单例多线程 开始时间绑定在线程上
- private ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();
- @Override
- public String filterType() {
- // 在请求被处理之后, 会进入该过滤器
- return "post";
- }
- @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;
- }
- }
filterType: 返回一个字符串代表过滤器的类型, 在 zuul 中定义了四种不同生命周期的过滤器类型, 具体如下:
pre: 路由之前
routing: 路由之时
post: 路由之后
error: 发送错误调用
filterOrder: 过滤的顺序
shouldFilter: 这里可以写逻辑判断, 是否要过滤, 本文 true, 永远过滤.
run: 过滤器的具体逻辑. 可用很复杂, 包括查 sql,nosql 去判断该请求到底有没有权限访问.
浏览器分别请求 zuul 服务模块: http://localhost:9700/API/b/testzuul?name=young 码农, http://localhost:9700/API/b/testzuul?name=young 码农, 会打印如下日志:
2019-05-25 16:41:07.228 INFO 20984 --- [io-9700-exec-10] com.haly.filter.BaseZuulFilter : [进入 zuul 日志记录功能] 请求地址:/testzuul, 耗时 0:ms
有兴趣的可以自己参考上面代码, 做一个简单接口权限验证的功能.
四, 总结:
当前为止, 项目结构:
来源: https://www.cnblogs.com/haly/p/10923030.html