目录
一、案例背景
1.1 系统简介
首先看一下系统架构,方便解释:
页面给用户展示的功能就是,可以查看任何一台机器的某些属性(以下简称系统信息)。
消息流程是,页面发起请求查看指定机器的系统信息到后台,后台可以查询到有哪些server在提供服务,根据负载均衡算法(简单的轮询)指定由哪个server进行查询,并将消息发送到Kafka,然后所有的server消费Kafka的信息,当发现消费的信息要求自己进行查询时,就连接指定的machine进行查询,并将结果返回回去。
Server是集群架构,可能动态增加或减少。
至于架构为什么这么设计,不是重点,只能说这是符合当时环境的最优架构。
1.2 遇到问题
遇到的问题就是慢,特别慢,经过初步核实,最耗时的事是server连接machine的时候,基本都要5s左右,这是不能接受的。
1.3 初步优化
因为耗时最大的是server连接machine的时候,所以决定在server端缓存machine的连接,经过测试如果通过使用的连接缓存进行查询,那么耗时将控制在1秒以内,满足了用户的要求,不过还有一个问题因此产生,那就是根据现有负载均衡算法,假如server1已经缓存了到machine1的连接,但是再次查询时,请求就会发送到下一个server,如server2,这就导致了两个问题,一是,重新建立了连接耗时较长,二是,两个server同时缓存着到machine1的连接,造成了连接浪费。
1.4 继续优化
一开始想到最简单的就是将查询的machine进行hash计算,并除sever的数量取余,这样保证了查询同一个machine时会要求同一个server进行操作,满足了初步的需求。但是因为server端是集群,机器有可能动态的增加或减少,假如根据hash计算,指定的 machine会被指定的server连接,如下图:
然后又增加了一个server,那么根据当前的hash算法,server和machine的连接就会变成如下:
可以发现,四个machine和server的连接关系发生变化了,这将导致4次连接的初始化,以及四个连接的浪费,虽然server集群变动的几率很小,但是每变动一次将有一半的连接作废掉,这还是不能接受的,当时想的最理想的结果是:
- 当新增机器的时候,原有的连接分一部分给新机器,但是除去分出的连接以外保持不变
- 当减少机器的时候,将减少机器的连接分给剩下的机器,但剩下机器的原有连接不变
简单来说,就是变动不可避免,但是让变动最小化。根据这种思想,就想到了一致性hash,觉得这个应该可以满足要求。
二、使用一致性Hash解决问题
一致性Hash的定义或者介绍在第三节,现在写出一致性Hash的Java的解决方法。只写出示例实现代码,首先最重要的就是Hash算法的选择,根据现有情况以及已有Hash算法的表现,选择了FNV Hash算法,以下是其实现:
public static int FnvHash(String key) {
final int p = 16777619;
long hash = (int) 2166136261L;
for (int i = 0,n = key.length(); i < n; i++){
hash = (hash ^ key.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return ((int) hash & 0x7FFFFFFF);
}
然后是对能提供服务的server进行预处理:
public static ConcurrentSkipListMap<Integer, String> init(){
//创建排序Map方便后面的计算
ConcurrentS