架构师训练营 - 第 5 周学习总结
分布式架构缓存
什么是缓存Cache?
1. Cache:缓存区,是高速缓存,是位于CPU和主内存之间的容量较小但速度很快的存储器,因为CPU的速度远远高于主内存的速度,CPU从内存中读取数据需等待很长的时间,而 Cache保存着CPU刚用过的数据或循环使用的部分数据,这时从Cache中读取数据会更快,减少了CPU等待的时间,提高了系统的性能。
Cache并不是缓存文件的,而是缓存块的(块是I/O读写最小的单元);Cache一般会用在I/O请求上,如果多个进程要访问某个文件,可以把此文件读入Cache中,这样下一个进程获取CPU控制权并访问此文件直接从Cache读取,提高系统性能。
缓存Cache和缓冲Buffer的分别?
Buffer:缓冲区,用于存储速度不同步的设备或优先级不同的设备之间传输数据;通过buffer可以减少进程间通信需要等待的时间,当存储速度快的设备与存储速度慢的设备进行通信时,存储慢的数据先把数据存放到buffer,达到一定程度存储快的设备再读取buffer的数据,在此期间存储快的设备CPU可以干其他的事情。
Buffer:一般是用在写入磁盘的,例如:某个进程要求多个字段被读入,当所有要求的字段被读入之前已经读入的字段会先放到buffer中。
缓存的应用场景:
CPU缓存;操作系统缓存;数据库缓存;JVM缓存;CDN缓存;代理与反向代理缓存;应用程序缓存;分布式对象缓存。
缓存数据存储-Hash表
缓存的关键指标-缓存命中率
影响缓存命中率的主要指标:
1、缓存键集合大小:
键数量越少,缓存的效率越高。
2、缓存可使用内存空间:
物理上能缓存的对象越多,缓存命中率就越高。
3、缓存对象生存时间:
缓存对象生存时间称为TTL。对象缓存的时间越长,缓存对象被重用的可能性就越高。
几种常见的缓存:
代理缓存
反向代理缓存
多层反向代理缓存
内容分发网络-CDN
缓存的方式:
通读缓存(read-through):
1、代理缓存,反向代理缓存,CDN缓存都是通读缓存;
2、通读缓存给客户端返回缓存资源,并在请求为命中缓存时获取实际数据;
3、客户端连接的是通读好吃而不是生成响应的原始服务器
旁路缓存(cache-aside):
1、对象缓存是一种旁路缓存,旁路缓存通常是一个独立的键值对(key-value)存储;
2、应用代码通常会询问对象缓存需要的对象是否存在,如果存在,它会获取并使用缓存的对象,如果不存在或已过期,应用会链接主数据库源来组装对象,并将其保存回对象缓存中以便将来使用
浏览器对象缓存:
// 在WebStorage中缓存对象的JavaScript代码
var preferences = {/ *data object to be stored */};
localStorage.setItem('preferences', JSON.stringify(preferences));
// 访问缓存对象的JavaScript代码
var cachedData = localStorage.getItem('preferences');
var preferences = JSON.parse(cachedData)
本地对象缓存:
1、对象直接缓存在应用程序内存中;
2、对象存储在共享内存,同一台机器的多个进程可以访问它们;
3、缓存服务器作为独立应用和应用程序部署在同一个服务器上。
本地对象缓存构建分布式集群:
服务器之间的缓存数据需要更新同步,服务器多时,存在很大的同步开销。
远程分布式对象缓存:
Memcached分布式对象缓存:
//PHP客户端访问Memcached集群
$m = new Memcached();
//添加服务器集群
$cache->addServers(array(
array('cache1.example.com', 11211),
array('cache2.example.com', 11211),
array('cache3.example.com', 11211)
));
//写缓存,失效时间5分钟
$m->set('userCount', 123, 600);
Memcached分布式缓存访问模型:
分布式对象缓存的一致性hash算法:
key经过hash在hash环上顺时针找到hash环上最近的存储节点存取缓存数据
基于虚拟节点的一致性hash算法:
在真实存储节点无法均匀分布问题上,使用一个真实节点映射多个虚拟节点在hash环上,数据存取走虚拟节点在映射到真实节点,提高了存储均匀分布问题,减少了增减机器带来的数据重新从数据库加载的频率
从各种介质中取数据访问延迟:
技术栈各个层次的缓存:
缓存为什么能限制提升性能?
1、缓存数据通常来自内存,比磁盘上的数据有更快的访问速度;
2、缓存存储数据的最终结果形态,不需要中间计算,减少CPU资源的消耗;
2、缓存降低数据库、磁盘、网络的负载压力,使这些I/O设备获得更好的响应特性。
缓存是系统性能优化的大杀器:技术简单、性能提升显著、应用场景多。
合理使用缓存:使用缓存对提高系统性能有很多好处,但是不合理的使用缓存可能非但不能提高系统的性能,海辉成为系统的累赘,甚至风险。实践中,缓存滥用的情景屡见不鲜--过分依赖缓存、不合适的数据访问特性等。
频繁修改的数据:这种数据如果缓存起来,由于频繁修改,应用还来不及读取就已失效或更新,徒增系统负担。一般说来,数据的读写比在2:1以上,缓存才有意义。
没有热点的访问:缓存使用内存作为存储,内存资源宝贵而有限,不能将所有数据都缓存起来,如果应用系统访问数据没有热点,不遵循二八定律,即大部分数据访问不是集中在小部分数据上,那么缓存就没有意义,因为大部分数据还没有呗再次访问就已经被挤出缓存了。使用LRU算法
数据不一致与脏读:
一般会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载。因此应用要容忍一定时间的数据不一致,如卖家已经编辑了商品属性,但是需要过一段时间才能被卖家看到。在互联网应用中,这种延迟通常是可以接受的,但是具体应用仍需要慎重对待。还有一种策略是数据更新时立即更新缓存,不过也会带来更多系统开销和事物一致性的问题。因此数据更新时通知缓存失效,删除该缓存数据,是一种更加稳妥的做法。
“计算机科学中只有三件事最困难:缓存失效,命名事物,技术错误。”--Phil Karlton
缓存雪崩:
缓存是为了提高数据读取性能的,缓存数据丢失或者缓存不可用不会影响到应用程序的存取--它可以从数据库直接获取数据。但是随着业务的发展,缓存会承担大部分的数据访问压力,数据库已经习惯了有缓存的日子,所以当缓存服务崩溃的时候,数据库会因为完全不能承受如此大的压力而宕机,进而导致整个网站不可用。这种情况,被称作缓存雪崩,发生这种故障,甚至不能简单的重启缓存服务器和数据库服务器来恢复网站访问。
缓存预热:
缓存中存放的是热点数据,热带你数据有事缓存系统利用LRU(最近最久未用)算法对不断访问的数据筛选淘汰出来的,这个过程需要花费较长的时间,在这段时间,系统的性能和数据库负载都不太好,那么最好再缓存系统启动的时候就把热点数据加载好,这个缓存预加载手段叫做缓存预热(warm up)。对于一些元数据如城市地名列表、类目信息,可以启动时加载数据库中的全部数据到缓存进行预热。
缓存穿透:
如果不恰当的业务、或者恶意攻击持续高并发的请求某个不存在的数据,因为缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大的压力,甚至崩溃,一个简单的对策是将不存在的数据也缓存起来(其value值为null),并设定一个较短的时效时间。
Redis VS Memcached:
memcache和redis是互联网分层架构中,最常用的KV缓存。不少同学在选型的时候会纠结,到底是选择memcache还是redis?memcache提供的功能是redis提供的功能的子集,不用想太多,选redis准没错
Memcached基于一个存储键/值对的hashmap,只要内存足够,理论上hashmap的key值数量是没有限制的。key 长度不能超过255字符长度,value 长度不能超过1024 * 1024字符长度(即 1M)。
redis倾向:
String类型:一个String类型的value最大可以存储512M
List类型:list的元素个数最多为2^32-1个,也就是4294967295个。
Set类型:元素个数最多为2^32-1个,也就是4294967295个。
Hash类型:键值对个数最多为2^32-1个,也就是4294967295个。
Sorted set类型:跟Set类型相似。
复杂的数据结构:value是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择redis,因为mc无法满足这些需求。用户订单列表,用户消息,帖子评论列表等。
持久化: mc无法满足持久化的需求,只得选择redis。但是千万不要把redis真的做数据库用
a. redis的定期快照不能保证数据不丢失
b.redis的AOF会降低效率,并且不能支持太大的数据量
c.不要期望redis做固化存储会比mysql做得好,不同的工具做各自擅长的事情
d.redis挂掉重启后能够快速恢复热数据,但是如果着期间有数据修改,可能导致数据不一致,因此,只读场景,或者允许一些不一致的业务场景,可以尝试开启redis的固化功能
自带高可用集群: redis自身支持集群,实现主从读写分离功能,官方也提供sentinal哨兵的集群管理工具,实现主从监控,故障转移,memcached实现集群需要二次开发了,但是很多时候需要考虑,真的需要高可用么?缓存很多时候是运行cache miss的,cache挂了可以读db的存储的内容比较大 : macache 单个value最大存储 1M,超过1M只能用redis了。
注意:纯的k-v 而且数据量特别大,并发也很大 或许使用memcache更合适
a.内存分配:memcache使用 预分配内存池的方式管理内存,更节省内存分配时间,redis使用临时申请的方式,kennel导致碎片。对比看memcache更快一点
b.memcache把所有的数据存储在物理内存里。redis有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上。对比看数据量大时,memcache更快一点
c.memcache使用非阻塞IO复用模型,redis也是使用非阻塞IO复用模型,但由于redis还提供一些非KV存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度。从这一点上,由于redis提供的功能较多,mc会更快一些。
d.memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程中,可能存在锁冲突。redis使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。从这一点上,mc会快一些。
代码可读性,代码质量:看过mc和redis的代码,从可读性上说,redis是我见过代码最清爽的软件,甚至没有之一,或许简单是redis设计的初衷,编译redis甚至不需要configure,不需要依赖第三方库,一个make就搞定了。而memcache,可能是考虑了太多的扩展性,多系统的兼容性,代码不清爽,看起来费劲。
redis是ANSIC语言开发的;Memcached是用“C语言”编写的。
1、redis支持复杂的数据结构;
2、redis支持多路复用异步I/O高性能;
3、redis支持主从复制高可用;
4、redis原生集群与share nothing集群模式
redis集群:
1、redis集群预设分好了16384个桶,当需要在redis集群汇总放置一个key-value时,根据CRC16(key)mod16384的值,决定将一个key放到哪个桶中;
2、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster负责维护slot与服务器的映射关系;
3、客户端与redis节点直连,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
4、所有的redis节点彼此互联;
消息队列与异步架构
同步调用 VS 异步调用
1、同步调用
同步调用是一种阻塞式调用
可以形象理解为:考试,当一道题没有解出来,绝对不会去做下一题。 即一段代码逻辑没有执行完毕,代码会一直等待,而不会去执行下一段代码逻辑。
2、异步调用
异步调用是一种非阻塞式调用
可以形象理解为:考试,当这道题没有解决时,可以跳过这道题 去看下一题,而当这道题有思路的时候 可以返回解决这道题。
3、回调
回调是解决异步函数执行结果的处理方法。在异步调用的时候,如果希望将执行的结果返回并进行处理时,我们可以通过回调的方法进行解决。
消息队列构建异步调用架构
构成包含:消息生产者;消息队列;消息消费者;
多个消息生产者生产消息时,进入消息队列的顺序是先进先出,顺序处理消息
生产消费模式:
点对点模式,可以增加多个生产者生产同类消息,消费方也可以通过增加多个消费者来扩展吞吐量,每个消费者都消费的不同的消息,生产消息后不用管消费者是否在线,在线时立即处理消息;
发布订阅模式,多个生产者生产消息,由多个消费者消费消息,都会收到同样的消息,消费者收到消息处理消息,但是如果发布消息时消费者不在线或者没有接收到,一般会错过这条消息。
消息队列的好处:
1、实现异步处理,提升处理性能;
2、更好的伸缩性;
3、削峰填谷;
4、失败隔离和自我修复:因为发布者不直接依赖消费者,所以消息系统可以将消费者系统错误与生产者系统组件隔离;生产者和消费者互相不受对方失败影响;这意味着任意时刻,我们都可以对后端服务器执行维护和发布操作。我们可以重启、添加或删除服务器而不影响生产者可用性,这样简化了部署和服务器管理的难度。
5、解耦;只依赖消息队列中间件,不再管消费者,消费者也同样不用关心谁发布的消息
事件驱动架构EDA
使用发布订阅,生产者生产消息,通过消息队列,给订阅者推送消息,订阅者是不同的模块,接收到消息时在去根据消息,做自己的处理流程,各个模块或子系统同时进行,大大提升效率
主要MQ产品比较
1、RabbitMQ的主要特点是性能好,社区活跃,但是RabbitMQ用Erlang开发,对不熟悉Erlang的同学而言不便于二次开发和维护。(搜索引擎搜索处理词条数:49M)
2、ActiveMQ影响比较广泛,可以跨平台,使用Java开发,对Java比较友好。(搜索引擎搜索处理词条数:27M)
3、RocketMQ是阿里退出的一个开源产品,也是使用Java开发,性能比较好,可靠性也比较高。(搜索引擎搜索处理词条数:35M)
4、Kafka,由LinkedIn出品,Scala开发,专门针对分布式场景进行了优化,因此分布式的伸缩性会比较好。(搜索引擎搜索处理词条数:63M)
负载均衡架构
用户发起访问请求,通过负载均衡服务器的负载均衡算法,将请求分发到不同的服务器上,从而降低单台服务器的请求访问压力。
HTTP重定向负载均衡
HTTP重定向服务器是一台普通的应用服务器,其唯一个功能就是根据用户的HTTP请求计算出一台真实的服务器地址,并将该服务器地址写入HTTP重定向响应中(重定向响应状态码为302)返回给用户浏览器。用户浏览器在获取到响应之后,根据返回的信息,重新发送一个请求到真实的服务器上。浏览器访问www.apusapp.com,DNS服务器解析到IP地址为114.100.20.200,即HTTP重定向服务器的IP地址。重定向服务器计根据某种负载均衡算法算出真实的服务器地址为114.100.20.203并返回给用户浏览器,用户浏览器得到返回后重新对114.100.20.203发起了请求,最后完成访问。
这种负载均衡方案的有点是比较简单,缺点是浏览器需要两次请求服务器才能完成一次访问,性能较差;同时,重定向服务器本身的处理能力有可能成为瓶颈,整个集群的伸缩性规模有限;使用HTTP返回码302重定向,有可能使搜索引擎判断为SEO作弊,降低搜索排名。因此实践中很少使用这种负载均衡方案来部署。
DNS负载均衡
一个域名访问多个IP地址。当客户端请求解析域名时,DNS服务器查询文件中的记录按顺序返回不同的解析结果,从而将客户访问引导到不同的服务器上去,从而实现负载均衡。
优点:
实现简单,服务器不需要修改任何代码。
服务器可以位于互联网上任何位置
省去运维麻烦
缺点:
DNS是多级解析的,每一级都可能缓存A记录。当某台服务器下线,需要较长的时间才能生效。这段时间内,用户仍然会访问到下线的服务器,导致访问失败。
为保证本地DNS跟上级的DNS保持记录,需要设置比较短的刷新时间,这样会消耗很大的DNS流量。
实际应用:
大型网站实际上一般会把DNS解析作为第一级负载均衡手段,域名解析得到的地址一般不会是实际提供服务的服务器地址,而是一个内部的同样提供负载均衡的服务器。这个服务器再把请求转发到实际提供服务的服务器上。
反向代理负载均衡
反向代理隐藏了服务器的信息,它代理的是服务器端,代其接收请求。换句话说,反向代理的过程中,客户端并不知道具体是哪台服务器处理了自己的请求。如此一来,既提高了访问速度,又为安全性提供了保证。在请求真实服务器时根据配置的不同负载均衡算法进行分配。
IP负载均衡
1、客户端会向一个ip地址发出请求,这个ip地址是一个VIP
(虚拟IP),这也是调度器向外公布的一个地址。
2、请求达到调度器,调度器会根据负载均衡算法(详情请见8种负载均衡算法)从RealServer
列表中选取一个负载不高的服务器,然后把请求报文的目标地址,也就是VIP
和端口通过iptables
进行NAT
转换成选中的服务器的真实ip地址。最后,调度器会把其连接保存在一个hash表中,只要这个连接下次再发请求报文过来就会把其分发到上次选定的服务器中。
3、RealServer
收到报文之后,会把响应返回给调度器。
4、调度器收到报文之后,会把源地址和源端口改为虚拟ip和端口,最后再返回给客户端。
数据链路层负载均衡
在TCP/IP协议中数据链路层处于最底层,以帧的形式传输和接受数据。在这一层中MAC(Media Access Control)寻址是主要功能。在网络中MAC又称之为MAC地址,用于表示互联网上每个网卡的标识符,采用十六进制表示,共6个字节(48位),烧录在网卡内部。更形象的说MAC地址就像身份证号码,全球唯一。以太网中数据帧之间是通过MAC寻址来到达对应的计算机网卡或者路由的,因此,服务器集群可以充分利用这一特性来进行负载均衡。
数据链路层负载均衡通过修改通信协议数据包的mac地址进行负载均衡,集群可以通过如下图的部署来达到负载均衡:
这种数据传输方式又称为三角传输,负载均衡数据分发过程中不修改IP地址,只修改目的MAC地址,通过配置真实物理服务器集群所有机器虚拟IP和负载均衡服务器IP一致,从而达到不修改数据包的源地址和目的地址就可以进行数据分发的目的,由于实际处理请求的真实物理服务器IP和数据请求目的IP一致,不需要通过负载均衡服务器进行地址交换,可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈。这种负载均衡方式又称之为直接路由方式(DR).
如上图所示,用户请求到达负载均衡服务器114.100.20.200后,负载均衡服务器将数据包的目的MAC地址更改为00:1e:ec:bc:5e:03,并不修改数据包目的IP,由于服务器集群所有服务器的虚拟IP地址和负载均衡服务器IP地址一致,因此数据可以正常传输到达MAC地址为00:1e:ec:bc:5e:03的机器上,该服务器处理完之后,将响应数据包发送到网关服务器,网关服务器直接将数据包发送给用户浏览器,响应数据不需要通过负载均衡服务器,这样就避免了负载均衡服务器成为传输瓶颈的可能。
使用三角传输模式的链路层负载均衡是目前大型网站使用最为广泛的一种负载均衡手段。在Linux平台上最好的链路层负载均衡开源产品是LVS(Linux Virtual Server)。
负载均衡算法
轮询:所有的请求被一次分发到每个应用服务器上,适合于所有服务器硬件都相同的场景。
加权轮询:根据应用服务器硬件性能的情况,在轮询的基础上,按照配置的权重将请求分发到每个服务器,高性能的服务器分配更多的请求。
随机:请求被随机分配到各个应用服务器,在许多场合下,这种方案都很简单实用,因为好的随机数本身就很均衡。如果应用服务器硬件配置不同,也可以很容易的使用加权随机算法。
最少连接:记录每个应用服务器在处理的连接数(请求数),将新到的请求分发到最少连接的服务器上,应该说,这是最符合负载均衡定义的算法。
源地址散列:根据请求来源的IP地址进行Hash计算,得到应用服务器,改算法可以保证同一个来源的请求总在一个服务器上处理,实现会话粘滞。
应用服务器集群的Session管理
应用服务器的高可用架构设计主要基于服务无状态这一特性,但是事实上,业务总是有状态的,在交易类的电子商务网站,需要有购物车记录用户的购买信息,用户每次购买请求都是向购物车中增加商品;在社交类的网站中,需要记录用户的当前登录状态、最新发布的消息等以便及时将这些信息通知给他的好友。Web应用中将这些状态信息称作会话(Session),单机情况下,Session可交给Web容器管理,在使用负载均衡的集群环境中,Session管理主要有以下几种手段。
Session复制:在web应用中,为了应对大规模访问,必须实现应用的集群部署.要实现集群部署主要需要实现session共享机制,使得多台应用服务器之间会话统一, tomcat等多数主流web服务器都采用了session复制以及实现session的共享. 但问题还是很明显的。在节点持续增多的情况下,session复制带来的性能损失会快速增加.特别是当session中保存了较大的对象,而且对象变化较快时,性能下降更加显著.这种特性使得web应用的水平扩展受到了限制。session共享的另一种思路就是把session集中起来管理,首先想到的是采用数据库来集中存储session,但数据库是文件存储相对内存慢了一个数量级,同时这势必加大数据库系统的负担.所以需要一种既速度快又能远程集中存储的服务:memcached或者redis。
Session绑定:固定通过负载均衡让同一个ip,根据ip进行hash分配访问一台服务器,保证了session不需要同步到其他服务器。版本快速迭代时频繁重启服务器,会出现session丢失。
利用Cookie记录Session:请求和响应都需要携带cookie,带来额外的网络开销,遇到某些终端禁用cookie会没办法使用,小的session时适用,较大session时不适用。
Session服务器:利用redis或者数据库这类存储session管理。
版权声明: 本文为 InfoQ 作者【红了哟】的原创文章。
原文链接:【http://xie.infoq.cn/article/698ccfbad7f2a5038d9347977】。文章转载请联系作者。
评论