org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
2.2.9.RELEASE
spring-cloud-starter-netflix-zuul依赖,可以通过查看它的依赖内容了解到:
该模块中不仅包含了Netflix Zuul的核心依赖zuul-core,它还包含了下面这些网关服务需要的重要依赖。
-
spring-cloud-starter-hystrix: 该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。
-
spring-cloud-starter-ribbon: 该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。
-
spring-boot-starter-actuator: 该依赖用来提供常规的微服务管理端点。另外,在Spring Cloud Zuul中还特别提供了/routes端点来返回当前的所有路由规则。
2.在主应用类上添加注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
使用@EnablezuulProxy注解开启Zuul的API 网关服务功能
3.在application.properties中配置Zuul应用的基础信息
如应用名、服务端口号等,具体内容如下:
server.port=5555
spring.application.name=api-getway
注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=false
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
前缀,用来做版本控制
zuul.prefix=/v1
禁用默认路由,执行配置路由
zuul.ignored-services=“*”
7.2.2 请求路由
在application.properties添加以下配置就可以实现传统的路由转发功能
配置hello-service的服务路由
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.service-id=hello-service
feign-consumer的服务路由
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.service-id=feign-consumer
下面我大致解释一下以上代码的意思
- http://localhost:5555/api-a/hello: 该url符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceld为hello-service,所以最终/hello 请求会被发送到hello-service服务的某个实例上去,也就是对应的/hello接口
- http:/ /localhost:5555/api-b/feign-consumer: 该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer请求会被发送到feign-consumer服务的某个实例上去。
在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。
但是,每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。然而,目前的服务路由并没有限制权限这样的功能,所有请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的做法并不可取,它会增加日后系统的维护难度,因为同一个系统中的各种校验逻辑很多情况下都是大致相同或类似的,这样的实现方式会使得相似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是我们不希望看到的。所以,比较好的做法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。在完成了剥离之后,有不少开发者会直接在微服务应用中通过调用鉴权服务来实现校验,但是这样的做法仅仅只是解决了鉴权逻辑的分离,并没有在本质上将这部分不属于冗余的逻辑从原有的微服务应用中拆分出,冗余的拦截器或过滤器依然会存在。
对于这样的问题,更好的做法是通过前置的网关服务来完成这些非业务性质的校验。由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,既然这些校验与具体业务无关,那何不在请求到达的时候就完成校验和过滤,而不是转发后再过滤而导致更长的请求延迟。同时,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用接口的开发和测试复杂度也得到了相应降低。
为了在API网关中实现对客户端请求的校验,我们将继续介绍Spring Cloud Zuul的另外一个核心功能:请求过滤。
Zuul 允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方法非常简单,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的拦截和过滤了
下面的代码定义了一个简单的Zuul过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import javax.servlet.http.HttpServletRequest;
@Component
public class AccessFilter extends ZuulFilter {
private final String tokenSign = “accessToken”;
private static Logger log= LoggerFactory.getLogger(AccessFilter.class);
@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();
log.info(“send{} request to {}”, request.getMethod(), request.getRequestURL().toString());
Object accessToken = request.getHeader(tokenSign);
if (accessToken == null) {
log.warn(“token is empty”);
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info(“token is Ok”);
return null;
}
}
在上面实现的过滤器代码中,我们通过继承ZuulFilter抽象类并重写下面4个方法来实现自定义的过滤器。这4个方法分别定义了如下内容:
-
filterType: 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
-
filterOrder: 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
-
shouldFilter: 判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
-
run: 过滤器的具体逻辑。这里我们通过ctx.setsendzuulResponse(false),令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponsestatus-Code (401)设置了其返回的错误码,当然也可以进一步优化我们的返回,比如,ctx.setResponseBody (body)对返回的 body内容进行编辑等。
重启项目,访问http://localhost:5555/v1/api-a/hello
由于需要在请求头设置token,所以我们该用postman进行测试
到这里,对于API网关服务的快速入门示例就完成了。通过对Spring Cloud Zuul两个核心功能的介绍,相信大家已经能够体会到API网关服务对微服务架构的重要性了,就目前掌握的API 网关知识,我们可以将具体原因总结如下:
-
它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。
-
它可以与服务治理框架结合,实现自动化的服务实例维护以及负载均衡的路由转发。·它可以实现接口权限校验与微服务业务逻辑的解耦。
-
通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
默认情况下,Spring Cloud Zuul在请求路由时,会过滤掉HtTP请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器。
默认的敏感头信息通过:zuul. sensitiveHeaders参数定义,包括Cookie、Set-Cookie、Authorization三个属性。
所以,我们在开发Web项目时常用的Cookie在SpringCloudZuul网关中默认是不会传递的,这就会引发一个常见的问题:如果我们要将使用了Spring Security、 Shiro等安全框架构建的Web应用通过SpringCloudZuul构建的网关来进行路由时,由于Cookie信息无法传递,我们的Web应用将无法实现登录和鉴权。为了解决这个问题,配置的方法有很多。
- 通过设置全局参数为空来覆盖默认值保证通过网关访问服务时可以携带cookie,具体如下:
设置全局参数为空来覆盖默认值
zuul.sensitive-headers=
- 对指定路由开启自定义敏感头
对指定路由开启自定义敏感头
zuul.routes..customSensitiveHeaders=true
- 将指定路由的敏感头信息设置为空
zuul.routes..sensitiveHeaders=[这里设置要过滤的敏感头]
Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。
但是需要注意,当使用path与ur1的映射关系来配置路由规则的时候,对于路由转发的请求不会采用HystrixCommand来包装,所以这类路由请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用Zuul的时候尽量使用path和serviceId的组合来进行配置,这样不仅可以保证API网关的健壮和稳定,也能用到Ribbon的客户端负载均衡功能。
- 1.启用hystrix配置
hystrix.metrics.enabled=true
- 2.设置API网关中路由转发请求的HystrixCommand执行超时时间,单位为毫秒。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
当路由转发请求的命令执行时间超过该配置值之后,Hystrix 会将该执行命令标记为TIMEOUT并抛出异常,Zuul会对该异常进行处理并返回如下JSON信息给外部调用方。
{
“timestamp”: “2021-07-15T07:25:21.723+00:00”,
“status”: 504,
“error”: “Gateway Timeout”,
“message”: “”
}
- 3.路由转发请求时,创建请求连接的超时时间 ,单位ms
ribbon.ConnectTimeout=3000
注意:当ribbon.ConnectTimeout的配置值小于hystrix.command.default.execution.isolation.thread. timeoutInMilliseconds配置值的时候,若出现路由请求出现连接超时,会自动进行重试路由请求,如果重试依
然失败,Zuul会返回如下JSON信息给外部调用方。
{
“timestamp”:1481352582852,
“status”:500,
“error”:“Internal Server Error”,
“exception”:“com. netflix. zuul. exception. ZuulException”,
“message”:“NUMBEROF RETRIES NEXTSERVER EXCEEDED”
}
如果ribbon .ConnectTimeout的配置值大于hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds配置值的时候,当出现路由请求连接超时时,由于此时对于路由转发的请求命令已经超时,所以不会进行重试路由请求,而是直接按请求命令超时处理,返回TIMEOUT的错误信息。
{
“timestamp”: “2021-07-15T07:42:51.028+00:00”,
“status”: 500,
“error”: “Internal Server Error”,
“message”: “”
}
- 4.设置路由的重试机制 默认是true
zuul.retryable=false
指定路由的重试机制进行关闭
zuul.routes..retryable=false
注意:route就是你的服务名,比如zuul.routes.hello-service.retryable=false
首先,我们尝试创建一个pre类型的过滤器,并在该过滤器的run方法实现中抛出一个异常。比如下面的实现,在run方法中调用的doSomething方法将抛出Runt imeException异常。
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class ThrowExceptionFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);
@Override
public String filterType() {
return “pre”;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
log.info(“这是一个pre过滤器,将会抛出异常”);
this.doSomething();
return null;
}
private void doSomething() {
throw new RuntimeException("Exist some errors… ");
}
}
在添加了上面的过滤器之后,我们可以将该应用以及之前的相关应用运行起来,并根据网关配置的路由规则访问服务接口,比如http://localhost:5555/api-a/hello此时我们会发现,在API网关服务的控制台中输出了ThrowExceptionFilter的过滤逻辑中的日志信息。
但是返回给页面的信息中却拿不到我们想打印的信息
try-catch 处理
在catch异常的处理逻辑中并没有做任何输出操作,而是向请求上下文中添加了一些error相关的参数,主要
有下面三个参数。
-
error.status_code: 错误编码。
-
error.exception: Exception异常对象。
-
error.message: 错误信息。
其中,error.status_ code 参数就是SendErrorFilter过滤器用来判断是否需要执行的重要参数。分析到这里,实现异常处理的大致思路就开始明朗了,实现对ThrowExceptionFilter的run方法做一些异常处理的改造,具体如下:
@Component
public class ThrowExceptionFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);
@Override
public String filterType() {
return “pre”;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
log.info(“这是一个pre过滤器,将会抛出异常”);
RequestContext ctx = RequestContext.getCurrentContext();
try {
doSomething();
} catch (Exception e) {
e.printStackTrace();
ctx.set(“error.status_code”, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set(“error.exception”, e);
}
return null;
}
private void doSomething() throws Exception {
throw new Exception("Exist some errors… ");
}
}
禁用过滤器
在Zuul中特别提供了-一个参数来禁用指定的过滤器,该参数的配置格式如下:
zuul...disable=true
比如
zuul.AccessFilter.pre.disable=true
import com.cloud.apigateway.filter.ThrowExceptionFilter;
import com.netflix.hystrix.exception.HystrixTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@Component
public class FallBackConfig implements org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider {
private static Logger log = LoggerFactory.getLogger(FallBackConfig.class);
/**
- 定义构造函数
**/
public ClientHttpResponse fallbackResponse() {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
@Override
public String getRoute() {
return “*”;
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
// 捕获超时异常,返回自定义信息
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return fallbackResponse();
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() {
return status;
}
@Override
public int getRawStatusCode() {
return status.value();
}
@Override
public String getStatusText() {
return status.getReasonPhrase();
}
@Override
public void close() {
log.warn(“连接服务关闭,无法进行访问”);
}
@Override
public InputStream getBody() {
String message =
“{\n” +
““code”: 500,\n” +
““message”: “微服务飞出了地球”\n” +
“}”;
return new ByteArrayInputStream(message.getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
在微服务架构中,由于API网关服务担负着外部访问统一入口的重任,它同其他应用不同,任何关闭应用和重启应用的操作都会使系统对外服务停止,对于很多7×24小时服务的系统来说,这样的情况是绝对不被允许的。所以,作为最外部的网关,它必须具备动态更新内部逻辑的能力,比如动态修改路由规则、动态添加/删除过滤器等。
通过Zuul实现的API网关服务当然也具备了动态路由和动态过滤器的能力。我们可以在不重启API网关服务的前提下,为其动态修改路由规则和添加或删除过滤器。下面我们分别来看看如何通过Zuul来实现动态API 网关服务。
7.8.1 动态路由
通过之前对请求路由的详细介绍,我们可以发现对于路由规则的控制几乎都可以在配置文件application.properties或 application.yaml中完成。既然这样,对于如何实现Zuul 的动态路由,我们很自然地会将它与Spring Cloud Config 的动态刷新机制联系到一起。只需将API 网关服务的配置文件通过Spring Cloud Config连接的Git仓库存储和管理,我们就能轻松实现动态刷新路由规则的功能。
在介绍如何具体实现.API网关服务的动态路由之前,我们首先需要一个连接到Git仓库的分布式配置中心config-server应用。如果还没有搭建过分布式配置中心的话,建议先阅读第8章的内容《springCloud08—分布式配置中心:Spring Cloud Config》,对分布式配置中心的运作机制有一个基础的了解,并构建一个config-server应用,以配合完成下面的内容。
在具备了分布式配置中心之后,为了方便理解我们重新构建一个API网关服务,该服务的配置中心不再配置于本地工程中,而是从config-server中获取,构建过程如下所示。
-
创建一个基础的 Spring Boot web工程,命名为api-gateway-dynamic-route。
-
在pom. xml中引入对zuul、eureka和config 的依赖,具体内容如下:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd”>
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.4.8
com.cloud
api-gateway-dynamic-route
0.0.1-SNAPSHOT
api-gateway-dynamic-route
api-gateway-dynamic-route
<java.version>11</java.version>
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
2.2.9.RELEASE
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-bootstrap
org.springframework.cloud
spring-cloud-dependencies
2020.0.2
pom
import
创建bootstrap.properties文件
spring.application.name=api-getway
注册服务的时候使用服务的ip地址
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:1112/eureka/
开启通过服务来访问config server功能
spring.cloud.config.discovery.enabled=true
通过serviceId来指定config server注册的服务名
spring.cloud.config.discovery.service-id=config-server
#指定的环境
spring.cloud.config.profile=default
#指定分支,当使用git的时候,默认是master
spring.cloud.config.label=master
配置项目启动端口号
server.port=5556
设置安全访问的用户名和密码
spring.cloud.config.username=root
spring.cloud.config.password=123456
设置连接失败的快速响应
spring.cloud.config.fail-fast=true
#actuator配置
management.endpoints.enabled-by-default=true
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
management.endpoints.web.base-path=/actuator
同时在你的git仓库里放入api-getway.properties配置文件
文件配置如下:
路由配置
配置hello-service的服务路由
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
feign-consumer的服务路由
zuul.routes.api-b.path=/api-b/**
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
《MySql面试专题》
《MySql性能优化的21个最佳实践》
《MySQL高级知识笔记》
文中展示的资料包括:**《MySql思维导图》《MySql核心笔记》《MySql调优笔记》《MySql面试专题》《MySql性能优化的21个最佳实践》《MySq高级知识笔记》**如下图
关注我,点赞本文给更多有需要的人
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
《MySql面试专题》
[外链图片转存中…(img-QMtMkliN-1713327804346)]
[外链图片转存中…(img-TdFaIawt-1713327804346)]
《MySql性能优化的21个最佳实践》
[外链图片转存中…(img-c5TpyR8S-1713327804346)]
[外链图片转存中…(img-xZ4x5b99-1713327804346)]
[外链图片转存中…(img-1oVUUhRJ-1713327804347)]
[外链图片转存中…(img-iYDykKex-1713327804347)]
《MySQL高级知识笔记》
[外链图片转存中…(img-2s1WCfZM-1713327804347)]
[外链图片转存中…(img-kdyII7GY-1713327804347)]
[外链图片转存中…(img-a6ekxOJX-1713327804348)]
[外链图片转存中…(img-8NDYOAII-1713327804348)]
[外链图片转存中…(img-IJIhrtnO-1713327804348)]
[外链图片转存中…(img-yk4zZTNx-1713327804348)]
[外链图片转存中…(img-OOTJxLd4-1713327804348)]
[外链图片转存中…(img-pjtot5QS-1713327804349)]
[外链图片转存中…(img-3aYi945d-1713327804349)]
[外链图片转存中…(img-VuZfBy4z-1713327804349)]
文中展示的资料包括:**《MySql思维导图》《MySql核心笔记》《MySql调优笔记》《MySql面试专题》《MySql性能优化的21个最佳实践》《MySq高级知识笔记》**如下图
[外链图片转存中…(img-wItiO98i-1713327804349)]
关注我,点赞本文给更多有需要的人
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!