在线演示

演示地址:http://139.196.87.48:9002/kitty

用户名:admin 密码:admin

技术背景

前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢。

当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问题的,但出于种种原因,这并不是一个好的选择。

让客户端直接与各个微服务通讯,会有以下几个问题:

  • 客户端会多次请求不同的微服务,增加了客户端的复杂性。
  • 存在跨域请求,在一定场景下处理会变得相对比较复杂。
  • 实现认证复杂,每个微服务都需要独立认证。
  • 难以重构,项目迭代可能导致微服务重新划分。如果客户端直接与微服务通讯,那么重构将会很难实施。
  • 如果某些微服务使用了防火墙/浏览器不友好的协议,直接访问会有一定困难。

面对类似上面的问题,我们要如何解决呢?答案就是:服务网关!

使用服务网关具有以下几个优点:

  • 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可在服务网关上进行认证,然后再转发请求到微服务,无须在每个微服务中进行认证。
  • 客户端只跟服务网关打交道,减少了客户端与各个微服务之间的交互次数。
  • 多渠道支持,可以根据不同客户端(WEB端、移动端、桌面端...)提供不同的API服务网关。

Spring Cloud Zuul

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。

Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

在Spring Cloud体系中, Spring Cloud Zuul 封装了Zuul组件,作为一个API网关,负责提供负载均衡、反向代理和权限认证。

Zuul工作机制

过滤器机制

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

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 处理正常的请求响应

禁用指定的Filter

可以在application.yml中配置需要禁用的filter,格式为zuul.<SimpleClassName>.<filterType>.disable=true

比如要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter就设置如下。

zuul:
SendResponseFilter:
post:
disable: true

自定义Filter

实现自定义滤器需要继承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, 效果如下。

说明我们自定义的熔断器已经起作用了。

自定义Filter

创建一个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://www.cnblogs.com/xifengxiaoma/
版权所有,欢迎转载,转载请注明原文作者及出处。

spring cloud 配置zuul实用的更多相关文章

  1. spring cloud(二) zuul

    spring cloud 网关 zuul 搭建过程 1. 新建boot工程 pom引入依赖 <dependency> <groupId>org.springframework. ...

  2. Spring Cloud 配置服务

    Spring Cloud 配置服务 1. 配置服务简介 产生背景: 传统开发中,我们通常是将系统的业务无关配置(数据库,缓存服务器)在properties中配置,在这个文件中不会经常改变,但随着系统规 ...

  3. 记录一个 spring cloud 配置中心的坑,命令行端口参数无效,被覆盖,编码集问题无法读取文件等.

    spring cloud 配置中心 结合GIT , 可以运行时更新配置文件.发送指令让应用重新读取配置文件. 最近在测试服务器实现了一套,结果CPU 实用率暴增,使用docker compose启动 ...

  4. Spring Cloud Netflix Zuul 重试会自动跳过经常超时的服务实例的简单说明和分析

    在使用E版本的Spring Cloud Netflix Zuul内置的Ribbon重试功能时,发现Ribbon有一个非常有用的特性: 如果某个服务的某个实例经常需要重试,Ribbon则会在自己维护的一 ...

  5. Spring Cloud配置中心(Config)

    Spring Cloud配置中心(Config) Spring Cloud是现在流行的分布式服务框架,它提供了很多有用的组件.比如:配置中心.Eureka服务发现. 消息总线.熔断机制等. 配置中心在 ...

  6. spring cloud 配置中心

    1. spring cloud配置中心server 1.1 创建git仓库 首先在github上搭建一个存储配置中心的仓库,需要创建两个分支,一个是master,一个是dev分支.自己学习可以用公开库 ...

  7. springcloud(五):Spring Cloud 配置中心的基本用法

    Spring Cloud 配置中心的基本用法 1. 概述 本文介绍了Spring Cloud的配置中心,介绍配置中心的如何配置服务端及配置参数,也介绍客户端如何和配置中心交互和配置参数说明. 配置中心 ...

  8. springcloud(六):Spring Cloud 配置中心采用数据库存储配置内容

    Spring Cloud 配置中心采用数据库存储配置内容 转自:Spring Cloud Config采用数据库存储配置内容[Edgware+] Spring Cloud Server配置中心采用了G ...

  9. spring cloud 通过zuul网关去请求的时候报404的几个原因。

    spring cloud 中 zuul 网关的那些坑: 1.检查你的服务是否正常启动. 2.检查你的服务是否正常注册到注册中心. 3.zuul网关的路由规则是会把你注册在注册中心的serviceId ...

随机推荐

  1. WebStorm重复代码快捷表达

    一,问题 平时使用WebStorm时需要很多引用js,重复代码比较多,每次都要写很多次同样的代码,那么如何通过几个快捷键来简单的写出重复代码呢? 问题具体描述: 每次都要写两个script的重复: & ...

  2. BZOJ2649 : riddle

    题意同3495,但是内存限制收紧了,不能采用3495的前后缀优化建图的方式. 注意到“每个集合恰好选择一个点”可以放宽成“每个集合最多选择一个点”,对于最后求出的方案里,如果某个集合没选点,任选一个就 ...

  3. docker用法记录

    下载docker镜像 docker pull ubuntu 查看所有docker镜像 docker images 运行docker镜像且进入shell docker run -it ubuntu ba ...

  4. Hive中的Order by与关系型数据库中的order by语句的异同点

    在Hive中,ORDER BY语句是对查询结果集进行整体的排序,最终将会产生一个reducer进行全局的排序,达到的最终结果是和传统的关系型数据库是一样的. 在数据量非常大的时候,全局排序的单个red ...

  5. vue-cli跳转到新页面的顶部

    我这里有两种方法都是可以用的 1,利用vue-router的默认模式hash,可以记录上一页的位置,如果需要点话,如果没有记录,在进入新页面的时候是返回到新页面的最顶部的 scrollBehavior ...

  6. SpringBoot报错:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone

    解决方法: 在数据库连接url配置后边加&serverTimezone=GMT%2B8 例: jdbc:mysql://127.0.0.1:3306/test改为jdbc:mysql://12 ...

  7. 创建Jdbc封装工具类

    jdbc.propertie url=jdbc:mysql:///empye user=root password=root driver=com.mysql.jdbc.Driver 读取资源文件  ...

  8. SQL、索引

    (二)数据库索引 数据库索引是用于提高数据库表的数据访问速度的. 数据库索引的特点: a)避免进行数据库全表的扫描,大多数情况,只需要扫描较少的索引页和数据页,而不是查询所有数据页.而且对于非聚集索引 ...

  9. React Native 0.50版本新功能简介

    React Native在2017年经历了众多版本的迭代,从接触的0.29版本开始,到前不久发布的0.52版本,React Native作为目前最受欢迎的移动跨平台方案.虽然,目前存在着很多的功能和性 ...

  10. 3种方法来在Linux电脑上查找文件

    如果你不太了解Linux命令,那么在Linux系统里查找文件是比较困难的.只要使用多种不同的终端命令,可以很快地找到文件.Linux命令比其它操作系统的搜索功能更加强大,掌握这些命令就能你完全控制这些 ...