Spring Cloud服务网关/高可用

一、服务网关

通过之前几篇Spring Cloud中几个核心组件的介绍,我们已经可以构建一个简略的(不够完善)微服务架构了。比如下图所示:

![img](3_Spring Cloud 服务网关 高可用.assets/wps1.jpg)

  • 使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;
  • 服务间通过Ribbon或Feign实现服务的消费以及均衡负载;
  • 使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延;

这样架构存在的不足:

首先,破坏了服务无状态特点。为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外可续对接口访问的控制处理。

其次,无法直接复用既有接口。当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。

为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器,它就是本文将来介绍的:服务网关。

服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

1.1 zuul

Zuul是Netflix开源的微服务网关,可以和Eureka、Ribbon、Hystrix等组件配合使用,Spring Cloud对Zuul进行了整合与增强,Zuul默认的HTTP客户端是Apache HTTPClient,也可以使用RestClient或okhttp3.OkHttpClient。

Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/demo/test转发到到demo服务。zuul默认和Ribbon结合实现了负载均衡的功能

1.2 主要功能

路由

其中路由功能负责将外部请求转发到具体的微服务实例上去,是实现外部访问统一入口的基础。

过滤器

过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。

Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获取其他微服务的消息,也就是说,以后访问微服务都是通过Zuul跳转获得。

注意:Zuul服务最终还是会注册进Eureka

提供:代理+路由+过滤 三大功能

1.3 工作原理

zuul的核心是一系列的filters, 其作用类比Servlet框架的Filter,或者AOP。zuul把请求路由到用户处理逻辑的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等

![img](3_Spring Cloud 服务网关 高可用.assets/wps5.jpg)

Zuul使用一系列不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的边缘服务。这些过滤器可帮助我们执行以下功能

身份验证和安全性 - 确定每个资源的身份验证要求并拒绝不满足这些要求的请求

洞察和监控 - 在边缘跟踪有意义的数据和统计数据,以便为我们提供准确的生产视图

动态路由 - 根据需要动态地将请求路由到不同的后端群集

压力测试 - 逐渐增加群集的流量以衡量性能

Load Shedding - 为每种类型的请求分配容量并删除超过限制的请求

静态响应处理 - 直接在边缘构建一些响应,而不是将它们转发到内部集群

1.4 zuul的组件

zuul-core--zuul核心库,包含编译和执行过滤器的核心功能

zuul-simple-webapp--zuul Web应用程序示例,展示了如何使用zuul-core构建应用程序

zuul-netflix--lib包,将其他NetflixOSS组件添加到Zuul中,例如使用功能区进去路由请求处理

zuul-netflix-webapp--webapp,它将zuul-core和zuul-netflix封装成一个简易的webapp工程包

1.5 开发

服务准备

  • 创建注册中心服务1个

  • 创建生产者服务,至少两个,porductA和porductB

  • 创建消费者服务

  • 创建新的springboot服务,服务网关

  • 所有服务在注册中心进行注册

    ![image-20200812094859870](3_Spring Cloud 服务网关 高可用.assets/image-20200812094859870.png)

添加依赖关系

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>

在启动类上开启路由代理

// 启用网关代理
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class SpringZuulStart {

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

配置路由

zuul:
  # 配置服务网关服务组
  routes:
    # 一组路由,自己定义名字
    producta:
      # 路由请求路径,当请求路径符合producta的path路径匹配时,调用服务springcloud-porducta处理请求
      path: /pta/**
      # 路由对应的服务名
      serviceId: springcloud-porducta
    productb:
      path: /ptb/**
      serviceId: springcloud-porductb

针对我们在准备工作中实现的两个微服务porductA和porductB,定义了两个路由producta和productb来分别映射。另外为了让zuul能发现porductA和porductB,也加入了eureka的配置。

访问路由,当请求路径符合/pta/**,zuul根据路由配置,路由将请求转发给对应的服务springcloud-porducta,如果服务配置了负载均衡,Ribbon处理请求,将请求转发给具体的服务;没有配置负载均衡,eureka将请求转给具体的服务。

启动服务,访问

![image-20200812095402948](3_Spring Cloud 服务网关 高可用.assets/image-20200812095402948.png)

![image-20200812095420445](3_Spring Cloud 服务网关 高可用.assets/image-20200812095420445.png)

![image-20200812095329006](3_Spring Cloud 服务网关 高可用.assets/image-20200812095329006.png)

修改消费者访问

调用的服务改成调用网关服务,请求路径按照网关定义的请求路径规则书写

// 调用网关
@FeignClient(value = "springcould-zuul",fallback = PorductUserServiceImpl.class)
public interface PorductService {

    // ProductA
    @RequestMapping("/pta/user/getUser")
    String getUser();

    // ProductA
    @RequestMapping("/pta/user/add")
    Integer add(@RequestParam("a") Integer a, @RequestParam("b") Integer b);

    // ProductB
    @RequestMapping("/ptb/user/select")
    String select();
}

扩展

使用url请求路径,不推荐使用,服务端的ip地址和端口号有可能会更新

zuul:
  # 配置服务网关服务组
  routes:
    # 一组路由,自己定义名字
    producta:
      # 路由请求路径,当请求路径符合producta的path路径匹配时,调用服务springcloud-porducta处理请求
      path: /pta/**
      # 服务路径
      url: http://localhost:8011,http://localhost:8012
    productb:
      path: /ptb/**
      url: http://localhost:8021

二、高可用注册中心

2.1 Eureka Server的高可用

Eureka Server除了单点运行之外,还可以通过运行多个实例,并进行互相注册的方式来实现高可用的部署,所以我们只需要将Eureka Server配置其他可用的serviceUrl就能实现高可用部署。

2.2 开发

创建注册中心A和B

修改配置文件,向对方注册自己

server:
  port: 8001
  servlet:
    context-path: /springeureka

spring:
  # 注册中心集群,名字可以一致可以不一样
  application:
    name: springcloud-eureka1

eureka:
  instance:
    # 集群时,不能写ip地址,不能写localhost
    hostname: eureka1
    appname: eureka
  # 客户端
  client:
    # 禁用注册客户端:Eureka注册中心既可以作为服务端又可以作为客户端,默认会自动注册客户端,不注册客户端,避免自己注册自己
    # 单机false(不注册自己),集群true(向对方注册自己)
    register-with-eureka: true
    # 注册相邻节点
    # 单机false,集群true
    fetch-registry: true
    # 注册中心路径
    service-url:
      defaultZone: http://eureka2:8002

注意:

  1. eureka.client.serviceUrl.defaultZone配置项的地址,不能使用localhost,要使用service-center-1之类的域名,通过host映射到127.0.0.1;
  2. spring.application.name或eureka.instance.appname必须一致;
  3. 相互注册要开启: eureka.client.register-with-eureka=true eureka.client.fetch-registry=true

配置服务名

在host中添加地址指向,名字与eureka服务名一致

C:\Windows\System32\drivers\etc\hosts

127.0.0.1 eureka1
127.0.0.1 eureka2

General Info参数

![img](3_Spring Cloud 服务网关 高可用.assets/270324-20181208134102856-603989513.png)

total-avail-memory : 总共可用的内存

environment : 环境名称,默认test

num-of-cpus : CPU的个数

current-memory-usage : 当前已经使用内存的百分比

server-uptime : 服务启动时间

registered-replicas : 相邻集群复制节点

unavailable-replicas :不可用的集群复制节点,如何确定不可用? 主要是server1 向 server2和server3发送接口查询自身的注册信息, 如果查询不到,则默认为不可用,也就是说如果Eureka Server自身不作为客户端注册到上面去,则相邻节点都会显示为不可用。

available-replicas :可用的相邻集群复制节点

启动服务

![image-20200812105610098](3_Spring Cloud 服务网关 高可用.assets/image-20200812105610098.png)

此时访问eureka1注册中心,我们可以看到registered-replicas中已经有eureka2节点的eureka-server了。

同样地,访问eureka2的注册中心,能看到registered-replicas中已经有eureka1节点

并且这些节点在可用分片(available-replicase)之中。

可以尝试关闭eureka2,刷新eureka1,可以看到eureka2的节点变为了不可用分片(unavailable-replicas)

修改服务注册中心

eureka:
  client:
    service-url:
      defaultZone: http://eureka1:8001/eureka,http://eureka2:8002/eureka