作者简介:李智慧,前阿里巴巴技术专家 本文选自:拉勾教育专栏《
架构师的 36 项修炼》你好,我是李智慧。在实践中,总有一些棘手的问题让人困惑。其实,只要吃透本质,多数问题都可以迎刃而解。今天我们来讲一讲系统架构中,非常重要的一块内容:分布式对象缓存。
01 分布式对象缓存
分布式对象缓存是系统架构中比较重要的一部分内容。所谓的分布式对象缓存是指对象缓存以一个分布式集群的方式对外提供服务,多个应用系统使用同一个分布式对象缓存提供的缓存服务。这里的缓存服务器是由多台服务器组成的,这些服务器共同构成了一个集群对外提供服务。
所以使用分布式对象缓存一个重要的点就是,数据进行读写操作的时候,如何找到正确的缓存服务器进行读写操作。因为如果第一次写入数据的时候写入的是 A 服务器,但是数据进行缓存读操作的时候访问的是 B 服务器,就不能够正确地查找到数据,缓存也就没有了效果。
点击文末“了解更多”查看:拉勾教育专栏《架构师的 36 项修炼》
02 如何找到正确的缓存服务器
以 Memcached 服务器集群为例,我们来看一下分布式对象的缓存模型。
当需要进行分布式缓存访问的时候,依然是以 Key、value 这样的数据结构进行访问。比如说,我们这个例子中就是 BEIJING 作为 Key,一个DATA 数据作为它的 value。
当需要进行分布式对象访问的时候,应用程序需要使用分布式对象缓存的客户端 SDK。比如说 Memcached 提供的一个客户端 API 程序进行访问,客户端 API 程序会使用自己的路由算法进行路由选择,选择其中的某一台服务器,找到这台服务器的 IP 地址和端口以后,通过通讯模块和相对应的这台服务器进行通信。
因为进行路由选择的时候,就是使用缓存对象的 Key 进行的计算。下一次使用相同的 Key 使用相同路由算法进行计算的时候,算出来的服务器依然还是前面计算出来的这个服务器。
所以通过这种方法可以访问到正确的服务器进行数据读写。服务器越多,提供的缓存空间就越大,实现的缓存效果也就越好。通过集群的方式,提供了更多的缓存空间。
那么,路由算法又是如何进行服务器路由选择的?这里的主要算法通常讲依然可以使用上面讲到的哈希表的路由算法,也就是取模算法。
比如说,我们这里缓存服务器集群中有 3 台服务器,根据 Key 的哈希值对3 取模得到的余数一定在 0、1、2 三个数据之间,那么每一个数字都对应着一台服务器,根据这个数字查找对应的服务器 IP 地址就可以了。
使用余数取模这种方式进行路由计算非常简单,但这种算法也有一个问题,就是当服务器进行扩容的时候,比如说我们当前的服务器集群有 3 台服务器,如果我们 3 台服务器不够用了,需要添加 1 台服务器,这个时候对 3 取膜就会变成对 4 去取模,导致的后果就是以前对 3 取模的时候写入的数据,对 4 取模的时候可能就查找不到了。
刚才也讲过缓存雪崩的情况,实际上如果使用取模算法进行服务器添加,因为除数的变化会导致和缓存雪崩一样的后果,也就是说前面写入缓存服务器集群中的缓存数据,添加了 1 台服务器后很多数据都找不到了,类似于雪崩,
最后会导致整个服务器集群都崩溃。我们添加服务器的主要目的是提高它的处理能力,但是不正确的操作可能会导致整个集群都失效。
点击文末“了解更多”查看:拉勾教育专栏《架构师的 36 项修炼》
03 使用一致性哈希算法
一致性哈希和余数哈希不同,一致性哈希首先是构建一个一致性哈希环的结构。一致性哈希环的大小是 0 到 2 的 32 次方减 1 ,实际上就是我们计算机中无符号整型值的取值范围,这个取值范围 0 和最后一个值 2 的 32 次方减 1 首尾相连,就构成了一个一致性哈希环。
然后我们对每个服务器的节点取模,求它的哈希值把这个哈希值放到环上,所有的服务器都取哈希值放到环上,每一次进行服务器查找路由计算的时候,都是把 Key 也取它的哈希值,取到哈希值以后把 Key 放到环上,顺时针查找距离它最近的服务器的节点是哪一个它的路由节点就是哪一个。
通过这种方式也可以实现,Key 不变的情况下找到的总是相同的服务器。这种一致性哈希算法除了可以实现像余数哈希一样的路由效果以外,同时对服务器的集群扩容效果也非常好。
在一致性哈希环上进行服务器扩容的时候,新增加一个节点不需要改动前面取模算法里的除数,导致最后的取值结果全部混乱,它只需要在哈希环里根据新的服务器节点的名称计算它的哈希值,把哈希值放到这个环上就可以了。
放到环上后,它不会影响到原先节点的哈希值,也不会影响到原先服务器在哈希环上的分布,它只会影响到离他最近的服务器,比如说图中 node3 是新加入的服务器,那么它只会影响到 node1,原先访问 node1 的一部 key 会访问到 node3 上,也就是说对缓存的影响是比较小的,它只会影响到缓存里面的一小段。
我们知道缓存中如果一小部分数据受到了影响,不能够正确的命中,那么他可以去数据库中读取,而数据库的压力只要在它的负载能力之内,也不会崩溃,系统就可以正常运行。所以通过一致性哈希算法可以实现缓存服务器的顺利伸缩扩容。
但是一致性哈希算法有一个致命的缺陷。我们知道哈希值其实是一个随机值,把一个随机值放到一个环上以后,可能是不均衡的,也就是说某两个服务器可能距离很近,而和其它的服务器距离很远,这个时候就会导致有些服务器的负载压力特别大,有些服务器的负载压力非常小。
同时在进行扩容的时候,比如说加入一个节点 3 它影响的只是节点 1 ,而我们实际上希望加入一个服务器节点的时候,它能够分摊其他所有服务器的一部分访问压力和数据冲突。
但是使用上述描述的算法并不能够达到这个效果,所以对这个算法需要进行一些改进,改进办法就是使用虚拟节点。也就是说我们这一个服务器节点放入到一致性哈希环上的时候,并不是把真实的服务器的哈希值放到环上,而是将一个服务器虚拟成若干个虚拟节点,把这些虚拟节点的 hash 值放到环上去。
在实践中通常是把一个服务器节点虚拟成 200 个虚拟节点,然后把 200 个虚拟节点放到环上。Key 依然是顺时针的查找距离它最近的虚拟节点,找到虚拟节点以后,根据映射关系找到真正的物理节点。画带虚拟节点的一致性 hash 环比较乱,这里大家可以想象一下。
通过这种手段第一可以解决我们刚才提到的负载不均衡的问题,因为有更多的虚拟节点在环上,所以它们之间的距离总体来说大致是相近的。同时加入一个新节点的时候,他也是加入多个虚拟节点,比如说 200 个虚拟节点,那么加入进来以后在环上每个节点都可能会受到影响,从而分摊原先每个服务器的一部分负载。
好的,这节课的内容就到这里,下一次我会继续分享我的架构经验。加油,我们不见不散。
点击文末“了解更多”查看:拉勾教育专栏《架构师的 36 项修炼》
版权声明:本文版权归属拉勾教育及该专栏作者,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者必究。