【算法】哈希、一致性哈希

--------------------------------------------------------

此篇博客想写什么?我的理解:

1.关于哈希算法:概念、理解、应用

    MD5如何运用在项目中的用户名登录注册加密上?

2.关于一致性哈希:概念、详解、应用

  关于分布式、集群的概念和区别?

---------------------------------------------------------

这里首先贴两篇文章:总结的很详细

一篇关于【哈希】算法总结的文章:https://blog.csdn.net/asdzheng/article/details/70226007

(讲了哈希的概念、作用、算法特点、应用(密码学)、如何实现、哈希的几个流行算法(MD5)、哈希算法的碰撞)

一篇关于【一致性哈希】很好的文章:http://www.zsythink.net/archives/1182

(讲了一致性哈希算法的基本概念、优点、哈希环的偏斜、虚拟节点。感觉是目前看过的最通俗易懂的关于一致性哈希的文章。)

--------------------------------------------------

一、哈希:

哈希是什么?简单的说就是将一个文件名、字符串或者数字等等等等,通过一个算法,得到一个它的标识,这个标识具有一个可靠性,在之后的应用中起到关键作用。那么这个算法,就叫做哈希算法,也叫散列算法。

简单哈希:hash()函数,一般是求模运算。比如在文件传输项目中的例子:哈希解决了挨个遍历每台,服务器查找文件是否存在,然后再下载的步骤(逐个遍历效率低)。假设有3个服务器处理事物,客户端发出下载一个文件的请求,将文件名通过哈希函数hash(filename)得到一个散列值,将这个值%3,得到0/1/2,也就说该文件要在对应的0/1/2号服务器上进行下载,这样我们直接得到了要下载文件的服务器,提高了效率。

 但这个模型会有一个哈希冲突的问题,假设服务器中的某一台宕机了,或者3台服务器不够用,我们要增加服务器的数量,我们再次求模运算就会失效。于是,就有了一致性哈希的思想。

二、关于MD5算法应用在加密数据上:

将用户设置的密码进行md5运算后存入数据库。当用户登录时,只需再求一次md5值,并与数据库中的值进行比对。判断密码是否正确就知道用户的合法性了。因为md5不可逆,所以即使数据库被突破或下载,也不可能知道用户密码。而这样带来的问题就是用户也不能找回密码,而只能在达成一定的条件后让用户重置密码。这就是为什么现在越来越多的网站不能找回密码,而只能重置密码。因为多数使用了不可逆算法(此处不只md5,也包括其它不可逆算法的应用)。

在项目中,可以自己根据原理设计将函数封装:https://blog.csdn.net/black_ceo/article/details/54318733

https://blog.csdn.net/lindexi_gd/article/details/47005093

也可以用OpenSSL库(第三方库),调用MD5库函数,将字符串加密:https://blog.csdn.net/sinat_35297665/article/details/78244523

https://blog.csdn.net/jiabei2008/article/details/7387405

man MD5 后得到如下内容:库中的几个API以及其参数、返回值等

具体代码实现,后面博客介绍。。。

三、一致性哈希:

一致性哈希也使用取模的算法,只不过不是对服务器的数量,上面例子中的3取模,而是对2^32取模。

我们将0 ~2^32-1这个范围的数字想象成一个环,如图:

 然后,还是刚才的的文件传输中的3台服务器,现在我们将文件的名通过hash() % 2^32后,得到一个哈希值,这个值一定再这个环上,然后将这个哈希值与服务器(ip,端口)建立联系,如图:

 现在,我们的哈希环上有了3台真是的服务器,那么,当客户端要下载一个文件的时候,要选择服务器了。如何选择呢?

首先,将要下载的文件通过同样的哈希算法hash() % 2^32,得到一个哈希值,这个哈希值同样在这个哈希环上,如图:

 如图橘色五角星既是文件的哈希值,它沿顺时针遇到的第一个服务器,既是处理下载该文件的服务器。

这样,就解决的最开始一中哈希冲突的问题,如果环中有一台服务器宕机了,只有该宕机服务器的后一台服务器的压力会增大并且一部分数据会失效,,其他服务器并不会影响,并不会导致所有的哈希值失效。

但是,如图,会有一个哈希环偏斜的问题,上图中3台服务器离得很近,也就是说,绿色框代表的服务器压力会很大,这个模型并没有做到负载均衡,服务器的负载·····并不均衡。所以就引入了虚拟节点的解决方法。

四、虚拟节点:

虚拟节点就是不存在的结点,即该哈希值并不代表真是服务器,那么我们可以将该值与真是的服务器绑定,也就是该哈希值也代表一个服务器。

理论上,虚拟节点越多,我们的服务器的压力就越均衡。如图:

 

演示代码及其注释:

#include<iostream>
#include<string>
#include<map>
#include<stdint.h>

struct MIP //服务器的ip和prot 
{
	std::string ip;
	unsigned port; 
};

MIP arr[3];//数组:3个Server //数组的话,就可以用下标找到服务器了

class ConsistentHash
{
private:
	int RealServerNodeNum;  //真实服务器个数 
	int VirtualServerNodeNum;//虚拟服务器个数 
	std::map<uint32_t,size_t> mymap;  //map表:将 哈希值 和 服务器(数组下标) 建立联系 

public:
	//构造函数:真实服务器个数,虚拟默认1000个 
	ConsistentHash(int real,int virt = 1000)//real:real server   virt:virtual server
		:RealServerNodeNum(real),VirtualServerNodeNum(virt)
	{	}

	//初始化哈希环:该例中,将文件名与服务器绑定,只是实例中的文件名这样写,//真实项目中根据实际初始化
	int init()
	{
		for(int i = 0;i < RealServerNodeNum;i++)
		{
			for(int j = 0;j < VirtualServerNodeNum;j++)
			{
				std::stringstream ss;//ss为输入字符串 
				ss << "Shared-"<< i << "-" << j;     //shard-0-1:代表文件名
				
				//将ss经过murmur算法得到哈希值 (散列值) 
				uint32_t size = murmur3_32(ss.str().c_str(),strlen(ss.str().c_str()));
				std::cout<< size << std::endl;
				std::cout<< ss <<std::endl;

				//绑定:将哈希值 和 真实服务器绑定 插入带到map表中 
				mymap.insert(std::pair<uint32_t,size_t>(size,i)); /i:服务器数组的下标,代表了真实服务器
			}
		}		
	}

	//通过文件名 找到 对应服务器
	size_t getServerNode(string key)//test.txt  /根据一个key 寻找到对应服务器
	{
		//首先将文件名进行哈希,得到散列值
		uint32_t size = murmur3_32(key.c_str(),strlen(key.c_str()));
		
		//从map表中寻找该哈希值
		std::map<uint32_t,size_t>::iterator fit = mymap.upper_bound(size);//find server node 
		//upper_bound:find first bigger than size 寻找第一个大于size的结点 

		if(fit == mymap.end())
		{
			//没找到,说明该哈希值应该对应第一个服务器
			size_t tt = mymap.begin()->second;
		}
		else
		{
			//找到后map表的secon既是它对应的真实服务器
			size_t tt = fit->second;
		}
		return tt;//返回这个服务器数组下标
	}

	void delnode(size_t size) //从哈希环中删除一个服务器 //size :服务器的下标
	{
		for(int i = 0; i < VirtualServerNodeNum;i++)
		{
			std::stringstream ss;
			ss << "Shard-" << size << "-" << i;
			uint32_t hashval = murmur3_32(ss.str().c_str(),strlen(ss.str().c_str()));

			std::map<uint32_t,size_t>::iterator fit = mymap.find(hashval);
			if( fit != mymap.end())
			{
				mymap.erase(fit);
			}
		}
	}

	void addNode(size_t size)//从哈希环中添加一个服务器
	{
		for(int i = 0; i < VirtualServerNodeNum;i++)
		{
			std::stringstream ss;
			ss << "Shard-" << size << "-" << i;
			uint32_t hashval = murmur3_32(ss.str().c_str(),strlen(ss.str().c_str()));

			mymap.insert(std::pair<uint32_t,size_t>(hashval,i)); /i:Real server node
		}
	}
};

//散列算法,将一个char*的字符串,散列后返回一个哈希值。具体这里不做深入讨论。
int murmur3_32(const char *key, int len, int seed = 17)//fault seed=17 
{
	static const int c1 = 0xcc9e2d51;
	static const int c2 = 0x1b873593;
	static const int r1 = 15;
	static const int r2 = 13;
	static const int m = 5;
	static const int n = 0xe6546b64;

	int hash = seed;

	const int nblocks = len / 4;
	const int *blocks = (const int *)key;
	int i;
	for (i = 0; i < nblocks; i++)
	{
		int k = blocks[i];
		k *= c1;
		k = (k << r1) | (k >> (32 - r1));
		k *= c2;


		hash ^= k;
		hash = ((hash << r2) | (hash >> (32 - r2))) * m + n;
	}

	const int *tail = (const int *)(key + nblocks * 4);
	int k1 = 0;

	switch (len & 3)
	{
	case 3:
		k1 ^= tail[2] << 16;
	case 2:
		k1 ^= tail[1] << 8;
	case 1:
		k1 ^= tail[0];


		k1 *= c1;
		k1 = (k1 << r1) | (k1 >> (32 - r1));
		k1 *= c2;
		hash ^= k1;
	}

	hash ^= len;
	hash ^= (hash >> 16);
	hash *= 0x85ebca6b;
	hash ^= (hash >> 13);
	hash *= 0xc2b2ae35;
	hash ^= (hash >> 16);

	return hash;
}

 该代码没做实测,只是根据一致性哈希的逻辑写的一个实例。具体项目中,还需要根据实际应用。

 

 五、关于分布式和集群的概念和区别:
 

我的理解:

集群,就是多个服务器处理同样类型的事物,把这些服务器统一看待,就是一个集群。

分布式系统,就是讲一个系统拆分成不同模块,多个服务器运行不同的模块。

-----------(详细解释)---------

集群结构

集群模式在程序猿界有各种装逼解释,有的让你根本无法理解,其实就是一个很简单的玩意儿,且听我一一道来。

单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。

但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个牛逼了名字——负载均衡服务器。

集群结构的好处就是系统扩展非常容易。如果随着你们系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用微服务结构了。

分布式结构

从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模式,还是升级成微服务架构,这需要你们的架构师深思熟虑、权衡投入产出比。

OK,下面开始介绍所谓的分布式结构。

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。

举个例子,假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。

这样的好处有很多:

  1. 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
  2. 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。
  3. 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值