Spring-Cloud之Ribbon负载均衡-3
一、负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 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);我们可以知道RestTemplate被LoadBalancerInterceptor拦截了

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的更多相关文章
- spring cloud: 关闭ribbon负载均衡
spring cloud: 关闭ribbon负载均衡 1.eureka服务 2.2个user服务:7900/7901 3,movie服务 movie服务去请求 user的用户信息,而此时只想请求790 ...
- API网关spring cloud gateway和负载均衡框架ribbon实战
通常我们如果有一个服务,会部署到多台服务器上,这些微服务如果都暴露给客户,是非常难以管理的,我们系统需要有一个唯一的出口,API网关是一个服务,是系统的唯一出口.API网关封装了系统内部的微服务,为客 ...
- spring cloud 之 客户端负载均衡 Ribbon
一.负载均衡 负载均衡(Load Balance): 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽.增加吞吐量.加强网络数据处理能力.提高网络的灵活性和可用性.其意 ...
- 【Spring Cloud】客户端负载均衡组件——Ribbon(三)
一.负载均衡 负载均衡技术是提高系统可用性.缓解网络压力和处理能力扩容的重要手段之一. 负载均衡可以分为服务器负载均衡和客户端负载均衡,服务器负载均衡由服务器实现,客户端只需正常访问:客户端负载均衡技 ...
- 三、Spring Cloud之软负载均衡 Ribbon
前言 上一节我们已经学习了Eureka 注册中心,其实我们也使用到了Ribbon ,只是当时我们没有细讲,所以我们现在一起来学习一下Ribbon. 什么是Ribbon 之前接触到的负载均衡都是硬负载均 ...
- SpringCloud学习笔记(8)----Spring Cloud Netflix之负载均衡-Ribbon的负载均衡的策略
一. 内置 负载均衡策略的介绍的 IRule的实现类 2. 通过代码实现负载均衡 在第六节Riddom的使用的工程中,随机策略配置类 package com.wangx.cloud.springclo ...
- SpringCloud学习笔记(7)----Spring Cloud Netflix之负载均衡-Ribbon的深入理解
1. 注解@LoadBalanced 作用:识别应用名称,并进行负载均衡. 2. 入口类:LoadBalancerAutoConfiguration 说明:类头上的注解可以知道Ribbon 实现的负载 ...
- SpringCloud学习笔记(6)----Spring Cloud Netflix之负载均衡-Ribbon的使用
1. 什么是负责均衡? 负载均衡,就是分发请求流量到不同的服务器. 负载均衡一般分为两种 1. 服务器端负载均衡(nginx) 2. 客户端负载均衡(Ribbon) 2. 服务提供者(spring-c ...
- Spring Cloud 2-Ribbon 客户端负载均衡(二)
Spring Cloud Eureka 1.Hello-Service服务端配置 pom.xml application.yml 启动两个service 2.Ribbon客户端配置 pom.xml ...
- Spring Cloud中的负载均衡策略
在上篇博客(Spring Cloud中负载均衡器概览)中,我们大致的了解了一下Spring Cloud中有哪些负载均衡器,但是对于负载均衡策略我们并没有去详细了解,我们只是知道在BaseLoadBal ...
随机推荐
- 【转】Linux 系统如何处理名称解析
原文写的很好:https://blog.arstercz.com/linux-%E7%B3%BB%E7%BB%9F%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E5%90% ...
- js判断字符串是否以指定字符串开头或是否包含指定字符串
1. 用js判断一个字符串是否是以某个子字符串开头如:ssss001是否以ssss开头, 可以这样做: 1 2 3 4 5 6 var fdStart = strCode.indexOf(" ...
- 解决git 出现 Your account has been blocked问题
使用git 出现 Your account has been blocked 无法从远程pull代码下来, 解决方案如下: $ git push origin masterGitLab: Your a ...
- Densely semantically aligned person re-identification
Densely semantically aligned person re-identification https://arxiv.org/abs/1812.08967
- Android Studio的下载、安装与配置
1 下载 下载地址:http://www.android-studio.org/index.php ☟这里下载的是3.1.2版本 2 安装与配置 2.1 初步安装 默认就可以,点击红框内按钮依次进行安 ...
- 小程序 图表 antv f2 的使用
官方组件版 https://github.com/antvis/wx-f2/tree/custom-components 官方npm版 https://github.com/antvis/wx-f2 ...
- 查找算法(4)--Fibonacci search--斐波那契查找
1.斐波那契查找 (1)说明 在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割. 黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二, ...
- zookeeper+Dubbo环境搭建及简单Demo
1 安装zk https://www.cnblogs.com/feifeicui/p/11175502.html 2 安装 dubbo-admin https://www.cnblogs.com/fe ...
- nvarchar, varchar, nchar, char的差別
1. var,意思是可變動的,因為欄位長度可變動,所以會額外花費2Byte去儲存地址2. n,支援UNICODE UCS-2字元,因為萬國編碼(支援中文字),所以1字儲存2Byte nvarchar: ...
- Java分布式:分布式锁之数据库实现
Java分布式:分布式锁之数据库实现 分布式锁系列教程重点分享锁实现原理 锁实现原理 创建一张名为methodLock的数据库表,为方法名字段(method_name)添加唯一性约束. CREATE ...