Spring Boot 使用 Zuul 开发 API Gateway

简介: Spring Boot 使用 Zuul 开发 API Gateway《Spring Boot 实战开发》—— 基于 Gradle + Kotlin的企业级应用开发最佳实践Spring Boot 是构建单个微服务应用的理想选择,但是我们还需要以某种方式将它们互相联系起来。

Spring Boot 使用 Zuul 开发 API Gateway

《Spring Boot 实战开发》—— 基于 Gradle + Kotlin的企业级应用开发最佳实践

Spring Boot 是构建单个微服务应用的理想选择,但是我们还需要以某种方式将它们互相联系起来。这就是 Spring Cloud Netflix 所要解决的问题。Netflix 它提供了各种组件,比如:Eureka服务发现与Ribbon客户端负载均衡的结合,为内部“微服务”提供通信支持。
本章介绍如何通过使用 Netflix Zuul 实现一个微服务API Gateway 来实现简单代理转发和过滤器功能。

1.1 API Gateway简介

API Gateway 是随着微服务(Microservice)这个概念一起兴起的一种架构模式,它用于解决微服务过于分散,没有一个统一的出入口进行流量管理的问题。

不同的微服务一般有不同的网络域名(或 IP地址),而通常情况下,在大规模分布式架构系统中,外部的客户端可能需要调用多个服务的接口才能完成一个业务逻辑。比如,在京东、淘宝上下单购买一个商品的场景,通常会去商品数据服务、订单服务、支付服务等。如果客户端直接单独和这些微服务进行通信,可能会存在诸如如下的问题:

 客户端会多次请求不同微服务,增加客户端的复杂性
 存在跨域请求,在一定场景下处理相对复杂
 认证复杂,每一个服务都需要独立认证

诸如上述问题,我们可以引入一个中间代理层—— API Gateway 来解决。API Gateway 是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过微服务网关,架构图如下:

这样客户端只需要和API Gateway交互,而无需单独去调用特定微服务的接口,而且方便监控,易于认证,减少客户端和各个微服务之间的交互次数。
1.2 Zuul 简介
对于 API Gateway,常见的选型有:
 基于 Openresty 的 Kong
 基于 Go 的 Tyk
 基于 Java 的 Zuul
常规的选择我们会使用Nginx作为代理。但是Netflix带来了它自己的解决方案——智能路由Zuul。它带有许多有趣的功能,它可以用于身份验证、服务迁移、分级卸载以及各种动态路由选项。同时,它是使用Java编写的。

Zuul是Netflix开源的微服务网关,可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

Netflix Zuul 中提供了服务发现 (Eureka), Circuit Breaker (Hystrix), 智能路由 (Zuul) 和客户端负载均衡 (Ribbon) 等功能。

Zuul可以简单理解为一个类似于 Servlet 中过滤器(Filter)的概念。和大部分基于Java的Web应用类似,Zuul也采用了servlet架构,因此Zuul处理每个请求的方式是针对每个请求是用一个线程来处理。通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。这样的设计方式,足以应付一般的高并发场景。Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。
1.3 Zuul核心组件
Zuul的核心组件是一系列的过滤器,它们可以完成以下功能:
 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。
 审计和监控:实现对 API 调用过程的审计和监控,追踪有意义数据及统计结果,从而为我们带来准确的生产状态数据。
 动态路由:动态将请求路由到不同后端集群。
 压力测试:逐渐增加指向集群的流量,以了解系统的性能。
 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
 静态响应处理:边缘位置进行响应,避免转发到内部集群。
 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化。

Zuul 提供了四种过滤器的 API,分别为前置(pre)、后置(post)、路由(route)和错误(error)四种处理方式。其生命周期如下图所示

一个请求会先按顺序通过所有的前置过滤器,之后在路由过滤器中转发给后端应用,得到响应后又会通过所有的后置过滤器,最后响应给客户端。在整个流程中如果发生了异常则会跳转到错误过滤器中。

一般来说,如果需要在请求到达后端应用前就进行处理的话,会选择前置过滤器,例如鉴权、请求转发、增加请求参数等行为。在请求完成后需要处理的操作放在后置过滤器中完成,例如统计返回值和调用时间、记录日志、增加跨域头等行为。路由过滤器一般只需要选择 Zuul 中内置的即可,错误过滤器一般只需要一个,这样可以在 Gateway 遇到错误逻辑时直接抛出异常中断流程,并直接统一处理返回结果。

Spring Cloud 对 Zuul 进行了整合和增强。目前,Zuul使用的默认是Apache的HTTP Client。也可以通过设置ribbon.restclient.enabled=true 来使用Rest Client。在 Zuul 中,每一个后端应用都称为一个 Route,为了避免一个 Route 抢占了太多资源影响到其他 Route 的情况出现,Zuul 使用 Hystrix 对每一个 Route 都做了隔离和限流。
提示:更多关于 Zuul 的内容参考 https://github.com/Netflix/zuul

1.4 项目实战
本节介绍如何使用Spring Boot 集成 Zuul 来实现 API Gateway。
1.创建项目
首先我们来创建基于 Kotlin、Gradle 的 Spring Boot 项目。使用的Kotlin、Spring Boot、Spring Cloud的版本号分别配置如下

buildscript {
  ext {
    kotlinVersion = '1.2.20'
    springBootVersion = '2.0.1.RELEASE'
  }

  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
    classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
  }
}
...
ext {
  springCloudVersion = 'Finchley.M9'
}
...
dependencyManagement {
  imports {
    mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
  }
}

2.添加Zuul依赖

通常Zuul需要注册到Eureka上以实现高可用。这里我们为了简单演示,只实现一个简单单机版的 API Gateway。在 build.gradle 中添加 spring-cloud-starter-netflix-zuul 如下

repositories {
  mavenCentral()
  maven { url "https://repo.spring.io/milestone" }
}

ext {
  springCloudVersion = 'Finchley.M9'
}

dependencies {
  compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
  ...
}

dependencyManagement {
  imports {
    mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
  }
}

3.启用 Zuul 代理
在 Spring Boot 启动类上添加注解 @EnableZuulProxy,代码如下

@SpringBootApplication
@EnableZuulProxy
open class DemoZuulApplication

fun main(args: Array<String>) {
    runApplication<DemoZuulApplication>(*args)
}

@EnableZuulProxy简单理解为@EnableZuulServer的增强版,当Zuul与Eureka、Ribbon等组件配合使用时,我们使用@EnableZuulProxy。@EnableZuulProxy注解默认加上了@EnableCircuitBreaker。它的定义如下

@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy

4.配置application.properties
在application.properties中添加zuul.routes.*.url 配置如下

zuul.routes.book_api.url=http://127.0.0.1:9000
zuul.routes.user_api.url=http://127.0.0.1:9001
server.port=8000

其中, book_api 是微服务 Book 的服务 API 地址标识,user_api 是微服务 User 的服务 API 地址标识。这个请求流程可以简单如下图所示

5.启动测试微服务应用
分别启动我们的测试应用demo_microservice_api_book和demo_microservice_api_user。先测试一下,保证服务自身可用。
访问http://localhost:9000/book/1,输出

{
  "id": 1,
  "title": "Spring Boot 2.0 极简教程",
  "author": "陈光剑"
}

访问http://localhost:9001/user/1,输出

{
  "id": 1,
  "username": "user",
  "password": "123456"
}

6.启动 API Gateway 服务
运行demo_zuul应用,访问http://127.0.0.1:8000/user_api/user/1,可以得到输出

{
  "id": 1,
  "username": "user",
  "password": "123456"
}

访问http://127.0.0.1:8000/book_api/book/1,可以得到输出

{
  "id": 1,
  "title": "Spring Boot 2.0 极简教程",
  "author": "陈光剑"
}

这样我们就实现了一个简单的 API Gateway。

7.编写Zuul过滤器
下面我们在API Gateway实现一个简单的请求跟踪过滤器SimpleFilter。只需要继承抽象类ZuulFilter过滤器即可,让该过滤器打印请求日志。实现代码如下

package com.easy.springboot.demo_zuul

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

@Component
class SimpleFilter : ZuulFilter() {
    private val log = LoggerFactory.getLogger(SimpleFilter::class.java)
    override fun run(): Any? {
        val ctx = RequestContext.getCurrentContext()
        val request = ctx.request

        log.info(String.format("%s request to %s", request.method, request.requestURL.toString()))
        log.info(String.format("LocalAddr: %s", request.localAddr))
        log.info(String.format("LocalName: %s", request.localName))
        log.info(String.format("LocalPort: %s", request.localPort))

        log.info(String.format("RemoteAddr: %s", request.remoteAddr))
        log.info(String.format("RemoteHost: %s", request.remoteHost))
        log.info(String.format("RemotePort: %s", request.remotePort))

        return null
    }

    override fun shouldFilter(): Boolean {
        // 判断是否需要过滤
        return true
    }

    override fun filterType(): String {
        // 过滤器类型
        return "pre"
    }

    override fun filterOrder(): Int {
        // 过滤器的优先级,越大越靠后执行
        return 1
    }

}

其中,fun filterType()指定过滤器类型为"pre" 。下面是 Zuul 提供的几种标准的过滤器类型:
 pre:这种过滤器在请求到达Origin Server之前调用。比如身份验证,在集群中选择请求的Origin Server,记log等。
 route:在这种过滤器中把用户请求发送给Origin Server。发送给Origin Server的用户请求在这类过滤器中build。并使用Apache HttpClient或者Netfilx Ribbon发送给Origin Server。
 post:这种过滤器在用户请求从Origin Server返回以后执行。比如在返回的response上面加response header,做各种统计等。并在该过滤器中把response返回给客户。
 error:在其他阶段发生错误时执行该过滤器。

这些过滤器的核心处理逻辑在ZuulServlet类中。关键代码说明如下

public class ZuulServlet extends HttpServlet {
    ...
    @Override
    public void service(...) throws ServletException, IOException {
        try {
            // 初始化请求响应对象
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            ...
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                preRoute();// "pre"过滤器
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                route();// "route" 过滤器
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                postRoute();// "post"过滤器
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            // "error" 过滤器
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
        ...
    }
}

另外, Zuul 还可以通过调用 FilterProcessor.runFilters (类型) 来创建或添加并运行任何 filterType。关于自定义静态响应的"静态"类型, 请参见 StaticResponseFilter类。

8.测试SimpleFilter过滤器效果
重启应用,再次分别请求 http://127.0.0.1:8000/user_api/user/1http://127.0.0.1:8000/book_api/book/1,我们可以在API Gateway 应用的控制台后端看到类似下面的请求日志

GET request to http://127.0.0.1:8000/user_api/user/1
LocalAddr: 127.0.0.1
LocalName: localhost
LocalPort: 8000
RemoteAddr: 127.0.0.1
RemoteHost: 127.0.0.1
RemotePort: 61747
GET request to http://127.0.0.1:8000/book_api/book/1
LocalAddr: 127.0.0.1
LocalName: localhost
LocalPort: 8000
RemoteAddr: 127.0.0.1
RemoteHost: 127.0.0.1
RemotePort: 61747

提示:API Gateway 工程源代码:https://github.com/EasySpringBoot/demo_zuul
Book 微服务工程源代码:https://github.com/EasySpringBoot/demo_microservice_api_book
User 微服务工程源代码:https://github.com/EasySpringBoot/demo_microservice_api_user

1.5 本章小结
使用API Gateway 我们将"1对N"问题 转换成了"1对1”问题,同时在请求到达真正的服务之前,可以做一些预处理工作。API Gateway 的可以完成诸如鉴权、流量控制、系统监控、页面缓存等功能。使用 Spring Boot 加上 Spring Cloud “全家桶”来实现微服务架构无疑是一种相当不错的选择。

相关文章
|
4天前
|
安全 Java API
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(上)
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)
22 0
第7章 Spring Security 的 REST API 与微服务安全(2024 最新版)(上)
|
1月前
|
SpringCloudAlibaba Java 网络架构
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(七)Spring Cloud Gateway服务网关
90 0
|
11天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
20天前
|
前端开发 Java API
构建RESTful API:Java中的RESTful服务开发
【4月更文挑战第3天】本文介绍了在Java环境中构建RESTful API的重要性及方法。遵循REST原则,利用HTTP方法处理资源,实现CRUD操作。在Java中,常用框架如Spring MVC简化了RESTful服务开发,包括定义资源、设计表示层、实现CRUD、考虑安全性、文档和测试。通过Spring MVC示例展示了创建RESTful服务的步骤,强调了其在现代Web服务开发中的关键角色,有助于提升互操作性和用户体验。
构建RESTful API:Java中的RESTful服务开发
|
25天前
|
机器学习/深度学习 前端开发 API
实现以图搜货功能,淘宝API开发实战分享
实现以图搜货功能,淘宝API开发实战分享
24 0
|
1月前
|
缓存 前端开发 API
构建高效可扩展的RESTful API:后端开发的最佳实践
【2月更文挑战第30天】 在现代Web应用和服务端架构中,RESTful API已成为连接前端与后端、实现服务间通信的重要接口。本文将探讨构建一个高效且可扩展的RESTful API的关键步骤和最佳实践,包括设计原则、性能优化、安全性考虑以及错误处理机制。通过这些实践,开发者可以确保API的健壮性、易用性和未来的可维护性。
|
1月前
|
Java API Maven
使用Java和Spring Boot构建RESTful API
使用Java和Spring Boot构建RESTful API
16 0
|
14天前
|
缓存 前端开发 API
API接口封装系列
API(Application Programming Interface)接口封装是将系统内部的功能封装成可复用的程序接口并向外部提供,以便其他系统调用和使用这些功能,通过这种方式实现系统之间的通信和协作。下面将介绍API接口封装的一些关键步骤和注意事项。
|
21天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。