一、负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 Ngnix 。另一种是将负载均衡逻辑以代码的形式封装到服务消费者的客户端上,服务消费者客户端维护了一份服务提供的信息列表,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。

  Ribbon Netflix 公司开源的一个负载均衡的组件,它属于上述的第二种方式,是将负载均衡逻辑封装在客户端中,并且运行在客户端的进程里。 Ribbon是一个经过了云端测试的 IPC库,可以很好地控制 HTT TCP 客户端的负载均衡行为。

  Spring Cloud 构建的微服务系统中, Ribbon 作为服务消费者的负载均衡器,有两种使用方式, 1)RestTemplate 相结合,2)Feign 相结合默认方式)

  二、用于生产环境的Ribbon的子模块为

  1)ribbon-loadbalancer :可以独立使用或与其他模块 起使用的负载均衡器 API。

  2)ribbon-eureka :Ribbon 结合 Eureka 客户端的 API ,为负载均衡器提供动态服务注册列表信息。

  3)ribbon-core: Ribbon 的核心 API。

  三、使用RestTemple和Ribbon来消费服务

  1)通过Spring-Cloud之Eureka注册与发现-2来配置一个Eureka-Server和两个Eureka-Client端口分别为8670和8673/8674,效果如下:

  

  2)在Eureka-Clien中编写一个获取端口的rest接口

package com.cetc.web.rest;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/api/test")
public class TestResource { @Value("${server.port}")
private Integer port; @GetMapping("/getPort")
public Integer getPort() {
return port;
}
}

  3)将Ribbon注册到Eureka-Server中端口8675。

  a、添加依赖

  <dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

  b、配置文件

server:
port: 8675
eureka:
instance:
hostname: rest
client:
service-url:
defaultZone:
http://127.0.0.1:8670/eureka/ # 实际开发中建议使用域名的方式
spring:
application:
name: rest

  c、效果

  

  4)编写配置文件

package com.cetc.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate; @Configuration
public class RibbonConfiguration { @Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

  注意:@LoadBalanced加上过后,RestTemplate就可以结合Ribbon使用了

  5)编写服务测试

package com.cetc.service;

public interface IRibbonService {
Integer getPort();
}
package com.cetc.service.impl;

import com.cetc.service.IRibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; @Service
public class RibbonServiceImpl implements IRibbonService { @Autowired
private RestTemplate restTemplate; @Override
public Integer getPort() {
return restTemplate.getForObject("http://client/api/test/getPort", Integer.class);
}
}

  注意:这里使用的是生产者的应用名称来进行访问的。

package com.cetc.web.rest;

import com.cetc.service.IRibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/api/ribbon")
public class RibbonResource { @Autowired
private IRibbonService ribbonService; @GetMapping("/getPort")
public Integer getPort() {
return ribbonService.getPort();
}
}

  效果:

  四、LoadBalancerClient,负载均衡器的核心类, LoadBalancerCiient 可以获取负载均衡的服务提供者的实例信息。

  1)通过Eureka-Server获取方式来轮流访问接口

package com.cetc.web.rest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/api/ribbon")
public class RibbonResource { @Autowired
private LoadBalancerClient loadBalancerClient; @GetMapping("/testRibbon")
public String testRibbon() {
ServiceInstance client = loadBalancerClient.choose("client");
return client.getHost() + ":" + client.getPort();
}
}

  测试效果:

  

  说明:这里通过choose()方法轮流的去获取接口。

  2)不从Eureka-Server上面获取注册列表,那么久需要自己维护一份列表

  配置:

server:
port: 8675
ribbon:
eureka:
enabled: false
stores:
ribbon:
listOfServers: example.com, baidu.com
   @GetMapping("/testRibbon")
public String testRibbon() {
ServiceInstance client = loadBalancerClient.choose("stores");
return client.getHost() + ":" + client.getPort();
}

  测试:

  

  五、源码解析(因为源码部分偏多,所以我这里只做主要部分讲解)

  1)我们还是从LoadBalancerClient出发,跟踪实现可以发现RibbonLoadBalancerClient

  

  2)通过跟踪choose()方法我们可以接触到ILoadBalancer接口

package com.netflix.loadbalancer;

import java.util.List;

public interface ILoadBalancer {
   //添加Server服务
void addServers(List<Server> var1);
   //选择服务
Server chooseServer(Object var1);
   //标注服务下线
void markServerDown(Server var1); /** @deprecated */
@Deprecated
List<Server> getServerList(boolean var1);
   //获取可用服务
List<Server> getReachableServers();
   //获取全部服务
List<Server> getAllServers();
}

  3)追踪ILoadBalancer的实现类我们可以发现DynamicServerListLoadBalancer

  

  这个类的主要构造方法为:

   public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.isSecure = false;
this.useTunnel = false;
this.serverListUpdateInProgress = new AtomicBoolean(false);
this.updateAction = new UpdateAction() {
public void doUpdate() {
DynamicServerListLoadBalancer.this.updateListOfServers();
}
};
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats());
} this.restOfInit(clientConfig);
} public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
this.isSecure = false;
this.useTunnel = false;
this.serverListUpdateInProgress = new AtomicBoolean(false);
this.updateAction = new UpdateAction() {
public void doUpdate() {
DynamicServerListLoadBalancer.this.updateListOfServers();
}
};
this.initWithNiwsConfig(clientConfig);
}

  IRule:根据IRule的不同实现类的算法来达到处理负载均衡的策略

  

  IPing:用于向Server发送ping信号,来判断是否正常连接

  

  4)我们在构造方法里面跟踪都最终执行了restOfInit()方法,然后在restOfInit()中我们可以看到updateListOfServers()方法。 

@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList();
if (this.serverListImpl != null) {
servers = this.serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
if (this.filter != null) {
servers = this.filter.getFilteredListOfServers((List)servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
}
} this.updateAllServerList((List)servers);
}

  这里的核心就是this.serverListImpl.getUpdatedListOfServers();

  我们查看这里的serverListImpl发现他是ServerList接口。跟踪可以发现实现类DiscoveryEnabledNIWSServerList

  

  然后查看getUpdatedListOfServers()实现方法,跟踪里面的obtainServersViaDiscovery()方法。

private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList();
if (this.eurekaClientProvider != null && this.eurekaClientProvider.get() != null) {
EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
if (this.vipAddresses != null) {
String[] var3 = this.vipAddresses.split(",");
int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) {
String vipAddress = var3[var5];
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
Iterator var8 = listOfInstanceInfo.iterator(); while(var8.hasNext()) {
InstanceInfo ii = (InstanceInfo)var8.next();
if (ii.getStatus().equals(InstanceStatus.UP)) {
if (this.shouldUseOverridePort) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding port on client name: " + this.clientName + " to " + this.overridePort);
} InstanceInfo copy = new InstanceInfo(ii);
if (this.isSecure) {
ii = (new Builder(copy)).setSecurePort(this.overridePort).build();
} else {
ii = (new Builder(copy)).setPort(this.overridePort).build();
}
} DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, this.isSecure, this.shouldUseIpAddr);
des.setZone(DiscoveryClient.getZone(ii));
serverList.add(des);
}
} if (serverList.size() > 0 && this.prioritizeVipAddressBasedServers) {
break;
}
}
} return serverList;
} else {
logger.warn("EurekaClient has not been initialized yet, returning an empty list");
return new ArrayList();
}
}

  这里最重要的就是EurekaClient,通过他去获取具体的服务的注册列表信息。而EurekaClient的实现类DiscoveryClient我们在上一章就具体讲过了,这里不赘述了。

  5)另外一点,Ribbon什么时候向Eureka-Server获取Eureka-Client的信息呢。

  通过在BaseLoadBalancer类中我们可以发现调用了setupPingTask()方法

  void setupPingTask() {
if (!this.canSkipPing()) {
if (this.lbTimer != null) {
this.lbTimer.cancel();
} this.lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + this.name, true);
this.lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0L, (long)(this.pingIntervalSeconds * 1000));
this.forceQuickPing();
}
}

  这里不细讲了,这里主要类为ShutdownEnabledTimer。采用定时调度的方式实现pingIntervalSeconds 为10S。

  6)讲一下@LoadBalanced注解,了解Spring的应该都知道怎么去查找具体加入容器的类LoadBalancerAutoConfiguration(LoadBalanced自动装配的类)

   @Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> {
restTemplateCustomizers.ifAvailable((customizers) -> {
Iterator var2 = this.restTemplates.iterator(); while(var2.hasNext()) {
RestTemplate restTemplate = (RestTemplate)var2.next();
Iterator var4 = customizers.iterator(); while(var4.hasNext()) {
RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
customizer.customize(restTemplate);
}
} });
};
}

  这里我们可以了解到这里维护了被修饰的RestTemplate类,通过customizer.customize(restTemplate);我们可以知道RestTemplateLoadBalancerInterceptor拦截了

  

  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

  这里的this.loadBalancer也就是前面的LoadBalancerClient

  7)总结:

  1)Ribbon是通过LoadBalancerClient来实现负载均衡的。

  2)LoadBalancerClient交给了ILoadBalancer处理

  3)ILoadBalancer通过配置IRule,IPing等向Eureka-Server获取Eureka-Client列表。

  4)并且没10秒ping一次Eureka-Client以保证生产者正常

  5)最后在通过IRule进行负载均衡

  六、源码地址:https://github.com/lilin409546297/spring-cloud/tree/master/ribbon

Spring-Cloud之Ribbon负载均衡-3的更多相关文章

  1. spring cloud: 关闭ribbon负载均衡

    spring cloud: 关闭ribbon负载均衡 1.eureka服务 2.2个user服务:7900/7901 3,movie服务 movie服务去请求 user的用户信息,而此时只想请求790 ...

  2. API网关spring cloud gateway和负载均衡框架ribbon实战

    通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...

  3. spring cloud 之 客户端负载均衡 Ribbon

    一.负载均衡 负载均衡(Load Balance): 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性.其意 ...

  4. 【Spring Cloud】客户端负载均衡组件——Ribbon(三)

    一.负载均衡 负载均衡技术是提高系统可用性.缓解网络压力和处理能力扩容的重要手段之一. 负载均衡可以分为服务器负载均衡和客户端负载均衡,服务器负载均衡由服务器实现,客户端只需正常访问:客户端负载均衡技 ...

  5. 三、Spring Cloud之软负载均衡 Ribbon

    前言 上一节我们已经学习了Eureka 注册中心,其实我们也使用到了Ribbon ,只是当时我们没有细讲,所以我们现在一起来学习一下Ribbon. 什么是Ribbon 之前接触到的负载均衡都是硬负载均 ...

  6. SpringCloud学习笔记(8)----Spring Cloud Netflix之负载均衡-Ribbon的负载均衡的策略

    一. 内置 负载均衡策略的介绍的 IRule的实现类 2. 通过代码实现负载均衡 在第六节Riddom的使用的工程中,随机策略配置类 package com.wangx.cloud.springclo ...

  7. SpringCloud学习笔记(7)----Spring Cloud Netflix之负载均衡-Ribbon的深入理解

    1. 注解@LoadBalanced 作用:识别应用名称,并进行负载均衡. 2. 入口类:LoadBalancerAutoConfiguration 说明:类头上的注解可以知道Ribbon 实现的负载 ...

  8. SpringCloud学习笔记(6)----Spring Cloud Netflix之负载均衡-Ribbon的使用

    1. 什么是负责均衡? 负载均衡,就是分发请求流量到不同的服务器. 负载均衡一般分为两种 1. 服务器端负载均衡(nginx) 2. 客户端负载均衡(Ribbon) 2. 服务提供者(spring-c ...

  9. Spring Cloud 2-Ribbon 客户端负载均衡(二)

    Spring Cloud Eureka  1.Hello-Service服务端配置 pom.xml application.yml 启动两个service 2.Ribbon客户端配置 pom.xml ...

  10. Spring Cloud中的负载均衡策略

    在上篇博客(Spring Cloud中负载均衡器概览)中,我们大致的了解了一下Spring Cloud中有哪些负载均衡器,但是对于负载均衡策略我们并没有去详细了解,我们只是知道在BaseLoadBal ...

随机推荐

  1. Python 使用scapy 时报:ImportError: cannot import name 'NPCAP_PATH' 解决

    解决办法:下载源码 https://github.com/secdev/scapy 下载完成后解压后里面有个scapy文件夹,把这个文件夹替换\Lib\site-packages\ 下的scapy 整 ...

  2. OSPF协议介绍及配置

    一.OSPF概述 回顾一下距离矢量路由协议的工作原理:运行距离矢量路由协议的路由器周期性的泛洪自己的路由表,通过路由的交互,每台路由器都从相邻的路由器学习到路由,并且加载进自己的路由表中,而对于这个网 ...

  3. java判断文件真实类型

    代码如下: import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; /** * &l ...

  4. C++ int double float对应的长度以及二进制

    #include <iostream> using namespace std; void showIntBit(int a); void showDoubleBit(double a1) ...

  5. Qt编写安防视频监控系统16-设备播放

    一.前言 设备播放模块是后面增加的,核心就是通过组合rtsp视频流地址来播放实时视频和历史视频,目前市面上很多厂家比如排第一的海康都是支持直接rtsp通过NVR来播放某个通道视频流和回放某个通道的视频 ...

  6. Superset配置hive数据源

    1.在uri中配置 hive://localhost:10000/default 2.查询 3.如果你的hive集群是带有kerberos认证的,hive数据源需要这样配置 hive://xxx:xx ...

  7. Playbooks简介

    Playbooks简介 关于Playbooks Playbook简单示例 基本组成 主机和用户 任务列表 Handlers: 配置更改时运行操作 执行Playbook Ansible-Pull Lin ...

  8. EasyNVR摄像机网页无插件直播方案H5前端构建之:接口调用获取实时信息

    背景分析 熟悉EasyNVR产品的小伙伴应该知道,EasyNVR主要针对的是安防类的项目,通过RTSP/onvif协议将前端高清网络摄像机IPC.NVR等接入进来,然后将设备端的音视频通过采集.转换, ...

  9. Django框架 + Djiango安装 + First Djiango + 常用命令

    一.Django框架 MVC框架和MTV框架 参考:https://www.cnblogs.com/taosiyu/p/11260016.html MVC,全名是Model View Controll ...

  10. C++ & OpenCV 零散学习总结

    OpenCV中Mat基本用法: Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类.从功能上讲,Mat类在IplImage结构的基础上进一步增强,并且,由于引入C++高级编 ...