SpringCloud中的Ribbon开源项目,提供了客户端的负载均衡算法。这篇文章,我们来介绍下他是如何实现的。为了方便理解,我们以客户端调用的流程来介绍,其中会穿插介绍相关源代码。

简单回顾下Ribbon的使用,这里强调两点:

1、在启动类Application中,添加@LoadBalanced注解。

  1. @Bean
  2. @LoadBalanced
  3. RestTemplate restTemplate() {
  4. return new RestTemplate();
  5. }

2、结合RestTemplate发起调用,调用时采用服务名称(如:COMPUTE-SERVICE)来实现。

  1. return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();

先从拦截器LoadBalancerInterceptor开始介绍:

从请求中获取服务名称,即上文说到的COMPUTE-SERVICE,然后执行LoadBalancerClient的execute方法。

  1. public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
  2.  
  3. private LoadBalancerClient loadBalancer;
  4.  
  5. public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
  6. this.loadBalancer = loadBalancer;
  7. }
  8.  
  9. @Override
  10. public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  11. final ClientHttpRequestExecution execution) throws IOException {
  12. final URI originalUri = request.getURI();
  13. String serviceName = originalUri.getHost();
  14. return this.loadBalancer.execute(serviceName,
  15. new LoadBalancerRequest<ClientHttpResponse>() {
  16. @Override
  17. public ClientHttpResponse apply(final ServiceInstance instance)
  18. throws Exception {
  19. HttpRequest serviceRequest = new ServiceRequestWrapper(request,
  20. instance, loadBalancer);
  21. return execution.execute(serviceRequest, body);
  22. }
  23.  
  24. });
  25. }
  26. }

这里,先简单介绍下这几个类。

1)顶层接口

实现该接口的类,会使用一个负载均衡器来选择一个server来转发请求。

  1. public interface ServiceInstanceChooser {
  2.  
  3. ServiceInstance choose(String serviceId);
  4. }

2)继承接口

LoadBalancerClient

提供了两种不同的参数的execute()执行方法。

  1. public interface LoadBalancerClient extends ServiceInstanceChooser {
  2.  
  3. <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
  4.  
  5. <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
  6.  
  7. URI reconstructURI(ServiceInstance instance, URI original);
  8. }

3)实现类



在RibbonLoadBalancerClient类中,可以看到具体的执行方法。

主要做了一下几件事:

根据serviceId获取负载均衡器;

根据负载均衡器获取server;

将请求转到具体的服务实例。

  1. @Override
  2. public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  3. ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  4. Server server = getServer(loadBalancer);
  5. if (server == null) {
  6. throw new IllegalStateException("No instances available for " + serviceId);
  7. }
  8. RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
  9. serviceId), serverIntrospector(serviceId).getMetadata(server));
  10.  
  11. return execute(serviceId, ribbonServer, request);
  12. }
  13.  
  14. @Override
  15. public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  16. Server server = null;
  17. if(serviceInstance instanceof RibbonServer) {
  18. server = ((RibbonServer)serviceInstance).getServer();
  19. }
  20. if (server == null) {
  21. throw new IllegalStateException("No instances available for " + serviceId);
  22. }
  23.  
  24. RibbonLoadBalancerContext context = this.clientFactory
  25. .getLoadBalancerContext(serviceId);
  26. RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
  27.  
  28. try {
  29. T returnVal = request.apply(serviceInstance);
  30. statsRecorder.recordStats(returnVal);
  31. return returnVal;
  32. }
  33. // catch IOException and rethrow so RestTemplate behaves correctly
  34. catch (IOException ex) {
  35. statsRecorder.recordStats(ex);
  36. throw ex;
  37. }
  38. catch (Exception ex) {
  39. statsRecorder.recordStats(ex);
  40. ReflectionUtils.rethrowRuntimeException(ex);
  41. }
  42. return null;
  43. }

其中,ServiceInstance——服务实例接口

  1. public interface ServiceInstance {
  2.  
  3. String getServiceId();
  4. String getHost();
  5. int getPort();
  6. boolean isSecure();
  7.  
  8. URI getUri();
  9.  
  10. Map<String, String> getMetadata();
  11. }

RibbonServer——服务实例实现类

  1. protected static class RibbonServer implements ServiceInstance {
  2. private final String serviceId;
  3. private final Server server;
  4. private final boolean secure;
  5. private Map<String, String> metadata;
  6.  
  7. protected RibbonServer(String serviceId, Server server) {
  8. this(serviceId, server, false, Collections.<String, String> emptyMap());
  9. }
  10.  
  11. protected RibbonServer(String serviceId, Server server, boolean secure,
  12. Map<String, String> metadata) {
  13. this.serviceId = serviceId;
  14. this.server = server;
  15. this.secure = secure;
  16. this.metadata = metadata;
  17. }
  18.  
  19. //省去getter和setter
  20. @Override
  21. public String toString() {
  22. final StringBuffer sb = new StringBuffer("RibbonServer{");
  23. sb.append("serviceId='").append(serviceId).append('\'');
  24. sb.append(", server=").append(server);
  25. sb.append(", secure=").append(secure);
  26. sb.append(", metadata=").append(metadata);
  27. sb.append('}');
  28. return sb.toString();
  29. }
  30. }

getServer()中,默认使用的ZoneAwareLoadBalancer负载均衡器。

部分代码r

  1. public Server chooseServer(Object key) { Server server = null;
  2. try {
  3. //获取可用区域Zone
  4. LoadBalancerStats lbStats = getLoadBalancerStats();
  5. Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
  6. Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
  7. logger.debug("Available zones: {}", availableZones);
  8. if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
  9. String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
  10. logger.debug("Zone chosen: {}", zone);
  11. if (zone != null) {
  12. BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
  13. server = zoneLoadBalancer.chooseServer(key);
  14. }
  15. }
  16. } catch (Throwable e) {
  17. logger.error("Unexpected exception when choosing server using zone aware logic", e);
  18. }
  19. if (server != null) {
  20. return server;
  21. }
  22. }

具体的分配算法:

  1. static String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
  2. Set<String> chooseFrom) {
  3. if (chooseFrom == null || chooseFrom.size() == 0) {
  4. return null;
  5. }
  6. String selectedZone = chooseFrom.iterator().next();
  7. if (chooseFrom.size() == 1) {
  8. return selectedZone;
  9. }
  10. int totalServerCount = 0;
  11. for (String zone : chooseFrom) {
  12. totalServerCount += snapshot.get(zone).getInstanceCount();
  13. }
  14. int index = random.nextInt(totalServerCount) + 1;
  15. int sum = 0;
  16. for (String zone : chooseFrom) {
  17. sum += snapshot.get(zone).getInstanceCount();
  18. if (index <= sum) {
  19. selectedZone = zone;
  20. break;
  21. }
  22. }
  23. return selectedZone;
  24. }

Ribbon源码解析的更多相关文章

  1. 超详细的Ribbon源码解析

    Ribbon简介 什么是Ribbon? Ribbon是springcloud下的客户端负载均衡器,消费者在通过服务别名调用服务时,需要通过Ribbon做负载均衡获取实际的服务调用地址,然后通过http ...

  2. spring cloud ribbon源码解析(二)

    在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...

  3. spring cloud ribbon源码解析(一)

    我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...

  4. SpringCloud服务调用源码解析汇总

    相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...

  5. Eureka源码解析系列文章汇总

    先看一张图 0 这个图是Eureka官方提供的架构图,整张图基本上把整个Eureka的核心功能给列出来了,当你要阅读Eureka的源码时可以参考着这个图和下方这些文章 EurekaServer Eur ...

  6. springcloud源码解析(目录)

    springcloud是一个基于springboot的一站式企业级分布式应用开发框架.springboot为其提供了创建单一项目的便利性,springcloud组合了现有的.常用的分布式项目的解决方案 ...

  7. Feign 系列(05)Spring Cloud OpenFeign 源码解析

    Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  9. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

随机推荐

  1. python学习之python入门

    一.第一句Python代码 1.在d:/test_py目录下新建一个test.py文件,并在其中写上如下内容: print("Hello World") 2.在cmd命令行下执行t ...

  2. 从零开始一个http服务器(五)-模拟cgi

    从零开始一个http服务器-模拟cgi(五) 代码地址 : https://github.com/flamedancer/cserver git checkout step5 运行: make cle ...

  3. wav2midi 音乐旋律提取算法 附可执行demo

    前面提及过,音频指纹算法的思路. 也梳理开源了两个比较经典的算法. https://github.com/cpuimage/shazam https://github.com/cpuimage/Aud ...

  4. go_json解析

    总结: 其他类型转json func Marshal(v interface{}) ([]byte, error)  json 转其他类型 func Unmarshal(data []byte, v ...

  5. 5.18-笨办法学python-习题16(write)

    from sys import argv script,filename=argv #固定模式啦 print("we're going to erase %r."%filename ...

  6. hash环/consistent hashing一致性哈希算法

        一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的 ...

  7. 20155212——man指令的使用及mypwd的实现

    man指令的使用及mypwd的实现 man指令的使用 一.man -k的k参数以及代表的意思 代号 代表內容 1 使用者在shell中可以操作的指令或可执行档 2 系統核心可呼叫的函数与工具等 3 一 ...

  8. PostgreSQL 使用 LDAP 认证方式

    磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面: PostgreSQL杂记页     回到顶级页面:PostgreSQL索引页 [作者 高健@博客园  luckyjackgao@gmail. ...

  9. day2 CSS- 选择器

    1.CSS 语法 css是英文Cascading Style Sheets的缩写,称为层叠样式表 2.css的四种引入方式 1.行内式 行内式是在标记的style属性中设定CSS样式.这种方式没有体现 ...

  10. codevs 5429 多重背包

    5429 多重背包 http://codevs.cn/problem/5429 分析: f[i]=g[j-k*siz[i]]+k*val[i]; 发现一个状态d只会更新,d+siz[i],d+2*si ...