缓存:服务端缓存。

发布时间:2024-01-02 17:08:33

服务端缓存是整个缓存体系中的重头戏,从网站的架构演进中已经看到了服务端缓存是系统性能的重中之重了。数据库是整个系统中的“慢性子”,有时候数据库调优能够以小博大,在不改变架构和代码逻辑的前提下,缓存参数的调整往往是条捷径。在系统开发的过程中,可以直接在平台侧使用缓存框架,当缓存框架无法满足系统对性能的要求时,就需要在应用层自主开发应用级缓存了,即使利用可供参考的开源架构,应用级缓存的开发也是一件有挑战的事情。

数据库缓存

数据库属于IO密集型的应用,主要负责数据的管理及存储。数据库缓存是一类特殊的缓存,是数据库自身的缓存机制。大多数数据库不需要配置就可以快速运行,但并没有为特定的需求进行优化。在数据库调优的时候,缓存优化是一项很重要的工作。

以MySQL为例,MySQL中使用了查询缓冲机制,将SELECT语句和查询结果存放在缓冲区中,以后对于同样的SELECT语句,将直接从缓冲区中读取结果,以节省查询时间,提高了SQL查询的效率。

MySQL的查询缓存

Query Cache作用于整个MySQL为例,主要用于缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当打开了Query Cache功能,MySQL在接收到一条select语句的请求后,如果该语句满足Query Cache的请求,MySQL会直接根据预先设定好的HASH算法将接收到的select语句以字符串方式进行hash,然后到Query Cache中直接查找是否已经缓存。也就是说,如果已经有结果在缓存中,该select请求就会直接将数据返回,从而省略了后面的步骤(如SQL语句的解析,优化器优化以及向存储引擎请求数据等),从而极大的提升了性能。当然,当数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。

Query Cache的使用需要多个参数配合,其中最为关键的是query_cache_size和query_cache_type,前者设置用于缓存ResultSet的内存大小,后者设置在何种场景下使用Query Cache。这可以通过计算Query Cache的命中率来进行调整。query_cache_type可以设置为0(OFF),1(ON)或者2(DEMAND),分别表示完全不使用Query Cache,除显示要求不适用Query Cache之外的所有select都使用Query Cache,以及只有显示要求才使用Query Cache。

检查Query Cache的合理性

检查Query Cache是否合理,可以通过在MySQL控制台执行以下命令观察:

SHOW VARIABLES LIKE '%query_cache%';
SHOW STATUS LIKE 'Qcache%';

通过调节以下几个参数可以知道query_cache_size设置得是否合理:

  • Qcache inserts;
  • Qcache hits;
  • Qcache lowmem prunes;
  • Qcache free blocks。

如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况;如果Qcache_hits的值非常大,则表明查询缓冲使用非常频繁,如果该值较小反而会影响效率,那么可以考虑不用查询缓存;Qcache_free_blocks值非常大,则表明缓存区中的碎片很多,可能需要寻找合适的机会进行整理。

其中Qcache_hits表示多次命中,通过这个参数我们可以查看到Query Cache的基本效果;而Qcache_inserts表示多少次未命中然后插入。通过“Qcache_hits”和“Qcache_inserts”两个参数可以算出Query Cache的命中率:

Query Cache 命中率=Qcache_hits / (Qcache_hits+Qcache_inserts)

Qcache_lowmem_prunes表示多少条Query因为内存不足而被清除出Query Cache。通过Qcache_lowmem_prunes和Qcache_free_memory相互结合,能够更清楚的了解到系统中Query Cache的内存大小是否真的足够,是否频繁的出现因为内存不足而有Query被换出的情况。

InnoDB的缓存性能

当使用InnoDB存储引擎的时候,innodb_buffer_pool_size参数可能是影响最为关键的一个参数了,用来设置用于缓存InnoDB索引以及数据块的内存区域大小,更像是Oracle数据库的db_cache_size。简单来说,当操作一个InnoDB表的时候,返回的所有数据或者查询过程中用到的任何一索引块,都会在这个内存区域中去查询一遍。

和key_buffer_size对于MyISAM引擎一样,innodb_buffer_pool_size设置了InnoDB存储引擎需求最大的一块内存区域的大小,直接关系到InnoDB存储引擎的性能,所以如果有足够的内存,尽可能将该参数设置到足够大,将尽可能多的InnoDB的索引及数据都放入到该缓存区域中,直至全部。

可以通过(Innodb_buffer_pool_read_requests-Innodb_buffer_pool_reads)/Innodb_buffer_pool_read_requests*100%计算缓存命中率,并根据命中率来调整innodb_buffer_pool_size参数大小进行优化。

另外,table_cache是一个非常重要的MySQL性能参数,主要用于设置table高速缓存的数量。由于每个客户端连接都会至少访问一个表,因此该参数与max_connections有关。当某一连接访问一个表时,MySQL会检查当前已缓存表的数量。如果该表已经在缓存中打开,则会直接访问缓存中的表以加快查询速度;如果该表未被缓存,则会将当前的表添加进缓存并进行查询。在执行缓存操作之前,table_cache参数用于限制缓存表的最大数目:如果当前已经缓存的表未达到table_cache数目,则会将新表添加进来;若已经达到此值,MySQL将根据缓存表的最后查询时间、查询率等规则释放之前的缓存。

平台级缓存

在系统开发的时候,适当的使用平台级缓存,往往可以取得事半功倍的效果。平台级缓存在这里指的是用来写带有缓存特性的应用框架,或者可用于缓存功能的专用库(如PHP中的Smarty模板库)。

在Java语言中,缓存框架更多,例如Ehcache,Cacheonix,Voldemort,JBoss Cache,OSCache等等。

Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,是从hibernate的缓存开始被广泛使用起来的。EhCache有如下特点:

  • 轻量快速:EHcache线程机制是为大型高并发系统设计的。
  • 良性伸缩:数据可以伸缩到数G字节,节点可以达数百个。
  • 简洁灵活:运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目等是可以在运行时修改的。
  • 标准支持:Ehcache提供了对JSR107? JCache API最完整的实现。
  • 强扩展性:节点发现,冗余器和监听器都可以插件化。
  • 数据持久:缓存的数据可以在机器重启后从磁盘上冲i性能获得。
  • 缓存监听:提供了许多对缓存事件发生后的处理机制。
  • 分布式缓存:支持高性能的分布式缓存,兼具灵活性和扩展性。

Ehcache的系统结构如下图所示。

Voldemort是一款基于Java开发的分布式键-值缓存系统,像JBoss的缓存一样,Voldemort同样支持多台服务器之间的缓存同步,以增强系统的可靠性和读取性能。

Voldemort有如下特点:

  • 缓存数据可以自动在各个服务器节点之间同步复制。
  • 每一个服务器的缓存数据被横向分割,因此是总缓存的一个子集。
  • 严格保持缓存的一致性。
  • 提供服务器宕机快速恢复方案。
  • 可配置的数据存储引擎。
  • 可配置的数据序列化方式。
  • 每一个数据项都有版本标识,用来保证数据的完整性和可用性。
  • 每一个缓存节点都是独立的,因此任何一个节点的故障都不会影响系统的正常运行。

Voldemort的逻辑架构图如下图所示。

Voldemort相当于是Amazon Dynamo的一个开源实现,LinkedIn用他解决了网站的高扩展性存储问题。

简单来说,就平台级缓存而言,只需要在框架侧配置一下属性即可,而不需要调用特定的方法或函数。系统中引入缓存技术往往就是从平台级缓存开始的,平台级缓存也通常会作为一级缓存使用。

应用级缓存

当平台级缓存不能满足系统性能要求的时候,就要考虑使用应用级缓存了。

应用级缓存,需要开发者通过代码来实现缓存机制。这里是NoSQL的胜场,无论是Redis还是MongoDB,以及Memcached都可以作为应用级缓存的重要技术。一种典型的方法是每分钟或一段时间后统一生成某类页面存储在缓存中,或者可以在热数据变化时更新缓存。

面向Redis的缓存应用

Redis是一款开源的、基于BSD许可的、高级键值对缓存和存储系统,在应用级缓存中的作用举足轻重,例如,新浪微博有着几乎世界上最大的Redis集群。Redis支持主从同步,数据可以从主服务器向任意数量的从服务器同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树状复制。由于完全实现了发布/订阅机制,使得从服务器在任何地方同步树的时候,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

Redis 3.0版本加入cluster功能,解决了Redis单点无法横向扩展的问题。Redis集群采用无中心节点方式实现,无需proxy代理,客户端直接与Redis集群的每个节点连接,根据同样的哈希算法计算出key对应的slot,然后直接在slot对应的Redis上执行命令。在Redis看来,响应时间是最苛刻的条件,增加一层带来的开销是不能接受的。因此Redis实现了客户端对节点的直接访问,为了去中心化,节点之间通过Gossip协议交换相互的状态,以及探测新加入的节点信息。Redis集群支持动态加入节点,动态迁移slot,以及自动故障转移。一个Redis集群的架构示意如下图所示。

所有的Redis节点通过PING-PONG机制彼此互联,内部使用二进制协议优化传输速度和带宽。节点故障是通过集群中超过半数的节点检测失效时才会生效。客户端与Redis节点直连,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。Redis Cluster把所有的物理节点映射到slot上,cluster负责维护node、slot和value的映射关系。当节点发生故障的时候,选举过程是集群中所有master参与的,如果半数以上master节点与当前master节点间的通信超时,认为当前master节点挂掉。如果集群中超过半数以上master节点挂掉,无论是否有slave集群,Redis的整个集群将处于不可用状态。当集群不可用时,所有对集群的操作都不可用,都将收到错误信息【(error)CLUSTERDOWN The cluster is down】。

支持Redis的客户端编程语言众多,可以满足绝大多数的应用,如下图所示。

多级缓存实例

一个使用了Redis集群和其他多种缓存技术的应用系统架构如下图所示。

首先,用户的请求被负载均衡服务分发到Nginx上,此处常用的负载均衡算法是轮询或者一致性哈希,轮询可以使服务器的请求更加均衡,而一致性哈希可以提升Nginx应用的缓存命中率。

接着,Nginx应用服务器读取本地缓存,实现本地缓存的方式可以是Lua Shared Dict,或者面向磁盘或内存的Nginx Proxy Cache,以及本地的Redis实现等,如果本地缓存命中则直接返回。Nginx应用服务器使用本地缓存可以提升整体的吞吐量,降低后端的压力,尤其应对热点数据的反复读取问题非常有效。

如果Nginx应用服务器的本地缓存没有命中,就会进一步读取相应的分布式缓存——Redis分布式缓存的集群,可以考虑使用主从架构来提升性能和吞吐量,如果分布式缓存命中则直接返回相应数据,并回写到Nginx应用服务器的本地缓存中。

如果Redis分布式缓存也没有命中,则会回源到Tomcat集群,在回源到Tomcat集群时也可以使用轮询和一致性哈希作为负载均衡算法。当然,如果Redis分布式缓存没有命中的话,Nginx应用服务器还可以再尝试一次读主Redis集群操作,目的是防止当从Redis集群有问题时可能发生的流量冲击。

在Tomcat集群应用中,首先读取本地平台级缓存,如果平台级缓存命中则直接返回数据,并会同步写到主Redis集群,然后再同步到从Redis集群。此处可能存在多个Tomcat实例同时写主Redis集群的情况,可能会造成数据错乱,需要注意缓存的更新机制和原子化操作。

如果所有缓存都没有命中,系统就只能查询数据库或其他相关服务获取相关数据并返回,当然,我们已经知道数据库也是有缓存的。

整体来看,这时一个使用了多级缓存的系统。Nginx应用服务器的本地缓存解决了热点数据的缓存问题,Redis分布式缓存集群减少了访问回源率,Tomcat应用集群使用的平台级缓存防止了相关缓存失效/崩溃之后的冲击,数据库缓存提升数据库查询时的效率。正是多级缓存的使用,才能保障系统具备优良的性能。

缓存算法

在实现缓存应用的时候,需要了解缓存技术中的几个术语。

  • 缓存命中率:当客户发起一个请求时,系统接收到这个请求,如果该请求的数据就是在缓存中,这一数据就会被使用,这一行为叫做缓存命中。
  • 没有命中:cache miss是没有命中。如果缓存中还有存储空间,那么没有命中的对象会被存储到缓存中来。
  • 存储成本:当没有缓存命中时,系统会从数据库或其他数据源取出数据,然后放入缓存。而把这个数据放入缓存所需要的时间和空间,就是存储成本。
  • 缓存失效:当存储在缓存中的数据需要更新时,就意味着缓存中的这一数据失效了。
  • 替代策略:当缓存没有命中时,并且缓存容量已经满了,就需要在缓存中去除一条旧数据,然后加入一条新数据,而到底应该去除哪些数据,就是替代策略决定的。

替代策略的具体实现就是缓存算法,这里简要介绍一下主流的缓存算法:

Least-Recently-Used(LRU)

替换掉最近被请求最少的对象,这种传统策略在实际中应用最广。在CPU缓存淘汰和虚拟内存系统中效果很好。然而在直接应用与代理缓存中效果欠佳,因为Web访问的时间局部性常常变化很大。

浏览器就一般使用LRU作为缓存算法。新的对象会被放在缓存的顶部,当缓存达到了容量极限,底部的对象被去除,方法就是把最新被访问的缓存对象放到缓存池的顶部。

Least-Frequently-Used(LFU)

替换掉访问次数最少的缓存,这一策略意图是保留最常用的、最流行的对象,替换掉很少使用的那些数据。然而,有的文档可能有很高的使用频率,但之后再也不会用到。传统的LFU策略没有提供任何移除这类的文件的机制,因此会导致“缓存污染”,即一个先前流行的缓存对象会在缓存中驻留很长时间,这样,就阻碍了新进来可能会流行的对象对他替代。

Least Recently Used 2(LRU2)

LRU的变种,把被两次访问过的对象放入缓存池,当缓存池满了之后,会把有两次最少使用的缓存对象去除。因为需要跟踪对象2次,访问负载就会随着缓存池的增加而增加。

Two Queues(2Q)

Two Queues是LRU的另一个变种,把被访问的数据放到LRU的缓存中,如果这个对象再一次被访问,就把他转移到第二个、更大的LRU缓存,使用了多级缓存的方式。去除缓存对象是为了保持第一个缓存池是第二个缓存池的1/3。当缓存的访问负载是固定的时候,把LRU换成LRU2,就比增加缓存的容量更好。

SIZE

替换占用空间最大的对象,这一策略通过淘汰一个大对象而不是多个小对象来提高命中率。不过,可能有些进入缓存的小对象永远不会再被访问。SIZE策略没有提供淘汰这类对象的机制,也会导致“缓存污染”。

LRU-Threshold

不缓存超过某一size的对象,其他与LRU相同。

Log(Size)+LRU

替换size最大的对象,当size相同时,按LRU进行替换。

Hyper-G

LFU的改进版,同时考虑上次访问时间和对象size。

Pitkow/Recker

替换最近最少使用的对象,除非所有对象都是今天访问过的。如果是这样,则替换掉最大的对象。这一策略试图符合每日访问Web网页的特定模式。这一策略也被建议在每天结束时运行,以释放被“旧的”、最近最少使用的对象占用的空间。

Lowest-Latency-First

替换下载时间最少的文档。显然他的目标是最小化平均延迟。

Hybrid Hybrid

有一个目标是减少平均延迟。对缓存中的每个文档都会计算一个保留效用,保留效用最低的对象会被替换掉。

Lowest Relative Value(LRV)

LRV也是基于计算缓存中文档的保留效用,然后替换保留效用最低的文档。

Adaptive Replacement Cache(ARC)

ARC介于LRU和LFU之间,为了提高效果,由2个LRU组成,第一个包含的条目是最近只被使用过一次的,而第二个LRU包含的是最近被使用过两次的条目,因此,得到了新的对象和常用的对象。ARC能够自我调节,并且是低负载的。

Most Recently Used(MRU)

MRU与LRU是相对,移除最近最多被使用的对象。当一次访问过来的时候,有些事情是无法预测的,并且在缓存系统中找出最少最近使用的对象是一项时间复杂度非常高的运算,这时会考虑MRU,在数据库内存缓存中比较常见。

First in First out(FIFO)

FIFO通过一个队列去跟踪所有的缓存对象,最近最常用的缓存对象放在后面,而更早的缓存对象放在前面,当缓存容量满时,排在前面的缓存对象会被踢走,然后把新的缓存对象加进去。

Random Cache

随机缓存就是随意的替换缓存数据,比FIFO机制好,在某些情况下,甚至比LRU好,但是通常LRU都会比随机缓存更好些。

还有很多缓存算法,例如:Second Chance、Clock、Simple time-based、Extended time-based expiration、Sliding time-based expiration......各种缓存算法没有优劣之分,不同的实际应用场景,会用到不同的缓存算法。在实现缓存算法的时候,通常会考虑使用频率、获取成本、缓存容量和时间等因素。

使用公有云的缓存服务

国内的公有云服务提供商如阿里云、青云、百度云等都推出了基于Redis的云存储服务,这些服务有如下特点:

  • 动态扩容:用户可以通过控制面便升级所需的Redis存储空间,扩容的过程中服务不需要中断或停止,整个扩容过程对用户是透明的且无感知的,这点是非常实用的,在前面介绍的方案中,解决Redis平滑扩容是个很繁琐的任务,现在按几下鼠标就能搞定,大大减少了运维的负担。
  • 数据多备:数据保存在一主一备两台机器中,其中一台机器宕机了,数据还在另外一台机器上有备份。
  • 自动容灾:主机宕机后系统能自动检测并切换到备机上,实现了服务的高可用性。
  • 成本较低:在很多情况下,为了使Redis的性能更好,需要购买一台专门的服务器用于Redis的存储服务,但这样会导致某些资源的浪费,购买Redis云存储服务就能很好的解决这样的问题。

有了Redis云存储服务,能使后台开发人员从繁琐的运维中解放出来。应用后台服务中如果搭建了一个高可用、高性能的Redis集群服务,需要投入相当的运维成本和精力。如果使用云服务,就没必要投入这些成本和精力,剋呀让后台应用的开发人员更专注于业务。

总而言之,“缓存为王”是因为在现代的软件系统中,缓存无处不在,是一种以空间换时间的艺术。尽管缓存是一种非常复杂的技术,但是真正掌握缓存技术是保证软件系统性能的关键手段。

文章来源:https://blog.csdn.net/en_joker/article/details/102988659
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。