SOFA 源码分析 — 负载均衡和一致性 Hash

Stella981
• 阅读 569

SOFA 源码分析 — 负载均衡和一致性 Hash

前言

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!!!

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Stella981 Stella981
2年前
Nginx的负载均衡
上篇blog讲述了加权轮询算法的原理、以及负载均衡模块中使用的数据结构,接着我们来看看加权轮询算法的具体实现。 指令的解析函数 如果upstream配置块中没有指定使用哪种负载均衡算法,那么默认使用加权轮询。也就是说使用加权轮询算法,并不需要特定的指令,因此也不需要实现指令的解析函数。而实际上,和其它负载均衡算法不同(比如ip\_ha
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Dubbo的负载均衡算法
\toc\1简介Dubbo提供了4种负载均衡机制:权重随机算法:RandomLoadBalance最少活跃调用数算法:LeastActiveLoadBalance一致性哈希算法:ConsistentHashLoadBalance加权轮询算法:RoundRobinLoadBalan
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这