一、负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 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接口

  1. package com.cetc.web.rest;
  2.  
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7.  
  8. @RestController
  9. @RequestMapping("/api/test")
  10. public class TestResource {
  11.  
  12. @Value("${server.port}")
  13. private Integer port;
  14.  
  15. @GetMapping("/getPort")
  16. public Integer getPort() {
  17. return port;
  18. }
  19. }

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

  a、添加依赖

  1.   <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-web</artifactId>
  13. </dependency>
  14. </dependencies>

  b、配置文件

  1. server:
  2. port: 8675
  3. eureka:
  4. instance:
  5. hostname: rest
  6. client:
  7. service-url:
  8. defaultZone:
  9. http://127.0.0.1:8670/eureka/ # 实际开发中建议使用域名的方式
  10. spring:
  11. application:
  12. name: rest

  c、效果

  

  4)编写配置文件

  1. package com.cetc.config;
  2.  
  3. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.client.RestTemplate;
  7.  
  8. @Configuration
  9. public class RibbonConfiguration {
  10.  
  11. @Bean
  12. @LoadBalanced
  13. public RestTemplate restTemplate() {
  14. return new RestTemplate();
  15. }
  16. }

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

  5)编写服务测试

  1. package com.cetc.service;
  2.  
  3. public interface IRibbonService {
  4. Integer getPort();
  5. }
  1. package com.cetc.service.impl;
  2.  
  3. import com.cetc.service.IRibbonService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.stereotype.Service;
  6. import org.springframework.web.client.RestTemplate;
  7.  
  8. @Service
  9. public class RibbonServiceImpl implements IRibbonService {
  10.  
  11. @Autowired
  12. private RestTemplate restTemplate;
  13.  
  14. @Override
  15. public Integer getPort() {
  16. return restTemplate.getForObject("http://client/api/test/getPort", Integer.class);
  17. }
  18. }

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

  1. package com.cetc.web.rest;
  2.  
  3. import com.cetc.service.IRibbonService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.cloud.client.ServiceInstance;
  6. import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
  7. import org.springframework.web.bind.annotation.GetMapping;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RestController;
  10.  
  11. @RestController
  12. @RequestMapping("/api/ribbon")
  13. public class RibbonResource {
  14.  
  15. @Autowired
  16. private IRibbonService ribbonService;
  17.  
  18. @GetMapping("/getPort")
  19. public Integer getPort() {
  20. return ribbonService.getPort();
  21. }
  22. }

  效果:

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

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

  1. package com.cetc.web.rest;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.cloud.client.ServiceInstance;
  4. import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8.  
  9. @RestController
  10. @RequestMapping("/api/ribbon")
  11. public class RibbonResource {
  12.  
  13. @Autowired
  14. private LoadBalancerClient loadBalancerClient;
  15.  
  16. @GetMapping("/testRibbon")
  17. public String testRibbon() {
  18. ServiceInstance client = loadBalancerClient.choose("client");
  19. return client.getHost() + ":" + client.getPort();
  20. }
  21. }

  测试效果:

  

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

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

  配置:

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

  测试:

  

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

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

  

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

  1. package com.netflix.loadbalancer;
  2.  
  3. import java.util.List;
  4.  
  5. public interface ILoadBalancer {
       //添加Server服务
  6. void addServers(List<Server> var1);
  7.    //选择服务
  8. Server chooseServer(Object var1);
  9.    //标注服务下线
  10. void markServerDown(Server var1);
  11.  
  12. /** @deprecated */
  13. @Deprecated
  14. List<Server> getServerList(boolean var1);
  15.    //获取可用服务
  16. List<Server> getReachableServers();
  17.    //获取全部服务
  18. List<Server> getAllServers();
  19. }

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

  

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

  1.    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
  2. super(clientConfig, rule, ping);
  3. this.isSecure = false;
  4. this.useTunnel = false;
  5. this.serverListUpdateInProgress = new AtomicBoolean(false);
  6. this.updateAction = new UpdateAction() {
  7. public void doUpdate() {
  8. DynamicServerListLoadBalancer.this.updateListOfServers();
  9. }
  10. };
  11. this.serverListImpl = serverList;
  12. this.filter = filter;
  13. this.serverListUpdater = serverListUpdater;
  14. if (filter instanceof AbstractServerListFilter) {
  15. ((AbstractServerListFilter)filter).setLoadBalancerStats(this.getLoadBalancerStats());
  16. }
  17.  
  18. this.restOfInit(clientConfig);
  19. }
  20.  
  21. public DynamicServerListLoadBalancer(IClientConfig clientConfig) {
  22. this.isSecure = false;
  23. this.useTunnel = false;
  24. this.serverListUpdateInProgress = new AtomicBoolean(false);
  25. this.updateAction = new UpdateAction() {
  26. public void doUpdate() {
  27. DynamicServerListLoadBalancer.this.updateListOfServers();
  28. }
  29. };
  30. this.initWithNiwsConfig(clientConfig);
  31. }

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

  

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

  

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

  1. @VisibleForTesting
  2. public void updateListOfServers() {
  3. List<T> servers = new ArrayList();
  4. if (this.serverListImpl != null) {
  5. servers = this.serverListImpl.getUpdatedListOfServers();
  6. LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
  7. if (this.filter != null) {
  8. servers = this.filter.getFilteredListOfServers((List)servers);
  9. LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
  10. }
  11. }
  12.  
  13. this.updateAllServerList((List)servers);
  14. }

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

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

  

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

  1. private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
  2. List<DiscoveryEnabledServer> serverList = new ArrayList();
  3. if (this.eurekaClientProvider != null && this.eurekaClientProvider.get() != null) {
  4. EurekaClient eurekaClient = (EurekaClient)this.eurekaClientProvider.get();
  5. if (this.vipAddresses != null) {
  6. String[] var3 = this.vipAddresses.split(",");
  7. int var4 = var3.length;
  8.  
  9. for(int var5 = 0; var5 < var4; ++var5) {
  10. String vipAddress = var3[var5];
  11. List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
  12. Iterator var8 = listOfInstanceInfo.iterator();
  13.  
  14. while(var8.hasNext()) {
  15. InstanceInfo ii = (InstanceInfo)var8.next();
  16. if (ii.getStatus().equals(InstanceStatus.UP)) {
  17. if (this.shouldUseOverridePort) {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("Overriding port on client name: " + this.clientName + " to " + this.overridePort);
  20. }
  21.  
  22. InstanceInfo copy = new InstanceInfo(ii);
  23. if (this.isSecure) {
  24. ii = (new Builder(copy)).setSecurePort(this.overridePort).build();
  25. } else {
  26. ii = (new Builder(copy)).setPort(this.overridePort).build();
  27. }
  28. }
  29.  
  30. DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, this.isSecure, this.shouldUseIpAddr);
  31. des.setZone(DiscoveryClient.getZone(ii));
  32. serverList.add(des);
  33. }
  34. }
  35.  
  36. if (serverList.size() > 0 && this.prioritizeVipAddressBasedServers) {
  37. break;
  38. }
  39. }
  40. }
  41.  
  42. return serverList;
  43. } else {
  44. logger.warn("EurekaClient has not been initialized yet, returning an empty list");
  45. return new ArrayList();
  46. }
  47. }

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

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

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

  1.   void setupPingTask() {
  2. if (!this.canSkipPing()) {
  3. if (this.lbTimer != null) {
  4. this.lbTimer.cancel();
  5. }
  6.  
  7. this.lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + this.name, true);
  8. this.lbTimer.schedule(new BaseLoadBalancer.PingTask(), 0L, (long)(this.pingIntervalSeconds * 1000));
  9. this.forceQuickPing();
  10. }
  11. }

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

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

  1.    @Bean
  2. public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
  3. return () -> {
  4. restTemplateCustomizers.ifAvailable((customizers) -> {
  5. Iterator var2 = this.restTemplates.iterator();
  6.  
  7. while(var2.hasNext()) {
  8. RestTemplate restTemplate = (RestTemplate)var2.next();
  9. Iterator var4 = customizers.iterator();
  10.  
  11. while(var4.hasNext()) {
  12. RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
  13. customizer.customize(restTemplate);
  14. }
  15. }
  16.  
  17. });
  18. };
  19. }

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

  

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

  这里的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. [算法模板]ST表

    [算法模板]ST表 ST表和线段树一样,都能解决RMQ问题(范围最值查询-Range Minimum Query). 我们开一个数组数组\(f[maxn][maxn\log_2]\)来储存数据. 定义 ...

  2. mongo helper

    import datetime import pymongo import click # 数据库基本信息 db_configs = { 'type': 'mongo', 'host': '127.0 ...

  3. 集合类 Map接口 HashTable

    集合类的另外一种重要实现为Map接口,Map接口提供的方法如下: Map接口一个不常见实现为HashTable,HashTable对所有有并发访问问题的方法通过 synchronized 关键字进行并 ...

  4. java中HashSet对象内的元素的hashCode值不能变化

    因为不管是HashMap(或HashTable,还是HashSet),key值是以hashCode值存进去的,加入key值变了,将无法从集合内删除对象,导致内存溢出.

  5. zabbix使用自动发现功能批量监控服务器端口的可用性

    使用自动发现脚本批量监控服务器端口的可用性 .编写自动发现脚本 # cat /usr/local/zabbix_agents_3.2.0/scripts/web_site_code_status.sh ...

  6. some try on func swap about & and *

    import "fmt" func swap(x,y *int ) { //x ,y = y,x //fault /* t := *x *x = *y *y = t */ //tr ...

  7. 基于realsense的深度数据分析工具

  8. java Random 随机密码

    /** * Created by xc on 2019/11/23 * 生成随机密码:6位数字 */public class Test7_4 { public static void main(Str ...

  9. SpringBoot处理异常方式

    SpringBoot提供了多种处理异常方式,以下为常用的几种 1. 自定义错误异常页面 SpringBoot默认的处理异常的机制:SpringBoot默认的已经提供了一套处理异常的机制.一旦程序中出现 ...

  10. 【翻译】Flink Table Api & SQL — 流概念

    本文翻译自官网:Streaming Concepts  https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/st ...