springcloud-zuul

一、简介

Zuul 是一个基于JVM路由和服务的负载均衡器,提供路由、监控、安全等方面的服务框架。Zuul能够与Eureka、Ribbon、Hystrix等组件配合使用。

Zuul的核心是过滤器,通过这些过滤器我们可以扩展出很多功能,例如:

  • 动态路由:动态的将客户端的请求路由到后端不同的服务,做一些逻辑处理,比如聚合多个服务的数据返回。
  • 请求监控:可以对整个系统的请求进行监控,记录详细的请求响应日志,可以试试统计出当前系统的访问量以及监控状态。
  • 认证鉴权:对每一个访问的请求做认证。拒绝非法请求,保护好后端服务。
  • 压力测试:通过Zuul可以动态的将请求转发到后端服务的集群中,还可以识别测试流量和真实流量,从而做一些特殊处理。
  • 灰度发布:灰度帆布可以保证整体系统的稳定,在初始灰度时就可以发现,调整问题,以保证其影响度。

二、简单使用

1. 引用依赖

    <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

2. 在配置 文件中添加配置信息

spring.application.name=zuul-demo
server.port=2103
#通过zuul.routes配置路由转发 testzuul是自定义的名称,当访问 /testzuul/**地址时就会跳转到http://baidu.com/
zuul.routes.testzuul.path=/testzuul/**
zuul.routes.testzuul.url=http://baidu.com/

3. 在启动类加入 @EnableZuulProxy 注解

启动应用后 访问 http://localhost:2103/testzuul 就会跳转到 百度。

三、 集成 Eureka

1. 引入依赖

     <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

2. 在配置文件中添加配置

eureka.client.serviceUrl.defaultZone=XXXX

注意:因为 @EnableZuulProxy 中已经自带了 @EnableDiscoveryClient 所以在启动类中不用再加Eureka 的注解。

重启服务,我们可以通过默认的转发规则来访问Eureka中的服务,访问规则是:“API网关地址+访问的服务名称+接口URI”,例如 :http://localhost:2103/ribbon/test/fegin?userName=113

四、Zuul路由配置

Zuul的默认的转发规则 “API网关地址+访问的服务名称+接口URI”,在给服务制定名称时应该尽量短点,这样就可以使用默认的路由会泽进行请求,不需要为每个服务都定一个路由规则,默认规则举例:

API网关地址:http://localhost:2103

用户服务名称:ribbon

用户接口:/test/fegin?userName=113

那么通过Zuul的访问登录接口的规则就是:http://localhost:2103/ribbon/test/fegin?userName=113

1. 指定具体服务路由:

zuul.routes.ribbon.path=/user-service/**

这里将服务名称为 ribbon的服务配置为user-service,也就是说想访问 ribbon服务的接口要通过 user-service访问。

 注意:配置文件中的 /user-services/** 这里一定要是两个 *  ,如果配置一个 * 就只能转发一级,两个表示可以转发任意层级的URL  

 2. 配置路由前缀

zuul.prefix=/rest

配置了路由前缀后要访问前加你配置的val,例如我配置的rest,例如:http://localhost:2103/rest/user-service/test/fegin?userName=%E2%80%981111%E2%80%99

3. 本地跳转

zuul.routes.local.path=/api/**
zuul.routes.local.url=forward:/local

配置后,重启服务访问 http://localhost:2103/rest/api/1 自动跳转本地的local的接口中。

五、Zuul过滤器

1. 过滤器的类型

  • pre: 可以在请求被路由之前调用,适用于身份认证的场景,认证通过后再继续执行下面的流程。
  • route:在路由请求时被调用,适用于灰度发布场景,在将要路由的时候可以做一些自定义的逻辑。
  • post:在route 个error 过滤之后被调用,这种过滤器将请求路由到达具体的服务之后执行,适用于需要添加响应头、记录响应日志等应用场景。
  • error:处理请求时发生错误时被调用,在执行过程中发送错误时会进入error过滤器,可以用来统一记录错误信息。

整个执行顺序是,请求发过来首先到 pre 过滤器,然后到 routing 过滤器 最后到post 过滤器,任何一个过滤器有异常都会进入到 error过滤器。

2. 过滤器的使用

过滤器的创建

public class IpFilter extends ZuulFilter {

    private List<String> blackIpList = Arrays.asList("127.0.0.1");

    public IpFilter() {
        super();
    }

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    //true为过期生效 false 为过滤器不执行
    @Override
    public boolean shouldFilter() {
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        String ip = getIpAddr(ctx.getRequest());
        if(StringUtils.isNotBlank(ip) && blackIpList.contains(ip)){
            //禁止转发
            ctx.setSendZuulResponse(false);
            ctx.setResponseBody("非法请求");
            //禁止本地转发
            ctx.set("sendForwardFilter.ran",true);
            ctx.getResponse().setContentType("application/json;charset=utf-8");
            //过滤器中传递数据
            ctx.set("msg","hello---------");
            ctx.set("isSuccess" ,true);
            return null;
        }
        return null;
    }

}

说明:

  • shouldFilter:是否执行该过滤器,true 为执行,false 为不执行,这个也可以利用配置中心的配置来实现,达到动态的开启和关闭过滤器。filterType:过滤器类型,可选值有pre、route、post、error。
  • filterOrder:过滤器执行的顺序,数值越小,优先级越高。
  • run:执行自己的业务逻辑。

过滤器定义完成之后需要配置过滤器才能生效。

@Configuration
public class FilterConfig {

    @Bean
    public IpFilter ipFilter(){
        return new IpFilter();
    }

}

过滤器中的异常处理

ublic class ErrorFilter extends ZuulFilter {

    private Logger logger = LoggerFactory.getLogger(ErrorFilter.class);


    public ErrorFilter() {
        super();
    }

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        Throwable throwable = ctx.getThrowable();
        logger.error("Filter Error :{}",throwable.getCause().getMessage());
        return null;
    }
}
@Configuration
public class FilterConfig {

    @Bean
    public ErrorFilter errorFilter(){
        return new ErrorFilter();
    }
}

再创建一个error过滤器之后,我们需要在其他额过滤器中制造一个异常,我选择在 上面的pre的过滤器中 加入下面的代码制造一个异常

 System.out.println(2/0);

重启应用访问接口时会出现下图所示的界面

 我们可以自定义异常的返回格式

@RestController
public class ErrorHandlerController implements ErrorController {

    private ErrorAttributes errorAttributes;

    @Autowired
    public ErrorHandlerController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }


    @Override
    public String getErrorPath() {
        return "/error";
    }

    @GetMapping("/error")
    public String error(HttpServletRequest request){
        Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
        String message = (String) errorAttributes.get("message");
        String trace = (String) errorAttributes.get("trace");
        if(StringUtils.isNotBlank(trace)){
            message += String.format("and trace %s",trace);
        }
        return message;
    }
}

再次重启应用,访问同样的接口,返回的格式如下:

Zuul的容错机制

 引入依赖

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

配置文件添加如下配置:

#开启重试
zuul.retryable=true
#请求连接的超时时间
ribbon.connectTimeOut=500
#请求处理的超时时间
ribbon.readTimeOut=5000
#对当前实例的重试次数
ribbon.maxAutoRetries=1
#切换实例的最大重试次数
#ribbon.maxAutoRetriesNextServer=3
#对所有操作请求都进行重试
ribbon.okToRetryOnAllOperations=true
#对制定的Http响应码进行重试
ribbon.retryableStatusCode=500,404,502

Zuul回退机制

在springcloud中Zuul默认整合了Hystrix,当后端服务异常时可以为 Zuul 添加回退功能,返回默认的数据给客户端。

实现回退机制需要实现  FallbackProvider 接口,具体代码如下:

@Component
public class ServiceConsumerFallbackProvider implements FallbackProvider {

    private Logger logger = LoggerFactory.getLogger(ServiceConsumerFallbackProvider.class);
    @Override
    public String getRoute() {
        return "*";
    }

    @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 this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                if(null != cause){
                    logger.error("",cause.getCause());
                }
                RequestContext ctx = RequestContext.getCurrentContext();
                return new ByteArrayInputStream("服务器内部错误".getBytes());
            }

            @Override
            public HttpHeaders  getHeaders() {
                MediaType mediaType = new MediaType("application","json", Charset.forName("UTF-8"));
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }
        };
    }
}

getRoute 方法中法返回 * 代表对所有服务进行回退操作,如果只想对某个服务进行回退操作,那么就返回需要回退的服务名称,这个名称一定要是注册到 Eureka 中的名称。

通过 ClientHttpResponse 构造回退的内容,通过getStatusCode 返回响应的状态码,通过getStatusText 返回响应状态码对应的文本,通过getBody返回回退的内容。通过getHeaders 返回响应的请求头信息。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章