分布式缓存架构与负载均衡架构
本篇文章从以下三个方面进行知识点梳理与复习,对于分布式数据库的相关内容会在下周进行总结
分布式缓存架构
消息队列与异步架构
负载均衡架构
1. 分布式缓存架构
1.1 什么是缓存(Cache)
缓存是存储在计算机上的一个原始数据复制集,介于数据访问者与数据源之间的一种高速存储,当数据进行读取时,直接从高速存储中获取,如果可以获取到结果时则无需访问数据库直接返回缓存中的内容;如果缓存未命中时,再从数据库中获取,一方面可以提高数据的读取速度,另一方面还可以降低数据库的性能消耗,提高用户交互体验的同时对系统的稳定性也有了一定的保障。
缓存是系统性能优化的大杀器
技术简单
性能提升效果显著
可应用场景多
缓存的数据一般采用<Key-Value>的Hash表方式进行存储
缓存Cache与缓冲区Buffer的区别?
缓存是为了弥补高速设备和低速设备的速度不匹配而引入的中间层,最终起到加快访问速度 的作用;
缓冲区是系统两端处理速度平衡(从长时间尺度上看)时使用的。它的引入是为了减小短期内突发I/O的影响,起到流量整形的作用。
1.2 无处不在的缓存——缓存的应用
CPU缓存
操作系统缓存
数据库缓存
JVM编译缓存
CDN缓存
代理与反向代理缓存
应用程序缓存
分布式对象缓存
1.3 缓存的关键指标——命中率
缓存是否有效依赖于能多少次重用同一缓存响应业务请求,这个度量指标被称作缓存命中率。
例如:查询一个缓存,十次查询九次可以得到正确的结果,那么它的命中率就是90%
影响缓存命中率的主要指标:
缓存键集合大小
缓存中每个对象使用缓存键进行识别,定位一个对象的唯一方式就是对缓存键执行精确匹配。换句话说,缓存键空间时你的应用能够生成的所有键的数量;
应用生成的唯一键越多,重用的机会就越小 ;
缓存键的数量越少,缓存的效率也就越高 ,所以一定要想办法减少可能的缓存键数量。
例如: 如果基于客户IP地址缓存天气数据,则有多达40亿(所有可能的IP地址数量)个键,如果基于客户来源国家缓存天气数据,则可能仅需几百个缓存键。
缓存可使用内存空间
缓存可使用内存空间直接决定了缓存对象的平均大小和缓存对象数量;
因为缓存对象通常存储在内存中,缓存对象可用空间受到严格限制且相对昂贵;
如果想要缓存更多的对象,就需要先删除老的对象,再添加新的对象;
替换或者清除对象会降低缓存命中率;
物理上能缓存的对象越多,缓存命中率就越高。
缓存对象生存时间(TTL):Time To Live
对象缓存的时间越长,缓存对象被重用的可能性就越高。
1.4 常见的缓存
1.4.1 代理缓存
1.4.2 反向代理缓存
1.4.3 多层反向代理缓存
1.4.4 内容分发网络(CDN)
1.4.5 CDN同时配置静态文件和动态内容
1.4.6 浏览器对象缓存
1.4.7 本地对象缓存
对象直接缓存在应用程序内存中;
对象存储在共享内存,同一台机器的多个进程可以访问;
缓存服务器作为独立应用和应用程序部署在同一个服务器上
1.4.8 本地对象缓存构建分布式集群
采用内存复制的方式进行缓存数据对象的更新同步
1.4.9 远程分布式对象缓存
1.4.10 Memcached分布式对象缓存
分布式缓存一致性Hash算法 以及 基于虚拟节点的一致性Hash算法 参考本周的作业:
https://xie.infoq.cn/article/0c660a8f26689cb4ac94e8b17
Memcached分布式缓存访问模型
1.5 缓存的分类
1.5.1 通读缓存(read-throgh)
通读缓存给客户端返回缓存资源,并在请求未命中缓存时获取实际数据
客户端连接的是通读缓存而不是生成响应的原始服务器
代理缓存、反向代理缓存、CDN缓存都是通读缓存
1.5.2 旁路缓存(Cache-aside)
旁路缓存通常是一个独立的键值对(key-value)存储;
对象缓存是一种旁路缓存;
应用代码通常会询问对象缓存需要的对象是否存在,如果存在,它会获取并使用缓存的对象,如果不存在或已过期,应用会连接主数据源来组装对象,并将其保存回对象缓存中以便将来使用。
1.6 各种介质数据访问延迟
1.7 技术栈各个层次的缓存
1.8 缓存为什么能显著提升性能
缓存数据通常来自内存,内存比磁盘的数据访问有更快的访问速度
缓存存储的数据是最终结果形态,不需要中间计算,减少CPU资源的消耗(例如:从DB中查询出来的数据转换成对象,有时候还需要额外的计算处理)
缓存降低数据库、磁盘、网络的负载压力,使这些I/O设备能够获得更好的响应特性
1.9 合理使用缓存
使用缓存对提高系统性能有很多好处,但是不合理地使用缓存可能非但不能提高系统的性能,还会造成系统压力、甚至引发系统风险。在实践中,过分依赖缓存、不合适的数据访问特性等缓存滥用的情景屡见不鲜。
频繁修改的数据: 这种数据如果缓存起来,由于频繁修改,应用尚未读取就已经失效或者被更新,徒增系统负担,一般情况下,读写比在2:1以上的数据缓存才有意义
没有热点的访问: 缓存使用内存作为存储,内存资源宝贵而且有限,不能将所有的数据都缓存起来。如果应用系统访问数据没有热点,不满足二八定律(即大部分数据访问不是集中在小部分数据上)那么缓存就没有意义,因为大部分数据还没有被再次访问就已经被挤出缓存了
数据不一致于脏读: 一般回对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载,因此应用要容忍一定时间的数据不一致。
缓存失效的处理方式:
数据更新时立即更新缓存,这种做法会带来更多的系统开销以及事务一致性问题;
数据更新时通知缓存失效,删除缓存数据,这是一种更加稳妥的做法,也是使用缓存较为合理的一种做法。因为有可能这个缓存数据就不怎么访问,而花费一定的系统能力更新缓存,再者过一定时间又失效了,得不偿失;所以让缓存失效,有业务请求时发现未命中,从数据库中获取并更新缓存是一种更加稳妥且数据一致性可以得到保证的措施。
缓存雪崩: 缓存是为了提高数据读取性能的,缓存数据丢失或者缓存不可用不会影响到应用程序的处理(因为它可以从数据库直接获取数据)。但是随着业务的发展,缓存会承担大部分的数据访问压力,所以当缓存服务崩溃的情况下,数据库会因为完全无法承受的压力而宕机,进而导致整个网站不可用,这种情况被称作缓存雪崩 。一旦发生这种故障,处理起来会很麻烦,有时候不能通过简单的重启缓存服务器和数据库服务器来恢复网站的访问。
缓存预热: 缓存中存放的时热点数据,热点数据又是缓存系统利用LRU(最近最久未使用)算法对不断访问的数据筛选淘汰出来的,这个过程需要花费较长的时间,在这段时间,系统的性能和数据库负载都不太好,那么最好在缓存系统启动的时候就把热点数据加载进去,这个缓存预加载的手段叫做缓存预热 (Warm up)。
缓存穿透: 如果不恰当的业务,或者恶意攻击持续高并发的请求某个不存在的数据,因为缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大的压力,甚至导致崩溃,这种情况被称作缓存穿透 。一个简单的对策是将访问数据库后不存在的数据也缓存起来,并设定一个较短的失效时间,但是如果大量的这种数据访问出现时这种对策就显得力不从心,需要进行分析,是设计的缓存数据有问题还是恶意的攻击,针对不同的情况采取相应的措施。例如:对数据的格式进行校验,非法格式的请求就不去数据库请求而是直接返回,同时也可以做一些防护措施来防止恶意攻击的行为。
1.10 Redis
Redis支持复杂的数据结构
Redis支持多路复用异步 I/O 高性能
Redis支持主从复制高可用
Redis原生集群与share nothing集群模式
Reids集群
Redis集群预分好16384个桶,当需要在Redis集群中放置一个key-value时,根据CRC16(key)mod 16384的值,决定将一个key放到哪个桶中;
Redis-cluster把所有的物理节点映射到[0-16384)slot上(不一定是平均分配),cluster负责维护slot与服务器的映射关系;
客户端与Redis节点直连,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
所有的Redis节点彼此互联
2 消息队列与异步架构
2.1 同步调用VS异步调用
同步调用
邮件发送后返回的结果可以确定地知道是否发送成功
同步调用耗时长,用户操作后需要等待较长时间才可以获得响应
同步调用请求会长时间占用系统资源,降低服务处理效率
同步调用在后续处理出现问题后,只能返回失败
同步调用各业务模块耦合度高,业务流程变化需要修改代码
大量耗时长的同步调用请求可能会引发系统灾难
异步调用
邮件发送后返回的结果并不清楚邮件是否发送成功
异步调用只需要将请求简单校验后将消息扔到消息队列中,用户可以快速得到响应
异步调用可以快速响应,释放系统资源,服务处理效率高
异步调用只要请求校验通过放入消息队列后,后续处理出现问题可以恢复业务现场,继续执行后续流程
异步调用将请求接收与处理分离,处理模块只需实现业务处理逻辑,目标清晰且扩展灵活
异步调用对大量耗时长的业务请求,系统可以按照自己的处理能力稳定地处理
有回调的异步调用
多个耗时操作的同步调用会导致用户等待更长的请求响应时间
多个耗时操作的异步调用
2.2 使用消息队列构建异步调用架构
消息队列异步调用架构三要素:
消息生产者
消息队列
消息消费者
构建消息队列异步调用架构两种模型:
点对点模型(Producer - Consumer)
可以独立地添加消息生产者和消息消费者
消息只能被一个消息消费者进行消费
添加消息消费者可以扩展吞吐量
发布订阅模型(Publish - Topic)
消息订阅者可以订阅一个Topic
消息订阅者订阅Topic之后,消息发布者发布到该Topic上的消息会被所有该Topic的订阅者接收
对于订阅之前的消息,消息订阅者是不会接收到的
消息订阅者对消息的处理是不同的业务处理逻辑
2.3 使用消息队列的好处
实现异步处理,提升系统处理性能
可以使系统具有更好的伸缩性
失败隔离和自我修复
发布者不依赖消费者,所以消息系统可以将消费者系统错误与生产者系统组件隔离
生产者和消费者互相不受对方失败影响
这就意味着,后端服务可以在任意时刻进行维护和发布,重启、删除、添加服务不影响生产者可用性,简化了部署和服务器管理的难度
在发布者视角不关心下一步将会发生什么,消费者也不关心消息是谁发布的,这样就可以将消息接收与处理解耦,代码实现更加专注与清晰
消峰填谷
消峰——系统处理业务请求可以根据实际的处理能力从队列中获取消息稳定地处理,不会导致系统不堪重负
填谷——在业务请求下降时,消息队列仍有待处理消息,系统处理服务仍会持续处理
2.4 事件驱动架构(EDA)
2.5 主要MQ产品比较
RabbitMQ
性能好
社区活跃
使用Erlang开发,不熟悉Erlang的同学二次开发和维护成本和风险高
ActiveMQ
影响比较广泛
可以跨平台
使用Java开发,对Java比较友好
RocketMQ
阿里推出的一款开源产品
性能比较好
可靠性比较高
同样也是使用Java开发
Kafka
LinkedIn出品
使用Scala开发
针对分布式场景进行了优化,因此分布式的伸缩性会比较好
3 负载均衡架构
主要关注:负载均衡服务器如何把请求发送给哪个应用服务器
五层网络协议:
物理层
数据链路层
网络层(IP)
传输层(TCP、UDP)
应用层(HTTP)
3.1 常见的负载均衡架构
3.1.1 HTTP重定向负载均衡
特点:
实现简单
每次请求都需要2次HTTP通信,性能不高
由于HTTP负载均衡服务器采用302重定向的方式,这就要求访问者与应用服务器集群的网络是联通的,这样应用服务器直接暴露地址,会有很大的安全问题
增加或删除应用服务器,需要在HTTP负载均衡服务器配置
3.1.2 DNS负载均衡
特点:
没有性能问题
如果DNS域名解析的服务器地址是应用服务器地址就会有安全问题,但是在实际应用中,会采用域名解析到二级负载均衡服务器上,这样就不会有太大的安全问题
如果域名解析的某个应用服务器出现问题时,部分用户就会访问失败,所以还是需要二级负载
3.1.3 反向代理负载均衡
特点:
反向代理服务器通常也比较简单
通常小型网站(十来台)使用
这里转发的是HTTP请求,HTTP的包一般比较大,所以通信过程会比较慢,同时对服务器的压力比较大
反向代理服务器需要等到应用服务器响应后才能断开连接,如果并发业务量大的情况下,反向代理服务器就会成为瓶颈,所以这种方式应对的规模是有限的
3.1.4 IP负载均衡
特点:
网关服务器再接收到请求后,会把请求数据目的IP地址修改为负载到的应用服务器,源地址修改为自己的IP地址;
接收到应用服务器响应后,网关服务器会把响应数据的目的IP地址修改为用户请求的IP地址,源地址修改为自己的IP地址;
这种负载均衡服务器的处理能力会比较高
由于响应的数据一般都比较大,网关服务器的出口带宽就会成为性能瓶颈
3.1.5 数据链路负载均衡
特点:
这种负载均衡只处理请求的分发,修改的是分发到的应用服务器网卡的MAC地址,响应的数据直接返回给用户,虽然MAC地址被修改了,但是MAC地址不会影响TCP/IP协议;
目前是大型网站选用的负载均衡方案
应用服务器集群与负载均衡服务器共享同一个虚拟IP地址,所有的服务器都会收到请求,由于MAC地址被修改了,该MAC的应用服务器就会处理该请求
3.2 负载均衡算法
轮询: 所有请求被依次分发到每个应用服务器上,适合于所有服务器硬件都相同的场景;
加权轮询: 根据应用服务器硬件性能的情况,在轮询的基础上,按照配置的权重将请求分发到每个服务器,高性能的服务器分配更多的请求;
随机访问: 请求被随机分配到各个应用服务器,在许多场合下,这种方案都很简单而且使用,因为好的随机数本身就很均衡。如果应用服务器硬件配置不同,也可以很容易地使用加权随机算法 ;
最少连接: 记录每个应用服务器正在处理的连接数(请求数),将新到的请求分发到最少连接的服务器上,应该说,这是最符合负载均衡定义的算法;
源地址散列: 根据请求来源的IP地址进行Hash计算,得到应用服务器,该算法可以保证同一个来源的请求总在同一个服务器上处理,实现会话粘滞。
3.3 应用服务器集群的Session管理
应用服务器的高可用架构设计主要基于服务无状态这一特性,但是事实上,业务总是有状态的,在Web应用中将这些状态信息称作会话(Session) ,单机情况下,Session可以交给Web容器管理;在使用负载均衡的集群环境中,Session管理主要有以下几种手段:
3.3.1 Session复制
应用服务器集群之间进行Session复制,每个应用服务器的Session信息都保持统一,这样负载分发到任意一台服务器上,都可以获取到用户的会话信息;
Session复制会随着服务器的增加对资源的消耗也会急剧增加,反而会影响到应用服务的处理能力,所以在这样的情况下,集群规模不可能做大;
负载均衡的目的就是为了均衡请求,这种方法每台应用服务器上都会存储所有用户的会话信息,所以并未做到数据信息的有效分配,服务器集群在性能的提升上微乎其微,最多就是做了服务的高可用
3.3.2 Session绑定
通过对请求的IP地址进行Hash,将请求始终分发到一台应用服务器上
随着业务的快速发展和迭代,系统发布就会比较频繁,使用Session绑定的一个最大的问题就是在发布的过程中,用户的会话信息会丢失,用户体验会非常差
3.3.3 利用Cookie记录Session
每次请求响应都需要添加Cookie
当浏览器Cookie禁用的话,系统将不正常提供服务
同时会有数据安全问题
该方法缺点很明显,但是生命力还是很顽强的,早些年很多都是采用这种方式
3.3.4 Session服务器
评论