分布式缓存

2020-08-20  本文已影响0人  sknfie

概述

缓存是架构设计中一个重要的手段。缓存的主要特点是技术比较简单,同时对性能提升的效果又很显著,所以缓存在很多地方都会被用到。使用缓存需要注意几个关键指标:缓存键集合大小、缓存空间的大小以及缓存的使用寿命。这三个指标决定了缓存的有效性、缓存的使用效率、缓存实现的效果。缓存的类型主要有代理缓存、反向代理缓存、 CDN 缓存和对象缓存几种。缓存知识图谱如下图所示。

缓存不适用场景

不是所有的数据都适合使用缓存,使用缓存的时候需要注意以下几点:

缓存的特点

缓存的主要特点:

缓存提高性能的优势

缓存是架构性能优化的最重要的手段,使用缓存来提升系统性能有如下三方面优势:

缓存架构

1.缓存数据存储(Hash 表)

缓存是存储在内存中的,缓存使用的数据结构主要是哈希(Hash)表。哈希表最终的存储形式通常是一个顺序表,也就是一个数组结构。数组结构的特点是在内存中连续存储分配。那么,当我们要在哈希表中存储一个数据的时候,哈希表通常是以 key、value 这样的数据结构进行存储的。当我们把一个 key、value 数据结构存储在一个哈希表中的时候,主要的存储过程大致如下图所示。


缓存hash

通过哈希表可以使整个数据存储或检索效率时间复杂都是 O(1),因此即使存储成百上千万的数据量,通过哈希表也可以非常快地进行数据的查找和读写。通过这种手段缓存可以获得较快的读写访问特性,比数据库中的读写速度要快得多。

2.缓存的关键指标——命中率

影响缓存特性的一个关键指标是缓存的命中率,即一次写进去的缓存能不能够多次去读出来响应业务的请求,这个指标也是判断缓存是否有效的标准。
影响缓存命中率的主要因素有三个,分别是:

3.缓存键集合大小

缓存中的每个对象都是通过缓存键进行识别的。要尽可能减少缓存键的数量,这样使得键的数量越少,缓存的效率越高。设计缓存的时候要关注缓存键是如何进行设计的,它的整个的集合范围,限定在一个既能够高效使用,又可以减少它的数量,这个时候缓存的性能是最好的。

4.缓存内存空间大小

缓存可以使用的内存空间决定了缓存对象平均大小和缓存对象的数量。因为缓存通常是存储在内存中的,缓存对象可用的内存空间相对来说比较昂贵,而且受到严格限制。
如果想缓存更多的对象,就需要先删除老的对象,再添加新的对象。而这些老的对象被删除掉,就会影响到缓存的命中率。所以物理上缓存的空间越大,缓存的对象越多,缓存的命中率也就越高。

5.缓存对象生存时间(缓存寿命)

缓存对象的生存时间称为 TTL。对象缓存的时间越长,被重用的可能性就越高。使缓存失效的方法有两种:一种是超时失效;一种是清除失效,也就是实时清除。

缓存的主要类型

1.代理缓存

代理缓存是在在客户端—端的,代理客户端访问互联网。因为他代理了所有的客户端 HTTP 请求,所以它可以进行页面缓存,如果有一些其他的客户端已经访问过这个网页,那么当新的客户端连接的时候,就可以通过代理缓存中的数据直接返回,避免对服务端的访问。

2.反向代理缓存

反向代理则是代理服务端输出的,所以反向代理缓存是存在于服务端里的统一入口,代理其他服务器的应用处理。

3.内容分发网络 CDN 缓存

所谓的 CDN 是指在用户请求的前端(尽量前的前端)为用户提供数据服务。CDN 并不存在于我们的数据中心,也不存在于用户的访问系统一端,它介于两者之间,作为网络服务商的缓存服务。

4.通读缓存

上面讲到的代理缓存、反向代理缓存、CDN 缓存,都是通读缓存。它代理了用户的请求,也就是说用户在访问数据的时候,总是要通过通读缓存。
当通读缓存中有需要访问的数据的时候,直接就把这个数据返回;如果没有,再由通读缓存向真正的数据提供者发出请求。

5.旁路缓存

旁路缓存是客户端先访问旁路缓存中是否有自己想要的数据,如果旁路缓存中没有需要的数据,那么由客户端自己去访问真正的数据服务提供者去获取数据。客户端获得数据以后,会自己把这个数据写入到旁路缓存中,这样下一次或者其他客户端去读取旁路缓存的时候就可以获得想要的数据了。key、value 这样的对象缓存就属于旁路缓存。

6.合理使用缓存对象

缓存虽然效率非常高,使用缓存也非常简单,但是缓存并不是无所不能的,使用缓存的时候需要注意合理使用缓存对象。

7.注意频繁修改的数据

缓存数据是为一次写入多次读取准备的,但是如果写入的数据很快就被修改掉了,数据还没来得及读取就已经失效或者更新了,系统的负担就会很重,使用缓存也就没有太多的意义。一般说来,数据的读写比例至少在 2:1 以上,缓存才有意义。

8.注意没有热点的访问数据

上面提到缓存是一次写入多次读取的数据,但是如果写入的数据并不会被多次读取,也就是所谓的没有热点,这时候使用缓存也是没有意义的。

9.注意数据不一致和脏读

缓存中的数据有可能和主存储数据库中的数据不一致。这个问题主要是通过失效时间来解决的,也就是说这个业务能够容忍的失效时间之内,保持缓存中的数据和数据库中的数据不一致。
如果某些业务场景对更新非常敏感,必须要实时看到,这个时候就不能够使用失效时间进行缓存过期处理了,可能需要进行失效通知。当数据进行更新的时候,立即清除缓存中的数据,下次访问这个数据的时候,缓存必须要重新从主数据库中去加载,才能够得到最新的数据。

10.注意缓存雪崩

因为热点数据主要是从缓存中去读取的,而热点数据是数据访问压力最大的一类数据。这些数据都从缓存中读取,极大地降低了数据库的访问压力。
而数据库整个系统也是在有缓存的情况下进行设计的,数据库的处理能力是强依赖缓存的。如果缓存忽然崩溃了,那么所有的访问压力就都会传递到数据库上去。数据库不能够承受这样的访问压力,可能也会崩溃。数据库崩溃了以后,应用程序访问不到数据库,请求不断超时,负载压力不断升高,应用程序服务器也会崩溃,最后导致整个网站所有服务器崩溃。这就是缓存雪崩。这种情况下系统甚至无法启动,因为系统启动后,新的访问压力又过来,依然是那么大,还是会崩溃。
这时候重启缓存也是没有用的,因为重启的话缓存中是没有数据的。重新启动的缓存没有数据,它就不能够承担提供数据读操作的能力。所以,对缓存有重点依赖的系统,需要特别关注缓存的可用性。缓存用的部分数据丢失可以到数据库中加载,但是如果全部的缓存数据都丢失了,可能导致整个系统都会崩溃,特别需要注意。

分布式对象缓存

分布式对象缓存是系统架构中比较重要的一部分:对象缓存以一个分布式集群的方式对外提供服务,多个应用系统使用同一个分布式对象缓存提供的缓存服务。这里的缓存服务器是由多台服务器组成的,这些服务器共同构成了一个集群对外提供服务。所以使用分布式对象缓存的一个重要问题就是,数据进行读写操作的时候,如何找到正确的缓存服务器进行读写操作。如果第一次写入数据的时候写入的是 A 服务器,但是数据进行缓存读操作的时候访问的是 B 服务器,就不能够正确地查找到数据,缓存也就没有了效果。


分布式对象缓存

当需要进行分布式缓存访问的时候,客户端程序进行访问时使用自己的路由算法进行路由选择,选择其中的某一台服务器,找到这台服务器的 IP 地址和端口以后,通过通讯模块和相对应的服务器进行通信。
因为进行路由选择的时候,就是使用缓存对象的 key 进行计算。下一次使用相同的 key 使用相同路由算法进行计算的时候,算出来的服务器依然还是前面计算出来的这个服务器。所以通过这种方法可以访问到正确的服务器进行数据读写。服务器越多,提供的缓存空间就越大,实现的缓存效果也就越好。通过集群的方式,提供了更多的缓存空间。

1.一致性哈希算法

一致性哈希和余数哈希不同,一致性哈希首先是构建一个一致性哈希环的结构。一致性哈希环的大小是 0~2 的 32 次方减 1,实际上就是我们计算机中无符号整型值的取值范围,这个取值范围的 0 和最后一个值 2 的 32 次方减 1 首尾相连,就构成了一个一致性哈希环,如下图所示。


一致性哈希算法

对每个服务器的节点取模,求它的哈希值并把这个哈希值放到环上,所有的服务器都取哈希值放到环上,每一次进行服务器查找路由计算的时候,把 key 也取它的哈希值,取到哈希值以后把 key 放到环上,顺时针查找距离它最近的服务器的节点是哪一个,它的路由节点就是哪一个。通过这种方式也可以实现,key 不变的情况下找到的总是相同的服务器。这种一致性哈希算法除了可以实现像余数哈希一样的路由效果以外,对服务器的集群扩容效果也非常好。
在一致性哈希环上进行服务器扩容的时候,新增加一个节点不需要改动前面取模算法里的除数,导致最后的取值结果全部混乱,它只需要在哈希环里根据新的服务器节点的名称计算它的哈希值,把哈希值放到这个环上就可以了。放到环上后,它不会影响到原先节点的哈希值,也不会影响到原先服务器在哈希环上的分布,它只会影响到离它最近的服务器,比如上图中 NODE3 是新加入的服务器,那么它只会影响到 NODE1,原先访问 NODE1 的 key 会访问到 NODE3 上,也就是说对缓存的影响是比较小的,它只会影响到缓存里面的一小段。如果缓存中一小部分数据受到了影响,不能够正确的命中,那么可以去数据库中读取,而数据库的压力只要在它的负载能力之内,也不会崩溃,系统就可以正常运行。所以通过一致性哈希算法可以实现缓存服务器的顺利伸缩扩容。
但是一致性哈希算法有着致命的缺陷。我们知道哈希值其实是一个随机值,把一个随机值放到一个环上以后,可能是不均衡的,也就是说某两个服务器可能距离很近,而和其它的服务器距离很远,这个时候就会导致有些服务器的负载压力特别大,有些服务器的负载压力非常小。同时在进行扩容的时候,比如说加入一个节点 3,它影响的只是节点 1,而我们实际上希望加入一个服务器节点的时候,它能够分摊其它所有服务器的访问压力和数据冲突。
所以对这个算法需要进行一些改进,改进办法就是使用虚拟节点。也就是说我们这一个服务器节点放入到一致性哈希环上的时候,并不是把真实的服务器的哈希值放到环上,而是将一个服务器虚拟成若干个虚拟节点,把这些虚拟节点的 hash 值放到环上去。在实践中通常是把一个服务器节点虚拟成 200 个虚拟节点,然后把 200 个虚拟节点放到环上。key 依然是顺时针的查找距离它最近的虚拟节点,找到虚拟节点以后,根据映射关系找到真正的物理节点。
第一,可以解决我们刚才提到的负载不均衡的问题,因为有更多的虚拟节点在环上,所以它们之间的距离总体来说大致是相近的。
第二,在加入一个新节点的时候,是加入多个虚拟节点的,比如 200 个虚拟节点,那么加入进来以后环上的每个节点都可能会受到影响,从而分摊原先每个服务器的一部分负载。

总结

缓存的主要优点是实现方法比较简单,同时提升的效果又非常明显。所以缓存是架构性能优化的一个重要手段。
影响缓存的主要指标是缓存命中率。影响命中率的几个关键因素是缓存键集合的大小、缓存空间的大小和缓存对象的存在时间,也就是缓存的寿命。缓存的主要类型有代理缓存、反向代理缓存、CDN 缓存,这三类缓存叫作通读缓存。
客户端使用通读缓存的时候,不需要知道后面真实的数据存储服务器在哪里,只需要访问通读缓存,由通读缓存去访问真正的数据提供服务器。另一类缓存就是旁路缓存。这类缓存就是我们在系统架构中常用的对象缓存。使用旁路缓存的时候,应用程序一方面需要知道缓存,需要连接缓存服务器,通过缓存服务器去查找数据,如果缓存服务器中没有查到数据,那么就自己去连接数据库,从数据库中去查找数据,并且在返回数据以后,还要把这个数据当作缓存写入到缓存服务器中去,以便于下一次读取的时候从缓存中读取。
缓存虽然对系统性能提升非常明显,但是也还是需要对缓存进行合理的使用,在合适的场景下进行使用。第一点是关注频繁修改的数据,如果一个数据经常被修改,那么使用缓存可能就价值不大;第二点是缓存要有热点,因为缓存的空间总是有限的,只能存储一小部分的数据,如果被访问的数据概率都是一样的,没有热点,数据极有可能写入缓存以后很快又被清除掉了,没有被读取过,体现不出来缓存的价值;还有两点分别是注意缓存雪崩和关注缓存的数据一致性。
分布式对象缓存是我们分布式架构中用的比较多的一种缓存。使用分布式缓存要注意的是:缓存的路由算法是如何实现的?比较重要的、用的比较广泛的是一致性哈希算法。
总之,缓存是无处不在的。在整个计算机系统中,在各个地方,只要你能够想得到的,都可以使用缓存来提升性能,甚至应用程序、一段代码中都可以使用缓存。所以我们要多关注缓存的使用,同时也要关注使用缓存的那些注意点。

上一篇下一篇

猜你喜欢

热点阅读