<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Cache on IT 运维小秋</title>
        <link>/tags/cache.html</link>
        <description>Recent content in Cache on IT 运维小秋</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <managingEditor>chenwx716@139.com</managingEditor>
        <webMaster>chenwx716@139.com</webMaster>
        <lastBuildDate>Tue, 13 Dec 2022 00:00:00 +0800</lastBuildDate><atom:link href="/tags/cache/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>缓存设计</title>
        <link>/p/cache.html</link>
        <pubDate>Tue, 13 Dec 2022 00:00:00 +0800</pubDate>
        <author>chenwx716@139.com</author>
        <guid>/p/cache.html</guid>
        <description>&lt;p&gt;&lt;strong&gt;目的:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;加速访问&lt;/li&gt;
&lt;li&gt;减轻后端服务的压力&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;原理:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;离用户更近的位置存储&lt;/li&gt;
&lt;li&gt;离应用更近的位置存储&lt;/li&gt;
&lt;li&gt;更高速的存储介质存储&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要缓存的内容&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;热点数据&lt;/li&gt;
&lt;li&gt;静态资源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;过期策略&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;固定时间：比如指定缓存的时间是30分钟；&lt;/li&gt;
&lt;li&gt;相对时间：比如最近10分钟内没有访问的数据；&lt;/li&gt;
&lt;li&gt;事件通知: 缓存管理线程按事件通知进行删除&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存淘汰算法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FIFO (First In First Out) 先进先出&lt;/li&gt;
&lt;li&gt;LRU (Least Recently Used) 最近最少使用&lt;/li&gt;
&lt;li&gt;LFU (Least Frequently Used) 在一段时间内使用频率最小的数据被移除缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;缓存媒介&#34;&gt;缓存媒介&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;常用中间件&lt;/strong&gt;: Varnish, Ngnix, Squid, Memcache, Redis, Ehcache 等;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缓存的内容&lt;/strong&gt;: 文件, 数据, 对象;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缓存的介质&lt;/strong&gt;: CPU, 内存(本地, 分布式), 磁盘(本地, 分布式)&lt;/p&gt;
&lt;h2 id=&#34;分级缓存&#34;&gt;分级缓存&lt;/h2&gt;
&lt;p&gt;用户侧&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端程序内&lt;/li&gt;
&lt;li&gt;浏览器 cookie, sessionStorage, localStorage, IndexDB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CDN缓存&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户就近访问, 主要原理还是依靠智能 DNS, 主要缓存 HTML, CSS, JS, 视频等静态资源;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应用服务缓存&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx 本地缓存: html/css/js/ico 等少量小文件&lt;/li&gt;
&lt;li&gt;nginx + Lua Shared Dict 对简单的接口数据缓存&lt;/li&gt;
&lt;li&gt;squid/Varnish: 大量静态资源, 如视频 / 图片&lt;/li&gt;
&lt;li&gt;redis 缓存动态内容, 减轻数据库压力&lt;/li&gt;
&lt;li&gt;本地缓存, 即同一个主机或pod内附加一个缓存服务&lt;/li&gt;
&lt;li&gt;应用内缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;存储侧缓存&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;例如 mysql 增大 buffer_pool&lt;/li&gt;
&lt;li&gt;SSD 和 SATA 区分冷热文件缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;常见问题&#34;&gt;常见问题&lt;/h2&gt;
&lt;h3 id=&#34;数据一致性问题&#34;&gt;数据一致性问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;双写&lt;/li&gt;
&lt;li&gt;异步刷新&lt;/li&gt;
&lt;li&gt;先缓存后DB/先DB后缓存&lt;/li&gt;
&lt;li&gt;独立的缓存管理进程负责更新缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要考虑一致性的地方&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cache - db&lt;/li&gt;
&lt;li&gt;cache - 多级缓存&lt;/li&gt;
&lt;li&gt;cache - 副本&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;cache和db的前后更新顺序&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;insert key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先 write cache 再 DB;&lt;/p&gt;
&lt;p&gt;假如缓存写成功, 但写数据库失败或响应延迟, 则下次读取(并发读)缓存时, 就出现脏读，所以这种方式不可取。&lt;/p&gt;
&lt;p&gt;先 write DB, 再 cache;&lt;/p&gt;
&lt;p&gt;假如写数据库成功, 但写缓存失败, 则下次读取(并发读)缓存时, 则读不到数据，读不到数据时, 再请求一次数据库, 并刷新缓存；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;update key&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先 del cache key, 再 update db, 再 add key&lt;/p&gt;
&lt;p&gt;如果更新库失败, 则后续读 key 时会先读缓存, 引发 cache miss, 再读取库, 更新缓存, 并不会造成数据不一致；&lt;/p&gt;
&lt;p&gt;如果是分布式环境, 则可能服务 A 在 del cache key 后, 未完成更新 db 时, 服务 B 读库, 并更新了 cache, 造成数据不一致。&lt;/p&gt;
&lt;p&gt;解决办法:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;锁, 对此 key 添加写锁, 如果更新库成功或失败后才释放&lt;/li&gt;
&lt;li&gt;串行队列执行关于此 key 的业务&lt;/li&gt;
&lt;li&gt;关于此 KEY 的业务定点到某一个 server 去串行执行;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;缓存异步刷新&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果数据库操作和写缓存不在一个操作步骤中, 比如在分布式场景下, 无法做到同时写缓存或需要异步刷新(补救措施)时候；&lt;/p&gt;
&lt;p&gt;解决办法: 看业务特性进行针对性解决。&lt;/p&gt;
&lt;h3 id=&#34;热点数据缓存&#34;&gt;热点数据缓存&lt;/h3&gt;
&lt;p&gt;即只能缓存少量的数据时, 将访问比例最大的部分数据进行缓存;&lt;/p&gt;
&lt;p&gt;如: 最近登录用户, 最新活动数据&lt;/p&gt;
&lt;p&gt;队列模型&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立一个定长队列，放置最近1000个用户的UID，最新用户放置在队列顶部；&lt;/li&gt;
&lt;li&gt;当请求发送过来时，判断UID是否在队列中，是则访问热点缓存&lt;br /&gt;
不在队列中，则读取库或者冷缓存，然后将UID放入队列，并将数据写如热缓存；&lt;/li&gt;
&lt;li&gt;定期移除队列的后200位UID，并且移除热缓存的key；&lt;br /&gt;
热点用户将一直存在于队列顶部区域，不会被移除&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;场景2: 某几个热点key被hash到同一个缓存节点, 节点失效或重启时影响极大;&lt;/p&gt;
&lt;p&gt;办法: 定期或开发阶段 对缓存key进行统计分析, 业务上再进行key拆分；&lt;/p&gt;
&lt;h3 id=&#34;缓存雪崩&#34;&gt;缓存雪崩&lt;/h3&gt;
&lt;p&gt;雪崩是指当大量缓存集中失效时(ttl到期或重启缓存服务)，大量的请求直接访问数据库的场景&lt;/p&gt;
&lt;p&gt;应对方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;合理规划缓存的失效时间, 离散过期时间分布；&lt;/li&gt;
&lt;li&gt;合理评估数据库的负载压力, 限制连接数&lt;/li&gt;
&lt;li&gt;应用层熔断和限流机制；&lt;/li&gt;
&lt;li&gt;多级缓存设计，考虑备用缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;缓存穿透&#34;&gt;缓存穿透&lt;/h3&gt;
&lt;p&gt;场景1: 某个key不存在, 大量请求频繁落到数据库上去查询并更新缓存;&lt;/p&gt;
&lt;p&gt;场景2: 大量请求一些不存在的资源, 如通过遍历商品编号获取属性的恶意请求;&lt;/p&gt;
&lt;p&gt;办法&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存预热, 提前写入缓存或推送到用户侧&lt;/li&gt;
&lt;li&gt;对空值的结果也进行缓存, ttl 时间稍短&lt;/li&gt;
&lt;li&gt;入口安全拦截措施阻挡恶意访问;&lt;/li&gt;
&lt;li&gt;只有获取到分布式锁的线程才能去数据库查询并更新缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;太多分片导致的多次网络io开销&#34;&gt;太多分片导致的多次网络IO开销&lt;/h3&gt;
&lt;p&gt;例如一笔业务需要3次缓存操作；如果分为3个缓存实例，则有可能与3个设备发生3次网络IO，增加了业务时延；&lt;br /&gt;
办法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;连接池，避免新建连接&lt;/li&gt;
&lt;li&gt;缓存分布方式优化，通过对key进行范围存储，避免hash的无序，来确保关联key存放于固定实例上&lt;/li&gt;
&lt;li&gt;合并数据，将多次小操作，合并为一次大操作来减少网络IO&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;持久化问题&#34;&gt;持久化问题&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;设计上要求缓存不需要持久化&lt;/li&gt;
&lt;li&gt;如果缓存失效对数据库压力太大，还是需要持久化&lt;/li&gt;
&lt;li&gt;缓存节点的复制，由异步改为双写&lt;/li&gt;
&lt;li&gt;多级缓存&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;反向代理缓存&#34;&gt;反向代理缓存&lt;/h2&gt;
&lt;p&gt;一般指在网站服务器机房部署代理服务器, 实现负载均衡, 数据缓存, 安全控制等功能&lt;/p&gt;
&lt;p&gt;常用的代理缓存有 Varnish, Squid, Ngnix, nginx+lua 简单比较如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;varnish 和 squid 是专业的 cache 服务, 多用于资源站进行对象缓存;&lt;/li&gt;
&lt;li&gt;简易的静态资源缓存推荐使用nginx&lt;/li&gt;
&lt;li&gt;少量简易的动态内容缓存可以使用nginx+lua或类似网关服务&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;分布式缓存&#34;&gt;分布式缓存&lt;/h2&gt;
&lt;p&gt;主要指缓存用户经常访问数据的缓存，数据源为数据库&lt;/p&gt;
&lt;p&gt;一般起到热点数据访问和减轻数据库压力的作用&lt;/p&gt;
&lt;p&gt;主要使用 Memcache and Redis&lt;/p&gt;
&lt;p&gt;常用分片算法&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;哈希算法，哈希后取模&lt;/li&gt;
&lt;li&gt;一致性哈希算法: 一致性 Hash 是将数据按照特征值映射到一个首尾相接的 Hash 环上，同时也将缓存节点映射到这个环上&lt;/li&gt;
&lt;li&gt;Range Based 算法: 例如根据key前缀分片&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;进程内缓存&#34;&gt;进程内缓存&lt;/h2&gt;
&lt;p&gt;应用内部自身的缓存, 如应用字典等常用数据, 一般配合外部缓存或二级双缓存&lt;/p&gt;
&lt;p&gt;场景&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;频繁访问的配置项目&lt;/li&gt;
&lt;li&gt;高并发时的小规模的热点数据, 如果活动配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不需要序列化和反序列化，无网络开销, 速度最快&lt;/li&gt;
&lt;li&gt;总大小需要严格控制&lt;/li&gt;
&lt;li&gt;考虑数据一致性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;二级缓存面临的分布式一致性问题&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;消息队列修改方案&lt;br /&gt;
应用在修改完自身缓存数据和数据库数据之后，给消息队列发送数据变化通知，&lt;br /&gt;
其他应用订阅了消息通知，在收到通知的时候修改缓存数据。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Timer 修改方案&lt;br /&gt;
对&amp;quot;实时一致性&amp;quot;不敏感的情况下，每个应用都会启动一个 Timer，定时从数据库拉取最新的数据，更新缓存&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;cdn加速缓存&#34;&gt;CDN加速缓存&lt;/h2&gt;
&lt;p&gt;Content Delivery Network, 即内容分发网络&lt;/p&gt;
&lt;p&gt;CDN 主要解决将数据缓存到离用户最近的位置, 一般缓存静态资源文件(页面, 脚本, 图片, 视频, 文件等);&lt;/p&gt;
&lt;p&gt;工作路径:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端发起请求, 先进行 DNS 解析, cname 到 cdn 的 dns 负载均衡服务器;&lt;/li&gt;
&lt;li&gt;cdn 域名服务器将最近 CDN 节点的 IP 响应给用户&lt;/li&gt;
&lt;li&gt;客户端请求最近的 CDN 节点;&lt;/li&gt;
&lt;li&gt;CDN 节点如果有缓存则响应缓存, 无缓存则回源获取数据后再响应。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;动态接口加速&lt;/strong&gt;&lt;br /&gt;
cdn除了做静态资源缓存，另一个主要作用是对动态接口的加速。&lt;/p&gt;
&lt;p&gt;因为用户直接到源服务器的网络质量，不一定有 用户-cdn节点-源服务器 的网络质量好;&lt;/p&gt;
&lt;p&gt;所以cdn的接口加速一般也会有较好的效果;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其它用途&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;减少源站带宽资源&lt;/li&gt;
&lt;li&gt;抗攻击, 抵御 ddos 攻击&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;缓存一致性问题&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态资源文件名采用hash命名方式&lt;/li&gt;
&lt;li&gt;合理配置过期时间&lt;/li&gt;
&lt;li&gt;调用厂商缓存刷新接口释放缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&#34;缓存架构设计要点&#34;&gt;缓存架构设计要点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;容量规划&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存内容的大小：总容量大小，主要key的大小，最大key的大小，单实例大小，主机内存大小，实例数；&lt;/li&gt;
&lt;li&gt;缓存内容的数量&lt;/li&gt;
&lt;li&gt;淘汰策略&lt;/li&gt;
&lt;li&gt;缓存的数据结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;性能优化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每秒的读峰值&lt;/li&gt;
&lt;li&gt;每秒的写峰值&lt;/li&gt;
&lt;li&gt;线程模型&lt;/li&gt;
&lt;li&gt;预热方法&lt;/li&gt;
&lt;li&gt;缓存分片&lt;/li&gt;
&lt;li&gt;冷热数据的比例&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;高可用&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复制模型&lt;/li&gt;
&lt;li&gt;失效转移&lt;/li&gt;
&lt;li&gt;持久策略&lt;/li&gt;
&lt;li&gt;缓存重建: 迁移或清空后如何快速恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缓存监控&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;容量&lt;/li&gt;
&lt;li&gt;可用性&lt;/li&gt;
&lt;li&gt;响应时间&lt;/li&gt;
&lt;li&gt;大对象, 慢查询&lt;/li&gt;
&lt;li&gt;命中率&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是否有可能发生缓存穿透&lt;/li&gt;
&lt;li&gt;是否有大对象&lt;/li&gt;
&lt;li&gt;是否使用缓存实现分布式锁&lt;/li&gt;
&lt;li&gt;是否使用缓存支持的脚本（Lua）&lt;/li&gt;
&lt;li&gt;是否避免了 Race Condition(多线程竞争)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;业务设计&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不同的业务单元使用隔离的缓存实例，避免干扰&lt;/li&gt;
&lt;li&gt;缓存超时时间：较长的超时时间会占用过多线程池连接数&lt;/li&gt;
&lt;li&gt;key应该设置失效时间，避免内存不足&lt;/li&gt;
&lt;li&gt;失效时间不能集中，可加随机值，避免缓存雪崩&lt;/li&gt;
&lt;li&gt;低频访问的内容不需要放在缓存中&lt;/li&gt;
&lt;li&gt;单个key不宜过大，特别是redis这种单线程实例，会产生阻塞&lt;/li&gt;
&lt;li&gt;redis危险命令: keys HGETALL 容易造成请求阻塞&lt;/li&gt;
&lt;li&gt;有大量的更新数据时，尤其是批量处理时，可以使用批量模式加速&lt;/li&gt;
&lt;li&gt;在通常情况下，读的顺序是先缓存，后数据库；写的顺序是先数据库，后缓存&lt;/li&gt;
&lt;li&gt;要考虑到如果后期缓存迁移时的业务连续性&lt;/li&gt;
&lt;li&gt;所有集合型的数据结构(哈希、列表等)，则都要考虑为它设置最大限制，避免内存用光;&lt;/li&gt;
&lt;li&gt;考虑缓存降级，如主备切换期间，业务侧的缓存连接异常时，如何连接到备，或数据库&lt;/li&gt;
&lt;li&gt;key使用不同的前缀进行逻辑隔离，避免不同业务的干扰&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
