言午月月鸟
编程,带娃以及思考人生
首页
编程
带娃
思考人生
编程画图秀
PHP系统解析-缓存篇
dingusxp
2119
# PHP系统解析-缓存篇 ## 引子 缓存技术 在计算机软硬件体系中被广泛使用。 借用一个经典的面试题“当你在浏览器中输入 google.com 并且按下回车之后发生了什么?”。关于此问题,git有[一个仓库](https://github.com/skyline75489/what-happens-when-zh_CN),感兴趣的可以访问。 本文只侧重 缓存技术,稍微改一下题目“当你在wordpress(PHP开发的经典博客系统)查看这篇文章时,背后有哪些缓存技术”: - 浏览器解析URL 查询URL是否有对应缓存,如果已经存在: 1) 校验缓存标记(Cache-Control、Expires),如果符合条件,直接使用缓存;否则下一步; 2)携带 eTag 标记访问服务器,如果服务器返回 304 NOT MODIFED,则直接使用缓存。 - 域名解析 DNS查询是一个多级缓存。依次为: 1)浏览器查询本地DNS缓存,如果不存在,则调用系统库函数查询; 2)操作系统查询:首先检查hosts文件,然后查询系统DNS缓存;如果均无,请求域名服务器查询; 3)路由器一般也会建立DNS缓存,如果未命中,转向ISP DNS(即首选DNS服务器)查询; 4)ISP DNS一般能查询到;如果也没有,则转向根域名服务器查询; 5)根域名服务器(迭代)查询到结果返回后,上述各级会缓存对应域名。 - 应用服务器处理 这是PHP同学熟悉的。通常是在数据库访问之前加一层redis查询,如果命中则跳过数据库查询。**本文后面展开的主要是指这部分。** - PHP的Opcode PHP是解释型语言,一个PHP文件执行前会经过 加载语法和表达式 → 加载和解析文件 → 生成 Opcode → 执行 Opcode 的过程。在源代码不变的情况下,生成的 Opcode 不变,因此最新的 zend 引擎已经默认内置 Opcache ,开启后可以跳过文件解析,大大提升 PHP代码执行效率。 - MySQL 缓冲池 MySQL InnoDB引擎非常高效的原因之一,就是使用了 缓冲池。将数据按页的方式组织存放在内存中(按一定策略刷新到磁盘),让读写操作尽量直接内存操作就返回。 - CPU缓存 可能是计算机 缓存技术 的源头。因为CPU运算速率远高于内存读写速率,故CPU与内存之间引入一个临时存储器。按一定策略将少量内存块数据加载在其中,当需要访问的数据(地址)落在缓存中时,就不必访问内存,从而提升运行效率。 ## 缓存的定义 缓存 狭义上的定义指 CPU缓存。 广义上的一种定义是: 当两种硬件/软件(执行过程)之间存在较大的效率差时,引入用来协调两者速度差异的 结构。 ↑ 个人认为这个定义是从现象上的总结。有另一种思考维度,见:关于缓存本质的思考 不管怎样,缓存的作用,或者说使用缓存的主要目的 是一致的,即 “提升性能!” ## 缓存的特征 使用缓存需要关注这几个特征: ### 命中率 命中率 = 命中数 / 请求数。 这是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。 ### 最大元素(最大空间) 一旦缓存中元素数量超过这个值(或者缓存数据空间超过其最大支 持空间),将会触发淘汰策略 ### 淘汰策略 常见的有: - FIFO(first in first out)先进先出策略 最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。 策略算法主要比较缓存元素的创建时间。 在数据实效性要求场景下可选择该类策略,优先保障最新数据可用。 - LFU(less frequently used)最少使用策略 使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法,最低访问数的条目首先被移除。 这个方法并不经常使用,因为它无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责。策略算法主要比较元素的hitCount(命中次数)。 在保证高频数据有效性场景下,可选择这类策略。 - LRU(least recently used)最近最少使用策略 将最近使用的条目存放到靠近缓存顶部的位置。当一个新条目被访问时,LRU 将它放置到缓存的顶部。当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。 这是一个有较大系统开销的算法,它需要记录“年龄位”来精确显示条目是何时被访问的。此外,当一个 LRU 缓存算法删除某个条目后,“年龄位”将随其他条目发生改变。策略算法主要比较元素最近一次被 get 使用时间。 在热点数据场景下较适用,优先保证热点数据的有效性。 ## 什么时候使用缓存? 当系统性能或者并发能力出现瓶颈的时候,就可以条件反射地想到 缓存。 但具体是否使用,还要根据业务需求及缓存的优点(高性能、高并发)缺点(时效性损失、开发复杂度增加)综合分析取舍。 一些经验建议: 适合使用缓存的情况: 热点数据(如配置);读多写少的业务(如 博客);并发高的业务(如 秒杀); 不适合使用缓存的情况: 预计缓存命中率很低的场景; 频繁更新的数据; ## 如何使用缓存? ### PSR标准 PSR关于缓存标准定义有: - [PSR-6: Caching Interface](https://www.php-fig.org/psr/psr-6/) 主要关注: 1. 缓存Key的定义和数据操作接口的定义(Item 和 操作Item的Pool); 2. 缓存值应该支持可序列化的任何PHP数据类型; 3. 支持精确到秒级的过期时间设置; 4. 缓存操作不应该抛出除了 PSR标准中定义(CacheException)之外的异常。 - [PSR-16: Common Interface for Caching Libraries](https://www.php-fig.org/psr/psr-16/) PSR-6 对缓存的使用定义上略为繁琐,不符合其“简单”的特征。因此 PSR-16定义了一个独立的 SimpleCache协议,以便系统中更方便地使用Cache。 相比PSR-6的主要变化: 1. 简化了数据操作接口的定义,并支持Key的批量操作; ### 更新模式 常用的有 预更新 和 旁路更新。 #### 预更新 在用户可访问服务之前,将热点数据加载到缓存,有效避免上线后瞬时大流量造成系统不可用。 预更新的常见策略: 1、开发个缓存刷新功能,定时或手工刷新; 2、项目启动的时候自动加载(如 配置类数据); 3、在数据源变更操作后,触发事件进行缓存刷新。 预更新的好处是: 基本只针对热点数据预处理,缓存查询命中率高; 使用时一般不考虑缓存读取失败的情况,逻辑更简单,效率更高。 对应的坏处则是: 某些策略在系统初始化时增加了刷新缓存的操作,增加维护成本; 不考虑缓存失效策略,在缓存服务失效时,可能引起系统异常或降级。 #### 旁路更新 旁路更新即读时更新,更新缓存的操作只在读取操作触发。 常见策略: 读取操作: 查询缓存,如果命中则返回; 否则触发缓存更新,访问数据库,并将结果写入缓存。 写入操作: 缓存对应数据变更的操作触发时,在数据库操作完成后,删除对应缓存key。 好处是更新缓存的逻辑收拢到单点,维护方便;程序数据流逻辑上线性,开发调试便利。 另对于数据实时性要求不高的场景,可以不做写入删除缓存的操作,而是通过设置缓存失效时间实现数据更新的维护。 ### 常见问题 缓存是提升性能的利器,但在使用中也要注意这些常见问题: #### 缓存雪崩 因为某些原因 —— 如 1) 并发或者定时任务请求触发的缓存更新 或者 2) 某个缓存实例挂掉 —— 导致设置的大量缓存在同一时间失效。这是新的访问缓存miss,直接访问数据库层,导致数据库压力过大。 对应方案1:错开交错缓存失效时间或随机缓存失效时间。—— 针对 case 1。 对应方案2:主从热备(Redis Sentinel)。—— 针对 case 2。 对应方案3:集群/水平切分(Redis Cluster、一致性哈希)。 —— 针对case 2 #### 缓存穿透 当查询一个不存在的数据,因为从数据库查不到数据,程序逻辑上可能会跳过写入缓存阶段。这将导致这个不存在的数据请求都会跳过缓存去数据库查询。如果有人恶意构造请求攻击,可能造成数据库压力大。 对应方案1:查询空值也缓存。 对应方案2:针对查询空值增加专门的二级缓存,设置较短的过期时间。 #### 缓存击穿 网站高并发访问时,如果碰到热点缓存key失效,可能出现多个进程同时尝试更新缓存,集中访问数据库,造成数据库压力过大。 对应方案:对更新缓存的操作加锁。加锁不方便的PHP也有一些变通的确保同一时刻只有一个进程更新缓存的方法,见:PHP更新缓存防击穿的一种方法 ## 分布式缓存 在需要大规模使用缓存时,需要构建缓存集群。 这时需要考虑: - 数据读取 任意给定查询,要能如何快速定位到对应实例。 比如通过key的 hash 计算直接找到实例。 - 数据分布 数据应该尽量均分到每个缓存实例,避免单个实例存储和访问量过高,成为系统瓶颈。 特别关注 hash / list / set / zset 类型的值,如有可能,尽量打散为多个 key。 - 容灾方案 当集群中某个实例故障时,要有一定冗余机制,避免缓存雪崩。如主从备份或者hash环相邻节点备份。 实现上主要的方案是 一致性哈希,现在很多云服务已经实现对使用方透明的集群能力。感兴趣的同学可以参考这篇文章: [一致性哈希算法原理](https://www.cnblogs.com/williamjie/p/9477852.html) ## 参考文章及拓展阅读 [在浏览器输入 URL 回车之后发生了什么(超详细版)](https://4ark.me/post/b6c7c0a2.html) [当···时发生了什么?](https://github.com/skyline75489/what-happens-when-zh_CN#id10) [Opcode是啥以及如何使用好Opcache](https://www.zybuluo.com/phper/note/1016714) [mysql内核源代码深度解析 缓冲池 buffer pool 整体概述](https://blog.csdn.net/cjcl99/article/details/51063078) [CPU缓存一致性协议MESI](https://www.cnblogs.com/yanlong300/p/8986041.html) [程序优化:CPU缓存基础知识](https://zhuanlan.zhihu.com/p/80672073) [性能优化之缓存篇](https://www.cnblogs.com/yaomaomao/p/12195046.html) [缓存技术原理浅析](https://www.sohu.com/a/272322730_505779)
粤ICP备19051469号-1
Copyright©dingusxp.com - All Rights Reserved
Template by
OS Templates