返回总纲
一、Ribbon简介
在微服务架构中,每一个业务都可以成立一个独立的服务。每个服务之间需要都需要相互访问。就需要用到ribbon+rest或者用feign。我们先聊聊ribbon+rest的调用方式,feign后面再说。
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。ribbon虽然是spring cloud的一个插件,但是它不会单独存在或单独部署(注册中心、配置中心、API网关可以独立存在,单独部署)。微服务之间的相互调用,是通过API网关请求转发调用到具体的业务微服务。其实其实ribbon就起到了重要的作用,包括后续我们将要介绍的Feign,其实内部也是基于Ribbon实现。Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
二、ribbon的原理分析
负载均衡分为服务端和客户端,ribbon就是客户端负载均衡。ribbon的负载均衡是通过解决一个进程无法解决所有请求而产生的一种算法。就是服务端的nginx一样,可以使用负载均衡分配流量,ribbon为客户端提供负载均衡,dubbo服务调用里的负载均衡
ribbon的负载均衡策略如下:
随机 (Random)
轮询 (RoundRobin)
一致性哈希 (ConsistentHash)
哈希 (Hash)
加权(Weighted)
ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表
结构:
ribbon从EurekaClient(EurekaClient的实现类为DiscoveryClient)获取服务信息,根据IRule去路由,并且根据IPing判断服务的可用性。
1.负载均衡器多久一次去获取一次从Eureka Client获取注册信息呢?在BaseLoadBalancer类下,BaseLoadBalancer的构造函数,该构造函数开启了一个PingTask任务setupPingTask()
public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats,
IPing ping, IPingStrategy pingStrategy) {
if (logger.isDebugEnabled()) {
logger.debug("LoadBalancer: initialized");
}
this.name = name;
this.ping = ping;
this.pingStrategy = pingStrategy;
setRule(rule);
setupPingTask();
lbStats = stats;
init();
}
2.setupPingTask()的具体代码逻辑,它开启了ShutdownEnabledTimer执行PingTask任务,在默认情况下pingIntervalSeconds为10,即每10秒钟,向EurekaClient发送一次”ping”。
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
3.完整流程:LoadBalancerClient(RibbonLoadBalancerClient是实现类)在初始化的时候(execute方法),会通过ILoadBalance(BaseLoadBalancer是实现类)向Eureka注册中心获取服务注册列表,并且每10s一次向EurekaClient发送“ping”,来判断服务的可用性,如果服务的可用性发生了改变或者服务数量和之前的不一致,则从注册中心更新或者重新拉取。LoadBalancerClient有了这些服务注册列表,就可以根据具体的IRule来进行负载均衡
三、ribbon使用
1.创建具有负载均衡功能的RestTemplate实例
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
2.使用RestTemplate进行rest操作的时候,会自动使用负载均衡策略,它内部会在RestTemplate中加入LoadBalancerInterceptor这个拦截器,这个拦截器的作用就是使用负载均衡。如果不满足条件,也可以根据自己的需求去设置策略。
3.使用ribbon访问
@Autowired
LoadBalancerClient loadBalancerClient;
//测试负载均衡最终选中哪个实例
public String getChoosedService() {
ServiceInstance serviceInstance = loadBalancerClient.choose("USERINFO-SERVICE");
StringBuilder sb = new StringBuilder();
sb.append("host: ").append(serviceInstance.getHost()).append(", ");
sb.append("port: ").append(serviceInstance.getPort()).append(", ");
sb.append("uri: ").append(serviceInstance.getUri());
return sb.toString();
}
四、代码片段提供
创建项目:xping-ribbon
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xping.cloud</groupId>
<artifactId>xping-ribbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>xping-ribbon</name>
<parent>
<groupId>com.xping.cloud</groupId>
<artifactId>xping-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
</project>
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8764
spring:
application:
name: xping-ribbon
启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class XpingRibbonApplication {
public static void main(String[] args) {
SpringApplication.run( XpingRibbonApplication .class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
controller使用:
@RestController
public class HelloControler {
@Autowired
RestTemplate restTemplate;
@Autowired
LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/hi")
public String hi(@RequestParam String name) {
ServiceInstance serviceInstance = loadBalancerClient.choose("USERINFO-SERVICE");
StringBuilder sb = new StringBuilder();
sb.append("host: ").append(serviceInstance.getHost()).append(", ");
sb.append("port: ").append(serviceInstance.getPort()).append(", ");
sb.append("uri: ").append(serviceInstance.getUri());
System.out.println(sb.toString());
return restTemplate.getForObject(serviceInstance.getUri()+"/hi?name="+name,String.class);
}
}