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

    Zuma 这个题没有素质!它卡常! 我发现网上很多人的题解都写得很奇怪,也不好确定正确性,所以我借这篇题解表达一下愚见 定义$ dp[i][j][0...4]$表示 0:消完了 1:还剩1个0 2:还 ...

  2. 终于明白为什么要加 final 关键字了!

    阅读本文大概需要 2.8 分钟. 来源: www.jianshu.com/p/acc8d9a67d0c 在开发过程中,由于习惯的原因,我们可能对某种编程语言的一些特性习以为常,特别是只用一种语言作为日 ...

  3. [原创]敏捷管理实践Scrum思维导图

    [原创]敏捷管理实践Scrum思维导图

  4. jquery实现select数据回显

    [html] view plain copy     <select class="div_select_re" id="edit_technicalGrade&q ...

  5. JAVA字符编码二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换

    第二篇:JAVA字符编码系列二:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换   1.函数介绍 在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有 ...

  6. 自顶向下深入分析Netty(三)--Bootstrap

    自顶向下深入分析Netty(一)--预备知识 自顶向下深入分析Netty(二)--线程模型 自顶向下深入分析Netty(三)--Bootstrap 自顶向下深入分析Netty(四)--EventLoo ...

  7. Python中的日志记录方案-logging模块&loguru模块

    原文链接 原创: 崔庆才 在 Python 中,一般情况下我们可能直接用自带的 logging 模块来记录日志,包括我之前的时候也是一样.在使用时我们需要配置一些 Handler.Formatter ...

  8. 移除 WordPress 自动加载的 jQuery,使用自定义 jQuery 版本

    WordPress 使用的 jQuery 版本由于需要考虑到很多安全稳定的因素,所以一般不会使用最新版本的 jQuery, 可以通过以下方式移除 WordPress 自定加载的 jQuery,并加载自 ...

  9. 什么是依赖注入 IoC

    设计原则:依赖注入原则 依赖倒置原则,是一种程序设计模式的原则 高层模块不应该依赖底层模块,二者都应该依赖其抽象. 抽象不应该依赖细节,细节应该依赖抽象.依赖导致原则很好的体现了“面向接口编程”的思想 ...

  10. (转)Ngx_Lua使用分享

    原文:https://www.cnblogs.com/yanzi-meng/p/9450999.html ngx_lua 模块详细讲解(基于openresty)---https://www.cnblo ...