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 ...
随机推荐
- python学习之python入门
一.第一句Python代码 1.在d:/test_py目录下新建一个test.py文件,并在其中写上如下内容: print("Hello World") 2.在cmd命令行下执行t ...
- 从零开始一个http服务器(五)-模拟cgi
从零开始一个http服务器-模拟cgi(五) 代码地址 : https://github.com/flamedancer/cserver git checkout step5 运行: make cle ...
- wav2midi 音乐旋律提取算法 附可执行demo
前面提及过,音频指纹算法的思路. 也梳理开源了两个比较经典的算法. https://github.com/cpuimage/shazam https://github.com/cpuimage/Aud ...
- go_json解析
总结: 其他类型转json func Marshal(v interface{}) ([]byte, error) json 转其他类型 func Unmarshal(data []byte, v ...
- 5.18-笨办法学python-习题16(write)
from sys import argv script,filename=argv #固定模式啦 print("we're going to erase %r."%filename ...
- hash环/consistent hashing一致性哈希算法
一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的 ...
- 20155212——man指令的使用及mypwd的实现
man指令的使用及mypwd的实现 man指令的使用 一.man -k的k参数以及代表的意思 代号 代表內容 1 使用者在shell中可以操作的指令或可执行档 2 系統核心可呼叫的函数与工具等 3 一 ...
- PostgreSQL 使用 LDAP 认证方式
磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面: PostgreSQL杂记页 回到顶级页面:PostgreSQL索引页 [作者 高健@博客园 luckyjackgao@gmail. ...
- day2 CSS- 选择器
1.CSS 语法 css是英文Cascading Style Sheets的缩写,称为层叠样式表 2.css的四种引入方式 1.行内式 行内式是在标记的style属性中设定CSS样式.这种方式没有体现 ...
- 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 ...