Spring Cloud使用Ribbon实现负载均衡功能

本指南将使用Spring Boot构建应用,通过集成Netflix Ribbon和Spring Cloud Netflix为微服务提供负载均衡能力。

Netflix Ribbon构建环境

JDK 1.8 或 更新

Gradle 4+ 或 Maven 3.2+

Ribbon服务端程序

服务端提供两个接口,/greeting接口随机返回一句问候语,根路径(/)接口返回一句简单的"Hi"字符串。

/src/main/java/hello/SayHelloApplication.java
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

@RestController
@SpringBootApplication
public class SayHelloApplication {

  private static Logger log = LoggerFactory.getLogger(SayHelloApplication.class);

  @RequestMapping(value = "/greeting")
  public String greet() {
    log.info("Access /greeting");

    List<String> greetings = Arrays.asList("Hi there", "Greetings", "Salutations");
    Random rand = new Random();

    int randomNum = rand.nextInt(greetings.size());
    return greetings.get(randomNum);
  }

  @RequestMapping(value = "/")
  public String home() {
    log.info("Access /");
    return "Hi!";
  }

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

由于客户端应用也在本地运行,所以,需要修改服务端口,在src/main/resources目录创建application.yml文件,通过server.port参数修改默认端口。
并通过spring.application.name为服务设置别名。

/src/main/resources/application.yml
spring:
  application:
    name: say-hello

server:
  port: 8090

Ribbon客户端程序

使用Spring Boot创建一个客户端程序,并调用服务端/greeting接口,获得一个问候语。

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@RestController
public class UserApplication {

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

  @Autowired
  RestTemplate restTemplate;

  @RequestMapping("/hi")
  public String hi(@RequestParam(value="name", defaultValue="Artaban") String name) {
    String greeting = this.restTemplate.getForObject("http://localhost:8090/greeting", String.class);
    return String.format("%s, %s!", greeting, name);
  }

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

使用Spring RestTemplate发送HTTP GET请求,获取服务端数据。

在引导文件application.yml或配置文件application.properties中设置客户端应用参数。

/src/main/resources/application.yml
spring:
  application:
    name: user

server:
  port: 8888

Ribbon负载均衡配置

访问客户端应用的/hi接口,发送对服务端的调用,并返回数据:

$ curl http://localhost:8888/hi
Greetings, Artaban!

$ curl http://localhost:8888/hi?name=Orontes
Salutations, Orontes!

把现有应用过渡到负载均衡的解决方案中,在客户端应用中加入Ribbon参数。配置参数如下所示:

/src/main/resources/application.yml
say-hello:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:9092,localhost:9999
    ServerListRefreshInterval: 15000

Ribbon的负载均衡器,通常从Netflix Eureka服务注册中心获取服务实例列表。为了简单起见,将eureka.enabled设置为false,以跳过了Eureka。通过listOfServers参数,为Ribbon指定一组静态服务列表。使用ServerListRefreshInterval参数,配置Ribbon服务间的刷新时间,以毫秒为单位。

Ribbon负载均衡组件

Spring Cloud Netflix为应用中的每个Ribbon客户端名称,创建一个ApplicationContext,以下是为客户端提供的一组Ribbon组件。

IClientConfig:为客户端或负载均衡器提供配置。

ILoadBalancer:代表一个负载均衡器。

ServerList:定义了获取服务的方式。

IRule:定义负载均衡策略。

IPing,定义向服务端发起周期性ping的方式。

Ribbon负载均衡调用

在UserApplication类中,将RestTemplate切换为Ribbon client获取服务端地址:

src/main/java/hello/UserApplication.java
package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@SpringBootApplication
@RestController
@RibbonClient(name = "say-hello", configuration = SayHelloConfiguration.class)
public class UserApplication {

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

  @Autowired
  RestTemplate restTemplate;

  @RequestMapping("/hi")
  public String hi(@RequestParam(value="name", defaultValue="Artaban") String name) {
    String greeting = this.restTemplate.getForObject("http://say-hello/greeting", String.class);
    return String.format("%s, %s!", greeting, name);
  }

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

对UserApplication类进行修改,在该类中加入@RibbonClient注解,定义客户端应用名,使用SayHelloConfiguration类配置客户端应用。

RestTemplate现已被标记为LoadBalanced,以此,告诉Spring Cloud开启负载均衡支持(在本例中,由Ribbon提供)。

在src/main/java/hello目录下创建SayHelloConfiguration类。

/src/main/java/hello/SayHelloConfiguration.java

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import com.netflix.loadbalancer.AvailabilityFilteringRule;

public class SayHelloConfiguration {

  @Autowired
  IClientConfig ribbonClientConfig;

  @Bean
  public IPing ribbonPing(IClientConfig config) {
    return new PingUrl();
  }

  @Bean
  public IRule ribbonRule(IClientConfig config) {
    return new AvailabilityFilteringRule();
  }

}

在配置类中覆盖Spring Cloud Netflix Ribbon相关组件,使用自定义IPing、IRule实现。 IPing默认使用NoOpPing,IRule默认使用ZoneAvoidanceRule。

示例中IPing使用PingUrl实现,它使用指定URL来检测服务的状态。因为服务端定义了根路径(/)接口,Ribbon ping使用此接口获取服务状态(HTTP 200)。

示例中IRule使用AvailabilityFilteringRule实现,调用Ribbon内置断路器功能,过滤掉处于熔断状态的服务。

如果ping无法连接到指定服务,或者从服务端获取状态失败 ,Ribbon将认为该服务出现故障,并开启熔断,直至ping能正常获取应用状态。

Ribbon负载均衡测试

启动各组服务,访问localhost:8888/hi,查看Say Hello服务实例,能够看到Ribbon每隔15秒就发起一次ping请求:

2019-04-07 21:13:22.115  INFO 90046 --- [nio-8090-exec-1] hello.SayHelloApplication                : Access /
2019-04-07 21:13:22.629  INFO 90046 --- [nio-8090-exec-3] hello.SayHelloApplication                : Access /

现在,关闭一个Say Hello服务实例。 Ribbon通过ping发现某一服务停止工作,会立即启用熔断机制,此时,请求会被转移到剩余的服务实例中,以达到负载均衡的效果。