写点什么

架构师是怎样炼成的 05-1 分布式缓存,异步与集群

发布于: 2020 年 07 月 05 日

1. 什么是缓存(Cache)?

缓存:存储在计算机上的一个原始数据的复制集,以便于访问。缓存是介于数据访问者与数据源之间的一种高速存储,当数据需要多次读取的时候,用于加快读取的速度。



1.1 缓存(Cache)与缓冲(Buffer)的区别?

缓存:缓存是介于数据访问者与数据源之间的一种高速存储,当数据需要多次读取的时候,用于加快读取的速度。

缓冲:缓冲也是介于数据访问者与数据源之间的一种高速存储。但是目的并不是用来做多次读取的,多用于写数据的时候,是解决数据源与存储之间的处理速度不匹配的问题。如:写数据到磁盘,cpu是很快的,写磁盘操作是很慢的,写数据的时候如果没有缓冲,每次数据都要等到写到磁盘,那么会造成应用程序阻塞,有一定的风险,如果程序挂掉,会导致缓冲中的数据不能写到磁盘上,造成数据丢失。



缓存只有读操作的时候需要,而缓冲是读和写都需要,读写数据时都会遇到的问题,磁盘写和读都是慢的。都是用于加快数据访问速度的。



注意:缓存一定是为了多次读取的时候才需要,如果不是多次读取就没有意义。

1.2 无处不在的缓存

  • cpu缓存

  • 从内存中读取指令进行计算,比从cpu缓存中读取慢几百倍。

  • 操作系统缓存

  • 数据库缓存

  • JVM编译缓存

  • CDN缓存

  • 代理与反向代理缓存

  • 前端缓存

  • 应用程序缓存

  • 分布式缓存

1.3 缓存数据存储

常用的数据结构是Hash表。保证缓存本身访问是快速的。

缓存中存储的数据,一般都是key-value的形式存在的。当需要存储数据的时候,把key进行hash计算,得到一个整数,然后把这个整数和hash表的容量进行取摩。得到的就是这个key存储在hash表里面的位置了。当然了这个是最简单hash表。并没有处理hash冲突问题。hash表本身是一个连续的内存块,便于快速的随机访问。

1.4 缓存的关键指标

  • 缓存命中率

  • 缓存是否有效依赖于能多少次重用同一个缓存响应业务请求,这个度量指标被称为缓冲命中率。

  • 如果查询一个缓存,10次有9次能够得到正确的结果,那么它的命中率是90%。

再次强调:缓存就是为了多次读取,从而减轻数据库压力,避免从数据库中读取。如果不是多次读取,就说明你不需要缓存。

1.4.1 影响缓存命中率的主要指标

  • 缓存键集合大小

  • 缓存可用内存空间

  • 缓存对象生存时间



缓存键集合大小:

缓存中的每个对象使用缓存键进行识别,定位一个对象的唯一方式就是对缓存键执行精确匹配。换句话说,缓存键空间是你的应用能够生成的所有键的数量。一定要想办法减少可能的缓存键数量。键数量越少,缓存的效率越高。如:基于客户IP地址缓存天气数据,则可能多大40亿个键(ipv4地址数量),如果基于客户来源国家缓存天气数据,则可能仅需几百个缓存键(国家数量)。键越少,命中率就越高,并不是说不缓存,而是说相同的数据尽量抽象出相同的键,这样ip的天气抽象国家的天气,相当于提升了资源利用率。



缓存可用的内存空间:

缓存是介于数据访问者和数据源之间的高速存储。缓存可使用内存空间直接决定了缓存对象的平均大小和缓存对象数量。因为缓存通常存储在内存中,缓存对象可用空间受到了严格限制且相当昂贵。如果想缓存更多的对象,就需要先删除老的对象,在添加新的对象。替换(清除)对象会降低缓存命中率,因为缓存对象被删除后,将来的请求就无法命中了。物理上能缓存的对象越多,缓存命中率就越高。(磁盘和内存的大小通常都是不一致的,磁盘大,内存小,所以不可能把所有数据都缓存在内存中,只能缓存一部分。)



缓存生存时间:

缓存中的数据只是一个复制集,当数据源中的数据被修改以后,缓存和数据源就数据不一致了,这个时候就需要对缓存中的数据进行清理,通常是设置一个失效时间,还可以通知缓存失效,但是通常并把不会去更新缓存。数据写入到缓存以后,就是为了多次读取,生存时间太短,只读了一次就失效了。命中率也会下降。简单来讲对象缓存的时间越长,缓存对象被重用的可能性就越高。



以上这些都是完全可以设计的,提高缓存的使用效率。

2. 缓存的一些方法

2.1 代理缓存

数据缓存在用户方,这个只是一种手段,并不是系统架构的一部分,我们不能去设计用户端需要如何缓存,通常架构师需要关注的都是右边这一块。

2.1 方向代理缓存



请求进入到数据中心,在应用服务器之前加一层反向代理,代理所有的应用服务器的访问。当接收到用户的资源访问请求时,先看代理服务器上是否有该资源的缓存,如果有就直接返回,没有就把请求发送给应用服务器。

2.2 多层反向代理缓存



restful风格请求,path就是天然的key,比较方便缓存,对反向代理比较友好。

2.3 内容分发网络(CDN)

CDN服务器是放在网络供应商的服务中心的,这样保证离用户更近。当用户访问CDN服务器是,如果服务器内有用户需要的数据,就直接返回,如果没有在向业务服务器发送请求。CDN之所以归为缓存,是因为相对于应用服务器而言的。请求在CDN直接就被处理掉了,不需要到应用服务器,减轻了应用服务器的压力。

CDN服务器对网络流量的贡献非常大,速度快,效果好(应用服务器压力小)。比如有一个好看的网剧,同时观看的人可能有几百万,如果这些资源请求都到了应用服务器。那应用服务器是承受不了的。

2.4 CDN同时配置静态文件和动态内容



所有请求都发送到CDN,如果是静态资源则有CDN直接返回,如果是动态请求,由CDN发送给应用服务器。

3. 缓存类型

3.1 通读缓存(read-through)

  • 代理缓存,反向代理缓存,CDN缓存都是通读缓存。

  • 通读缓存给客户端返回缓存资源,并在请求为命中缓存时获取实际数据。

  • 客户端连接的是通读缓存而不是生成响应的原始服务器。



3.2 旁路缓存(cache-aside)

  • 对象缓存是一种旁路缓存,旁路缓存通常是一个独立的键值对(key-value)存储。

  • 应用代码通常会询问对象缓存需要的对象是否存在,如果存在,他会获取并使用缓存的对象,如果不存在或已过期,应用会连接主数据源来组装对象,并将其保存回对象缓存中以便将来使用。



主:当数据源的数据更新的时候,是不会更新旁路缓存中的数据的,常用的都是通知失效或者缓存自身设置过期时间。

3.3 浏览器对象缓存



3.4 本地对象缓存

  • 对象直接缓存在应用程序内存中(HashMap)

  • 对象存储在共享内存中,同一台机器的多个进程可以访问

  • 缓存服务器作为独立应用和应用程序部署在同一个及服务器上

4. 本地对象缓存的部署

4.1 本地对象缓存构建分布式集群



早期的时候(十几年前),这种方式还是很普遍的。其中一个缓存更新数据以后,需要同步给其它几个服务器。当时这种方法是有问题的,当集群比较大的时候,同步会占用很多带宽,而且缓存本身也会占用资源,存的东西多了资源就会不够,而且还分不出去。



4.2 远程分布式对象缓存

以集群的方式对外提供服务,所有客户端共享集群当中的数据。缓存服务器互相是不感知的,每个服务器都保存独立的数据,不需要进行同步。提供客户端api实时计算缓存所存储的服务器信息。一台服务器有100GB内存,那么10台就有1000GB存储空间,随着服务器的增加集群的容量增加。



4.2.1 Memcached分布式对象缓存

服务器之间是不感知的。服务器越多存储的数据就越多。



访问模型:

应用服务器使用memcached客户端操作缓存,客户端会在用户存储的时候计算出这个key要存储到的目标服务器。通常可以使用一致性hash算法计算选择数据存储的服务器。



4.2.3 一致性hash算法。



时候用一致性hash算法进行服务器选择,在增加新的服务器时,影响会比较小。增加服务器时只会导致一部分缓存获取不到。这种方法是有问题的,会出现节点分布不均匀的情况,即使刚开始是均匀的,后续增加一台新服务器时也不能很好的分摊整个集群的压力,只能分摊离新节点近的那个服务器,所以可以通过增加虚拟节点的方式解决分布不均匀的情况。

4.2.4 基于虚拟节点的一致性hash算法



5. 缓存的价值

各种介质数据访问延迟



6. 技术栈各个层次的缓存



缓存命中越早越好。

7. 缓存为什么能显著提升性能?

  • 缓存数据通常来自内存,比磁盘上的数据有更快的访问速度。

  • 缓存存储数据的最终结果形态,不需要中间计算,减少cpu资源消耗。

  • 缓存降低数据库,磁盘,网络的负载压力,使这些I/O设备获得更好的响应特性。(降低数据库压力,能处理更多其它请求。)

缓存是系统性能优化的大杀器:

  • 技术简单

  • 性能提升显著

  • 应用场景多

有读的地方都可以考虑使用缓存。

8. 合理使用缓存

使用缓存对提高系统性能有很多好处,但是不合理的使用缓存可能非但不能提高系统的性能,还会成为系统的累赘。实践中,缓存的滥用情景屡见不鲜,过分依赖缓存,不适合的数据访问特性等。



频繁修改的数据:这种数据如果缓存起来,由于频繁修改,应用还来不及读取就已经失效了或更新了。徒增系统负担。一般来说数据的读写比在2:1以上,缓存才有意义。即:被缓存的数据失效或更新之前被读取了2次以上,才算是起到了缓存的作用。



没有热点的访问:缓存使用内存作为存储,内存资源宝贵而有限,不能讲所有数据都缓存起来,如果应用系统访问数据没有热点,不遵循二八定律,即:大部分数据访问而不是集中在小部分数据上,那么缓存就没有意义了,因为大部分数据还没有被再次访问就已经被挤出缓存了。(LRU淘汰算法)



数据不一致与脏读:一般会对缓存的数据设置失效时间,一旦超过失效时间,就要从数据库中重新加载。因此要容忍一定时间的数据不一致,但是仍需谨慎对待。还有一种策略是数据更新时立即更新缓存,不过也会带来更多的系统开销和事务一致性。因此数据更新时通知缓存失效,删除该缓存数据,是一种更加稳妥的做法。

例如:卖家已经编辑了商品属性,但是需要过一段时间才能被买家看到。在互联网应用中,这种延迟通常是可以接受的。在卖家更新提交后弹出提示框,数据正在审核,预计时间3分钟,这样用户就可以接受了,如果没有这个提示,用户不知道具体什么时候生效,只会以为立即生效,但是又没有,这个时候投诉就来了。所以缓存允许一段时间数据并不一致。



“计算机科学中只有三件事最困难:缓存失效,命名事物,计算错误。”-- Phil Karlton



缓存雪崩:缓存是为了提高数据读取性能的,缓存数据丢失或缓存不可用可能不会影响到应用陈旭的处理(可以从数据库直接获取数据)。但是随着业务的发展,缓存承担这大部分的数据访问压力,数据库已经习惯了有缓存的日志,所以当缓存服务崩溃的时候,数据库会因为完全不能承受如此大的压力而宕机,进而导致整个系统不可用。这种情况被称作为缓存雪崩。

发生这种故障,甚至不能简单的重启缓存服务器和数据库来恢复应用的访问。因为应用一启动,因为缓存崩溃数据失效,所有的数据读取请求还是会到数据库中(假设有大量的用户访问)。



缓存预热:缓存中存放的是热点数据,热点数据又是缓存系统利用LRU算法对不断访问的数据筛选淘汰出来的,这个过程需要花费较长的时间,在这段时间,系统的性能和数据库的负载都不太好,那么最好在缓存系统启动的时候就把热点数据都加载好,这个缓存预加载的手段就叫做缓存预热(warm up)。对于一些元数据如城市地名列表,类目信息,可以启动时加载数据库中全部数据到缓存进行预热。



缓存穿透:如果不恰当的业务,或者恶意攻击持续高并发访问某个不存在的数据,因为缓存没有保存该数据,所有的请求都会落到数据库上,会对数据库造成很大的压力,甚至崩溃。一个简单的对策是将不存在的数据也缓存起来(值是null),并设定一个较短的失效时间。



用户头像

还未添加个人签名 2018.11.15 加入

还未添加个人简介

评论

发布
暂无评论
架构师是怎样炼成的 05-1 分布式缓存,异步与集群