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 请求过滤

请求过滤的本质就是对接口的访问权限进行限制,实现的思路大致有以下几种:

  1. 为每个服务写一套校验签名和鉴别权限的过滤器和拦截器。(代码是真的冗余)。
  2. 将校验权限剥离,构成一个独立的鉴权服务。(换汤不换药,还是代码冗余)。
  3. 在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:
posted @   zpk-aaron  阅读(351)  评论(0编辑  收藏  举报
编辑推荐:
· .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视觉模型分析图像,实现图片分类、搜索等功能
点击右上角即可分享
微信分享提示