Spring Cloud 实操笔记 - 服务网关zuul

一、为什么需要服务网关

  1. 简化客户端调用,否则客户端需要集成eureka,知道哪些服务是可用的。
  2. 数据裁剪与聚合,用户只关心需要的数据。
  3. 多渠道支持,每个端需要的数据不一样,感觉和第二点有点像
  4. 遗留系统的服务化改造 - 渐进式

二、添加依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>
        <!-- Eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
        <!-- Eureka客户端-->

三、配置

spring.application.name=gateway-service-zuul
server.port=8888

#方式一:硬编码
zuul.routes.hello.path=/hello/**
zuul.routes.hello.url=http://localhost:9000/

#方式二:注册中心配置,灵活、高可用
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/

zuul.routes.api-a.path=/producer/**
zuul.routes.api-a.serviceId=spring-cloud-producer

方式一:采用硬编码,类似于nginx的反向代理,无需Eureka支持。无法实现灵活性和高可用
在这里插入图片描述

方式二:采用服务化方式,可以实现灵活性和高可用,负载均衡
在这里插入图片描述

方式三:不配置path和serviceId,采用默认的方式,直接路径上接serviceId
在这里插入图片描述

四、Zuul网关 Filter

比如实现权限验证

  1. 配置
zuul.FormBodyWrapperFilter.pre.disable: true
  1. 自定义Filter
package com.geebox.gateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

public class TokenFilter extends ZuulFilter {
    
    

    private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    @Override
    public String filterType() {
    
    
        return "pre"; // 可以在请求被路由之前调用
    }

    @Override
    public int filterOrder() {
    
    
        return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
    }

    @Override
    public boolean shouldFilter() {
    
    
        return true;// 是否执行该过滤器,此处为true,说明需要过滤
    }

    @Override
    public Object run() {
    
    
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("token");// 获取请求的参数

        if (StringUtils.isNotBlank(token)) {
    
    
            ctx.setSendZuulResponse(true); //对请求进行路由
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);
            return null;
        } else {
    
    
            ctx.setSendZuulResponse(false); //不对其进行路由
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess", false);
            return null;
        }
    }

}
  1. 注入
package com.geebox.gateway.config;

import com.geebox.gateway.filter.TokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Common {
    
    
    @Bean
    public TokenFilter tokenFilter() {
    
    
        return new TokenFilter();
    }
}

  1. 测试
    在这里插入图片描述

在这里插入图片描述

五、路由熔断

当服务出现异常时,打印相关异常信息,并返回”The service is unavailable.”

  1. 创建熔断的处理类
package com.geebox.gateway.fallback;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 ProducerFallback implements FallbackProvider {
    
    
    private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);

    //指定要处理的 service。
    @Override
    public String getRoute() {
    
    
        return "spring-cloud-producer";
    }

    public ClientHttpResponse fallbackResponse() {
    
    
        return new ClientHttpResponse() {
    
    
            @Override
            public HttpStatus getStatusCode() throws IOException {
    
    
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
    
    
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
    
    
                return "OK";
            }

            @Override
            public void close() {
    
    

            }

            @Override
            public InputStream getBody() throws IOException {
    
    
                return new ByteArrayInputStream("The service is unavailable.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
    
    
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
    
    
        if (cause != null && cause.getCause() != null) {
    
    
            String reason = cause.getCause().getMessage();
            logger.info("route:{}, Excption {}", route, reason);
        }
        return fallbackResponse();
    }
}
  1. 测试
    在这里插入图片描述
    Zuul 目前只支持服务级别的熔断,不支持具体到某个URL进行熔断。

六、zuul路由重试

需要改进网关服务

  1. pom
<!--重试机制-->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.2.5.RELEASE</version>
        </dependency>
        <!--重试机制-->
  1. 配置
#开启Zuul Retry
zuul.retryable=true
#对当前服务的重试次数
ribbon.MaxAutoRetries=2
#切换实例的重试次数
ribbon.MaxAutoRetriesNextServer=0

根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),如果依然不行,返回失败信息。

  1. 为了测试方便,修改内部服务,加入日志
package com.geebox.producer.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    

    @RequestMapping("/hello")
    public String index(@RequestParam String name) {
    
    
        Logger logger = LoggerFactory.getLogger(HelloController.class);
        logger.info("this is index function in Hello");
        try{
    
    
            Thread.sleep(1000000);
        }catch (Exception e){
    
    
            logger.error(e.getMessage());
        }
        return "hello "+name+",this is first messge from producer2";
    }
}
  1. 测试
    在这里插入图片描述
    日志打印三次,因为第一次是正常请求,第二、三次是重试。
    在这里插入图片描述
    Note:
    开启重试在某些情况下是有问题的,比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。

说到底,断路器的其中一个作用就是防止故障或者压力扩散。

用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。

不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。

七、zuul网关的高可用保证

搭建多个相同的zuul网关实例(可能端口不一样)来保证高可用,前端通过NGINX或者F5来做请求转发
在这里插入图片描述
不同的客户端使用不同的负载将请求分发到后端的Zuul,Zuul在通过Eureka调用后端服务,最后对外输出。因此为了保证Zuul的高可用性,前端可以同时启动多个Zuul实例进行负载,在Zuul的前端使用Nginx或者F5进行负载转发以达到高可用性。

おすすめ

転載: blog.csdn.net/hudmhacker/article/details/107696226