一、负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 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. 【BIEE】导出大量数据报错处理请求时出现致命错误。服务器响应为 com.siebel.analytics.utils.InputStreamWithLimit$ReadOverTheLimitException

    问题描述 在使用BIEE导出大量数据的时候,报错如下: 处理请求时出现致命错误.服务器响应为: com.siebel.analytics.utils.InputStreamWithLimit$Read ...

  2. bzoj4868 期末考试 题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4868 显然我们只关注最后出分的学科. 刚开始想的是dp,然而不知道如何记录状态. 突然就想到了正解 ...

  3. Asp.net 与 Core .net 用法区别

    1.  定义一个类 如下,注意int?这里 public class A{ public int? num{get;set;} } 2. 如果传递的参数不能转换成int类型,则core里面接受不了参数 ...

  4. linux安装yasm报错

    进入yasm-1.2.0, 输入指令 ./configure //编译yasm make && make install //安装yasm,安装完成即可. 报错信息 make[2]: ...

  5. ThreadPoolExecutor的坑

    ExecutorService executorService = new ThreadPoolExecutor(0, MAX_THREAD_NUM, 60, TimeUnit.SECONDS, ne ...

  6. TOKEN验证防止CSRF攻击的原理

    TOKEN验证防止CSRF攻击的原理.CSRF中文名是跨站请求伪造攻击,黑客可以通过CSRF攻击来伪造我们的身份,从而进行不法的活动.比如说是以我们的身份进行转账,发送邮件等操作. 要想做到预防CSR ...

  7. 【基础】Qt SCXML Calculator QML Example

    Qt SCXML Calculator QML Example 这个系统自带的例子原本主要是用来说明SCXML机制的,但是由于计算器的经典和简洁,我认为用来练习QML非常合适,原本的例子还有一些问题, ...

  8. 查找算法(7)--Hash search--哈希查找

    1.哈希查找 (1)什么是哈希表(Hash) 我们使用一个下标范围比较大的数组来存储元素.可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用 ...

  9. 深入理解JVM虚拟机

    JVM平台上还可以运行其他语言,运行的是Class字节码.只要能翻译成Class的语言就OK了.挺强大的. JVM厂商很多 垃圾收集器.收集算法 JVM检测工具 关于类的加载: Java代码中,类型( ...

  10. Ubuntu 安装docker CE以及harbor

    Docker CE安装 系统建议版本:Ubuntu 16.04 官方安装文档连接:https://docs.docker.com/install/linux/docker-ce/ubuntu/#pre ...