一、案例分析(1)问题概述假设我们的图像数据均匀分布在三个服务上(分别标记为服务器A、服务器B、服务器C)。现在我们想从中获取到图片后,服务器在收到请求后如何指定这张图片是存储在服务器A、服务器B还是服务器C上呢?如果是遍历的话,说两三句还好,但是那也太out了。当服务器数量达到数百或数千时,你还敢说要遍历吗?(2)解决方案a.通过存储映射关系,首先我们可能想到可以通过一个中间层来记录图片存储在哪个服务器上,如下:logo1.png=====》ServiceAogo2.png=====》ServiceBlogo3.png=====》ServiceC这样每当有请求过来,我们先请求图片和服务器的映射关系,找到存放图片的服务器,发送请求到指定的服务器。从实现的角度来说,这是可行的,但是在存储图片的时候,我们还必须存储图片和服务器的映射关系,这显然增加了工作量,而且它的维护也是一个问题。一旦存储的图片和服务器的映射关系出现问题,整个系统就挂了。b.哈希算法既然我们要排除存储映射关系,这时候,人们就想到了哈希算法。存储以下图片时,根据图片名称(logo1.png),通过哈希算法得到哈希值val,对val取模得到的值可以确定图片应该存储在哪个服务器上。如下:key=hash(imgName)%n其中:imgName是图片的名称,n是服务器的数量,key代表图片应该存放在哪台服务器上。当有请求进来,比如请求图片logo1.png,服务器可以根据上面公式计算出的key,判断出logo1.png存放在哪个服务器上。PHP实现如下:$hostsMap=['img1.findme.wang','img2.findme.wang','img3.findme.wang'];函数getImgSrc($imgName){global$hostsMap;$key=crc32($imgName)%count($hostsMap);返回'??http://'。$hostsMap[abs($key)]。'/'。$imgName;}//测试var_dump(getImgSrc("logo1.png"));var_dump(getImgSrc("logo2.png"));var_dump(getImgSrc("logo3.png"));输出:此时我们将存储映射关系改为计算服务器的序号,大大简化了工作量。但是一旦增加了新的机器就很麻烦,因为n变了,几乎所有的序列号key也都变了,所以需要做大量的数据迁移工作。C.一致性哈希算法一致性哈希算法是一种特殊的哈希算法,旨在解决节点数量(如存储图片的服务器数量)发生变化时最小化数据迁移的问题。基本思路:1.首先将0到2的32个点均匀分布成一个圆,如下:2.然后通过hash计算计算出所有的节点(存储图片的服务器),然后计算232取余,和然后映射到hash环上,如下:3.当请求来的时候,比如请求图片logo1.png,经过hash计算,取232的余数,然后映射到hash环上,如下:4,再顺时针旋转,第一个到达的node节点被认为是存放logo1.png图片的服务器。从上面我们可以知道,consistenthash的亮点在于,hash计算和映射是在node节点(存储图片的服务器)和object(图片)上进行的,二是闭环设计。优点:添加新机器时,只有标记的区域受到影响,如下图:缺点:当节点较少时,往往会出现不平衡的情况,因为经过hash计算后,映射到node节点在哈希环上。分布不均,导致部分机器负载高,部分机器闲置。PHP实现如下:$hostsMap=['img1.findme.wang','img2.findme.wang','img3.findme.wang'];$hashRing=[];函数getImgSrc($imgName){全局$hostsMap;全局$hashRing;//将节点映射到哈希环if(empty($hashRing)){foreach($hostsMapas$h){$hostKey=fmod(crc32($h),pow(2,32));$hostKey=abs($hostKey);$hashRing[$hostKey]=$h;}//从小到大排序,方便查找ksort($hashRing);}//计算图像哈希$imgKey=fmod(crc32($imgName),pow(2,32));$imgKey=abs($imgKey);foreach($hashRingas$hostKey=>$h){if($imgKey<$hostKey){返回'http://'.$小时。'/'。$img名称;}}返回'http://'。当前($hashRing)。'/'。$imgName;}var_dump(getImgSrc("logo1.png"));var_dump(getImgSrc("logo2.png"));var_dump(getImgSrc("logo3.png"));输出结果如下:至于为什么在使用fmod函数时取余运算符%不适用,主要是因为pow(2,32)在32位操作系统上运行上面PHP_INT_MAX超高。具体可以参考之前的文章《UncaughtDivisionByZeroError:Moduloby零”d。通过虚拟节点优化一致性哈希算法为了提高一致性哈希算法的平衡性,我们首先能想到的就是增加节点数,但是机器毕竟需要资金,并不是说可以随意增加。然后添加虚拟节点,这样就没有问题了。思路如下:1、假设host1、host2、host3各有3个虚拟节点。比如host1的虚拟节点分别是host1_1、host1_2、host1_32,然后所有的虚拟节点(存放图片的服务器)通过hash计算出232的余数后,也映射到hash环上,如下:然后,接下来的步骤都是采用一致性哈希算法,但是最后需要将虚拟节点转换为真实的单节点。PHP实现如下:$hostsMap=['img1.findme.wang','img2.findme.wang','img3.findme.wang'];$hashRing=[];函数getImgSrc($imgName){全局$hostsMap;全局$hashRing;$virtualNodeLen=3;//每个节点的虚拟节点数//将节点映射到哈希环if(empty($hashRing)){foreach($hostsMapas$h){$i=0;while($i<$virtualNodeLen){$hostKey=fmod(crc32($h.'_'.$i),pow(2,32));$hostKey=abs($hostKey);$hashRing[$hostKey]=$h;$i++;}}//从小到大排序,方便查找ksort($hashRing);}//计算图像哈希$imgKey=fmod(crc32($imgName),pow(2,32));$imgKey=abs($imgKey);foreach($hashRingas$hostKey=>$h){if($imgKey<$hostKey){返回'http://'.$小时。'/'。$img名称;}}返回'http://'。当前($hashRing)。'/'。$imgName;}var_dump(getImgSrc("login1.png"));var_dump(getImgSrc("login2.png"));var_dump(getImgSrc("login3.png"));执行结果如下:二、备注1、取模和取余有什么区别?取余遵循商尽可能接近0的原则取模,遵循商尽可能接近负无穷大的原则1、什么是CRC算法?CRC(CyclicalRedundancyCheck)即循环冗余码校验,主要用于数据校验。常用的有CRC16和CRC32,其中16和32代表多项式的最高次方
