1.深入分布式缓存:从原理到实践 --- 缓存为王

	缓存:凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输差异的结构,都称为cache。
	缓存是一种设计模式:这种模式利用增加存储空间的方式,实现低俗部件与高速部件之间的解耦。

1.什么是缓存
	缓存:存储在计算机上的一个原始数据复制集,以便于访问。
	缓存是系统快速响应中的一种关键技术,是一组被保存起来以备将来使用的东西,介于应用开发和系统开发之间。
	
	CPU缓存:
		是指位于cpu与内存之间的临时存储器,容量比内存小很多但交换速度却比内存要快很多。由于cpu的运算速度比内存读写速度快很多,cpu总有
	  等待数据的时候,而高速缓存则解决了cpu运算速度和内存读写速度不匹配的矛盾。cpu是有多级缓存的,有时候也称为几级流水。

	文件缓存:
		我们编程的时候,接触到的都是虚拟地址而不是真实地址,这是虚拟内存的主要功能之一。加入请求一页的地址,需要将页的虚拟地址转为页的物理
	  地址。页表(page table)和内存管理单元(MMU)就负责将页的虚拟地址映射到物理地址。页表负责记录哪些是物理页,哪些是虚拟页,以及这些页的
	  页表条目(PTE)。而MMU是一个物理硬件,MMU负责进行虚拟地址到物理地址的翻译,翻译的过程中需要从页表获取页的PTE,MMU也会使用翻译后备存储
	  器(TLB)的缓存页号。

	缓存大体可以分三类:
		1.客户端缓存
		2.服务端缓存
		3.网络中的缓存

	根据规模和部署方式分类:	
		1.单体缓存
		2.缓存集群
		3.分布式缓存

2.为什么使用缓存
	1.为什么使用缓存
		用户体验 : 人们对正在使用或期望使用的产品,系统或者服务的认知印象和回应。因此,用户体验是主观的,且注重实际应用的。
		即用户在使用一个产品或系统之前,使用期间和使用之后的全部感受,包括情感,信仰,喜好,认知印象,生理反应,心理反应,行为和成就等各个方面。
		有许多因素可以影响用户体验,分为三类:使用者的状态,系统性能,和环境。
		系统性能是一种非功能特性,它关注的不是某种特定的功能,而是在完成功能时所展示出来的及时性。

	2.关于系统的性能
		系统的性能一般包括响应时间,延迟时间,吞吐量,并发用户数和资源利用率等。
		吞吐量是指系统在单位时间内处理请求的数量。
		缓存离客户端更近,从缓存请求内容比从源服务器所用的时间更少,呈现速度更快。

3.从网站的架构看缓存
	页面静态缓存 => 动态缓存 => 页面片段缓存 => 数据缓存 => 数据库缓存 => 分表分库 => 数据访问层(通用的框架来实现分库分表的数据访问) => 分布式缓存

4.客户端缓存
	对于互联网应用而言,也就是BS架构,可以分为页面缓存和浏览器缓存。对于移动互联网,是指APP自身所使用的缓存。

	1.页面缓存
		页面缓存有两层意思:一个是页面自身对某些元素或者全部元素进行缓存;另一层意思是服务端将静态页面或动态页面的元素进行缓存,然后给客户端使用。
		页面缓存是将之前渲染的页面保存为文件,当用户再次访问的时候可以避开网络连接,从而减少负载,提升性能和用户体验。随着单页面应用(SAP),和HTML5
	  支持了离线存储和本地存储,大部分BS应用的页面缓存都可以举重若轻了。

	    HTML5提供的离线应用存储机制,使得网页应用可以离线使用,这种特性在网页上支持度非常广,可以放心的使用该特性来加速页面的访问。开启离线缓存的步骤
	  如下:
	  	1.准备用于描述页面需要缓存的资源列表清单文件(manifest text/cache-manifest)
	  	2.在需要离线使用的页面中添加 manifest 属性,指定缓存清单文件的路径。

	  	HTML5离线缓存的工作流程:
	  	1.当浏览器访问了一个包含 manifest 属性的页面时,如果应用的缓存不存在,浏览器会加载文档,获取所有在清单文件中的文件,生成初始缓存。
	  	2.当对该文件再次访问时,浏览器会直接从应用缓存中加载页面以及在清单列表中列出的资源。同时,浏览器还会向 window.applicationCache 对象发送一个
	  	表示检查的事件,以获取清单文件。
	  	3.如果当前缓存的清单副本是最新的,浏览器向 window.applicationCache 对象发送一个表示无需更新的事件,从而结束更新过程。如果在服务端修改了任何
	  	缓存资源,必须同时修改清单文件,这样浏览器才能知道重新获取资源。
	  	4.如果清单列表已经改变,那么文件中列出的所有文件会被重新获取并放到一个临时缓存中。对于每个加入到临时缓存中的文件,浏览器会向 window.applicationCache
	  	对象发送一个表示进行的事件。
	  	5.一旦所有的文件都获得了成功,它们会自动移动到真正的离线缓存中,并向window.applicationCache 对象发送一个表示已经缓存的事件。鉴于文档早已经从缓存加载
	  	到浏览器中,所有更新后的文档不会重新渲染,直到页面重新加载。
	  	需要注意的是:manifest 文件中列出的资源URL必须和manifest 本身使用同样的网络协议。

	2.浏览器缓存
		浏览器缓存是根据一套与服务器约定的规则进行工作的,工作规则很简单:检查以确保副本是最新的,通常只需要一次会话。浏览器会在硬盘上专门开辟一个空间来存储资源
	  副本作为缓存。在用户触发'后退'操作或者点击之前看过的一个链接,浏览器缓存会很管用。
	    对浏览器而言,HTTP1.0 提供了一些很基本的缓存特性,比如服务端设置 Expires 的 http 头来告诉客户端在重新请求文件之前缓存多久是安全的,可以通过
	  if-modified-since 的条件请求来使用缓存。其中,发送的时间是文件最初被下载的时间,而不是即将过期的时间,如果文件没有改变,服务器可以用 304-Not Modified
	  来应答。客户端收到 304 代码,就可以使用缓存的文件版本了。
	    HTTP1.1 有了比较大的增强,缓存系统被形式化了,引入了实体标签 e-tag。e-tag 是文件或对象的唯一标识,这意味着可以请求一个资源,以及提供所持有的文件,然后
	  询问这个文件是否有变化。如果某一个文件的e-tag是有效的,那么服务器会生成304-Not Modified 应答,并提供正确的文件e-tag,否则,发送200-OK应答。
	    在配置了Last-Modified/ETag 的情况下,浏览器再一次访问统一URI资源时,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器只发送一个304给浏览器。
	  浏览器则直接从本地缓存取数据;如果数据有变化,就将整个数据重新发给浏览器。
	    Last-Modified/ETag 与 Cache-Control/Expires 的作用是不一样的,如果检测到本地的缓存还在有效时间范围内,浏览器则直接使用本地缓存,不会发送任何请求。
	  两者一起使用的时,Cache-Control/Expires 的优先级高于 Last-Modified/ETag。即当本地副本根据 Cache-Control/Expires 发现还在有效期内时,则不会再次发送
	  请求去服务器询问修改时间(Last-Modified)或者实体标签(e-tag)了。
	    Cache-Control 和 Expires 的功能一致,都是指明当前资源的有效期,控制浏览器是直接从浏览器缓存存取数据还是重新发请求到服务器取数据。只不过 Cache-Control
	  的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
	    一般情况下,使用 Cache-Control/Expires 会配合 Last-Modified/ETag 一起使用,因为即使服务器设置缓存时间,当用户点击'刷新'按钮时,浏览器会忽略缓存继续
	  向服务器发送请求,这是Last-Modified/ETag 能够很好的利用服务器的返回码304,从而减少响应开销。
	    通过在html页面的节点加入 meta 标签,可以告诉浏览器当前页面不被缓存,每次都需要去服务器拉去,代码如下:
	    <META HTTP-EQUIV=”Pragma“ CONTENT="no-cache">
	    令人遗憾的是,只有部分浏览器支持这一用法,而且一般缓存代理服务器都不支持,因为代理不解析html本身内容。

	3.APP缓存
		如何把app缓存对于业务组件透明,以及app缓存数据的及时更新,是app缓存能够成功应用起来的关键。app可以将内容缓存在内存,文件或者本地数据库(例如SQLite)中,
	  但基于内存的缓存要谨慎使用。
	    app 使用数据库缓存的方法:在下载完数据文件后,把文件相关的信息,如url,路径,下载时间,过期时间等存放到数据库,下次下载的时候根据url先从数据库中查询,
	  如果查到当前时间并未过期,就根据路径读取本地文件,从而实现缓存效果。
	    对于app的某些页面,可以采用文件缓存的方法。这种方法使用文件操作的相关api得到文件的最后修改时间,与当前时间判断是否过期,从而实现缓存效果,操作比较简单,
	  代价比较低。需要注意的是,不同类型文件的缓存时间不一样。例如,图片文件的内容是相对不变的,直到最终被清理掉,app可以永远读取缓存中的图片内容。而配置文件中
	  的内容可能更新,需要设置一个可接受的缓存时间。同时,不同环境下的缓存时间标准也不一样,wifi环境下,缓存时间设置少一点,一是网速快,二是不产生流量费。而在
	  移动数据流量的环境下,缓存时间可以设置久一点,节省流量,用户体验也更好。

5.网络中的缓存
	网络中的缓存位于客户端与服务端之间,代理或响应客户端的网络请求,从而对重复的请求返回缓存中的数据资源。同时,接受服务端的请求,更新缓存中的内容。

	1.Web代理
		常用的web代理分为正向代理,反向代理和透明代理。
		Squid

	2.边缘缓存
		如果反向代理服务器能够做到和用户来自同一个网络,那么用户访问反向代理服务器,就会得到很高质量的响应速度,所以可以将这样的反向代理称为边缘缓存。
	  边缘缓存在网络上位于靠近用户的一侧,可以处理来自不同用户的请求,主要向用户提供静态的内容,以减少应用服务器的介入。边缘缓存的一个有名的开源工具
	  就是Varnish,在默认情况下进行保守缓存。也就是说, Varnish 只缓存它所知的安全内容。
	    边缘缓存中典型的商业化服务就是CDN了。
	    CDN 边缘节点的缓存策略因服务商不同而有所变化,但一般都会遵守http标准协议,通过http响应头中的 Cache-control:max-age 的字段来设置cdn边缘
	  节点的数据缓存时间。当客户端向cdn节点请求数据时,cdn节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,cdn
	  节点就会向源站发出回源请求,从源站拉去最新数据,更新本地缓存,并将最新的数据返回给客户端。
	    cdn服务商一般会基于文件后缀,目录等多个维度来指定在cdn上的缓存时间,为用户提供更精细化的缓存管理。cdn上的缓存时间会对'回源率'产生直接的影响。
	  若数据在cdn上的缓存时间比较短,则cdn边缘节点上的数据会经常失效,导致频繁回源,增加了源站的负载,同时也增大了访问时延;若数据在cdn上的缓存时间
	  比较长,会带来数据更新比较慢的问题。
	    一般的,cdn 边缘节点对开发者来说是透明的,开发者可以通过cdn服务器提供的 '缓存刷新' 接口来清理位于cdn 边缘节点上的缓存数据。

6.服务端缓存
	1.数据库缓存
		数据库属于IO密集型的应用,主要负责数据的管理和存储。数据库缓存是一类特殊的缓存,是数据库自身的缓存机制。

		1.MySQL 的查询缓存
			query cache 作用于整个mysql实例,主要用于缓存mysql中的 ResultSet,也就是说一条sql语句执行的结果集,所以仅仅只能针对select语句。当打开了
		  query cache 功能,mysql 在接收到一条select语句的请求后,如果该语句满足query cache的请求,mysql 会直接根据预先设定好的hash算法将接收到的
		  select语句以字符串方式进行hash,然后到query cache中直接查找是否已经缓存。也就是说,如果已经有结果在缓存中,该select请求就会直接将数据返回,
		  从而省略了后面的所有步骤,从而提高性能。当然,数据变化非常频繁的情况下,使用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.

		2.检查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_blocks 互相结合,能够
		  很清楚的了解到系统中 query cache 的内存大小是否真的足够,是否频繁的出现因为内存不足而有 query 被换出的情况。

		3.InnoDB 的缓存性能
			当使用innodb存储引擎的时候,innodb_buffer_pool_size 参数可能是影响性能的最为关键的一个参数了,用来设置用于缓存 innodb 索引及数据块的内存区域
		  大小。简单来说,当操作一个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 将根据缓存表的最后查询时间,查询率等规则释放之前的缓存。

	2.平台级缓存
		平台级缓存在这里指的是用来写带有缓存特性的应用框架,或者可用于缓存功能的专用库(如PHP的Smarty模板库)。
		如 Java 的 Eccache,Voldemort。
		系统引入缓存技术往往就是从平台级缓存开始的,平台级缓存也通常会作为一级缓存使用。

	3.应用级缓存
		应用级缓存,需要开发者通过代码来实现缓存机制。

		1.面向Redis的缓存应用
			redis 支持主从同步,数据可以从主服务器向任意数量的从服务器同步,从服务器可以是关联其他从服务器的主服务器。这使得redis可执行单层树状复制。由于完全实现了
		  发布/订阅机制,使得从服务器在任何地方同步树的时候,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。
		    redis 3.0 版本加入了 cluster 功能,解决了 redis 单点无法横向扩展的问题。redis集群采用无中心节点方式实现,无需proxy代理,客户端直接与redis集群的每个
		  节点连接,根据同样的哈希算法计算出key对应的slot,然后直接在slot对应的redis上执行命令。在redis看来,响应时间是最苛刻的条件,增加一层带来的开销是不能接受的。
		  因此,redis实现了客户端对节点的直接访问,为了去中心化,节点之间通过 Gossip 协议交换互相的状态,以及探测新加入的节点信息。redis集群支持支持动态加入节点,
		  动态迁移slot,以及自动故障转移。
		    所有的redis节点通过 PING-PONG 机制彼此互连,内部使用二进制协议优化传输速度和带宽。节点故障是通过集群中超过半数的节点检测失效时才会生效。客户端与redis
		  节点直连,客户端不需要连接集群中的所有节点,连接集群中任何一个可用节点即可。redis cluster 把所有物理节点映射到slot上,cluster负责维护node,slot和value
		  的映射关系。当节点发生故障的时候,选举过程是集群中所有的master参与的,如果半数以上master节点与当前master节点间的通信超时,认为当前master节点挂掉了。如果
		  集群中超过半数以上master节点挂掉,无论是否有slave集群,redis的整个集群将处于不可用状态。当集群不可用时,所有对集群的操作都不可用,将收到错误信息。

		2.多级缓存实例
			首先,用户的请求被负载均衡服务分发到nginx上,此处常用的负载均衡算法是轮询或者一致性哈希,轮询可以使服务器的请求更加均衡,而一致性哈希可以提升nginx应用
		  的缓存命中率。
		    接着,nginx应用服务器读取本地缓存,实现本地缓存的方式可以是 Lua Share Dict,或者是面向磁盘或者是内存的Nginx Proxy Cache,以及本地的redis实现等,
		  如果本地缓存命中则直接返回。nginx应用服务器使用本地缓存可以提升整体的吞吐量,然后降低后端压力,尤其对应热点数据的反复读取问题非常有效。
		    如果nginx应用服务器的本地缓存没有命中,就会进一步读取相应的分布式缓存---redis分布式缓存的集群,可以考虑使用主从架构来提升性能和吞吐,如果分布式缓存
		  命中则直接返回相应的数据,并会写到nginx应用服务器的本地缓存中。
		    如果redis分布式缓存也没有命中,则会回源到tomcat集群,在回源到tomcat集群时也可以使用轮询和一致性哈希作为负载均衡算法。当然,如果redis分布式缓存没有
		  命中的话,nginx应用服务器还可以尝试一次读取主redis集群操作,目的是防止从redis集群有问题时可能发生的流量冲击。
		    在tomcat集群应用中,首先读取本地平台级缓存,如果平台级缓存命中则直接返回数据,并会同步写到主redis集群,然后再同步到从redis集群。此处可能存在多个tomcat
		  实例同时写主redis集群的情况,然后会造成数据混乱,需要注意缓存的更新机制和原子化操作。
		    如果所有的缓存都没有命中,系统就只能查询数据库或者其他相关服务获取数据并返回,当然,我们知道数据库也是有缓存的。
		    整体看来,这是一个使用了多级缓存的系统。nginx应用服务器的本地缓存解决了热点数据的缓存问题,redsi分布式缓存集群减少了访问回源率,tomcat应用集群使用的
		  平台级缓存则防止了相关缓存失效/崩溃之后的冲击,数据库缓存提升数据库查询时的效率。

		3.缓存算法
			缓存替代策略的具体实现就是缓存算法,主要的缓存算法如下:
			1.Least-Recently-Used(LRU)
				替换掉最近请求最少的对象。
			2.Least-Frequently-Used(LFU)
				替换掉访问次数最少的缓存。
			3.Least Recently Used 2(LRU 2)
				LUR的变种,把被两次访问的对象放入缓存池,当缓存池满了之后,会把有2次最少使用的缓存对象移除。
			4.Tow Queues(2Q)
				Tow Queues 是 LRU 的另外一个变种,把被访问的数据放到LRU的缓存中,如果这个对象再一次被访问,就把它转移到第二个,更大的LRU缓存,使用了多级缓存的方式。
			5.SIZE
				替换占用空间最大的对象。
			6.LRU-Threshold
				不缓存超过某一size的对象,其他与LRU相同。
			7.Log(SIZI) + LRU
				替换size最大的对象,当size相同的时候,按LRU进行替换
			8.Hyper-G
				LRU 的改进版,同时考虑上次访问时间和对象size
			9.Pitkow/Recker
				替换最近最少使用的对象,除非所有的对象都是今天访问过的。如果是这样,则替换掉最大的对象。
			10.Lowest-Latency-First
				替换下载时间最少的文档。
			11.Hybrid Hybrid
				有一个目标是减少平均延迟。对缓存中的每个文档都会计算一个保留效用,保留效用最低的对象会被替换掉。
			12.Lowest Relative Value(LRV)
				同上。
			13.Adaptive Replacement Cache(ARC)
				ARC 介于 LRU和 LFU 之间。
			14.Most Recently Used(MRU)
				MRU和LRU是相对的,移除最近最多被使用的对象。
			15.First in First out(FIFO)
				先进先出。
			16.Random Cache
				随机缓存。

		4.使用公有云的缓存服务
			优点:
			1.动态扩容
			2.数据多备
			3.自动容灾
			4.成本较低

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值