大家知道在分布处理中,使用取余法来达到分布式处理是最方便、最快捷的方法,但是这种方法存在一系列的问题就是,那就是随着服务器的增加或減少,数据转移与命中率都会是件很头疼的事,在这里我们举个最简单的例子来说明这种方法带来的坏处。
假设现有5台服务器来存放一部分数据,那么我们按取余的方法来得到服务器分布的位置, 按hash(key) % 5得到的值会在0-5之间,在这里我们假设为2,也就是说数据存放在第二台服务器上,那么在一切正常的情况下读写都不会有任何问题,但是如果第2台服务器down掉之后(这种事很有可能发生,不可避免),随着服务器的数量减少之后,问题就来了,如果我们再用这种算法,那么得到的是第2台服务器,但是第2台服务器已经挂了,这就意味着无论我们是写还是读,都不可能完成。或者这时候我们来改进一下算法,用hash(key) % 4,这样一算之后,完全乱掉了,你想要得到的数据不但没在第2台服务器上,同时别的数据也跟着混乱。按照这种结论,我们将服务器增加到10台后,也会产生同样的问题。那么如何在服务器的减少或者是增加的情况下有效的去控制命中率呢?随着这一系列问题的出现,Consistent Hashing算法便由此诞生。
Consistent Hashing算法原理是先构造一个由N个虚拟节点组成的环形分布图,然后将服务器的hash值映射到这个环形分布图的节点上,然后用存储的key也按相同的方式映射到分布图的的节点上,按顺时针的方向找到离他最近的服务器节点,如果一个环形绕完再没有找到,那么就将数据存在第一台服务器节点上,如下图所示:
当我们需要增加与减少服务器时,只会影响key映射的位置到逆时针方向离他最近的服务器节点的数据,其增加服务器时影响的数据段如下图所示:
下面C++现实Consistent Hashing算法的代码
#include <winsock2.h>
#include <iostream>
#include <map>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
typedef struct _mem_item
{
unsigned int offset;
char *ip;
} MemcacheServerItem;
unsigned long hash(const char *key, unsigned int keySize)
{
unsigned long h = 5813;
for(int i = 0; i < keySize; i++){
h = (h << 5) + h + *key++;
}
return h;
}
class Consistent
{
private:
const unsigned int m_VirtualCount;
map<unsigned int, MemcacheServerItem *> m_Map;
private:
unsigned int _getOffset(const char *ip)
{
return inet_addr(ip) % m_VirtualCount;
}
public:
Consistent(unsigned int count):m_VirtualCount(count)
{
}
~Consistent()
{
m_Map.clear();
}
//添加节点
int addNode(const char *ip, unsigned int ipSize)
{
unsigned int offset = _getOffset(ip);
MemcacheServerItem *p = (MemcacheServerItem *)malloc(sizeof(MemcacheServerItem));
if(p == NULL){
return 0;
}
p->offset = offset;
p->ip = (char *)malloc(ipSize + 1);
if(p->ip == NULL){
return 0;
}
memcpy(p->ip, ip, ipSize);
*(p->ip + ipSize) = NULL;
m_Map.insert(map<unsigned int, MemcacheServerItem *>::value_type(offset, p));
printf("添加节点到:%d,IP:%s\n", offset, ip);
return 1;
}
//移除节点
int removeNode(const char *ip, unsigned int ipSize)
{
unsigned int offset = _getOffset(ip);
map<unsigned int, MemcacheServerItem *>::const_iterator it = m_Map.find(offset);
if(it != m_Map.end()){
printf("移除第 %d 个节点的服务器,IP:%s\n", it->first, it->second->ip);
m_Map.erase(offset);
}
return 1;
}
//找到KEY所分布的真实节点
MemcacheServerItem *findNode(unsigned int offset)
{
map<unsigned int, MemcacheServerItem *>::const_iterator it = m_Map.lower_bound(offset);
if(it != m_Map.end()){
return it->second;
}
return m_Map.begin()->second;
}
//打印所有节点
void printNode(void)
{
map<unsigned int, MemcacheServerItem *>::const_iterator it;
for(it = m_Map.begin(); it != m_Map.end(); it++){
printf("%d\t%s\n", it->first, it->second->ip);
}
}
//存数据
void addData(const char *key, unsigned int keySize)
{
unsigned int h = hash(key, keySize) % m_VirtualCount;
MemcacheServerItem *p = findNode(h);
if(p){
printf("%d:数据从第 %d 节点写入,IP:%s\n", h, p->offset, p->ip);
}
}
//读数据
void getData(const char *key, unsigned int keySize)
{
unsigned int h = hash(key, keySize) % m_VirtualCount;
MemcacheServerItem *p = findNode(h);
if(p){
printf("%d:数据从第 %d 节点取出,IP:%s\n", h, p->offset, p->ip);
}
}
};
int main(int argc, char* argv[])
{
Consistent S(50000);//虚拟50000个节点
//添加服务器
S.addNode("192.168.1.2", 11);
S.addNode("192.168.1.3", 11);
S.addNode("192.168.1.4", 11);
S.addNode("192.168.1.5", 11);
S.addNode("192.168.1.6", 11);
S.addNode("192.168.1.7", 11);
S.addNode("192.168.1.8", 11);
S.addNode("192.168.1.9", 11);
printf("----------------------------------------------\n");
//写入数据
S.addData("uid", 3);
S.addData("username", 8);
S.addData("title", 5);
S.addData("content", 7);
S.addData("home", 4);
S.addData("email", 5);
S.addData("sina", 4);
printf("----------------------------------------------\n");
//再添加服务器
S.addNode("192.168.1.10", 12);
S.addNode("192.168.1.11", 12);
printf("----------------------------------------------\n");
//读取数据,测试命中率
S.getData("uid", 3);
S.getData("username", 8);
S.getData("title", 5);
S.getData("content", 7);
S.getData("home", 4);
S.getData("email", 5);
S.getData("sina", 4);
printf("----------------------------------------------\n");
S.printNode();
return 0;
}