转载

一起来学SpringCloud之 - 路由网关(Zuul)

上一篇已经讲了微服务组件中的分布式配置中心,本章讲述 由JAVA编写的服务路由网关 Zuul

- Zuul

路由是微服务体系结构的一个组成部分。例如 / 可以映射到您的Web应用程序,/api/users映射到用户服务,/api/shop映射到商店服务。Zuul是Netflix的基于JVM的开发的路由和服务器端负载均衡器。

为什么需要服务网关?

如果没有服务网关,多个服务提供给前端调用地址管理错综复杂,增加了客户端的复杂性,认证也相对麻烦,每个服务都需要编写相同的认证….

一起来学SpringCloud之 - 路由网关(Zuul)

画图工具: https://www.processon.com/

Zuul 可以做什么?

  • 身份认证
  • 审查与监控
  • 压力测试
  • 金丝雀测试
  • 动态路由
  • 服务迁移
  • 负载分配
  • 安全
  • 静态响应处理
  • 主动/主动流量管理

Zuul的规则引擎允许基本上写任何JVM语言的规则和过滤器,内置Java和Groovy的支持。

注意:配置属性 zuul.max.host.connections 已被取代的两个新的属性, zuul.host.maxTotalConnections 并且 zuul.host.maxPerRouteConnections 它的缺省值分别200和20。

注意:所有路线的默认Hystrix隔离模式(ExecutionIsolationStrategy)为 SEMAPHOREzuul.ribbonIsolationStrategy 如果此隔离模式是首选,可以更改为 THREAD

官方文档: http://cloud.spring.io/spring-cloud-static/Dalston.SR2/#_router_and_filter_zuul

- 准备工作

1.启动Consul

2.创建 battcn-gateway-servicebattcn-hello-service

- battcn-gateway-service

1.在项目 pom.xml 中添加 ZUUL 依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.App.java中开启 zuul 代理

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

3. zuul.routes.routesName.ZuulProperties.ZuulRoute 配置的方式,详细可以参考源码 org.springframework.cloud.netflix.zuul.filters.ZuulProperties

server:
  port: 9000

zuul:
  routes:
    hello-service:    #自定义的 路由名称
      path: /api-hello/**   #路由路径
      serviceId: battcn-hello-service #VIP 模式中的 serviceId

spring:
  application:
    name: battcn-gateway-service
  cloud:
    consul:
      host: localhost
      port: 8500
      enabled: true
      discovery:
        enabled: true
        prefer-ip-address: true

- battcn-hello-service

hello-service 只需要以下依赖,能注册到consul中就行(单纯的一个服务)

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
@EnableDiscoveryClient
@SpringBootApplication
public class HelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}
server:
  port: 9001

spring:
  application:
    name: battcn-hello-service
  cloud:
    consul:
      host: localhost
      port: 8500
      enabled: true
      discovery:
        enabled: true
        prefer-ip-address: true

- 测试一把

访问: http://localhost:9000/api-hello/hello 从地址中可以看出我们访问的是 battcn-gateway-service ,且满足 zuul.routes.routesName.path 规则,因此实际请求地址是 http://localhost:9001/hello

hello:battcn-hello-service        #表示请求成功

- 自定义过滤器

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 *  网关认证过滤器(Demo演示,实际根据自身业务考虑实现)
 * @author Levin
 * @date 2017-08-14.
 */
@Component
public class GatewayZuulFilter extends ZuulFilter {

    /**
     * per:路由之前,如实现认证、记录调试信息等
     * routing:路由时
     * post:路由后,比如添加HTTP Header
     * error:发生错误时调用
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 过滤器顺序,类似@Filter中的order
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * 这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。
     */
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        if(token == null) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(404);
            ctx.setResponseBody("token cannot be empty");
        }
        return null;
    }
}

实际开发中 token 应该从请求头中获取,该处只是为了方便演示效果

访问: http://localhost:9000/api-hello/hello?token=1

hello:battcn-hello-service

访问: http://localhost:9000/api-hello/hello

token cannot be empty

- 路由映射流程

图不是很严谨,凑合着看看吧…

一起来学SpringCloud之 - 路由网关(Zuul)

初次启动项目我们可以看到

2017-08-14 17:19:06.090  INFO 11544 --- [nio-9000-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/api-hello/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2017-08-14 17:19:06.090  INFO 11544 --- [nio-9000-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/battcn-gateway-service/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2017-08-14 17:19:06.090  INFO 11544 --- [nio-9000-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/battcn-hello-service/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
2017-08-14 17:19:06.090  INFO 11544 --- [nio-9000-exec-1] o.s.c.n.zuul.web.ZuulHandlerMapping      : Mapped URL path [/consul/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]

有兴趣的还可以阅读下 com.netflix.loadbalancer.LoadBalancerContext ,通过它可以发现每次请求的IP和端口,需要在 application.yml 中配置

logging:
  level:
    com.netflix: DEBUG
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.zuul.http.HttpServletRequestWrapper  : Path = null
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.zuul.http.HttpServletRequestWrapper  : Transfer-Encoding = null
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.zuul.http.HttpServletRequestWrapper  : Content-Encoding = null
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.zuul.http.HttpServletRequestWrapper  : Content-Length header = -1
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.loadbalancer.ZoneAwareLoadBalancer   : Zone aware logic disabled or there is only one zone
2017-08-14 17:32:16.793 DEBUG 14316 --- [nio-9000-exec-7] c.n.loadbalancer.LoadBalancerContext     : battcn-hello-service using LB returned Server: 192.168.31.221:9001 for request /hello

- Zuul Fallback

定义 GatewayZuulFallback 实现 ZuulFallbackProvider 接口

package com.battcn.config;

import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
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;

/**
 * @author Levin
 * @date 2017-08-14.
 */
@Component
public class GatewayZuulFallback implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "battcn-hello-service";//指定回退的服务名
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR; //INTERNAL_SERVER_ERROR(500, "Internal Server Error")
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();// 500
            }

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

            @Override
            public void close() {}

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(getStatusText().getBytes());//也可以随意写自己想返回的内容
            }

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

- 路由配置详解

zuul 相关的官方文档还是比较其全了,本文也是参考官方然后简单讲述应用场景,具体开发请结合自身业务扩展….

1.自定义指定微服务路径,以下两种配置方式结果相同

zuul:
  routes:
    battcn-hello-service: /api-hello/**
#两种配置方式结果相同
zuul:
  routes:
    hello-service: 
      path: /api-hello/**
      serviceId: battcn-hello-service

2.忽略指定服务,多个服务逗号分隔, Set<String> ignoredServices 这样一来zuul就会忽略service1和service2,只会代理其它的

zuul:
  ignored-services: battcn-hello-service1,battcn-hello-service2

3.指定Path和Url

zuul:
  routes:
    hello-service:
      path: /api-hello/**   #路由路径
      url: http://localhost:9001/   #指定URL地址

4.使用Zuul但不指定Url(该方式在Eureka有效,Consul还未找到解决方法…)

zuul:
  routes:
    battcn-hello-service: /api-hello/**
ribbon:
  eureka:
    enabled: false    #为Ribbon禁用 eureka,不会破坏Zuul的Hystrix和Ribbon特性
battcn-hello-service:
  ribbon:
    listOfServers: localhost:9001,localhost:9002

- 说点什么

本章代码(battcn-gateway-service/battcn-hello-service): https://git.oschina.net/battcn/battcn-cloud/tree/master/battcn-cloud-zuul

如有问题请及时与我联系

1.个人QQ:1837307557

2.Spring Cloud中国社区①:415028731

3.Spring For All 社区⑤:157525002

转载标明出处,thanks

谢谢你请我吃糖果

一起来学SpringCloud之 - 路由网关(Zuul) 支付宝

一起来学SpringCloud之 - 路由网关(Zuul) 微信

原文  http://blog.battcn.com/2017/08/14/spring-cloud-zuul/
正文到此结束
Loading...