分布式缓存、消息系统和异步架构
Date: 2020/7/7 V1.0
Author:Jessie
前言
本周对分布式缓存、消息系统和异步架构进行了知识进行了学习和梳理。
分布式缓存
缓存:介于数据访问和数据源之前的告诉存储。用于数据多次读取时,加快读取的速度。缓存记录在内存中,用Hash表或树结果进行实现。本质利用数据,根据地址快速找到内容。
关键指标:缓存命中率。该指标由:键集合大小、可使用的内存空间、缓存生命时间TTL决定。
这里特别重点讲解了一致性Hash算法实现分布式对象缓存。
这种算法,本质角色当服务器水平伸缩时,用余数Hash带来的缓存雪崩问题。
一致性哈希算法的思路为:先构造出一个长度为232整数环,根据节点名称的hash值(分布为[0,232-1])放到这个环上。现在要存放资源,根据资源的Key的Hash值(也是分布为[0,232-1])值Haaa,在环上顺时针的找到离Haaa最近(第一个大于或等于Haaa)的一个节点,就建立了资源和节点的映射关系。下图为基于虚拟节点的一致性Hash算法。
各种介质的数据访问延迟不同,内存的访问比磁盘的访问更快,优先访问本地缓存。
而在各个技术栈中,各个层次的缓存节省的资源也效率不同。所以要尽量在客户端、CDN做缓存,避免对主数据存储的直接访问,使得这些I/O设备获得更好的响应。
但缓存不能滥用,将缓存集中在简单的读取业务,如果将其用于大量的写操作、NOSQL、复杂业务(工作流)的使用、或者没有热点的访问,反而使得系统性能下降。
消息队列与异步架构
消息队列构建的异步调用架构有两种模型:
点对点模型
发布订阅模型
异步模式,带来的好处:
更强的伸缩性
销峰填谷
生产者和消费者的直接依赖
主要MQ产品的对比
负载均衡
分布式数据库
MySQL主从复制、主主复制的原理如下:
主从复制,通过主服务器对数据库的操作写BinLog,从而通知到从服务器,写服务器的RelayLog。异步通知从DB根据RelayLog进行SQL执行。通过多台从服务器,从而提供多台对外的数据读取服务。
主主复制,不会两台主DB同时写,避免数据冲突。每台BinLog都是接收直接的数据库操作;而Relay Log负责从另一台操作读取同步信息。
主主复制中对于数据表的表更新操作,都是线下单独手工操作。不能直接更新表结构,避免延迟带来的数据巨大延迟。
考文献/推荐书籍
思考帮助
本次印象深刻的第一个方面:本地对象缓存做分布式集群的方案与我们曾经一个业务的方案类似。原方案实际中会因集群间内部数据同步到达极限时,集群无法扩容受到限制;这种集群方案,会因为Cache的访问用户量大、数据同步,导致缓存很快不够用、及同步带来的网络带宽不够用。
第二个深刻的就是一致性Hash算法,在没有深入研究算法前,我们对于用户的调度都是采用了余数取模。仅仅考虑技术,而没有从系统伸缩性考虑水平增加或减少服务器时,对系统命中率的影响——甚至带来缓存雪崩。通过实践一致性Hash算法,从而尝试将服务器调度更加均衡。
番外——“知其所以然”
学习首先就是要知其然。本次对两个重要知识点再详细梳理:
分布式缓存与找到正确的缓存服务器
分布式对象缓存是系统架构中比较重要的一部分内容。所谓的分布式对象缓存是指对象缓存以一个分布式集群的方式对外提供服务,多个应用系统使用同一个分布式对象缓存提供的缓存服务。这里的缓存服务器是由多台服务器组成的,这些服务器共同构成了一个集群对外提供服务。利用集群方式,提供更多的缓存空间。
看一下分布式对象的缓存模型:
当需要进行分布式缓存访问的时候,依然是以 Key、value 这样的数据结构进行访问。比如说,我们这个例子中就是 BEIJING 作为 Key,一个DATA 数据作为它的 value。
当需要进行分布式对象访问的时候,应用程序需要使用分布式对象缓存的客户端 SDK。比如说 Memcached 提供的一个客户端 API 程序进行访问,客户端 API 程序会使用自己的路由算法进行路由选择,选择其中的某一台服务器,找到这台服务器的 IP 地址和端口以后,通过通讯模块和相对应的这台服务器进行通信。
因为进行路由选择的时候,就是使用缓存对象的 Key 进行的计算。下一次使用相同的 Key 使用相同路由算法进行计算的时候,算出来的服务器依然还是前面计算出来的这个服务器。
Memcached利用这种缓存模型,集群设备间相互无感知,通过路由算法获取对应服务器缓存。
Redis 分桶与一致性Hash对比
对于Redis 分桶与一致性Hash的缓存模型,借这次学习做个总结:
Hash槽(分桶)
Redis cluster集群没有采用一致性哈希方案,而是采用数据分片中的哈希槽(预分桶)来进行数据存储与读取的。
预分好16384(214)个桶,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。每个Redis物理结点负责一部分桶的管理,当发生Redis节点的增减时,调整桶的分布即可。
例如,假设Redis Cluster三个节点A/B/C,则
Node A 包含桶的编号可以为: 0 到 5500。
Node B 包含桶的编号可以为: 5500 到 11000。
Node C包含桶的编号可以为: 11001 到 16384。
Redis Cluster支持在线增/减节点。当发生Redis节点的增减时,调整桶的分布即可。基于桶的数据分布方式大大降低了迁移成本,只需将数据桶从一个Redis Node迁移到另一个Redis Node即可完成迁移。当桶从一个Node A向另一个Node B迁移时,Node A和Node B都会有这个桶,Node A上桶的状态设置为MIGRATING,Node B上桶的状态被设置为IMPORTING。
客户端请求时:所有在Node A上的请求都将由A来处理,所有不在A上的key都由Node B来处理。同时,Node A上将不会创建新的key。
和一致性哈希相比
它并不是闭合的,key的定位规则是根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点,而一致性哈希是根据hash(key)的值来顺时针找第一个hash(ip)的节点,从而确定key存储在哪个节点。
一致性哈希是创建虚拟节点来实现节点宕机后的数据转移并保证数据的安全性和集群的可用性的。
Redis cluster是采用master节点有多个slave节点机制来保证数据的完整性的,master节点写入数据,slave节点同步数据。当master节点挂机后,slave节点会通过选举机制选举出一个节点变成master节点,实现高可用。但是这里有一点需要考虑,如果master节点存在热点缓存,某一个时刻某个key的访问急剧增高,这时该mater节点可能操劳过度而死,随后从节点选举为主节点后,同样宕机,一次类推,造成缓存雪崩。
预分桶的方案介于“硬Hash”和“一致性Hash”之间,牺牲了一定的灵活性,但相比“一致性Hash“,数据的管理成本大大降低。
利用一致性哈希环上进行服务器扩容的时候,新增加一个节点不需要改动前面取模算法里的除数,导致最后的取值结果全部混乱,它只需要在哈希环里根据新的服务器节点的名称计算它的哈希值,把哈希值放到这个环上就可以了。
一致性Hash利用环和虚拟节点,达到分布的均衡,但要考虑虚拟节点的平衡和性能:如果实际服务对应虚拟节点过多,那么在集群伸缩时,缓存失效的数据也会更多。具体多少达到平衡,需要根据自己的Hash算法和虚拟节点做测试。
实践中通常是把一个服务器节点虚拟成 200 个虚拟节点,然后把 200 个虚拟节点放到环上。Key 依然是顺时针的查找距离它最近的虚拟节点,找到虚拟节点以后,根据映射关系找到真正的物理节点。
参考资料
https://zhuanlan.zhihu.com/p/134055639
https://www.iteye.com/blog/uule-2431878
https://www.cnblogs.com/taosim/articles/4238674.html
https://www.jianshu.com/p/4163916a2a8a
版权声明: 本文为 InfoQ 作者【架构5班杨娟Jessie】的原创文章。
原文链接:【http://xie.infoq.cn/article/69eb028b7f0027e311dd4a914】。文章转载请联系作者。
评论