前言

SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃)。

一起看看他们的实现(重点还是一致性 hash)。

源码分析

具体源码在 AbstractLoadBalancer 类中,子类需要实现 doSelect 方法:


public abstract ProviderInfo doSelect(SofaRequest invocation, List<ProviderInfo> providerInfos);

随机是默认算法,RandomLoadBalancer 类是具体实现,基本是就是 providerInfos.get(random.nextInt(size)) 的逻辑,但考虑到权重,会按总权重数随机找个数字,然后这个数字会递减直到小于 0 的时候,确定那个节点。好像看起来和权重没什么关系? 有大佬懂的可以指导一下。

本地优先算法,则是找本机的 localhost 进行匹配,优先选择和本机地址相同的服务,然后在这些服务列表进行随机选一个。

轮询就是一个个来。使用取于算法。

然后就是一致性 Hash 了,重点讲讲。 有必要复习一下我们之前写过的一致性 hash 算法 demo: 自己实现一个一致性 Hash 算法

SOFA 具体实现是 ConsistentHashLoadBalancer 类。内部维护一个 Map,每个服务对应一个选择器,这个选择器内部维护着一个 TreeMap,SOFA 会将所有节点均匀的散列在 Map 中,也就是 hash 环上,使用了虚拟节点。当根据服务的 key 获取节点的时候(如果服务列表没变),会通过 hash 值找到比他大的那个节点,相同的请求每次找到的都是同一个节点(根据第一个参数 hash)。

来看看具体实现。

先看看 doSelect 方法:

@Override
public ProviderInfo doSelect(SofaRequest request, List<ProviderInfo> providerInfos) {
String interfaceId = request.getInterfaceName();
String method = request.getMethodName();
String key = interfaceId + "#" + method;
int hashcode = providerInfos.hashCode(); // 判断是否同样的服务列表
Selector selector = selectorCache.get(key);
if (selector == null // 原来没有
||
selector.getHashCode() != hashcode) { // 或者服务列表已经变化
selector = new Selector(interfaceId, method, providerInfos, hashcode);
selectorCache.put(key, selector);
}
return selector.select(request);
}

根据接口名和方法名从 map 中找到对应的服务选择器,如果没有,或者服务列表变了,则重新创建一个,这点和缓存的一致性 Hash 设计还是有点不一样。

缓存的一致性 Hash 的目的是:如果服务列表变了,比如节点的增减,那么,缓存的 key 通过相同的 hash 算法依然能够找到对应的缓存节点(最多失效一个节点的数据——如果增减一个节点)。

但 RPC 服务的一致性 hash 的目的是:希望相同的请求总是落在同一个节点上。

而这里无法确定增加的是哪一个节点,索性直接创建一个新的。

然后,调用选择的 select 方法返回一个服务节点。

先看看选择器的构造方法:

public Selector(String interfaceId, String method, List<ProviderInfo> actualNodes, int hashcode) {
this.interfaceId = interfaceId;
this.method = method;
this.hashcode = hashcode;
// 创建虚拟节点环 (默认一个provider共创建128个虚拟节点,较多比较均匀)
this.virtualNodes = new TreeMap<Long, ProviderInfo>();
int num = 128;
for (ProviderInfo providerInfo : actualNodes) {
for (int i = 0; i < num / 4; i++) {
byte[] digest = messageDigest(providerInfo.getHost() + providerInfo.getPort() + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualNodes.put(m, providerInfo);
}
}
}
}

主要逻辑就是构造虚拟节点,使用 TreeMap,和我们之前实现的一样。那么虚拟节点是如何设计的呢?

SOFA 为每个节点分配了 128 个虚拟节点,保存在 Map 中,也就是 128 个引用指向同一个对象。这里的 hash 算法用来 md5 然后再复杂的 hash 一波,为了更加的均衡吧。

当使用 select 方法的时候,怎么找到相同的节点呢?

代码:

private ProviderInfo sekectForKey(long hash) {
ProviderInfo providerInfo = virtualNodes.get(hash);
if (providerInfo == null) {
SortedMap<Long, ProviderInfo> tailMap = virtualNodes.tailMap(hash);
if (tailMap.isEmpty()) {
hash = virtualNodes.firstKey();
} else {
hash = tailMap.firstKey();
}
providerInfo = virtualNodes.get(hash);
}
return providerInfo;
}

hash 该方法第一个参数,找到比他的 hash 值大节点集合中的第一个节点,如果没有比他大的,则最小的那个节点(回到原点)。

标准的一致性 hash 算法。保证了每次相同的请求都会落在同一个节点上。

总结

RPC 的一致性 hash 和缓存的一致性 hash 的目的是不同的。

缓存的目的是:当集群中缓存节点增减的时候,服务访问相同 key 依然能够访问到相同的节点(增减造成的失效节点很少)。不会像普通的取于算法那样造成无法访问,进而引起缓存雪崩,甚至 DB 宕机。

而 RPC 的目的是:希望相同的请求(第一个参数相同),每次都会打在相同的节点上。

换个角度想想,其实都是一样的,目的都是为了相同的请求每次都访问到相同的节点。

好啦,关于 SOFA 的负载均衡就到这里啦。

bye!!!

SOFA 源码分析 — 负载均衡和一致性 Hash的更多相关文章

  1. Spring Cloud Ribbon 源码分析---负载均衡算法

    上一篇分析了Ribbon如何发送出去一个自带负载均衡效果的HTTP请求,本节就重点分析各个算法都是如何实现. 负载均衡整体是从IRule进去的: public interface IRule{ /* ...

  2. Spring Cloud Ribbon源码分析---负载均衡实现

    上一篇结合 Eureka 和 Ribbon 搭建了服务注册中心,利用Ribbon实现了可配置负载均衡的服务调用.这一篇我们来分析Ribbon实现负载均衡的过程. 从 @LoadBalanced入手 还 ...

  3. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  4. SOFA 源码分析 — 自动故障剔除

    前言 集群中通常一个服务有多个服务提供者.其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应.单机故障剔除功能会将这部分异常的服 ...

  5. SOFA 源码分析 — 预热权重

    前言 SOFA-RPC 支持根据权重对服务进行预热功能,具体地址:预热权重. 引用官方文档: 预热权重功能让客户端机器能够根据服务端的相应权重进行流量的分发.该功能也常被用于集群内少数机器的启动场景. ...

  6. SOFA 源码分析 — 调用方式

    前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...

  7. SOFA 源码分析— 事件总线

    前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...

  8. SOFA 源码分析 — 自定义线程池原理

    前言 在 SOFA-RPC 的官方介绍里,介绍了自定义线程池,可以为指定服务设置一个独立的业务线程池,和 SOFARPC 自身的业务线程池是隔离的.多个服务可以共用一个独立的线程池. API使用方式如 ...

  9. SOFA 源码分析 — 链路数据透传

    前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...

随机推荐

  1. 实战项目:通讯录&nbsp;UI—第十一天

     1.推出视图的两种方式:  1.通过导航控制器push到下一个界面,使用pop返回到上一个界面 2.通过模态的形式推出视图,不需要依赖于导航控制器,通过使用present到下一个界面,通过dismi ...

  2. xml作用以及语法

    2 XML作用 2.1 描述带关系的数据(软件的配置文件) web服务器(PC): 学生管理系统 -> 添加学生功能 -> 添加学生页面 -> name=eric&email ...

  3. 数据结构-自平衡二叉查找树(AVL)详解

    介绍: 在计算机科学中,AVL树是最先发明的自平衡二叉查找树. 在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树. 查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  4. Eclipse/Myeclipse生成serialVersionUID方法

    serialVersionUID作用: 序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性. 如果你修改代码重新部署后出现序列化错误,可以考虑给相应的类增加serialVersio ...

  5. 对Linux0.11 中 进程0 和 进程1分析

    1. 背景 进程的创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节.比如,子进程复制父进程所拥有的资源,或者子进程和父进程共享相同的物理页面,拥有 ...

  6. libevent之event

    就如libevent官网上所写的“libevent - an event notification library”,libevent就是一个基于事件通知机制的库,可以看出event是整个库的核心.e ...

  7. Android中代码运行指定的Apk

    有时候,当我们编写自己的应用的时候,需要通过代码实现指定的apk,安装指定的主题,或者安装新的apk.可以通过以下方法实现: private void installAPK(String apkUrl ...

  8. 程序员的视角:java 线程

    在我们开始谈线程之前,不得不提下进程. 无论进程还是线程都是很抽象的概念,有一个关于进程和线程很形象的比喻能帮我们更好的理解. 进程就像个房子,房子是一个包含了特定属性的容器,例如空间大小.卧室数量等 ...

  9. 关于App启动加载广告页面思路

    需求 很多app(如淘宝.美团等)在启动图加载完毕后,还会显示几秒的广告,一般都有个跳过按钮可以跳过这个广告,有的app在点击广告页之后还会进入一个广告页面,点击返回进入首页.虽然说这个广告页面对用户 ...

  10. Android性能优化之界面UI篇

    1.使用style.color.string.dimen样式来分离xml布局文件,减少代码的重复使用,增加代码复用率,防止hardcode,下面是一个例子: 在定义layout时候,因为每个View或 ...