Spring Cloud之服务网关Zuul
一.Zuul的简单介绍
1.zuul是什么
zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。Zuul是基于jvm路由和服务的负载均衡器,专为微服务而生的,认证,鉴权,限流,动态路由,监控,弹性,安全,负载均衡,协作单点压测。Zuul相当于外部环境和web网站后端所有请求的前门。
2.zuul的工作原理
zuul的核心是一系列的过滤器,大部分功能都是在过滤器中完成的。
(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(4) ERROR:在其他阶段发生错误时执行该过滤器。
为什么需要zuul?
在微服务中,服务于服务之间的调用是很复杂的并且是调用的服务很多,如果服务与服务之间打交道的话又麻烦,还可能会导致系统问题。所以我们就设置一个网关,如果想调用服务的话就必须要经过这个网关,这个网关在进行请求的分发。前提是网关必须具备这些条件。
- 稳定性,高可用
- 安全性
- 性能和并发
- 扩展性
二.zuul的使用
1.添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<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.配置文件
3,启动类上添加注解
@EnableZuulProxy
4,访问路径
可以访问eureka上注册的所有服务,只不过要带上要访问的服务名称。
5,自定义路由,因为路由需要进行自定义,所以我们还要实现动态刷新
zuul:
routes:
#/myProduct/list -> /product/list自定义路由
# myProduct:
# path: /myProduct/**
# serviceId: product
#简单的写法
product: /myProduct/**
#排除某些路由
ignored-patterns:
- /product/list
- /myProduct/list
6,动态路由和动态刷新配置
远端git配置,我这里配置了动态刷新
Apigateway.yml
spring:
cloud:
bus:
refresh:
enabled: true
rabbitmq:
host: 10.64.1.127
port: 5672
username: guest
password: guest
zuul:
routes:
#忽略全部服务的敏感头(全部服务都可以获取token)
sensitive-headers:
#/myProduct/list -> /product/list自定义路由
# myProduct:
# path: /myProduct/**
# serviceId: product
#简单的写法
product: /myProduct/**
#排除某些路由
ignored-patterns:
- /product/list
# - /myProduct/list
代码扫描,如果使用自动刷新配置必须要有这个。
过滤器
限流
限流就是很多请求来的时候,zuul可以把他们给挡回去。
限流是zuul的其中一个功能,限流是在请求被转发之前,即使有权限拦截的情况下,依然要把限流放到权限拦截的前边。
在限流的时候会有一个令牌桶算法,以固定的速度去令牌桶放令牌,然后获取的时候在令牌桶获取令牌,如果获取到的话就进行下一步,获取不到的话就进行拦截。
谷歌用RateLimiter封装了这种令牌桶的算法。
直接定义一个过滤器
@Component
public class RateLimitFilter extends ZuulFilter {
//设置100个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(100);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER-1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
if (!RATE_LIMITER.tryAcquire()){
throw new RateLimitException();
}
return null;
}
}
下面是FilterConstants的部分源码,里面定义了请求类型和请求的数字,数字越小,优先级越高。
public static final int DEBUG_FILTER_ORDER = 1;
public static final int FORM_BODY_WRAPPER_FILTER_ORDER = -1;
public static final int PRE_DECORATION_FILTER_ORDER = 5;
public static final int RIBBON_ROUTING_FILTER_ORDER = 10;
public static final int SEND_ERROR_FILTER_ORDER = 0;
public static final int SEND_FORWARD_FILTER_ORDER = 500;
public static final int SEND_RESPONSE_FILTER_ORDER = 1000;
public static final int SIMPLE_HOST_ROUTING_FILTER_ORDER = 100;
public static final int SERVLET_30_WRAPPER_FILTER_ORDER = -2;
public static final int SERVLET_DETECTION_FILTER_ORDER = -3;
public static final String ERROR_TYPE = "error";
public static final String POST_TYPE = "post";
public static final String PRE_TYPE = "pre";
public static final String ROUTE_TYPE = "route";
添加报头
@Component
public class addResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletResponse response = requestContext.getResponse();
response.setHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
验证token
@Component
public class TokenFilter extends ZuulFilter {
//验证过滤器,就是设置权限的过滤器
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//这个只是模拟加上token
String token = request.getParameter("token");
if (StringUtils.isEmpty(token)){
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
return null;
}
}
权限拦截验证
@Component
public class AuthBuyerFilter extends ZuulFilter {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//验证过滤器,就是设置权限的过滤器
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
if ("/order/order/create".equals(request.getRequestURI())){
return true;
}
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
/**
* order/create创建订单只能买家访问(cookie中有openid)
*/
Cookie cookie = CookieUtil.get(request,"openid");
if (cookie == null||StringUtils.isEmpty(cookie.getValue())){
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
return null;
}
}
跨域
一般跨域有两种解决方式,一种是咋子类或者方法上添加@CrossOrigin注解,这个是基于方法的,放在哪个方法上,哪个方法生效。一种是增加CorsFilter过滤器,看下面代码。
@Component
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(Arrays.asList("*")); //http://www.a.com
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
//在这个时间段里,相同的跨域请求就不再进行检查
configuration.setMaxAge(300l);
source.registerCorsConfiguration("/**",configuration);
return new CorsFilter(source);
}
}
注意
zuul必须要保证高可用,zuul的高可用就是把多个zuul节点注册到Eurekaserver上面去。
还有一种方法就是Nginx和Zuul混合使用的方式,Nginx做负载均衡,把请求转发到多个zuul上,这样的话就把Nginx和Zuul的优点发挥出来。
推荐阅读
-
spring cloud 使用Hystrix 实现断路器进行服务容错保护的方法
-
Spring Cloud之服务监控turbine的示例
-
Spring Cloud微服务架构的构建:分布式配置中心(加密解密功能)
-
spring cloud 使用Eureka 进行服务治理方法
-
Spring Cloud zuul自定义统一异常处理实现方法
-
Spring Cloud学习教程之Zuul统一异常处理与回退
-
详解spring cloud整合Swagger2构建RESTful服务的APIs
-
Spring Cloud Gateway 服务网关快速实现解析
-
使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务的方法(推荐)
-
Spring Cloud Gateway 之请求坑位[微服务IP不同请求会失败]