19 分布式缓存集群的伸缩性设计
应用程序通过 Memcached 客户端访问 Memcached 服务器集群,Memcached 客户端主 要由一组 API、Memcached 服务器集群路由算法、Memcached 服务器集群列表及通信模 块构成。
其中路由算法负责根据应用程序输入的缓存数据 KEY 计算得到应该将数据写入到 Memcached 的哪台服务器(写缓存)或者应该从哪台服务器读数据(读缓存)。
一个典的型缓存写操作如图 6.10 中箭头所示路径。应用程序输入需要写缓存的数据 v,BEIJING,DATA>, API 将 KEY (?BEIJING’)输入路由算法模块,路由算法根据 KEY 和 Memcached 集群服务器列表计算得到一台服务编号(NODE1),进而得到该机器的 IP 地 址和端口( 10.0.0.0:91000)。API 调用通信模块和编号为 NODE1 的服务器通信,将数据 <,BEIJING\DATA>写入该服务器。完成一次分布式缓存的写操作。
读缓存的过程和写缓存一样,由于使用同样的路由算法和服务器列表,只要应用程 序提供相同的 KEY ('BEIJING*), Memcached 客户端总是访问相同的服务器(NODE1 ) 去读取数据。只要服务器还缓存着该数据,就能保证缓存命中。
[](()2 Memcached 分布式缓存集群的伸缩性挑战
由上述讨论可得知,在 Memcached 分布式缓存系统中,对于服务器集群的管理,路由算法至关重要,和负载均衡算法一样,决定着究竟该访问集群中的哪台服务器。
简单的路由算法可以使用余数 Hash:用服务器数目除以缓存数据 KEY 的 Hash 值, 余数为服务器列表下标编号。假设图 6.10 中 BEIJING 的 Hash 值是 490806430 ( Java 中的 HashCode()返回值),用服务器数目 3 除以该值,得到余数 1,对应节点 NODElo 由于 HashCode 具有随机性,因此使用余数 Hash 路由算法可保证缓存数据在整个 Memcached 服务器集群中比较均衡地分布。
对余数 Hash 路由算法稍加改进,就可以实现和负载均衡算法中加权负载均衡一样的加权路由。事实上,如果不需要考虑缓存服务器集群伸缩性,余数 Hash 几乎可以满足绝 大多数的缓存路由需求。
但是,当分布式缓存集群需要扩容的时候,事情就变得棘手了。
假设由于业务发展,网站需要将 3 台缓存服务器扩容至 4 台。更改服务器列表,仍旧使用余数 Hash,用 4 除以 EEIJING 的 Hash 值 49080643,余数为 2,对应服务器 NODE 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 2o 由于数据<,BEIJING,DATA>缓存在 NODE1,对 NODE2 的读缓存操作失败,缓存没有命 中。
很容易就可以计算出,3 台服务器扩容至 4 台服务器,大约有 75%( 3/4)被缓存了的数据不能正确命中,随着服务器集群规模的增大,这个比例线性上升。当 100 台服务 器的集群中加入一台新服务器,不能命中的概率是 99% (N/(N+1))。
这个结果显然是不能接受的,在网站业务中,大部分的业务数据读操作请求事实上 是通过缓存获取的,只有少量读操作请求会访问数据库,因此数据库的负载能力是以有 缓存为前提而设计的。当大部分被缓存了的数据因为服务器扩容而不能正确读取时,这 些数据访问的压力就落到了数据库的身上,这将大大超过数据库的负载能力,严重的可 能会导致数据库宕机(这种情况下,不能简单重启数据库,网站也需要较长时间才能逐 渐恢复正常。详见本书第 13 章。)
本来加入新的缓存服务器是为了降低数据库的负载压力,但是操作不当却 导致了数据库的崩溃。如果不对问题和解决方案有透彻了解,网站技术总有想 不到的陷阱让架构师一脚踩空。遇到这种情况,用某网站一位资深架构师的话说,就是“一股寒气从脚底板窜到了脑门心”。
一种解决办法是在网站访问量最少的时候扩容缓存服务器集群,这时候对数据库的 负载冲击最小。然后通过模拟请求的方法逐渐预热缓存,使缓存服务器中的数据重新分 布。但是这种方案对业务场景有要求,还需要技术团队通宵加班(网站访问低谷通常是 在半夜)o
能不能通过改进路由算法,使得新加入的服务器不影响大部分缓存数据的正确命中 呢?目前比较流行的算法是一致性 Hash 算法。
[](()3 分布式缓存的一致性 Hash 算法
一致性 Hash 算法通过一个叫作一致性 Hash 环的数据结构实现 KEY 到缓存服务器的 Hash 映射,如图 6.11 所示。
具体算法过程为:先构造一个长度为。02 的 32 次方的整数环(这个环被称作一致性 Hash 环),根据节点名称的 Hash 值(其分布范围同样为 02 的 32 次方)将缓存服务器节点放置在这个 Hash 环上。然后根据需要缓存的数据的 KEY 值计算得到其 Hash 值(其分布范围也同样为 0~2 的 32 次方),然后在而‘代环上顺时针查找距离这个 KEY 的 Hash 值最近的缓存服务器节点,完成 KEY 到服务器的 Hash 映射查找。
在图 6.11 中,假设 N0DE1 的 Hash 值为 3,594,963,423, NODE2 的 Hash 值为 1,845,328,979,而 KEYO 的 Hash 值为 2,534,256,785,那么 KEYO 在环上顺时针查找,找
到的最近的节点就是 NODEl。
当缓存服务器集群需要扩容的时候,只需要将新加入的节点名称(NODE3 )的 Hash 值放入一致性 Hash 环中,由于 KEY 是顺时针查找距离其最近的节点,因此新加入的节点只影响整个环中的一小段,如图 6.12 中深色一段。
假设 NODE3 的 Hash 值是 2,790,324,235,那么加入 NODE3 后,KEY0( Hash 值 2,534, 256,785 )顺时针查找得到的节点就是 NODE3。
评论