Ribbon源码解析
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源码解析的更多相关文章
- 超详细的Ribbon源码解析
Ribbon简介 什么是Ribbon? Ribbon是springcloud下的客户端负载均衡器,消费者在通过服务别名调用服务时,需要通过Ribbon做负载均衡获取实际的服务调用地址,然后通过http ...
- spring cloud ribbon源码解析(二)
在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...
- spring cloud ribbon源码解析(一)
我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...
- SpringCloud服务调用源码解析汇总
相信我,你会收藏这篇文章的,本篇文章涉及Ribbon.Hystrix.Feign三个组件的源码解析 Ribbon架构剖析 这篇文章介绍了Ribbon的基础架构,也就是下图涉及到的6大组件: Ribbo ...
- Eureka源码解析系列文章汇总
先看一张图 0 这个图是Eureka官方提供的架构图,整张图基本上把整个Eureka的核心功能给列出来了,当你要阅读Eureka的源码时可以参考着这个图和下方这些文章 EurekaServer Eur ...
- springcloud源码解析(目录)
springcloud是一个基于springboot的一站式企业级分布式应用开发框架.springboot为其提供了创建单一项目的便利性,springcloud组合了现有的.常用的分布式项目的解决方案 ...
- Feign 系列(05)Spring Cloud OpenFeign 源码解析
Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
随机推荐
- 浅谈Vue响应式(数组变异方法)
很多初使用Vue的同学会发现,在改变数组的值的时候,值确实是改变了,但是视图却无动于衷,果然是因为数组太高冷了吗? 查看官方文档才发现,不是女神太高冷,而是你没用对方法. 看来想让女神自己动,关键得用 ...
- R语言学习笔记(二十一五):如何如何提升R语言运算的性能以及速度
在R中获得快速运行代码的方法 使用向量化运算 R语言的并行计算可以用parallel和foreach包 加快R运行速度还可以使用cmpfun()函数即字节码编译器 再者就是在R中调用C或C++ 同时还 ...
- java一些封装好的常用算法
1.简单排序Collections.sort(): //简单排序 List<String> staff= new LinkedList<>(); staff.add(" ...
- 20155215 《Java程序设计》实验二( Java面向对象程序设计)实验报告
20155215 <Java程序设计>实验二( Java面向对象程序设计)实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉 ...
- 20155227 2016-2017-2 《Java程序设计》第二周学习总结
20155227 2016-2017-2 <Java程序设计>第二周学习总结 教材学习内容总结 本周学习的第三章内容与以前学过的C语言有很多共通的地方,学习起来还是比较快的. 主要的内容有 ...
- 20155321 《Java程序设计》实验报告一:Java开发环境的熟悉(Windows+IDEA)
实验要求 使用JDK编译.运行简单的Java程序: 使用IDEA 编辑.编译.运行.调试Java程序. 实验内容 命令行下Java程序开发 打开windows下的cmd → 输入cd Code命令进入 ...
- apt-get 0%
错误如下: www.2cto.com 使用apt-get命令安装软件时出现“0%[正在等待报头]“的提示且无法继续安装,一般出现此问题是因为安装过程中Ctrl+C强行中断或其它原因导致上 ...
- DSP5509项目之用FFT识别钢琴音调(4)之麦克风输入和Line in输入
1. 麦克风输入需要修改的内容,之前的版本是LINE IN的输入.实现功能,检测麦克风的输入,并且同时在耳机里面播放. #include <csl.h> #include <csl_ ...
- Spring学习(二)-----eclipse新建spring项目
一:准本工作(下载需要的jar包) 1.下载准备Spring-framework-4.2.0 链接为: http://repo.springsource.org/libs-release-local/ ...
- 如何运用 Powershell 修改Office365和AD账户
这段时间需要大量地修改AD用户的一些属性,例如邮件,UPN,登录名等等,以便和Office365的登录账号保持一致.写了个简单脚本进行批量修改. #Import AD ModuleImport-Mod ...