Zuul网关
API网关
- API网关,顾名思义,是统一管理API的一个网络关口、通道,是整个微服务平台所有请求的唯一入口
- 所有的客户端和消费端都通过统一的通过网关接入微服务,在网关层处理所有的非业务功能
有网关和没有网关
没有 | 有 |
---|---|
没有网关的时候,用户可以随意的访问每一台微服务 | 有了网关后,请求必须得要先经过网关确定这个请求是否合法 如果合法,zuul 会对其做出判断,转发到指定的微服务,也会自动帮助做负责均衡 |
Zuul概述
- Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器(网关)
- Zuul 包含了对请求的路由和过滤两个最主要的功能
- 路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
- 过滤器功能负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础
- Zuul 也作为一个客户端注册进Eureka
- 将 Zuul 自身注册为 Eureka 服务治理下的应用
- 同时从 Eureka 中获得其他微服务的消息,也即以后的访问微服务都是通过 Zuul 跳转后获得
使用Zuul
- 在工程当中创建一个网关微服务
- 导入 zuul 相关依赖
<dependencies>
<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>
</dependencies>
- 创建启动类,在启动类上添加
@EnableZuulProxy
,这里不需要添加@EnableEurekaClient
注解了
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
- 创建配置文件 application.yml
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: zuul-0
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 1
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 3
spring:
application:
# 此实例注册到eureka服务端的name
name: client-zuul
server:
port: 8003
- 启动工程后,访问Eureka
- 启动一个 goods 工程
- 可以直接使用网关来调用 goods 工程当中的 api
- http://localhost:8003/client-goods/getGoods.do
- http://localhost:网关端口号/服务名称/api
Zuul配置路由
在zuul 的配置文件当中添加如下配置
zuul:
routes:
goods:
# 自己定义的名称,商品服务,这个名称是对应的application.yml中的 spring.application.name
serviceId: client-goods
# /* 是一级 /** 是多级
path: /goods/**
order:
# 订单服务
serviceId: server-order
path: /order/**
配置完成后重启,再访问地址:http://localhost:8003/goods/getGoods.do
禁用通过微服务的名称直接调用
ignored-services: client-goods
- 再使用 http://localhost:8003/client-goods/getGoods.do 调用就会失败
- 如果想要禁用所有直接是 ignored-services: "*" 即可
- 官方:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.1.RELEASE/reference/html/#netflix-zuul-starter
- prefix
# 添加访问前缀
prefix: /api
- 访问时:http://localhost:8003/api/goods/getGoods.do
- strip-prefix
- 前缀默认会从请求路径中移除,当 strip-prefix: true 时(会移除)false不移除
strip-prefix: false
过滤器
- 过滤器(filter)是 Zuul 的核心组件 Zuul 大部分功能都是通过过滤器来实现的
- Zuul 中定义了 4 种标准过滤器类型
- PRE
- 过滤器在请求,被路由之前调用
- 也就是你的请求被路由到服务之前会调用这个过滤器
- 可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等
- ROUTING
- 过滤器将请求路由到微服务
- 意思是请求到达了服务了
- 过滤器用于构建发送给微服务的请求,并使用 Apache HttpCIient 或 Netfilx Ribbon 请求微服务
- POST
- 过滤器在路由到微服务之后执行
- 这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
- ERROR
- 在其他阶段发生错误时执行该过滤器
自定义过滤器
package com.bntang666.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author BNTang
* @version V1.0
* @program springcloud-netflix
* @date Created in 2020/9/9 11:19
* @description
**/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 返回过滤器的类型
*
* @return 前置过滤器
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 返回指定过滤器的执行顺序,越小越靠前执行
*
* @return +1,在此过滤器之后执行
*/
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER + 1;
}
/**
* 判断该过滤器是否要执行,true表示执行,false表示不执行
*
* @return 执行该过滤器
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器具体要做的逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String remoteAddr = request.getRemoteAddr();
System.out.println("访问者IP:" + remoteAddr + "访问地址:" + request.getRequestURI());
// 可以通过 application.yml 配置文件中的 strip-prefix: false/true,确定是否要移除前缀
System.out.println("路由后的地址:" + ctx.get(FilterConstants.REQUEST_URI_KEY));
return null;
}
}
- 注意 application.yml 配置项的结构,如果通过网关访问不成功,可以把前缀移除改为
true
- 也就是说你访问的时候带上前缀,但是真正调用的时候会默认帮你移除掉前缀
Zuul容错与回退
- Zuul 默认是整合了 Hystrix 和 Ribbon 的,提供降级回退,当服务访问不通的时候,会调用自己提供的方法,是服务级别的
- 实现步骤
- 编写一个类,实现
FallbackProvider
🐸方法介绍
方法名 | 概述 |
---|---|
getRoute | 制定为那个微服务提供回退,这里写微服务名,写* 代表所有微服务 |
fallbackResponse | 此方法需要返回一个ClientHttpResponse对象 |
🦄response方法
🐤完整代码
@Component
public class ZuulFallBackProvider implements FallbackProvider {
/**
* 制定为哪个微服务提供回退(这里写微服务名,写 * 代表所有微服务)
*
* @return
*/
@Override
public String getRoute() {
return "*";
}
/**
* 此方法需要返回一个ClientHttpResponse对象,ClientHttpResponse是一个接口,具体的回退逻辑要实现此接口
*
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
// 这里可以判断根据不同的异常来做不同的处理,也可以不判断,完了之后调用response方法并根据异常类型传入HttpStatus
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
// 这里返回一个 ClientHttpResponse 对象,并实现其中的方法,关于回退逻辑的详细,便在下面的方法中
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
// 返回一个HttpStatus对象,这个对象是个枚举对象,里面包含了一个 status code 和 reasonPhrase 信息
return status;
}
@Override
public int getRawStatusCode() throws IOException {
// 返回status的code 比如 404,500等
return status.value();
}
@Override
public String getStatusText() throws IOException {
// 返回一个HttpStatus对象的reasonPhrase信息
return status.getReasonPhrase();
}
@Override
public void close() {
// close的时候调用的方法, 讲白了就是当降级信息全部响应完了之后调用的方法
System.out.println("close调用");
}
@Override
public InputStream getBody() throws IOException {
// 把降级信息响应回前端
return new ByteArrayInputStream("系统繁忙,请稍后再试".getBytes());
}
@Override
public HttpHeaders getHeaders() {
// 需要对响应报头设置的话可以在此设置
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
重启 Zuul 模块,进行测试即可,需要移除前缀,我这里就不再贴图了,很简单
Zuul集群
- 创建两个 Zuul 服务
- 代理服务
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: zuul-0
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 1
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 3
spring:
application:
# 此实例注册到eureka服务端的name
name: client-zuul
server:
port: 8003
zuul:
# 禁止所有服务使用服务名称访问
ignored-services: "*"
# 添加访问前缀
prefix: /api
# 前缀默认会从请求路径中移除,当 strip-prefix: true 时(会移除)false不移除
strip-prefix: true
routes:
goods:
# 自己定义的名称,网关服务
serviceId: zuul-server
# /* 是一级 /** 是多级
path: /zuul/**
- Zuul 服务1
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: zuul-server-1
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 1
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 3
spring:
application:
# 此实例注册到eureka服务端的name
name: zuul-server
server:
port: 8004
zuul:
# 禁止所有服务使用服务名称访问
ignored-services: "*"
# 添加访问前缀
prefix: /api
# 前缀默认会从请求路径中移除,当 strip-prefix: true 时(会移除)false不移除
strip-prefix: true
routes:
# 商品服务
goods:
serviceId: client-goods
# /* 是一级 /** 是多级
path: /goods/**
# 订单服务
order:
serviceId: server-order
path: /order/**
# 用户服务
user:
serviceId: client-user
path: /user/**
- Zuul 服务2
eureka:
client:
serviceUrl:
# eureka服务端提供的注册地址 参考服务端配置的这个路径
defaultZone: http://eureka:3000/eureka,http://eureka1:3001/eureka,http://eureka2:3002/eureka
instance:
# 此实例注册到eureka服务端的唯一的实例ID
instance-id: zuul-server-2
# 是否显示IP地址
prefer-ip-address: true
# eureka客户需要多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30 秒 (与下面配置的单位都是秒)
leaseRenewalIntervalInSeconds: 1
# Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认为90秒
leaseExpirationDurationInSeconds: 3
spring:
application:
# 此实例注册到eureka服务端的name
name: zuul-server
server:
port: 8005
zuul:
# 禁止所有服务使用服务名称访问
ignored-services: "*"
# 添加访问前缀
prefix: /api
# 前缀默认会从请求路径中移除,当 strip-prefix: true 时(会移除)false不移除
strip-prefix: true
routes:
# 商品服务
goods:
serviceId: client-goods
# /* 是一级 /** 是多级
path: /goods/**
# 订单服务
order:
serviceId: server-order
path: /order/**
# 用户服务
user:
serviceId: client-user
path: /user/**
Zuul1 - Zuul2 中 filter 中的 LoginFilter 代码
@Component
public class LoginFilter extends ZuulFilter {
/**
* 返回过滤器的类型
*
* @return 前置过滤器
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 返回指定过滤器的执行顺序,越小越靠前执行
*
* @return +1,在此过滤器之后执行
*/
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER + 1;
}
/**
* 判断该过滤器是否要执行,true表示执行,false表示不执行
*
* @return 执行该过滤器
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器具体要做的逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String remoteAddr = request.getRemoteAddr();
System.out.println("访问者IP:" + remoteAddr + "访问地址:" + request.getRequestURI());
// 可以通过 application.yml 配置文件中的 strip-prefix: false/true,确定是否要移除前缀
System.out.println("路由后的地址:" + ctx.get(FilterConstants.REQUEST_URI_KEY));
return null;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具