Spring Cloud Zuul 构建微服务网关

为何要有服务网关?在使用微服务架构时,一个客户端的业务需求可能会调用多个服务的接口,例如一次购物,须要调用商品服务,下单的时候要调用订单服务(服务划分更细的话还要调用更多的微服务)。客户端直接跟各个服务通讯会有一些问题或者说弊端,客户端的调用会复杂,更重要的是会有跨域请求问题和复杂的权限控制认证java

为了对外服务的安全性,不得不在原有的服务接口上作有关权限控制的校验逻辑,而这些权限相关的逻辑应该要把它从各个服务中抽离出来,做为外部调用和各个服务之间的负载均衡器。服务网关就是这样一个角色,对外统一 Rest API 接口,对内服务路由、负载均衡,同时还提供身份认证安全和监控功能git

Zuul

Zuul 是 Netflix 开源的服务网关,它的核心就是一系类的过滤器,经过一系列的过滤器在请求的各个阶段进行处理,具体的路由器功能见 Spring Cloud Zuul 过滤器github

准备

这里使用 Eureka Server 做为服务注册中心web

spring:
  application:
    name: eureka-server
server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

另外准备一个服务 product-servicespring

spring:
  application:
    name: product-service
server:
  port: 8071
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

构建 Zuul 微服务网关

建立 Spring Boot 工程命名为 zuul-gateway 做为服务网关,添加以下依赖json

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

在主类上添加注解 @EnableZuulProxy 开启 Zuul 代理,这个代理会使用 Ribbon 获取注册服务的实例,同时还整合了 Hystrix 实现容错,全部请求都会在 Hystrix 命令中执行。api

@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayZuulApplication.class, args);
    }
}
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

配置信息,添加 eureka 注册中心的地址跨域

spring:
  application:
    name: gateway-zuul
server:
  port: 8090
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

到这里一个简单的服务网关就完成了,只是简单吧把网关服务注册到 Eureka,Spring Cloud Zuul 整合了 Eureka,会提供默认的服务路由功能,默认状况下,Zuul 会代理全部注册到 Eureka Server 上的微服务安全

gateway-zuul 的请求 /{serviceId}/** 会转发到对应注册在 Eureka Server 上对应 serviceId 的服务 /**
启动 eureka-server、product-service、zuul-gateway,而后访问 http://localhost:8090/product-service/product/1 ,该请求会被转发到 product-service 的 /product/1架构

另外想要跳过一些服务能够设置 zuul.ignoredServices,要得到对路由的更细粒度控制,能够单独指定路径和 serviceId

zuul:
  ignoredServices: '*'
  routes:
    product:
      path: /product/**
      serviceId: product-service

上面设置的是除 product 服务以外的服务都忽略,而访问 product-service 服务的路径为 /product/**

参考代码见:demo

Zuul 路由端点

@EnableZuulProxy 注解配合 Spring Boot Actuator,Zuul 会暴露额外的两个管理端点:RoutesFilters。分别是关于路由和过滤器的端点(过滤器的端点在这里介绍 Spring Cloud Zuul 过滤器

spring-cloud-starter-netflix-zuul 已经依赖了 spring-boot-starter-actuator,因此上面的工程已经包含了路由管理的功能。关于路由端点的路径为 /routes/routes/details

访问路径 http://localhost:8090/actuator/routes

{
    "/product/**": "product-service"
}

访问路径 http://localhost:8090/actuator/routes/details,能够查看路由的详细信息

{
    "/product/**": {
        "id": "product",
        "fullPath": "/product/**",
        "location": "product-service",
  		"path": "/**",
        "prefix": "/product",
        "retryable": false,
        "customSensitiveHeaders": false,
        "prefixStripped": true
    }
}

访问404是由于没有暴露端点,能够设置 management.endpoints.web.exposure.include: '*'

路由配置

默认状况下,Zuul 网关会代理全部注册到 Eureka Server 上的服务,但咱们能够经过配置来让其只代理其中一部分的服务或者是本身控制 URL。Zuul 的路由配置很是的灵活

自定义访问路径。zuul.routes.{serviceId}={costomUrl}

zuul:
  routes:
    product-service: /product/**

忽略指定服务,多个用逗号隔开,忽略所有用 *

zuul:
  ignored-services: product-service1,product-service2
zuul:
  ignored-services: '*'
  routes:
    product-service: /product/**
# 忽略全部服务,只路由 product-service

同时指定微服务的 serviceId 和对应路径

zuul:
  ignoredServices: '*'
  routes:
    product:  # 只是一个路由名称
      service-id: product-service
      path: /product/**

同时指定地址和访问路径

zuul:
  ignoredServices: '*'
  routes:
    product:
      url: http://localhost:8081/
      path: /product/**

添加路由前缀

zuul:
  perfix: /api
  strip-perfix: false
  routes:
    product-service: /product/**
# /api/product/**  -> /api/**
zuul:
  routes:
    product-service: /product/**
    strip-perfix: false
# /product/**  -> /product/**

忽略某些路径

zuul:
  ignoredPatterns: /**/admin/** #忽略全部包含 /admin/ 的路径
  routes:
    product-service: /product/**

Zuul 的容错和回退

在 Spring Cloud 中,Zuul 已经默认整合了 Hystrix,关于 Hystrix 介绍和监控面板能够见 Spring Cloud 断路器 Hystrix。咱们使用上面的例子

由上图能够看出,Zuul 的 Hystrix 监控粒度为服务,而不是接口。

接下来咱们能够关闭服务 product-service,而后再访问接口 http://localhost:8090/product/product/1

为 Zuul 添加回退

为 Zuul 添加回退,须要实现 FallbackProvider 接口,须要指定回退用于的路由ID,并提供一个 ClientHttpResponse 做为回退返回

public class ProductFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "product-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return status;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return status.value();
            }

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

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

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

若是您想为全部路由提供默认的回退,您能够建立一个类型为 FallbackProvider 的bean,并让 getRoute 方法返回 *null

重启服务网关,访问接口 http://localhost:8090/product/product/1