负载均衡怎样保证数据一致_一致性哈希负载均衡算法的探讨

2cfeb16e1f6fac00c38e2f357696dc3b.png

来源:《舰队Collection》岛风

前言

一致性哈希算法在很多领域有应用,例如分布式缓存领域的 MemCache,Redis,负载均衡领域的 Nginx,各类 RPC 框架。不同领域场景不同,需要顾及的因素也有所差异,本文主要讨论在负载均衡中一致性哈希算法的设计。

在介绍一致性哈希算法之前,我将会介绍一些哈希算法,讨论它们的区别和使用场景。也会给出一致性哈希算法的 Java 通用实现,可以直接引用,文末会给出 github 地址。

友情提示:阅读本文前,最好对一致性哈希算法有所了解,例如你最好听过一致性哈希环这个概念,我会在基本概念上缩短篇幅。

一致性哈希负载均衡介绍

负载均衡这个概念可以抽象为:从 n 个候选服务器中选择一个进行通信的过程。负载均衡算法有多种多样的实现方式:随机、轮询、最小负载优先等,其中也包括了今天的主角:一致性哈希负载均衡。一致性哈希负载均衡需要保证的是“相同的请求尽可能落到同一个服务器上”,注意这短短的一句描述,却包含了相当大的信息量。“相同的请求” — 什么是相同的请求?一般在使用一致性哈希负载均衡时,需要指定一个 key 用于 hash 计算,可能是:

  1. 请求方 IP

  2. 请求服务名称,参数列表构成的串

  3. 用户 ID

“尽可能” —为什么不是一定?因为服务器可能发生上下线,所以少数服务器的变化不应该影响大多数的请求。这也呼应了算法名称中的“一致性”。

同时,一个优秀的负载均衡算法还有一个隐性要求:流量尽可能均匀分布。

综上所述,我们可以概括出一致性哈希负载均衡算法的设计思路。

  • 尽可能保证每个服务器节点均匀的分摊流量

  • 尽可能保证服务器节点的上下线不影响流量的变更

哈希算法介绍

哈希算法是一致性哈希算法中重要的一个组成部分,你可以借助 Java 中的 inthashCode()去理解它。 说到哈希算法,你想到了什么?Jdk 中的 hashCode、SHA-1、MD5,除了这些耳熟能详的哈希算法,还存在很多其他实现,详见 HASH 算法一览。可以将他们分成三代:

  • 第一代:SHA-1(1993),MD5(1992),CRC(1975),Lookup3(2006)

  • 第二代:MurmurHash(2008)

  • 第三代:CityHash, SpookyHash(2011)

这些都可以认为是广义上的哈希算法,你可以在 wiki 百科 中查看所有的哈希算法。当然还有一些哈希算法如:Ketama,专门为一致性哈希算法而设计。

既然有这么多哈希算法,那必然会有人问:当我们在讨论哈希算法时,我们再考虑哪些东西?我大概总结下有以下四点:

  1. 实现复杂程度

  2. 分布均匀程度

  3. 哈希碰撞概率

  4. 性能

先聊聊性能,是不是性能越高就越好呢?你如果有看过我曾经的文章 《该如何设计你的 PasswordEncoder?》,应该能了解到,在设计加密器这个场景下,慢 hash 算法反而有优势;而在负载均衡这个场景下,安全性不是需要考虑的因素,所以性能自然是越高越好。

优秀的算法通常比较复杂,但不足以构成评价标准,有点黑猫白猫论,所以 2,3 两点:分布均匀程度,哈希碰撞概率成了主要考虑的因素。

我挑选了几个值得介绍的哈希算法,重点介绍下。

  1. MurmurHash 算法:高运算性能,低碰撞率,由 Austin Appleby 创建于 2008 年,现已应用到 Hadoop、libstdc++、nginx、libmemcached 等开源系统。2011 年 Appleby 被 Google 雇佣,随后 Google 推出其变种的 CityHash 算法。官方只提供了 C 语言的实现版本。Java 界中 Redis,Memcached,Cassandra,HBase,Lucene 都在使用它。 在 Java 的实现,Guava 的 Hashing 类里有,上面提到的 Jedis,Cassandra 里都有相关的 Util 类。

  2. FNV 算法:全名为 Fowler-Noll-Vo 算法,是以三位发明人 Glenn Fowler,Landon Curt Noll,Phong Vo 的名字来命名的,最早在 1991 年提出。 特点和用途:FNV 能快速 hash 大量数据并保持较小的冲突率,它的高度分散使它适用于 hash 一些非常相近的字符串,比如 URL,hostname,文件名,text,IP 地址等。

  3. Ketama 算法:将它称之为哈希算法其实不太准确,称之为一致性哈希算法可能更为合适,其他的哈希算法有通用的一致性哈希算法实现,只不过是替换了哈希方式而已,但 Ketama 是一整套的流程,我们将在后面介绍。

以上三者都是最合适的一致性哈希算法的强力争夺者。

一致性哈希算法实现

f3dd76ff7dbc192641de9e1e305fc98c.png

一致性哈希的概念我不做赘述,简单介绍下这个负载均衡中的一致性哈希环。首先将服务器(ip+端口号)进行哈希,映射成环上的一个节点,在请求到来时,根据指定的 hash key 同样映射到环上,并顺时针选取最近的一个服务器节点进行请求(在本图中,使用的是 userId 作为 hash key)。

当环上的服务器较少时,即使哈希算法选择得当,依旧会遇到大量请求落到同一个节点的问题,为避免这样的问题,大多数一致性哈希算法的实现度引入了虚拟节点的概念。

422776ad31738fcb5b5203de42a59d65.png

在上图中,只有两台物理服务器节点:11.1.121.1 和 11.1.121.2,我们通过添加后缀的方式,克隆出了另外三份节点,使得环上的节点分布的均匀。一般来说,物理节点越多,所需的虚拟节点就越少。

介绍完了一致性哈希换,我们便可以对负载均衡进行建模了:

public interface LoadBalancer {

   Server select(List<Server> servers, Invocation invocation);

}

下面直接给出通用的算法实现:

public class ConsistentHashLoadBalancer implements LoadBalancer{

   private HashStrategy hashStrategy = new JdkHashCodeStrategy();

   private final static int VIRTUAL_NODE_SIZE = 10;

   private final static String VIRTUAL_NODE_SUFFIX = "&&";

   @Override

   public Server select(List<Server> servers, Invocation invocation) {

       int invocationHashCode = hashStrategy.getHashCode(invocation.getHashKey());

       TreeMap<Integer, Server> ring = buildConsistentHashRing(servers);

       Server server = locate(ring, invocationHashCode);

       return server;

   }

   private Server locate(TreeMap<Integer, Server> ring, int invocationHashCode) {

       // 向右找到第一个 key

       Map.Entry<Integer, Server> locateEntry = ring.ceilingEntry(invocationHashCode);

       if (locateEntry == null) {

           // 想象成一个环,超过尾部则取第一个 key

           locateEntry = ring.firstEntry();

       }

       return locateEntry.getValue();

   }

   private TreeMap<Integer, Server> buildConsistentHashRing(List<Server> servers) {

       TreeMap<Integer, Server> virtualNodeRing = new TreeMap<>();

       for (Server server : servers) { <

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值