Spring Cloud负载均衡 - Ribbon

以Netflix Ribbon为Spring Cloud应用提供负载均衡能力。

服务端应用

服务端提供两个接口:

/greeting接口随机返回一句问候语,

根路径(/)接口返回一个字符串。

/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

spring:
  application:
    name: say-hello

server:
  port: 8090

客户端应用

客户端应用调用服务端应用的/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发送请求,测试应用。

设置客户端应用:

/src/main/resources/application.yml

spring:
  application:
    name: user

server:
  port: 8888

负载均衡配置

将现有应用过渡到负载均衡方案中,在客户端应用中加入Ribbon参数:

/src/main/resources/application.yml

say-hello:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:8090,localhost:9092,localhost:9999
    ServerListRefreshInterval: 15000

Ribbon负载均衡器,通常从Eureka服务注册中心获取服务实例列表。

简单起见,将eureka.enabled设置为false跳过Eureka,使用listOfServers参数,为Ribbon指定一组静态服务列表。

通过ServerListRefreshInterval参数,配置Ribbon服务间的刷新时间,以毫秒为单位。

负载均衡组件

Spring Cloud Netflix为应用中每个Ribbon客户端,创建一个ApplicationContext,提供一组Ribbon组件:

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

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

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

IRule:定义负载均衡策略。

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

负载均衡调用

在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);
  }
}

加入@RibbonClient注解,定义客户端应用名,使用@LoadBalanced为RestTemplate添加负载均衡能力。

创建一个类配置客户端应用:

/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();
  }

}

IPing默认使用NoOpPing,IRule默认使用ZoneAvoidanceRule。

配置中覆盖Netflix Ribbon相关组件,使用自定义IPing、IRule。

示例中IPing使用PingUrl实现,它使用指定URL检测服务状态。Ribbon ping调用服务端接口检测服务状态。

示例中IRule使用AvailabilityFilteringRule实现,调用Ribbon内置断路器功能。

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

测试

访问:

http://localhost:8888/hi

查看Say Hello服务的Log,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发现服务停止工作,会启用熔断,请求被转移到其他服务中。