一致性哈希学习与实现

一致性哈希学习与实现

一致性哈希算法的学习

  1. 基本场景:假设有N个cache服务器,对服务器集群的管理,路由算法至关重要,就和负载均衡算法一样,路由算法决定着究竟该访问集群中的哪台服务器。
  2. 余数哈希:计算 object 的 hash 值,然后均匀的映射到到 N 个 cache 服务器;
    hash(object) % N
    但是当需要增加或者减少cache服务器的时候,这个算法的问题就暴露出来了,将导致几乎所有的cache服务器失效。
  3. 一致性哈希:一致性哈希是一种hash算法,通过一个叫做一致性hash环的数据结构实现key到cache服务器的hash映射。
    以下分步骤阐述一致性hash算法原理:
    • 环形hash空间。构造一个长度为232的整数环(这个环被称为一致性Hash环)
    • 把cache服务器映射到hash空间。根据节点名称的Hash值(其分布为[0, 232-1])将缓存服务器节点放置在这个Hash环上
    • 把对象映射到hash空间和cache服务器。根据需要缓存的数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),然后在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();
}

猜你喜欢

转载自blog.csdn.net/lllllyt/article/details/80727583
今日推荐