> 技术文档 > Web 架构之缓存策略实战:从本地缓存到分布式缓存

Web 架构之缓存策略实战:从本地缓存到分布式缓存


文章目录

    • 一、思维导图
    • 二、正文内容
      • (一)本地缓存
        • 1. 简介
        • 2. 常见实现
        • 3. 使用场景
        • 4. 优缺点
      • (二)分布式缓存
        • 1. 简介
        • 2. 常见实现
        • 3. 使用场景
        • 4. 优缺点
        • 5. 缓存问题及解决方案
    • 三、总结

一、思维导图

#mermaid-svg-aKn15g3HsVhtgM2P {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .error-icon{fill:#552222;}#mermaid-svg-aKn15g3HsVhtgM2P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aKn15g3HsVhtgM2P .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-aKn15g3HsVhtgM2P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aKn15g3HsVhtgM2P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aKn15g3HsVhtgM2P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aKn15g3HsVhtgM2P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aKn15g3HsVhtgM2P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aKn15g3HsVhtgM2P .marker.cross{stroke:#333333;}#mermaid-svg-aKn15g3HsVhtgM2P svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aKn15g3HsVhtgM2P .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .cluster-label text{fill:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .cluster-label span{color:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .label text,#mermaid-svg-aKn15g3HsVhtgM2P span{fill:#333;color:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .node rect,#mermaid-svg-aKn15g3HsVhtgM2P .node circle,#mermaid-svg-aKn15g3HsVhtgM2P .node ellipse,#mermaid-svg-aKn15g3HsVhtgM2P .node polygon,#mermaid-svg-aKn15g3HsVhtgM2P .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aKn15g3HsVhtgM2P .node .label{text-align:center;}#mermaid-svg-aKn15g3HsVhtgM2P .node.clickable{cursor:pointer;}#mermaid-svg-aKn15g3HsVhtgM2P .arrowheadPath{fill:#333333;}#mermaid-svg-aKn15g3HsVhtgM2P .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aKn15g3HsVhtgM2P .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aKn15g3HsVhtgM2P .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-aKn15g3HsVhtgM2P .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-aKn15g3HsVhtgM2P .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aKn15g3HsVhtgM2P .cluster text{fill:#333;}#mermaid-svg-aKn15g3HsVhtgM2P .cluster span{color:#333;}#mermaid-svg-aKn15g3HsVhtgM2P div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aKn15g3HsVhtgM2P :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}#mermaid-svg-aKn15g3HsVhtgM2P .startend>*{fill:#F5EBFF!important;stroke:#BE8FED!important;stroke-width:2px!important;}#mermaid-svg-aKn15g3HsVhtgM2P .startend span{fill:#F5EBFF!important;stroke:#BE8FED!important;stroke-width:2px!important;}#mermaid-svg-aKn15g3HsVhtgM2P .process>*{fill:#E5F6FF!important;stroke:#73A6FF!important;stroke-width:2px!important;}#mermaid-svg-aKn15g3HsVhtgM2P .process span{fill:#E5F6FF!important;stroke:#73A6FF!important;stroke-width:2px!important;} 缓存策略实战 本地缓存 分布式缓存 简介 常见实现 使用场景 优缺点 Guava Cache Caffeine 简介 常见实现 使用场景 优缺点 Redis Memcached 缓存穿透 缓存击穿 缓存雪崩 解决方案 解决方案 解决方案

二、正文内容

(一)本地缓存

1. 简介

本地缓存是指在应用程序的进程内部进行缓存,数据存储在应用程序所在的服务器内存中。它的访问速度非常快,因为不需要进行网络通信。

2. 常见实现
  • Guava Cache
    • Guava 是 Google 开源的 Java 库,其中的 Guava Cache 是一个非常方便的本地缓存实现。它支持多种缓存回收策略,如基于大小、时间等。
    • 示例代码:
import com.google.common.cache.Cache;import com.google.common.cache.CacheBuilder;import java.util.concurrent.TimeUnit;public class GuavaCacheExample { public static void main(String[] args) { Cache<String, String> cache = CacheBuilder.newBuilder()  .maximumSize(100)  .expireAfterWrite(10, TimeUnit.MINUTES)  .build(); cache.put(\"key1\", \"value1\"); String value = cache.getIfPresent(\"key1\"); System.out.println(value); }}
  • Caffeine
    • Caffeine 是一个基于 Java 8 的高性能本地缓存库,它在性能上比 Guava Cache 更优秀。Caffeine 采用了多种优化策略,如 W-TinyLFU 算法来管理缓存。
    • 示例代码:
import com.github.benmanes.caffeine.cache.Cache;import com.github.benmanes.caffeine.cache.Caffeine;import java.util.concurrent.TimeUnit;public class CaffeineCacheExample { public static void main(String[] args) { Cache<String, String> cache = Caffeine.newBuilder()  .maximumSize(100)  .expireAfterWrite(10, TimeUnit.MINUTES)  .build(); cache.put(\"key1\", \"value1\"); String value = cache.getIfPresent(\"key1\"); System.out.println(value); }}
3. 使用场景
  • 数据量较小且访问频繁的场景,如配置信息、常用数据字典等。
  • 对响应时间要求极高的场景,因为本地缓存的访问速度极快。
4. 优缺点
  • 优点
    • 访问速度快,无需网络开销。
    • 实现简单,不需要额外的服务器。
  • 缺点
    • 缓存数据无法在多个应用实例之间共享。
    • 受应用程序所在服务器内存限制,缓存容量有限。

(二)分布式缓存

1. 简介

分布式缓存是将缓存数据存储在多个服务器节点上,通过网络进行访问。它可以解决本地缓存无法共享数据和容量有限的问题。

2. 常见实现
  • Redis
    • Redis 是一个开源的高性能键值对存储数据库,支持多种数据结构,如字符串、哈希、列表、集合等。它不仅可以作为缓存使用,还可以用于消息队列、分布式锁等场景。
    • 示例代码(使用 Jedis 客户端):
import redis.clients.jedis.Jedis;public class RedisExample { public static void main(String[] args) { Jedis jedis = new Jedis(\"localhost\", 6379); jedis.set(\"key1\", \"value1\"); String value = jedis.get(\"key1\"); System.out.println(value); jedis.close(); }}
  • Memcached
    • Memcached 是一个高性能的分布式内存对象缓存系统,主要用于减轻数据库负载。它只支持简单的键值对存储。
    • 示例代码(使用 Spymemcached 客户端):
import net.spy.memcached.MemcachedClient;import java.io.IOException;import java.net.InetSocketAddress;import java.util.concurrent.Future;public class MemcachedExample { public static void main(String[] args) throws IOException { MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress(\"localhost\", 11211)); Future<Boolean> future = memcachedClient.set(\"key1\", 3600, \"value1\"); Object value = memcachedClient.get(\"key1\"); System.out.println(value); memcachedClient.shutdown(); }}
3. 使用场景
  • 数据需要在多个应用实例之间共享的场景,如用户会话信息、商品库存信息等。
  • 数据量较大,本地缓存无法满足需求的场景。
4. 优缺点
  • 优点
    • 可以在多个应用实例之间共享缓存数据。
    • 缓存容量可以通过增加服务器节点进行扩展。
  • 缺点
    • 存在网络开销,访问速度相对本地缓存较慢。
    • 系统复杂度较高,需要维护缓存服务器集群。
5. 缓存问题及解决方案
  • 缓存穿透
    • 问题描述:指查询一个一定不存在的数据,由于缓存中没有,每次都会去数据库查询,导致数据库压力过大。
    • 解决方案
      • 布隆过滤器:在缓存之前加一层布隆过滤器,判断数据是否存在。
      • 缓存空值:对于查询结果为空的数据,也将其缓存起来,并设置较短的过期时间。
  • 缓存击穿
    • 问题描述:指一个非常热点的 key,在缓存过期的瞬间,大量请求同时访问该 key,导致这些请求全部落到数据库上。
    • 解决方案
      • 互斥锁:在缓存失效时,通过加锁的方式,只允许一个线程去查询数据库并更新缓存。
      • 永不过期:对于热点 key,设置为永不过期,在更新数据时同时更新缓存。
  • 缓存雪崩
    • 问题描述:指大量的缓存 key 在同一时间过期,导致大量请求同时落到数据库上,造成数据库压力过大甚至崩溃。
    • 解决方案
      • 随机过期时间:为缓存 key 设置随机的过期时间,避免大量 key 同时过期。
      • 缓存预热:在系统启动时,将热点数据提前加载到缓存中。
      • 多级缓存:使用本地缓存和分布式缓存结合的方式,减轻数据库压力。

三、总结

本地缓存和分布式缓存各有优缺点,在实际应用中需要根据具体的业务场景选择合适的缓存策略。本地缓存适用于数据量小、访问频繁且对响应时间要求极高的场景;分布式缓存适用于数据需要共享、数据量较大的场景。同时,在使用分布式缓存时,需要注意缓存穿透、缓存击穿和缓存雪崩等问题,并采取相应的解决方案。通过合理使用缓存策略,可以显著提高 Web 应用的性能和稳定性。

哈雷摩托