SpringCloud-Zuul服务网关与Ribbon负载均衡

1,003 阅读6分钟

今天继续学习SpringCloud。

上篇我们讲了Eureka和服务提供者、消费者

这一篇针对服务网关和负载均衡详细说说

以下代码皆用最简单的代码示例,并非真正的业务代码

学习中用到的学习资料如下:

文章:SpringCloud极简入门

视频:Spring Cloud从入门到实战


服务网关的概念

在分布式项目架构中,会将服务进行拆分,不同的微服务负责各自的业务功能,实现软件架构层面的解耦合。但是如果拆分之后的微服务数量太多,是不利于系统开发的,因为每个服务都有不同的网络地址,客户端多次请求不同的微服务需要调用不同的 URL,如果同时去维护多个不同的 URL 无疑会增加开发的成本。

如下图所示,一个订餐系统,需要调用多个微服务接口才能完成一次订餐的业务流程,如果能有一种解决方案可以统一管理不同的微服务 URL,肯定会增强系统的维护性,提高开发效率。

这个解决方案就是 API 网关,API 网关可以对所有的 API 请求进行管理维护,相当于为系统开放出一个统一的接口,所有的外部请求只需要访问这个统一入口即可,系统内部再通过 API 网关去映射不同的微服务。对于开发者而言就不需要关注具体的微服务 URL 了,直接访问 API 网关接口即可,API 网关的结构如下图所示。

如此一来我们就解决了上述问题,开发变得更加简单方便。

Zuul

什么是 Zuul?

Zuul 是 Netflix 提供的一个开源的 API 网关服务器,是客户端和网站后端所有请求的中间层,对外开放一个 API,将所有请求导入统一的入口,屏蔽了服务端的具体实现逻辑,Zuul 可以实现反向代理的功能,在网关内部实现动态路由、身份认证、IP 过滤、数据监控等。我们可以使用 Zuul 来实现微服务网关,Spring Cloud 集成了 Zuul。

代码实现

  • 创建 Maven 工程,pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
 
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
</dependencies>
  • 创建配置文件 application.yml
server:
  port: 8030
spring:
  application:
    name: gateway
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
zuul:
  routes:
    provider: /p/**

属性说明:

zuul.routes.provider:给服务提供者 provider 设置映射

  • 创建启动类
package com.janeroad;

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

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

注解说明:

@EnableZuulProxy:包含了 @EnableZuulServer,设置该类是网关的启动类。

@EnableAutoConfiguration:可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置加载到当前 Spring Boot 创建并使用的 IoC 容器中。

  • Zuul 自带了负载均衡功能,修改 provider 的代码。
package com.janeroad.controller;

import com.janeroad.entity.Student;
import com.janeroad.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

@RestController
@RequestMapping("/student")
public class StudentHandler {
    @Autowired
    private StudentRepository studentRepository;

    @Value("${server.port}")
    private String port;

    @GetMapping("/findAll")
    public Collection findAll(){
        return studentRepository.findAll();
    }

    @GetMapping("/findById/{id}")
    public Student findById(@PathVariable("id") long id){
        return studentRepository.findById(id);
    }

    @PostMapping("/save")
    public void save(@RequestBody Student student){
        studentRepository.saveOrUpdate(student);
    }

    @PutMapping("/update")
    public void update(@RequestBody Student student){
        studentRepository.saveOrUpdate(student);
    }

    @DeleteMapping("/deleteById/{id}")
    public void deleteById(@PathVariable("id") long id){
        studentRepository.deleteById(id);
    }

    @GetMapping("/index")
    public String index(){
        return "当前端口:"+this.port;
    }
}

服务网关zuul启动效果

可以看到注册中心多了GATEWAY

访问8030/p/student/findAll 相当于访问服务提供者 8010/student/findAll,因为配置文件做了相关的映射

同时 Zuul 自带了负载均衡功能,在EurekaClient中的Handler里面增加一个返回当前服务端口的方法,重启EurekaServer和EurekaClient 然后修改 provider 的端口为 8011,创建一个新的 provider 启动类并启动,最后重新启动 gateway,访问 http://localhost:8761 可以看到有两个provider

访问两次 http://localhost:8030/p/student/index,分别请求了端口为 8010 和 8011 的 provider 微服务,实现了负载均衡。

Ribbon 负载均衡

RestTemplate 的调用只是实现了最基本的需求,如果在某个具体的业务场景下,对于某服务的调用需求激增,这时候在一个大型的分布式应用系统中,负载均衡(Load Balancing)是必备的。

什么是 Ribbon?

Spring Cloud 提供了实现负载均衡的解决方案:Spring Cloud Ribbon,Ribbon 是 Netflix 发布的负载均衡器,而 Spring Cloud Ribbon 则是基于 Netflix Ribbon 实现的,是一个用于对 HTTP 请求进行控制的负载均衡客户端。

Spring Cloud Ribbon 官网地址:

cloud.spring.io/spring-clou…

Ribbon 的使用同样需要结合 Eureka Server,即需要将 Ribbon 在 Eureka Server 进行注册,注册完成之后,就可以通过 Ribbon 结合某种负载均衡算法,如轮询、随机、加权轮询、加权随机等帮助服务消费者去调用接口。除了 Ribbon 默认提供的这些负载均衡算法外,开发者也可以根据具体需求来设计自定义的 Ribbon 负载均衡算法。实际开发中,Spring Cloud Ribbon 需要结合 Spring Cloud Eureka 来使用,Eureka Server 提供所有可调用的服务提供者列表,Ribbon 基于特定的负载均衡算法从这些服务提供者中挑选出要调用的实例,如下图所示。

Ribbon 常用的负载均衡策略有以下几种:

  • 随机:访问服务时,随机从注册中心的服务列表中选择一个。
  • 轮询:当同时启动两个服务提供者时,客户端请求会由这两个服务提供者交替处理。
  • 加权轮询:对服务列表中的所有微服务响应时间做加权处理,并以轮询的方式来访问这些服务。
  • 最大可用:从服务列表中选择并发访问量最小的那个微服务。

代码实现

  • 创建 Module,pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
</dependencies>
  • 创建配置文件 application.yml
server:
  port: 8040
spring:
  application:
    name: ribbon
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
  • 创建启动类
package com.janeroad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

@LoadBalanced:声明一个基于 Ribbon 的负载均衡。

  • Handler
package com.janeroad.controller;

import com.janeroad.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Collection;

@RestController
@RequestMapping("/ribbon")
public class RibbonHandler {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/findAll")
    public Collection findAll(){
        return restTemplate.getForObject("http://provider/student/findAll",Collection.class);
    }

    @GetMapping("/index")
    public String index(){
        return restTemplate.getForObject("http://provider/student/index",String.class);
    }
}

打开浏览器,访问 http://localhost:8761,看到如下界面

可以看到 Provider 和 Ribbon 已经在注册中心完成注册,接下来用 Postman 工具测试 Ribbon 相关接口,如下图所示。

  • findAll 接口

  • findById 接口

  • save 接口

添加完成之后再来查询,调用 findAll 接口,可以看到新数据已经添加成功。

  • update 接口

修改完成之后再来查询,调用 findAll 接口,可以看到修改之后的数据。

  • deleteById 接口

删除完成之后再来查询,调用 findAll 接口,可以看到数据已经被删除。

接下来测试 Ribbon 的负载均衡,在 RibbonHandler 中添加如下代码

@RequestMapping("/ribbon")
@RestController
public class RibbonHandler {

    @GetMapping("/index")
    public String index(){
        return restTemplate.getForObject("http://provider/student/index",String.class);
    }

}

分别启动注册中心,端口为 8010 的 Provider,端口为 8011 的 Provider、Ribbon。

打开浏览器,访问 http://localhost:8761

可以看到两个 Provider 和 Ribbon 已经在注册中心完成注册,访问 http://localhost:8040/ribbon/index,交替出现下图所示情况,实现了负载均衡。