分布式hash系统设计

前言:

分布式hash系统在高性能分布式领域使用范围非常广泛。我目前正在开发维护的一个
即时通信项目就使用了它,取得了良好的效果,良好的支撑了300w用户1分钟内同时访问。


正文:

目前有1台登录服务器,3台hash服务器,1台配置服务器。当一个包含用户id的报文发到登录服务器时,登录服务器根据这个id来进行hash计算,看属于那台hash服务器的服务对象。经过计算,发现是第一台hash,于是就发送给第一台hash服务器。该服务器收到报文后,直接把该id对应的ip。port等通信信息更新。比如:1-10000个用户id,第一台hash里放1-3000,第二台hash里放3000-6000,第三台hash里放6000-10000。当第一台的1-3000个id访问需求变频繁以后,可以调整。

如何调整呢?首先再配置一台计算机充当hash服务器,安装hash服务端,然后设置“配置服务器”,让“配置服务器”通知“登录服务器”,以后凡是1-2000的都发送到“第四台hash服务器”上而不是“第一台hash服务器”上。这样,“第一台hash”服务器只能收到2000-3000的id,而不会收到1-2000的id了,达到在不用重启服务器的前提下实现了分担压力的作用。“第一台hash服务器”的1-2000的id就不会被更新了,属于冗余,可以逐渐删除掉。

hash算法没什么特殊性,这里就不再给出hash服务器关键代码段,我会在不久公布本系统全部源代码的。
这里给出DHT最为关键的分布式管理机制。

服务器:4台linux 2.6内核操作系统,gcc编译器,c语言,支持tcp/ip协议。
分别为:登录服务器,hash服务器1,hash服务器2,hash服务器3。


1。登录服务器设计:

登录服务器的网络通信程序可以采用udp或tcp协议,由于是即时通信项目,所以采用Udp协议。
用户的客户端通过udp发送报文到“登录服务器”,登录服务器立刻开启线程处理,然后把结果返回给客户端。


struct _PACKET
{
unsigned long id;
...
...

}PACKET;

int hash_num = 0x3;

struct _HASH_SELECT
{
unsigned long hashId;
unsigned long hashIp;
unsigned long hashPort;
...

}HASH_SELECT;

int main()
{

int intsocket_to_manserver = CreateManServer();

int inisocket = CreateTcpClient();


int sock = CreateServer(SOCK_DGRAM, 0);
if(sock <= 0)
printf("Create UDP Socket Server Failed!\n");
while(1)
{
recvlen = 0;
memset(&addr, 0x0, sizeof(struct sockaddr_in));
memset((char *)recvbuffer, 0x0, MAX_BUFFER);
recvlen = recvfrom(sock,
(char *)recvbuffer,
MAX_BUFFER,
0,
(struct sockaddr *)&addr,&len);

if(recvlen <= 0 || len <= 0)
continue;

pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if(pthread_create(&thid, &attr, (void *)fp, recvbuffer,!= 0)
{
free(para);
para = NULL;
}
}
close(sock);
if(data != NULL)
free(data);
data = NULL;

}

void fp(char* buffer)
{
(unsigned char*) p = (unsigned char*)buffer;
unsigned long id = ((PACKET*)p->id;

int hashServerId = SelectHashServer(id);//选择hash服务器

char* res = callbackHash(hashServerId,p);//向hash服务器发送报文,收到报文,同步调用

sendto(socket,res,....);//返回客户端

}


int intsocket_to_manserver = CreateManServer();
这条语句的意思是说,登录服务器跟配置服务器建立连接(其中,登录服务器做服务器,配置服务器做客户端)。
用来接收配置服务器发来的更改配置请求。intsocket_to_manserver()是一个阻塞函数,只用启动配置服务器,收到了报文,才会退出。

int inisocket = CreateTcpClient();
这条语句的意思是说,在登录服务器上建立3个对hash服务器的tcp连接。CreateTcpClient会读取放在登录服务器中的一个内存数据结构,包括hash服务器的ip,port等信息。这个数据结构中的数据是从配置服务器中发送过来的。

int hashServerId = SelectHashServer(id);
这条语句的意思是说,根据用户的id选择一个hash服务器,把这个id和buffer发送到那个hash服务器上。选择方法是:除留余数法,将id除以hash服务器的个数,就能定位到一台服务器了。可以根据hashId在HASH_SELECT中得到ip,port等,对这台hash服务器发送就可以了。

2。配置服务器设计:

配置服务器作为客户端同登录服务器和hash服务器相连。向登录服务器发送配置更改指令。当分布式hash系统的某台服务器发生了性能瓶颈,增加服务器后,要通过配置服务器向登录服务器发送指令,更改,增加HASH_SELECT体数据。登录服务器在初始化时就会开启一个线程专门接收来自配置服务器的消息,一旦得到以后,就会更改HASH_SELECT数据结构和hash_num变量的。


3。hash服务器设计:

hash服务器一般人都会设计,不再重复。但是在分布式hash系统中有一些特别地方要注意。不要定时删除hash里的数据元素。有些系统采用定时器每隔一段时间会遍历hash,然后删除时间字段超时的元素。我认为不必要,因为性能是最主要的,本来hash服务器的内存就是存储数据的,不要为了节约内存,非要线程同步操作hash,会造成时间的延迟,得不偿失。删除hash垃圾数据(也叫超时数据)是由配置服务器发指令来操作的,可以删除一部分超时数据,也可以全部删除等。

当配置服务器通知登录服务器改变hash配置时,hash服务器就会出现垃圾数据。比如一个用户id为123456的报文,一直向hash1服务器发送报文,hash1也一直存储着这个报文。但当hash配置改变以后,用户id为123456的报文向hash2服务器发送了,这时hash1里的id为123456就成为垃数据,这个数据元素不会再被更新和读取了。对它予以删除当然会改善其他id查询和更新操作效率,但在删除过程中,也会降低那些操作的效率,我建议不要直接删除,因为在hash配置被改以后,会在hash1上同时出现很多的垃圾id,由于量太大,统一删除会造成hash1上的其他正常id的操作效率大大降低,会出现一段时间的阻塞,互联网用户就会抱怨了。


上面的算法是最为普遍的分布式hash算法。以下说明这个算法的优缺点:

优点:可以在不重启任何服务器的前提下,实现压力的分担,可以随时增加,减少服务器,不会影响用户的使用,实现容易,效果明显。

缺点:对压力的分担可控性差,不够灵活。由于是根据用户id的除法得到hash id的,可能会出现某个地区的用户集中使用系统,而这些id恰巧又集中在了某台hash服务器中,没有达到分担压力的效果。


针对上述缺点,其实还有其他的办法,也就是根据业务来划分hash的方法。


小结:

由于即时通信项目对hash的更新非常频繁,所以没有采用传统的同步数据到不同服务器,然后根据压力找到一个服务器读取的策略,传统的策略适合频繁查询读取,不适合频繁更新。所以分布式hash系统必须采用新的思维方式。另外,有的公司会采用数据库的内存hash表来完成上述任务,但是当压力增大时,数据库本身占用的资源会成为无谓的消耗,因为在类似的项目当中统计需求很少出现,即使有这样的需求,我们可以采用先用分布式hash,而后再同步到对应数据库的方式,而不要直接使用数据库。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值