一、spring cloud zuul的介绍
(1)spring cloud zuul是服务网管,是所有外部请求的统一入口。理论上服务网管是处理非业务逻辑的绝佳场所。如果服务网管出现问题,系统就会全部瘫痪,所以必须具备的要素有:稳定性、高可用、高并发、安全性以及可扩展性。
(2)spring cloud zuul最核心的组件就是一系列的过滤器,所有的请求经过zuul,都需要经过过滤器的过滤,再返回请求给客户端。zuul的过滤器分为:前置过滤器(pre)、路由过滤器(route)、后置过滤器(post)、处理异常的过滤器(error)以及自定义过滤器。
这是zuul的组件架构图,可以看到每个过滤器之间是没有通信的,都是通过requestContext这个静态类进行数据的传递的。
(3)在zuul中一次http请求的生命周期
由上图可以看出一次http请求首先经过前置过滤器(pre),可以在这里进行请求参数的加工,比如参数校验等。接下来到达路由过滤器(route),该类型的过滤器是将外部的请求转发到服务提供方,假如服务提供方有多个实例,就会用到负载均衡组件。接下来到达后置过滤器(post),在这里可以对返回的结果进行加工和处理。当前面说的这三种类型的过滤器出现异常的时候,会被error filter过滤器捕获到,因此可以在这里面进行统一的异常处理。
二、spring cloud zuul的使用
1.zuul的路由:首先要将zuul注册进eureka中,当调用一个服务的时候,zuul会拿着这个服务的服务名去eureka中寻找该服务的实例,如果只有一个服务实例就直接调用,如果有多个服务实例,就会根据负载均衡策略选择一个服务实例进行调用。
(1)添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-zuul
(2)启动类上添加注解
@EnableZuulProxy
这样就可以实现最基本的路由功能了。
(3)如果要自定义路由的映射地址需要修改配置文件
zuul:
routes:
product: /myProduct/** #product指的是需要调用服务的服务名。/myProduct/**是你自定义的映射地址
ignored-patterns: #改配置指的是你要排除的某些地址,也就是不对外暴露的地址
- /**/product/getProductInfo
ignored-services: /product #改配置指的是将默认的映射地址关闭,不对外暴露。因为zuul默认的映射地址就是调用服务的服务名。如果要全部禁用可以写个"*"
prefix: /wangbin #改配置指的是,指定统一的访问路径的前缀
#设置全局的敏感头
sensitive-headers: #改配置指的是将敏感头设置为空,因为默认的敏感头设置有"Cookie", "Set-Cookie", "Authorization",如果不设置此项像cookie等这三个值通过路由传递不过去
(4)实现动态路由
(1)将配置文件中zuul的配置参考配置中心放到git上。
(2)添加一个配置类
//因为zuulPropertis中所有关于zuul的属性都有,所以直接new zuulProperties()即可。
@Component
public class ZuulConfig {
@ConfigurationProperties("zuul")
@RefreshScope
public ZuulProperties properties(){
return new ZuulProperties();
}
}
这样就可以实现在动态修改路由配置的时候,不用重启项目就可以自动刷新过来,即动态路由。
(5)路由的高可用
服务网管是所有请求的统一入口,一旦服务网管出现故障,系统就会全部瘫痪。所以在生产环境上都会部署多态zuul,可以把zuul看成普通的微服务,将多个zuul注册到eureka server上,这样就可以避免单个zuul服务出现故障,导致整个系统不可用的情况。
因为zuul的性能没有Nginx的性能高,所以在使用的时候可以继续让nginx继续实现负载均衡和反向代理的作用,从nginx将请求转发到各个zuul节点,继续让zuul发挥自身的优势。
2.zuul的过滤
(1)因为所有的请求都会先经过前置(pre)类型的过滤器,所以在这可以限流、鉴权、参数校验等。而后置(post)类型的过滤器,所以在这可以进行数据的统计、记录日志等。error filter类型的过滤器是在这些过滤器发生异常的时候会被拦截到,多以可以在这里进行异常的统一处理。
(2)前置过滤器的使用
/**
* 自定义pre类型的filter进行鉴权操作
* PRE_TYPE、PRE_DECORATION_FILTER_ORDER属性可以在zuul中的FilterConstants类中找到
* */
@Component
public class TokenFilter extends ZuulFilter {
/**
* filterType指定过滤器的类型
* */
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* filterOrder指定过滤器的执行顺序,返回的值越小,说明越先执行
* */
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER -1;
}
/**
* shouldFilter指定是否开启该过滤器
* */
@Override
public boolean shouldFilter() {
return true;
}
/**
* run是拦截到请求后具体要实现的逻辑。
* 这里为了举例只是简单的判断请求中有没有携带cookie,若携带cookie则表明有权限,若没有携带cookie则说明没有权限
* */
@Override
public Object run() throws ZuulException {
//RequestContext是zuul组件中请求上下文,各个过滤器之间没有通信,是通过该静态类进行数据的交互
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if(StringUtils.isEmpty(token)){
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
(2)后置过滤器的使用
/**
* 自定义post类型的过滤器可以对请求响应参数处理加工
* POST_TYPE、SEND_RESPONSE_FILTER_ORDER属性可以在zuul中的FilterConstants类中找到
* */
@Component
public class AddResponseHeadsFilter extends ZuulFilter {
/**
* filterType指定过滤器的类型
*/
@Override
public String filterType() {
return POST_TYPE;
}
/**
* filterOrder指定过滤器的执行顺序,返回的值越小,说明越先执行
*/
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
/**
* shouldFilter指定是否开启该过滤器
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* run是拦截到请求后具体要实现的逻辑。
* 这里为了举例只是简单在响应头中设置了一个值
*/
@Override
public Object run() throws ZuulException {
//RequestContext是zuul组件中请求上下文,各个过滤器之间没有通信,是通过该静态类进行数据的交互
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletResponse response = currentContext.getResponse();
response.setHeader("RES", "123456");
return null;
}
}
(3)在前置过滤器中实现限流(服务器的承载能力是有限的,当请求量非常大时可以做限流保护,防止网络攻击)
限流的算法有很多种,这里介绍的是比较常用的令牌桶算法。令牌桶算法的基本原理就是:以固定的速率往令牌桶中放入令牌,当令牌桶存满时新生成的令牌就会直接抛弃掉。所有的请求都先要从令牌桶中获取令牌,若获取到令牌直接放行,若获取不到令牌则被拦截。
/**
* 限流保护过滤器---使用令牌桶算法来实现,guawa提供了开源的组件ratelimit
* */
@Component
public class RateLimitFilter extends ZuulFilter {
//参数指的是每秒钟放多少个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(10);
@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;
}
}