Zuul API网关
Zuul API网关
解决问题: 服务器集群的集成调用, 集成了ribbon, hystrix, actuator等
zuul API 网关,为微服务应用提供统一的对外访问接口。
zuul 还提供过滤器,对所有微服务提供统一的请求校验。
快速开始
前提
例如我们有三中业务服务器, 服务器id名为: item-service
, order-service
, user-service
并且配置了eureka注册中心
1. 创建项目导入依赖
创建springboot项目, 并导入依赖Zuul
, Eureka Discovery Client
<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>
2. yml配置文件
spring: application: name: zuul # 服务器id/名称 server: port: 3001 # 服务器端口 # 配置eureka eureka: client: service-url: defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka # 配置zuul # 默认规则: # 如果不配置,zuul会根据注册表的注册信息,完成自动配置 # 最好自己手动配置,避免注册表信息不全,自动配置不完整 zuul: routes: item-service: /item-service/** user-service: /user-service/** order-service: /order-service/**
3. 主程序注解
添加 @EnableZuulProxy
和 @EnableDiscoveryClient
(高版本springboot可省略此注解) 注解
@EnableZuulProxy // @EnableDiscoveryClient // springboot高版本可省略 @SpringBootApplication public class Sp11ZuulApplication { public static void main(String[] args) { SpringApplication.run(Sp11ZuulApplication.class, args); } }
4. 访问测试
例如我们item-service
有个/getItem
请求, 我们可以访问我们的zuul服务器来发送请求, 如下
http://localhost:3001/item-service/getItem
// 3001 为zuul的端口, 即访问的zuul服务器
Zuul 请求过滤
请求过滤的本质就是对接口的访问权限进行限制,实现的思路大致有以下几种:
- 为每个服务写一套校验签名和鉴别权限的过滤器和拦截器。(代码是真的冗余)。
- 将校验权限剥离,构成一个独立的鉴权服务。(换汤不换药,还是代码冗余)。
- 在API网关中实现对客户端请求的校验。(请求过滤)。
过滤方式
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
1. 定义过滤器
过滤器需要继承ZuulFilter
抽象类, 并重写其4个方法, 使用@Component
注解注入spring容器
package cn.tedu.sp11.filter; import cn.tedu.web.util.JsonResult; 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.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class AccessFilter extends ZuulFilter { /** * 指定过滤器的类型: pre(前置), post(发布), routing(路由), error(错误) <br> * 多数情况下用pre * @return 可以直接返回字符串, 例如"pre", 或者使用FilterConstants中的常量 */ @Override public String filterType() { return FilterConstants.PRE_TYPE; } /** * 顺序号 <br> * 在默认过滤器中, 第5个过滤器在上下文对象中添加了 service-id(服务id) <br> * 所以在第5个过滤器之后, 才能从上下文对象访问 service-id(服务id) <br> * 如果需要用到service-id(服务id), 建议使用5之后的数字 * @return 6 */ @Override public int filterOrder() { return 6; } /** * 对当前请求, 是否要进行过滤 <br> * @return * 如果返回true, 要进行过滤, 会执行下面的过滤方法run() <br> * 如果返回false, 不需要过滤, 跳过run()方法, 继续执行后面的流程 */ @Override public boolean shouldFilter() { // 判断用户调用的是否是商品服务 // 如果是商品服务进行过滤, 其他服务不过滤 // 获取上下文请求对象 RequestContext ctx = RequestContext.getCurrentContext(); // 从上下文对象获取客户端调用的service-id String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY); // 如果是商品服务, 返回true, 如果不是则返回false return "item-service".equals(serviceId); } /** * 过滤代码, 如果有token参数, 则继续执行, 如果为空, 则返回对应内容 * @return 返回值再当前zuul版本中没有启动, 返回任何数据都无效, 作者考虑了返回值后续可能会有用 * @throws ZuulException 异常 */ @Override public Object run() throws ZuulException { // http://localhost:3001/item-service/123?token=ds54f6af // 获取上下文请求对象 RequestContext ctx = RequestContext.getCurrentContext(); // 从上下文对象获request对象 HttpServletRequest request = ctx.getRequest(); // 获取请求参数 String token = request.getParameter("token"); if (StringUtils.isBlank(token)) { // 判断是否为空串 // 如果为空, 没有token参数, 阻止这次调用继续执行 ctx.setSendZuulResponse(false); // 在这里, 直接向客户端发送响应 // JsonResult: {code: 400, msg: not log in, data: null} ctx.setResponseStatusCode(JsonResult.NOT_LOGIN); ctx.setResponseBody( JsonResult.err() .code(JsonResult.NOT_LOGIN) .msg("not log in") .toString()); } return null; } }
2. 访问测试即可
测试访问: http://localhost:3001/item-service/35
和 http://localhost:3001/item-service/35?token=1234
一个有token参数, 一个没有
zuul集成ribbon
负载均衡和重试
zuul已经集成了ribbon, 默认已经实现了负载均衡, 但是没有默认开启重试功能(zuul框架不推荐开启重试)
1. zuul + ribbon 重试
需要 spring-retry 依赖 springboot依赖里没有, 从这里复制即可
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
2. 配置重试 yml
需要配置zuul.retryable=true
来开启重试功能, 可配置重试参数
zuul: routes: item-service: /item-service/** user-service: /user-service/** order-service: /order-service/** retryable: true # 开启重试功能 # zuul: # retryable: true # 配置重试参数 (可选) # ribbon: # ConnectTimeout: 1000 # ReadTimeout: 1000 # MaxAutoRetriesNextServer: 1 # MaxAutoRetries: 1
zuul集成hystrix
1. 创建降级类
创建降级类, 需要实现FallbackProvider
接口, 重写其两个方法, 记得加@Component
注解, 注入ioc容器
package cn.tedu.sp11.fallback; import cn.tedu.web.util.JsonResult; 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; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @Component public class ItemFallback implements FallbackProvider { /** * 返回服务id, 即service-id <br> * 针对哪个服务应用当前降级类 * @return 如果返回"*"或null, 表示对所有服务都执行当前降级类 <br> * 如果返回"item-service" 只对item-service服务降级 */ @Override public String getRoute() { return "item-service"; } /** * 降级执行的代码 * ClientHttpResponse 是封装降级响应的对象 */ @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { return new ClientHttpResponse() { // 状态文本和状态码 @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } // 状态码 @Override public int getRawStatusCode() throws IOException { return HttpStatus.OK.value(); } // 状态文本 @Override public String getStatusText() throws IOException { return HttpStatus.OK.getReasonPhrase(); } @Override public void close() { } // 返回响应体 @Override public InputStream getBody() throws IOException { // JsonResult: {code: 200, msg: "调用后台服务失败", data: null} String json = JsonResult.err().msg("调用商品服务失败").toString(); ByteArrayInputStream in = new ByteArrayInputStream(json.getBytes()); return in; } // 返回响应头 @Override public HttpHeaders getHeaders() { // Content-Type: application/json HttpHeaders h = new HttpHeaders(); h.setContentType(MediaType.APPLICATION_JSON_UTF8); return h; } }; } }
2. hystrix 超时时间
为了方便测试, 这里设置为500, 一般设置比ribbon重试时间长即可
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 500
3. 测试
自行想办法测试, 相信大家都会
zuul + hystrix 数据监控
暴露 hystrix.stream 监控端点
- zuul 已经包含 actuator 依赖
management: endpoints: web: exposure: include: hystrix.stream
启动服务,查看暴露端点
所有暴露的监控点: http://localhost:3001/actuator
hystrix监控点: http://localhost:3001/actuator/hystrix.stream
启动仪表盘项目
关于dashboard 断路器仪表盘项目的创建: dashboard 断路器仪表盘
启动仪表盘项目后, 填入上面的hystrix的监控点链接即可
注意: 必须通过zuul网关访问后台服务才会长生监控数据
zuul + turbine 聚合监控
我们可以在turbin的项目中把zuul添加进去即可 (使用逗号隔开)
turbine中配置
turbine: app-config: order-service, zuul cluster-name-expression: new String("default")
熔断测试
并发工具下载地址: http://httpd.apache.org/docs/current/platform/windows.html#down
熔断测试,ab -n 20000 -c 50 http://localhost:3001/order-service/123abc
zuul Cookie过滤
zuul 会过滤敏感 http 协议头,默认过滤以下协议头:
- Cookie
- Set-Cookie
- Authorization
可以设置 zuul 不过滤这些协议头
zuul: sensitive-headers:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
· 现代计算机视觉入门之:什么是视频
· Sdcb Chats 技术博客:数据库 ID 选型的曲折之路 - 从 Guid 到自增 ID,再到
· 99%的人不知道,桥接模式失败的真正原因是它!
· .NET Core GC压缩(compact_phase)底层原理浅谈
· Winform-耗时操作导致界面渲染滞后
· Phi小模型开发教程:C#使用本地模型Phi视觉模型分析图像,实现图片分类、搜索等功能