哈希算法
定义
将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法。
而通过原始数据映射之后得到的二进制值串就是哈希值。
特点
- 从哈希值不能反向推导出原始数据(所以哈希算法也叫单向哈希算法);
- 对输入数据非常敏感,哪怕原始数据只修改了一个 Bit,最后得到的哈希值也大不相同;
- 散列冲突的概率要很小,对于不同的原始数据,哈希值相同的概率非常小;
- 哈希算法的执行效率要尽量高效,针对较长的文本,也能快速地计算出哈希值。
应用一:安全加密
- 常用于加密的哈希算法:
MD5(MD5 Message-Digest Algorithm,MD5 消息摘要算法)
SHA(Secure Hash Algorithm,安全散列算法)。
- 其他加密算法:DES(Data Encryption Standard,数据加密标准)、AES(Advanced Encryption Standard,高级加密标准)。
应用二:唯一标识
- 图片唯一标识
- 从图片的二进制码串开头取 100 个字节,从中间取 100 个字节,从最后再取 100 个字节,然后将这 300 个字节放到一块,通过哈希算法(比如 MD5),得到一个哈希字符串。
- 用它作为图片的唯一标识。通过这个唯一标识来判定图片是否在图库中,这样就可以减少很多工作量。
应用三:数据校验(利用输入数据敏感性)
- BT下载原理
- 基于 P2P 协议的。从多个机器上并行下载一个 2GB 的电影,这个电影文件可能会被分割成很多文件块(比如可以分成 100 块,每块大约 20MB)。等所有的文件块都下载完成之后,再组装成一个完整的电影文件就行了
- BT下载文件的数据检验:
- 通过哈希算法,对 100 个文件块分别取哈希值,并且保存在种子文件中
- 当文件块下载完成之后,通过相同的哈希算法,对下载好的文件块逐一求哈希值,然后跟种子文件中保存的哈希值比对。
- 如果不同,说明这个文件块不完整或者被篡改了,需要再重新从其他宿主机器上下载这个文件块。
应用四:散列函数
- 散列函数关注点:
- 不关心是否能反向解密
- 关注散列后的值是否能平均分布
- 关注散列算法的性能
如何防止数据库中的用户信息被脱库?
- 通过哈希算法,对用户密码进行加密之后再存储,不过最好选择相对安全的加密算法,比如 SHA 等(因为 MD5 已经号称被破解了)
- 字典攻击风险:维护一个常用密码的字典表,把字典中的每个密码用哈希算法计算哈希值,然后拿哈希值跟脱库后的密文比对。如果相同,基本上就可以认为,这个加密之后的密码对应的明文就是字典中的这个密码。
- 针对字典攻击策略:引入一个盐(salt),跟用户的密码组合在一起,增加密码的复杂度。我们拿组合之后的字符串来做哈希算法加密,将它存储到数据库中,进一步增加破解的难度。
应用五:负载均衡
- 负载均衡算法:轮询、随机、加权轮询等。
- 那如何才能实现一个会话粘滞(session sticky)的负载均衡算法呢?也就是说,我们需要在同一个客户端上,在一次会话中的所有请求都路由到同一个服务器上。
- 直接的方法:
- 维护一张映射关系表,这张表的内容是客户端 IP 地址或者会话 ID 与服务器编号的映射关系。客户端发出的每次请求,都要先在映射表中查找应该路由到的服务器编号,然后再请求编号对应的服务器。
- 弊端:如果客户端很多,映射表可能会很大,比较浪费内存空间;客户端下线、上线,服务器扩容、缩容都会导致映射失效,这样维护映射表的成本就会很大;
- 借助hash算法:
- 通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将取得的哈希值与服务器列表的大小进行取模运算,最终得到的值就是应该被路由到的服务器编号。 这样,我们就可以把同一个 IP 过来的所有请求,都路由到同一个后端服务器上。
- 直接的方法:
应用六:数据分片
- 统计“搜索关键词”出现的次数: 1T 的日志文件,记录了用户的搜索关键词,快速统计出每个关键词被搜索的次数,该怎么做呢?
- 先对数据进行分片,然后采用多台机器处理的方法,来提高处理速度:
- 用 n 台机器并行处理,从搜索记录的日志文件中,依次读出每个搜索关键词,并且通过哈希函数计算哈希值,然后再跟 n 取模,最终得到的值,就是应该被分配到的机器编号。
- 快速判断图片是否在图库中:
- 判断一个图片是否在图库中的时候,通过应用二同样的哈希算法,计算这个图片的唯一标识,然后与机器个数 n 求余取模。假设得到的值是 k,那就去编号 k 的机器构建的散列表中查找。
应用七:分布式存储
- 互联网面对的都是海量的数据、海量的用户。为了提高数据的读取、写入能力,一般都采用分布式的方式来存储数据,比如分布式缓存。海量的数据需要缓存,需要将数据分布在多台机器上
- 借用前面数据分片的思想,即通过哈希算法对数据取哈希值,然后对机器个数取模,这个最终值就是应该存储的缓存机器编号。
- 机器扩容问题:如果原有10台机器,数据13,通过hash算法算出机器编号为13%10=3,现扩容至11台,则机器编号为13%11=2,缓存机器编号计算不一致了,缓存就失效了,都要去数据库查询,大大影响性能。
- 一致性哈希算法:
- 把全量的缓存空间当做一个环形存储结构。环形空间总共分成2^32个缓存区,在Redis中则是把缓存key分配到16384个slot。
- 每一个缓存key都可以通过Hash算法转化为一个32位的二进制数,也就对应着环形空间的某一个缓存区。我们把所有的缓存key映射到环形空间的不同位置。
- 我们的每一个缓存节点(Shard)也遵循同样的Hash算法,比如利用IP做Hash,映射到环形空间当中。
- 如何让key和节点对应起来呢?很简单,每一个key的顺时针方向最近节点,就是key所归属的存储节点。所以图中key1存储于node1,key2,key3存储于node2,key4存储于node3。
-
增加节点:当缓存集群的节点有所增加的时候,整个环形空间的映射仍然会保持一致性哈希的顺时针规则,所以有一小部分key的归属会受到影响。
-
图中加入了新节点node4,处于node1和node2之间,按照顺时针规则,从node1到node4之间的缓存不再归属于node2,而是归属于新节点node4。因此受影响的key只有key2。
- 最终把key2的缓存数据从node2迁移到node4,就形成了新的符合一致性哈希规则的缓存结构
-
-
删除节点与增加类似
-
注意缓存迁移的意思:并不是把数据复制过去,而是在所属的新的节点上缓存不命中时刷新缓存。
- 虚拟节点:为了防止节点在环上分布不均匀使得数据分布不均匀,创建多个虚拟节点,虚拟节点按一定的规则与真实节点形成映射关系。
其他应用
- 网络协议中的 CRC 校验
- Git commit id