Java之 Spring Cloud 微服务搭建网关 nginx,Zuul(第三个阶段)【一】【SpringBoot项目实现商品服务器端是调用】

x33g5p2x  于2021-12-02 转载在 Java  
字(8.8k)|赞(0)|评价(0)|浏览(564)

一、微服务网关概述

在学习完前面的知识后,微服务架构已经初具雏形。但还有一些问题:不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护。如下图:

如果让客户端直接与各个微服务通讯,可能会有很多问题:

  • 客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
  • 在某些场景下存在跨域请求的问题
  • 加大身份认证的难度,每个微服务需要独立认证
    因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可,这样简化了开发还有以下优点:
1、易于监控 
  2、易于认证 
  3、减少了客户端与各个微服务之间的交互次数

1、服务网关的概念

(1)什么是微服务网关

API网关是一个服务器,是系统对外的唯一入口。
API网关封装了系统内部架构,为每个客户端提供一个定制的API。

API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。

通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。

(2)作用和应用场景

网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。

2、常见的API网关实现方式

  • Kong
    基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
  • Zuul
    Netflix开源,功能丰富,使用JAVA开发,易于二次开发;需要运行在web容器中,如Tomcat。
    问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如
    Nginx;
  • Traefik
    Go语言开发;轻量易用;提供大多数的功能:服务路由,负载均衡等等;提供WebUI
    问题:二进制文件部署,二次开发难度大;UI更多的是监控,缺乏配置、管理能力;
    Spring Cloud Gateway
    SpringCloud提供的网关服务
  • Nginx+lua实现
    使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
    问题:自注册的问题和网关本身的扩展性

3、基于Nginx的网关实现

(1) Nginx介绍

(2) 正向/反向代理

1)正向代理

正向代理,“它代理的是客户端,代客户端发出请求”,是一个位于客户端和原始服务器(origin server)之间的服务器,

为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。

客户端必须要进行一些特别的设置才能使用正向代理。

2)反向代理

多个客户端给服务器发送的请求,Nginx服务器接收到之后,按照一定的规则分发给了后端的业务处理服务器进行处理了。

此时~请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,Nginx扮演的就是一个反向代理角色。

客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。因为客户端不需要任何配置就可以访问。

反向代理,“它代理的是服务端,代服务端接收请求”,主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息如果只是单纯的需要一个最基础的具备转发功能的网关,那么使用Ngnix是一个不错的选择。

(3) 准备工作

使用第一次的工程来练习项目
您可以直接下载并导入我的项目

源代码下载:https://download.csdn.net/download/qq_44757034/52708487

运行项目打开

访问:http://localhost:9002/order/buy/1

访问:http://localhost:9002/order/1

访问:http://localhost:9001/product/1

(4)配置Nginx的请求转发

location /api-product{
            proxy_pass http://127.0.0.1:9001/;
        }
        location /api-order  {
           proxy_pass http://127.0.0.1:9002/;
         }

(5)启动nginx进行测试

访问:http://localhost/api-product/product/1

http://localhost/api-order/order/1

访问:http://localhost/api-order/order/buy/1

二、微服务网关Zuul

1、 Zuul简介

ZUUL是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用,Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行了整合和增强。

Spring Cloud对Zuul进行了整合和增强

2、 搭建Zuul网关服务器(了解)

(1)创建工程导入依赖

在IDEA中创建ZUUL网关工程 zuul_server ,并添加响应依赖

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

(2)编写启动类

package cn.itbluebox.zuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
//开启Zuul
@EnableZuulProxy
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class,args);
    }
}

@EnableZuulProxy : 通过 @EnableZuulProxy 注解开启Zuul网管功能

(3)编写配置文件

创建配置文件 application.yml ,并添加相应配置

server:
  port: 8080 #服务端口
spring:
  application:
    name: server-zuul #指定服务名

3、 Zuul中的路由转发

最直观的理解:“路由”是指根据请求URL,将请求分配到对应的处理程序。在微服务体系中,Zuul负责接收所有的请求。

根据不同的URL匹配规则,将不同的请求转发到不同的微服务处理。

zuul:
	routes:
		product-service: # 这里是路由id,随意写
			path: /product-service/** # 这里是映射路径
			url: http://127.0.0.1:9001 # 映射路径对应的实际url地址

只需要在application.yml文件中配置路由规则即可:

  • product-service:配置路由id,可以随意取名
  • url:映射路径对应的实际url地址
  • path:配置映射路径,这里将所有请求前缀为/product-service/的请求,转发到http://127.0.0.1:9002处理

运行测试,运行测试前将nginx关闭掉

运行测试

访问:http://localhost:8080/product-service/product/1

(1) 面向服务的路由

微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。
如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。

1)添加Eureka客户端依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
2)开启Eureka客户端发现功能

@SpringBootApplication
//开启Zuul
@EnableZuulProxy
//eureka的服务发现
@EnableDiscoveryClient
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class,args);
    }
}
3)添加Eureka配置,获取服务信息,修改映射配置,通过服务名称获取

server:
  port: 8080 #服务端口
spring:
  application:
    name: server-zuul #指定服务名
zuul:
  routes:
    # 以商品微服务举例子
    product-service: # 这里是路由id,随意写
      path: /product-service/** # 这里是映射路径 localhost:8080/product-service/xxxx
      #url: http://127.0.0.1:9001 # 映射路径对应的实际url地址
      serviceId: service-product  # 配置转发的微服务的服务名称
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册
4)运行测试

访问:http://localhost:8080/product-service/product/1

(2) 简化的路由配置

在刚才的配置中,我们的规则是这样的:

  • zuul.routes.<route>.path=/xxx/** : 来指定映射路径。 是自定义的路由名
  • zuul.routes.<route>.serviceId=/product-service :来指定服务名。

而大多数情况下,我们的 路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的配置语法: zuul.routes.<serviceId>=<path>上面的配置可以简化为一条:

server:
  port: 8080 #服务端口
spring:
  application:
    name: server-zuul #指定服务名
zuul:
  routes:
    #如果路由id和对应微服务的serviceid一致的话
    service-product: /product-service/**
#配置Eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/
  instance:
    prefer-ip-address: true #使用ip地址注册

运行测试

访问:http://localhost:8080/product-service/product/1

(3)默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

  • 默认情况下,一切服务的映射路径就是服务名本身。

  • 例如服务名为: service-product ,则默认的映射路径就是: /service-product/**

# zuul中默认的路由配置
# 如果当前的微服务名称 service-order,默认的请求的映射路径/service-order/**

访问:http://localhost:8080/service-order/order/1

(4)Zuul加入后的架构

4、 Zuul中的过滤器

通过之前的学习,我们得知Zuul它包含了两个核心功能:对请求的路由和过滤。
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;
而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。其实,路由功能在真正运行时,它的路由映射和请求转发同样也由几个不同的过滤器完成的。
所以,过滤器可以说是Zuul实现API网关功能最为核心的部件,每一个进入Zuul的HTTP请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
那么接下来,我们重点学习的就是Zuul的第二个核心功能:过滤器。

(1)ZuulFilter简介

Zuul 中的过滤器跟我们之前使用的 javax.servlet.Filter 不一样,javax.servlet.Filter 只有一种类型,可以通过配置 urlPatterns 来拦截对应的请求。而 Zuul 中的过滤器总共有 4 种类型,且每种类型都有对应的使用场景。

  1. PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  2. ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  3. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
  4. ERROR:在其他阶段发生错误时执行该过滤器。Zuul提供了自定义过滤器的功能实现起来也十分简单,只需要编写一个类去实现zuul提供的接口。
  • 正常流程:

  • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。

  • 异常流程:

  • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。

  • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。

  • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

  • 不同过滤器的场景:

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了

  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。

  • 服务调用时长统计:pre和post结合使用。

  • ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法

  • shouldFilter :返回一个 Boolean 值,判断该过滤器是否需要执行。返回true执行,返回false不执行。

  • run :过滤器的具体业务逻辑。

  • filterType :返回字符串,代表过滤器的类型。包含以下4种:

  • pre :请求在被路由之前执行

  • routing :在路由请求时调用

  • post :在routing和errror过滤器之后调用

  • error :处理请求时发生错误调用

  • filterOrder :通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

package cn.itbluebox.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
/* 自定义的zull过滤器 继承抽象的父类 */
@Component
public class LoginFilter  extends ZuulFilter {
    //定义过滤器类型 pre routing post error
    @Override
    public String filterType() {
        return "pre";
    }
    //指定过滤器的执行顺序 返回值越小执行顺序越高
    @Override
    public int filterOrder() {
        return 1;
    }
    //当前过滤器是否生效 true :使用此过滤器 false:不使用此过滤器
    @Override
    public boolean shouldFilter() {
        return true;
    }
    //执行过滤器当中业务逻辑
    @Override
    public Object run() throws ZuulException {
        System.out.println("执行了过滤器!!!!!");
        return null;
    }
}

运行测试

访问测试:http://localhost:8080/service-order/order/1

查看控制台

(2)身份验证

完善LoginFilter

package cn.itbluebox.zuul.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/* 自定义的zull过滤器 继承抽象的父类 */
@Component
public class LoginFilter  extends ZuulFilter {
    //定义过滤器类型 pre routing post error
    @Override
    public String filterType() {
        return "pre";
    }
    //指定过滤器的执行顺序 返回值越小执行顺序越高
    @Override
    public int filterOrder() {
        return 1;
    }
    //当前过滤器是否生效 true :使用此过滤器 false:不使用此过滤器
    @Override
    public boolean shouldFilter() {
        return true;
    }
    //执行过滤器当中业务逻辑
    /* 身份认证: 1、所有的请求都需要携带参数:access-token 2、获取request请求 3、通过request获取参数:access-token 4、判断token是否为空 1 token == null : 身份验证失败 2 token != null : 执行后续操作 在Zuul网关当中,通过RequestContext的上下文对象,可以获取request对象 */
    @Override
    public Object run() throws ZuulException {
        System.out.println("执行了过滤器!!!!!");
        //1、获取Zuul提供上下文对象RequestContext
        RequestContext ctx = RequestContext.getCurrentContext();
        //2、从RequestContext当中获取request
        HttpServletRequest request = ctx.getRequest();
        //3、获取请求参数access
        String token = request.getParameter("access-token");
        //4、判断
        //4.1 如果token==null ,拦截请求,返回认证失败
        if(token == null){
            ctx.setSendZuulResponse(false);//拦截请求
            ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());//设置状态码
        }
        //4.2 如果token!=null ,继续后续操作
        return null;
    }
}

运行测试

不携带哦token直接访问:http://localhost:8080/product-service/product/1

写到token访问http://localhost:8080/product-service/product/1?access-token=1

5、服务网关Zuul的核心源码解析

相关文章