一致性哈希学习与实现

一致性哈希学习与实现

一致性哈希算法的学习
  1. 基本场景: 假设有N个cache服务器,对服务器集群的管理,路由算法至关重要,就和负载均衡算法一样,路由算法决定着究竟该访问集群中的哪台服务器。
  2. 余数哈希: 计算 object 的 hash 值,然后均匀的映射到到 N 个 cache 服务器;
    hash(object) % N
    但是当需要增加或者减少cache服务器的时候,这个算法的问题就暴露出来了,将导致几乎所有的cache服务器失效。
  3. 一致性哈希: 一致性哈希是一种hash算法,通过一个叫做一致性hash环的数据结构实现key到cache服务器的hash映射。
    以下分步骤阐述一致性hash算法原理:
    • 环形hash空间。构造一个长度为 2 32 2^{32} 232的整数环(这个环被称为一致性Hash环)
    • 把cache服务器映射到hash空间。根据节点名称的Hash值(其分布为[0, 2 32 − 1 2^{32}-1 2321])将缓存服务器节点放置在这个Hash环上
    • 把对象映射到hash空间和cache服务器。根据需要缓存的数据的Key值计算得到其Hash值(其分布也为[0, 2 32 − 1 2^{32}-1 2321]),然后在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
    • 当cache服务器数量发生改变。当新增一个cache服务器的时候,受影响的将仅是那些沿着新增cache服务器顺时针遍历直到下一个cache服务器之间的对象,其他的不会有影响;当减少一个cache服务器的时候,受影响的将仅是那些沿着新增cache服务器逆时针遍历直到下一个cache服务器之间的对象,其他的不会有影响;
    • 虚拟节点。“虚拟节点”( virtual node )是实际节点在 hash 空间的复制品( replica ),一个实际节点对应了若干个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以 hash 值排列。
一致性哈希算法的实现 全部源码
  • 定义虚拟服务节点:
/* 虚拟节点的结构定义 */
struct virtualNode {
	string corrNodeName;
	string virNodeName;
	string hashCode;

	virtualNode(string _corrNodeName, string _virNodeName) {
		corrNodeName = _corrNodeName;
		virNodeName = _virNodeName;
	}

	string toString() {
		string str = "The virtual node: " + virNodeName + " is a virtual node of the real node: " + corrNodeName;
		return str;
	}	
};
  • 定义cache服务器节点:
/* 真实节点的结构定义 */
struct hashNode {
	string name;						// 节点标记
	string hashCode;					// 节点的哈希值
	int numOfVirtualhashNode;			// 虚拟节点的个数
	int routeService;					// 记录负载的路由			

	// 初始化
	hashNode(string _name, int _numOfVirtualhashNode) {
		name = _name;
		numOfVirtualhashNode = _numOfVirtualhashNode + 1;
		routeService = 0;
	}
};
  • 一致性哈希的方法实现:
    • 实现思路:为了实现快速查找,使用c++的stl中map来对节点进行存储和管理,其相对于一个hash环。其中map的key值为节点的hash值,map默认按照key值进行升序排序,value则存储定义好的节点的指针。至于hash值的生成则使用murmurhash算法计算得到,主要该算法计算得到的hash值随机分布性比较好,使得各个节点更加的均匀分布在hash环中
    • 主要代码:
//conHash.h
class conHash {

public:

	conHash();
	
	~conHash();

	// 添加一个节点
	bool addNode(hashNode* node);

	// 删除一个节点
	bool delNode(hashNode* node);

	// 通过哈希码查找一个服务节点
	hashNode* find(string hashKey);

	// 计算得到相应一个哈希值
	string getHashCode(string str);

	// 判断当前节点是否为0
	bool empty();

	// 得到应当路由到的结点
	string getService(string node);

	// 获得真实节点的个数
	int getServiceNodeNum();

	// 获得虚拟节点的个数
	int getVirtualNodeNum();

	// 显示当前服务器路由情况
	void showService();
private:
	
	murMurHash _murMurHash;

	/*
	*	管理真实节点
	* 	first为节点的哈希值,second为节点 
	*/ 
	map<string, hashNode*> mapHashNodes;

	/*
	*	管理所有虚拟节点
	* 	first为节点的哈希值,second为节点 
	*/
	map<string, virtualNode*> mapVirtualNodes;

};
//conHash.cpp
#include "conHash.h"

conHash::conHash() {
	_murMurHash = murMurHash();
}

conHash::~conHash() {
	for(auto iter: mapVirtualNodes)
		delete iter.second;
	mapHashNodes.clear();
	mapVirtualNodes.clear();
}

// 添加一个节点
bool conHash::addNode(hashNode* node) {

	string name = node->name;
	node->hashCode = getHashCode(name);	//计算获得相应哈希值

	// 添加真实节点
	pair< map<string, hashNode*>::iterator, bool> result = mapHashNodes.insert(pair<string, hashNode*>(node->hashCode, node));

	/**
	*	添加其虚拟节点
	*	自己也看成一个虚拟节点
	**/
	virtualNode* vn = new virtualNode(node->name, node->name);
	mapVirtualNodes.insert(pair<string, virtualNode*>(node->hashCode, vn));

	int numOfVirtualhashNode = node->numOfVirtualhashNode;
	for(int i = 0; i < numOfVirtualhashNode - 1; ++i) {
		char buffer[20];
		sprintf(buffer, ":%04d", i);
		string virNodeName = name + buffer;
		virtualNode* vn = new virtualNode(name, virNodeName);
		vn->hashCode = getHashCode(virNodeName.substr(6));
		mapVirtualNodes.insert(pair<string, virtualNode*>(vn->hashCode, vn));
	}

	if(result.second)
		cout << "Add the service node: " << node->name << " and corresponding hash code : " + node->hashCode << endl;

	return (bool)result.second;
}

// 删除一个节点
bool conHash::delNode(hashNode* node) {
	string serviceName = node->name;
	string key = node->hashCode;
	auto iter = mapHashNodes.find(key);
	if(iter != mapHashNodes.end()) {
		mapHashNodes.erase(iter);
		delete node;
		node = NULL;
		cout << "Delete the service node: " << serviceName << " and corresponding hash code : " << key << endl;
		for(auto virIter = mapVirtualNodes.begin(); virIter != mapVirtualNodes.end(); ) {  
			if(virIter->second->corrNodeName == serviceName) {
				virtualNode* vnode = virIter->second;
				mapVirtualNodes.erase(virIter++);  
				delete vnode;
			}
			else  
				++virIter;  
		} 
		return true;
	}
	
	return false;
}

// 通过哈希码查找一个服务节点
hashNode* conHash::find(string hashKey){
	auto iter = mapHashNodes.find(hashKey);
	if(iter != mapHashNodes.end())
		return iter->second;
	return NULL;
}

// 计算得到相应一个哈希值
string conHash::getHashCode(string str) {
	return to_string(_murMurHash.getMurMurHash(&str, keyLen));
}

// 判断当前节点是否为0
bool conHash::empty() {
	return mapHashNodes.empty();
}

// 得到应当路由到的结点
// 此处的node指需要被进行处理的数据
string conHash::getService(string node) {
	string hashCode = getHashCode(node);	// 计算获得相应哈希值
	string route;
	for(auto iter = mapVirtualNodes.begin(); iter != mapVirtualNodes.end(); iter++) {
        if(iter->first >= hashCode) {
        	route = node + " is routed to the virtual node " + iter->second->virNodeName + " and the real node is " + iter->second->corrNodeName + "\n";
        	for(auto i : mapHashNodes)
        		if(i.second->name == iter->second->corrNodeName) {
        			i.second->routeService++;
        			break;
        		}
        	break;
        }
    }
    return route;
}

// 获得真实节点的个数
int conHash::getServiceNodeNum() {
	return mapHashNodes.size();
}

// 获得虚拟节点的个数
int conHash::getVirtualNodeNum() {
	return mapVirtualNodes.size();
}

// 显示当前服务器路由情况
void conHash::showService() {

	cout << "Current situation: " << endl;

	if(empty())
		cout << "There are no service node now" << endl;
	else {
		for(auto iter : mapHashNodes)
			cout << "The service node: " << iter.second->name << " and the corresponding hash code: " << iter.second->hashCode << " provides " << iter.second->routeService << " service" << endl;
	}
}
  • 哈希值计算
    • 本文中一致性哈希算法的哈希值hash值的计算通过Murmurhash算法计算得到, MurmurHash 是一种非加密型哈希函数,适用于一般的哈希检索操作。 由Austin Appleby在2008年发明, 并出现了多个变种,都已经发布到了公有领域(public domain)。与其它流行的哈希函数相比,对于规律性较强的key,MurmurHash的随机分布特征表现更良好。
    • 代码:
#ifndef MUR_MUR_HASH
#define MUR_MUR_HASH 

class murMurHash
{
public:
    unsigned int getMurMurHash(const void *key, int len)  {  
        const unsigned int m = 0x5bd1e995;  
        const int r = 24;  
        const int seed = 97;  
        unsigned int h = seed ^ len;  

        // Mix 4 bytes at a time into the hash  
        const unsigned char *data = (const unsigned char *)key;  
        while(len >= 4)  
        {  
            unsigned int k = *(unsigned int *)data;  
            k *= m;   
            k ^= k >> r;   
            k *= m;   
            h *= m;   
            h ^= k;  
            data += 4;  
            len -= 4;  
        }  

        // Handle the last few bytes of the input array  
        switch(len)  
        {  
            case 3: h ^= data[2] << 16;  
            case 2: h ^= data[1] << 8;  
            case 1: h ^= data[0];  
            h *= m;  
        };  



        // Do a few final mixes of the hash to ensure the last few  
        // bytes are well-incorporated.  
        h ^= h >> 13;  
        h *= m;  
        h ^= h >> 15;


        return h;  
    }  

};
#endif

  • 测试函数(main.cpp)
    • 测试说明:测试文件(main.cpp)主要测试了一致性哈希算法的平衡性,只设置了3个cache服务器,每个cache服务器有30个虚拟节点,10000个随机产生的请求对象模拟对cache服务器的访问。输出结果如下:
      这里写图片描述
      由输出结果看出,每个cache服务器节点处理的对象数目相近,负载均衡,假如希望某个cache服务器处理更多的对象,可以增大其虚拟节点的个数,则该cache服务器将处理更多的对象。
/**
 *	测试 
 */


#include "conHash.h"
#include <sstream>
#include <fstream>
using namespace std;

//产生随机IP地址
string RandIP();

int main() {
	
	printf("A simple test of Consistent Hashing\n");

	conHash _conhash = conHash();

	cout << "-----------------ADD SERVICE NODES----------------------" << endl;
	// 创建服务器节点
	hashNode* node_0 = new hashNode("192.168.127.140", 30);
	hashNode* node_1 = new hashNode("192.168.127.141", 30);
	hashNode* node_2 = new hashNode("192.168.127.142", 30);

	// 加入服务器节点
	_conhash.addNode(node_0);
	_conhash.addNode(node_1);
	_conhash.addNode(node_2);

	
	// 分别输出真实和虚拟服务节点的个数
	cout << "The total num of service node: " << _conhash.getServiceNodeNum() << endl;
	cout << "The total num of virtual service node: " << _conhash.getVirtualNodeNum() << endl;

	cout << "-----------------------TEST--------------------------" << endl;
	// 模拟请求
	for(int i = 0; i < 10000; ++i) 
		cout << _conhash.getService(RandIP());


	cout << "-------------------SHOW SERVICE----------------------" << endl;
	// 查看每个节点负责的请求
	_conhash.showService();


	// 删除某个服务节点
	cout << "----------------------DELETE-------------------------" << endl;
	_conhash.delNode(node_0);
	cout << "The total num of service node: " << _conhash.getServiceNodeNum() << endl;
	cout << "The total num of virtual service node: " << _conhash.getVirtualNodeNum() << endl;


	delete node_1;
	delete node_2;
	
	return 0;
}

//产生随机IP地址
string RandIP()
{
	stringstream ip;
	for (int i = 0; i < 4; ++i) {
		ip << (rand() % 256);
		if (i < 3)
			ip << '.';
	}

	return ip.str();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值