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

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

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

@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}

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

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

先从拦截器LoadBalancerInterceptor开始介绍:

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

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
} @Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ClientHttpResponse>() {
@Override
public ClientHttpResponse apply(final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request,
instance, loadBalancer);
return execution.execute(serviceRequest, body);
} });
}
}

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

1)顶层接口

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

public interface ServiceInstanceChooser {

    ServiceInstance choose(String serviceId);
}

2)继承接口

LoadBalancerClient

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

public interface LoadBalancerClient extends ServiceInstanceChooser {

	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

	URI reconstructURI(ServiceInstance instance, URI original);
}

3)实现类



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

主要做了一下几件事:

根据serviceId获取负载均衡器;

根据负载均衡器获取server;

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

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request);
} @Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); try {
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}

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

	public interface ServiceInstance {

		String getServiceId();
String getHost();
int getPort();
boolean isSecure(); URI getUri(); Map<String, String> getMetadata();
}

RibbonServer——服务实例实现类

	protected static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map<String, String> metadata; protected RibbonServer(String serviceId, Server server) {
this(serviceId, server, false, Collections.<String, String> emptyMap());
} protected RibbonServer(String serviceId, Server server, boolean secure,
Map<String, String> metadata) {
this.serviceId = serviceId;
this.server = server;
this.secure = secure;
this.metadata = metadata;
} //省去getter和setter
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("RibbonServer{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append(", server=").append(server);
sb.append(", secure=").append(secure);
sb.append(", metadata=").append(metadata);
sb.append('}');
return sb.toString();
}
}

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

部分代码r

public Server chooseServer(Object key) {		        Server server = null;
try {
//获取可用区域Zone
LoadBalancerStats lbStats = getLoadBalancerStats();
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Throwable e) {
logger.error("Unexpected exception when choosing server using zone aware logic", e);
}
if (server != null) {
return server;
}
}

具体的分配算法:

static String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
Set<String> chooseFrom) {
if (chooseFrom == null || chooseFrom.size() == 0) {
return null;
}
String selectedZone = chooseFrom.iterator().next();
if (chooseFrom.size() == 1) {
return selectedZone;
}
int totalServerCount = 0;
for (String zone : chooseFrom) {
totalServerCount += snapshot.get(zone).getInstanceCount();
}
int index = random.nextInt(totalServerCount) + 1;
int sum = 0;
for (String zone : chooseFrom) {
sum += snapshot.get(zone).getInstanceCount();
if (index <= sum) {
selectedZone = zone;
break;
}
}
return selectedZone;
}

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. vue 解决跨域问题

    1.后端处理允许跨域 2.反向代理跨域 代理服务器英文全称是Proxy Server,其功能就是代理网络用户去去的网络信息.形象的说:它是网络信息的中转站. vue中设置代理: 1.config/in ...

  2. (译)我为什么用Go语言来做区块链——Syed Jafar Naqvi——Co-Founder/CEO at Karachain

    原文地址:https://medium.com/karachain/why-i-am-building-a-blockchain-in-go-6395a60b24dd Go语言现在常常被用来做去中心化 ...

  3. css3动画性能优化--针对移动端卡顿问题

    一.使用css,jquery,canvas制作动画 1.Canvas 优点:性能好,强大,支持多数浏览器(除了IE6.IE7.IE8),画出来的图形可以直接保存为 .png 或者 .jpg的图形: 缺 ...

  4. (数据科学学习手札47)基于Python的网络数据采集实战(2)

    一.简介 马上大四了,最近在暑期实习,在数据挖掘的主业之外,也帮助同事做了很多网络数据采集的内容,接下来的数篇文章就将一一罗列出来,来续写几个月前开的这个网络数据采集实战的坑. 二.马蜂窝评论数据采集 ...

  5. kubernetes基本概念和术语

    一. kubernetes是什么 kubernetes是一个全新得基于容器技术得分布式架构领先方案.是google Borg得一个开源版本.Borg是google得一个久负盛名得内部使用得大规模集群管 ...

  6. 《阿里巴巴Java开发手册》代码格式部分应用——idea中checkstyle的使用教程

    <阿里巴巴Java开发手册>代码格式部分应用--idea中checkstyle的使用教程 1.<阿里巴巴Java开发手册> 这是阿里巴巴工程师送给各位软件工程师的宝典,就像开车 ...

  7. 20155232 实验四 Android程序设计

    20155232 实验四 Android程序设计 一.实验内容 1.基于Android Studio开发简单的Android应用并部署测试; 2.了解Android.组件.布局管理器的使用: 3.掌握 ...

  8. molokai

    git clone https://github.com/windy/ruby-vimrc.git~/.vim/colors/

  9. 【BZOJ4566】[HAOI2016]找相同字符

    [BZOJ4566][HAOI2016]找相同字符 题面 给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数.两个方案不同当且仅当这两个子串中有一个位置不同. 其中\(1\le ...

  10. Ansible开发之路

    一.初识Ansible 链接:https://www.cnblogs.com/baishuchao/articles/9164083.html 二.Ansible的架构 链接:https://www. ...