原理
将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值。
设计要求
- 从哈希值不能反向推导出原始数据(所以哈希算法也叫单向哈希算法);
- 对输入数据非常敏感,哪怕原始数据只修改了一个 Bit,最后得到的哈希值也大不相同;
- 散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率非常小;
- 哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值。
MD5哈希算法
MD5的哈希值是 128 位的 Bit 长度,为了方便表示,把它们转化成了 16 进制编码。
- 无论哈希文本的多长多短,使用MD5哈希后的得到的哈希值长度相同。
MD5("今天我来讲哈希算法") = bb4767201ad42c74e650c1b6c03d78fa
MD5("jiajia") = cd611a31ea969b908932d44d126d195b
- 即使非常相似的文本,其哈希值也是完全不同的。
MD5("我今天讲哈希算法!") = 425f0d5a917188d2c3c3dc85b5e4f2cb
MD5("我今天讲哈希算法") = a1fb91ac128e6aa37fe42c663971ac3d
- 通过哈希值“a1fb91ac128e6aa37fe42c663971ac3d”很难推出哈希的文本。
- MD5哈希算法速度较快;
应用
应用加密
最常用于加密的哈希算法是 MD5(MD5 Message-Digest Algorithm,MD5 消息摘要算法)和 SHA(Secure Hash Algorithm,安全散列算法),还有其他算法:DES(Data Encryption Standard,数据加密标准)、AES(Advanced Encryption Standard,高级加密标准)。
- 对于加密算法,最重要的设计要求在于:不能反向推导以及散列冲突概率要小。
- 一般情况下,哈希值越长的哈希算法,散列冲突的概率越低。
- 没有绝对安全的加密。越复杂、越难破解的加密算法,需要的计算时间也越长。 在理论上,散列冲突不可避免:这是因为哈希算法产生的哈希值长度是固定且有限的。在MD5中,最多能表示2^128个数据,多于这个数据,则会产生冲突(鸽巢原理)。但即使存在散列冲突,冲突的概率也是极低的,所以相对来说较难破解,因为要在2^128个哈希值找相同的那个,耗费时长是较大的,所以在资源和时间有限的条件下,哈希算法较难破解。
防止用户信息“脱库”
加salt,也可理解为为密码加点佐料后再进行hash运算。比如原密码是123456,不加盐的情况加密后假设是是xyz。 黑客拿到脱机的数据后,通过彩虹表匹配可以轻松破解常用密码。如果加盐,密码123456加盐后可能是12ng34qq56zz,再对加盐后的密码进行hash后值就与原密码hash后的值完全不同了。而且加盐的方式有很多种,可以是在头部加,可以在尾部加,还可在内容中间加,甚至加的盐还可以是随机的。
唯一标识
如果要在海量的图库中,搜索一张图是否存在,不能单纯地用图片的元信息(比如图片名称)来比对,因为有可能存在名称相同但图片内容不同,或者名称不同图片内容相同的情况。
- 较慢:任何文件在计算中都可以表示成二进制码串,所以,比较笨的办法就是,拿要查找的图片的二进制码串与图库中所有图片的二进制码串一一比对。如果相同,则说明图片在图库中存在。但是,每个图片小则几十 KB、大则几 MB,转化成二进制是一个非常长的串,比对起来非常耗时。
- 较快:可以给每一个图片取一个唯一标识,或者说信息摘要。比如,可以从图片的二进制码串开头取 100 个字节,从中间取 100 个字节,从最后再取 100 个字节,然后将这 300 个字节放到一块,通过哈希算法(比如 MD5),得到一个哈希字符串,用它作为图片的唯一标识。通过这个唯一标识来判定图片是否在图库中,这样就可以减少很多工作量。
- 更快:可以把每个图片的唯一标识,和相应的图片文件在图库中的路径信息,都存储在散列表中。当要查看某个图片是不是在图库中的时候,先通过哈希算法对这个图片取唯一标识,然后在散列表中查找是否存在这个唯一标识。如果不存在,那就说明这个图片不在图库中;如果存在,我们再通过散列表中存储的文件路径,获取到这个已经存在的图片,跟现在要插入的图片做全量的比对,看是否完全一样。如果一样,就说明已经存在;如果不一样,说明两张图片尽管唯一标识相同,但是并不是相同的图片。
数据校验
下载BT文件,会分成很多块,并行下载完成后合并。其中文件块存在被篡改的可能。因此,可以先通过哈希算法对文件块算哈希值并保存。若下载完成后,文件内容有所改变,那么其哈希值就会有所不同,若不同,则证明文件被篡改需重新下载。
散列函数
散列函数也是哈希算法的一种应用。
- 相对哈希算法的其他应用,散列函数对于散列算法冲突的要求要低很多。即便出现个别散列冲突,只要不是过于严重,都可以通过开放寻址法或者链表法解决。
- 散列函数对于散列算法计算得到的值,是否能反向解密也并不关心。散列函数中用到的散列算法,更加关注散列后的值是否能平均分布。
- 散列函数执行的快慢,也会影响散列表的性能,所以,散列函数用的散列算法一般都比较简单,比较追求效率。
负载均衡
负载均衡算法有很多,比如轮询、随机、加权轮询等。那如何才能实现一个会话粘滞(session sticky)的负载均衡算法呢——我们需要在同一个客户端上,在一次会话中的所有请求都路由到同一个服务器上。
- 最直接的方法就是,维护一张映射关系表,这张表的内容是客户端 IP 地址或者会话 ID 与服务器编号的映射关系。客户端发出的每次请求,都要先在映射表中查找应该路由到的服务器编号,然后再请求编号对应的服务器。这种方法简单直观,但也有几个弊端:
- 如果客户端很多,映射表可能会很大,比较浪费内存空间;
- 客户端下线、上线,服务器扩容、缩容都会导致映射失效,这样维护映射表的成本就会很大;
- 可以通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将取得的哈希值与服务器列表的大小进行取模运算,最终得到的值就是应该被路由到的服务器编号。 这样,我们就可以把同一个 IP 过来的所有请求,都路由到同一个后端服务器上。
数据分片
如何统计“关键词”出现的次数?
- 搜索日志很大,没办法放到一台机器的内存中。
- 如果只用一台机器来处理这么巨大的数据,处理时间会很长。 将数据分片到N台机器并行处理:先从日志中读出所有搜索关键词,并通过哈希函数计算哈希值,对N取模,根据得到的哈希值,将搜索关键词分配到相应的机器编号。那么,同一个搜索关键词就被分配到同一个机器。每个机器会分别计算关键词的次数,最后合并起来就是最终结果。这里的处理过程也是 MapReduce 的基本设计思想。
如何快速判断图片是否在图库中?
将数据分片到N台图片处理:从图库中读取图片并计算通过哈希函数计算唯一标识,将唯一标识对N取模,得到对应的机器编号,然后将这个图片的唯一标识和图片路径发往对应的机器构建散列表。在判断时,先计算唯一标识并找到对应编号机器,去其散列表中查找。
分布式储存
- 数据分片的思想,即通过哈希算法对数据取哈希值,然后对机器个数取模,这个最终值就是应该存储的缓存机器编号。但是,如果数据增多就需要扩容了,原本存储在编号为 3 这台机器上,在新加了一台机器后,就被分配到 2 号这台机器上了。
因此,所有的数据都要重新计算哈希值,然后重新搬移到正确的机器上。这样就相当于,缓存中的数据一下子就都失效了。所有的数据请求都会穿透缓存,直接去请求数据库。这样就可能发生雪崩效应,压垮数据库。
- 一致性哈希算法 由于分布式缓存系统的节点部署变化比较频繁,所以一致性哈希算法较为经常使用,而传统的关系型数据库的分库分表较为稳定。
- 首先,我们把全量的缓存空间当做一个环形存储结构。环形空间总共分成2^32个缓存区。
- 每一个缓存key都可以通过Hash算法转化为一个32位的二进制数,也就对应着环形空间的某一个缓存区。我们把所有的缓存key映射到环形空间的不同位置。
- 每一个缓存节点(Shard)【服务器】也遵循同样的Hash算法,比如利用IP做Hash,映射到环形空间当中。
- 如何让key和节点对应起来呢?很简单,每一个key的顺时针方向最近节点,就是key所归属的存储节点。所以图中key1存储于node1,key2,key3存储于node2,key4存储于node3。
- 增加节点与删除节点类似,只有部分key的归属有变化,如下图,只有key2的归属变成了node4的;
- 出现分布不均的情况时,一致性缓存引入了虚拟节点:基于原来的物理节点映射出N个子节点,在把所有子节点映射在环形空间上。
如上图所示,假如node1的ip是192.168.1.109,那么原node1节点在环形空间的位置就是hash(“192.168.1.109”)。 我们基于node1构建两个虚拟节点,node1-1 和 node1-2,虚拟节点在环形空间的位置可以利用(IP+后缀)计算,例如: hash(“192.168.1.109#1”),hash(“192.168.1.109#2”) 此时,环形空间中不再有物理节点node1,node2,只有虚拟节点node1-1,node1-2,node2-1,node2-2。由于虚拟节点数量较多,缓存key与虚拟节点的映射关系也变得相对均衡了。
其他应用
网络协议中的 CRC 校验、Git commit id等
思考
- 区块链使用的是哪种哈希算法吗?是为了解决什么问题而使用的呢? 区块链是一块块区块组成的,每个区块分为两部分:区块头和区块体。区块头保存着 自己区块体 和 上一个区块头 的哈希值。 因为这种链式关系和哈希值的唯一性,只要区块链上任意一个区块被修改过,后面所有区块保存的哈希值就不对了。区块链使用的是 SHA256 哈希算法,计算哈希值非常耗时,如果要篡改一个区块,就必须重新计算该区块后面所有的区块的哈希值,短时间内几乎不可能做到。
资料来源
time.geekbang.org/column/arti… time.geekbang.org/column/arti…