LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

[点晴永久免费OA]工作中最常用的6种缓存

zhenglin
2026年1月4日 15:50 本文热度 498

前言

这些年我参与设计过很多系统,越来越深刻地认识到:一个系统的性能如何,很大程度上取决于缓存用得怎么样

同样是缓存,为何有人用起来系统飞升,有人却踩坑不断?


有些小伙伴在工作中可能遇到过这样的困惑:知道要用缓存,但面对本地缓存、Redis、Memcached、CDN等多种选择,到底该用哪种?

今天这篇文章跟大家一起聊聊工作中最常用的6种缓存,希望对你会有所帮助。

01 为什么缓存如此重要?

在正式介绍各种缓存之前,我们先要明白:为什么要用缓存?

想象这样一个场景:你的电商网站首页,每次打开都要从数据库中查询轮播图、热门商品、分类信息等数据。

如果每秒有1万个用户访问,数据库就要承受1万次查询压力。

// 没有缓存时的查询

public Product getProductById(Long id) {

    // 每次都直接查询数据库

    return productDao.findById(id); // 每次都是慢速的磁盘IO

}

这就是典型的无缓存场景

数据库的磁盘IO速度远低于内存,当并发量上来后,系统响应变慢,数据库连接池被占满,最终导致服务不可用。

缓存的核心价值可以用下面这个公式理解:

系统性能 = (缓存命中率 × 缓存访问速度) + ((1 - 缓存命中率) × 后端访问速度)



缓存之所以能提升性能,基于两个计算机科学的基本原理:

  1. 局部性原理:程序访问的数据通常具有时间和空间局部性

  2. 存储层次结构:不同存储介质的速度差异巨大(内存比SSD快100倍,比HDD快10万倍)


理解了缓存的重要性,接下来我们逐一剖析这六种最常用的缓存技术。


02 本地缓存:最简单直接的性能提升

本地缓存指的是在应用进程内部维护的缓存存储,数据存储在JVM堆内存中。

核心特点

  • 访问最快:直接内存操作,无网络开销

  • 实现简单:无需搭建额外服务

  • 数据隔离:每个应用实例独享自己的缓存

常用实现

1. Guava Cache:Google提供的优秀本地缓存库

// Guava Cache 示例

LoadingCache<Long, Product> productCache = CacheBuilder.newBuilder()

    .maximumSize(10000) // 最大缓存项数

    .expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期

    .expireAfterAccess(5, TimeUnit.MINUTES) // 访问后5分钟过期

    .recordStats() // 开启统计

    .build(new CacheLoader<Long, Product>() {

        @Override

        public Product load(Long productId) {

            // 当缓存未命中时,自动加载数据

            return productDao.findById(productId);

        }

    });


// 使用缓存

public Product getProduct(Long id) {

    try {

        return productCache.get(id);

    } catch (ExecutionException e) {

        throw new RuntimeException("加载产品失败", e);

    }

}


2. Caffeine
:Guava Cache的现代替代品,性能更优

// Caffeine 示例(性能优于Guava Cache)

Cache<Long, Product> caffeineCache = Caffeine.newBuilder()

    .maximumSize(10_000)

    .expireAfterWrite(10, TimeUnit.MINUTES)

    .refreshAfterWrite(1, TimeUnit.MINUTES) // 支持刷新,Guava不支持

    .recordStats()

    .build(productId -> productDao.findById(productId));


// 异步获取

public CompletableFuture<Product> getProductAsync(Long id) {

    return caffeineCache.get(id, productId -> 

        CompletableFuture.supplyAsync(() -> productDao.findById(productId)));

}

适用场景

  • 数据量不大(通常不超过10万条)

  • 数据变化不频繁

  • 对访问速度要求极致

  • 如:配置信息、静态字典、用户会话信息(短期)

优缺点分析

  • 优点:极速访问、零网络开销、实现简单

  • 缺点:数据不一致(各节点独立)、内存限制、重启丢失

有些小伙伴在工作中可能会犯一个错误:在分布式系统中过度依赖本地缓存,导致各节点数据不一致。记住:本地缓存适合存储只读或弱一致性的数据


03 分布式缓存之王:Redis的深度解析

当数据需要在多个应用实例间共享时,本地缓存就不够用了,这时需要分布式缓存。而Redis无疑是这一领域的王者。

Redis的核心优势

代码高亮:

// Spring Boot + Redis 示例

@Component

public class ProductCacheService {

    @Autowired

    private RedisTemplate<String, Object> redisTemplate;

    

    private static final String PRODUCT_KEY_PREFIX = "product:";

    private static final Duration CACHE_TTL = Duration.ofMinutes(30);

    

    // 缓存查询

    public Product getProduct(Long id) {

        String key = PRODUCT_KEY_PREFIX + id;

        

        // 1. 先查缓存

        Product product = (Product) redisTemplate.opsForValue().get(key);

        

        if (product != null) {

            return product;

        }

        

        // 2. 缓存未命中,查数据库

        product = productDao.findById(id);

        if (product != null) {

            // 3. 写入缓存

            redisTemplate.opsForValue().set(key, product, CACHE_TTL);

        }

        

        return product;

    }

    

    // 使用更高效的方式:缓存空值防止缓存穿透

    public Product getProductWithNullCache(Long id) {

        String key = PRODUCT_KEY_PREFIX + id;

        String nullKey = PRODUCT_KEY_PREFIX + "null:" + id;

        

        // 检查是否是空值(防缓存穿透)

        if (Boolean.TRUE.equals(redisTemplate.hasKey(nullKey))) {

            return null;

        }

        

        Product product = (Product) redisTemplate.opsForValue().get(key);

        if (product != null) {

            return product;

        }

        

        product = productDao.findById(id);

        if (product == null) {

            // 缓存空值,短时间过期

            redisTemplate.opsForValue().set(nullKey, "", Duration.ofMinutes(5));

            return null;

        }

        

        redisTemplate.opsForValue().set(key, product, CACHE_TTL);

        return product;

    }

}


Redis的丰富数据结构

Redis不只是简单的Key-Value存储,它的多种数据结构适应不同场景:

数据结构适用场景示例
String缓存对象、计数器SET user:1 '{"name":"张三"}'
Hash存储对象属性HSET product:1001 name "手机" price 2999
List消息队列、最新列表LPUSH news:latest "新闻标题"
Set标签、共同好友SADD user:100:tags "数码" "科技"
Sorted Set排行榜、延迟队列ZADD leaderboard 95 "玩家A"
Bitmap用户签到、活跃统计SETBIT sign:2023:10 1 1


集群模式选择

适用场景

  • 会话存储(分布式Session)


  • 排行榜、计数器


  • 消息队列


  • 分布式锁


  • 热点数据缓存


有些小伙伴在工作中使用Redis时,只把它当简单的Key-Value用,这就像用瑞士军刀只开瓶盖一样浪费。

深入理解Redis的数据结构,能让你的系统设计更优雅高效


04 Memcached:简单高效的分布式缓存

在Redis崛起之前,Memcached是分布式缓存的首选。

虽然现在Redis更流行,但Memcached在某些场景下仍有其价值。

Memcached vs Redis 核心区别


// Memcached 客户端示例(使用XMemcached)

public class MemcachedService {

    private MemcachedClient memcachedClient;

    

    public void init() throws IOException {

        // 创建客户端

        memcachedClient = new XMemcachedClientBuilder(

            AddrUtil.getAddresses("server1:11211 server2:11211"))

            .build();

    }

    

    public Product getProduct(Long id) throws Exception {

        String key = "product_" + id;

        

        // 从Memcached获取

        Product product = memcachedClient.get(key);

        if (product != null) {

            return product;

        }

        

        // 缓存未命中

        product = productDao.findById(id);

        if (product != null) {

            // 存储到Memcached,过期时间30分钟

            memcachedClient.set(key, 30 * 60, product);

        }

        

        return product;

    }

}

 

两者的核心差异对比

特性RedisMemcached
数据结构丰富(String、Hash、List等)简单(Key-Value)
持久化支持(RDB/AOF)不支持
线程模型单线程多线程
内存管理多种策略,可持久化纯内存,重启丢失
使用场景缓存+多样化数据结构纯缓存


何时选择Memcached?

  1. 纯缓存场景:只需要简单的Key-Value缓存

  2. 超大Value存储:Memcached对超大Value支持更好

  3. 多线程高并发:Memcached的多线程模型在极端并发下可能表现更好


05 CDN缓存:加速静态资源的利器

有些小伙伴可能会疑惑:CDN也算缓存吗?当然算,而且是地理位置最近的缓存。

CDN的工作原理

CDN(Content Delivery Network)通过在各地部署边缘节点,将静态资源缓存到离用户最近的节点。

// 在应用中生成CDN链接

public class CDNService {

    private String cdnDomain = "https://cdn.yourcompany.com";

    

    public String getCDNUrl(String relativePath) {

        // 添加版本号或时间戳,防止缓存旧版本

        String version = getFileVersion(relativePath);

        return String.format("%s/%s?v=%s", cdnDomain, relativePath, version);

    }

    

    // 上传文件到CDN的示例(伪代码)

    public void uploadToCDN(File file, String remotePath) {

        // 1. 上传到源站

        uploadToOrigin(file, remotePath);

        

        // 2. 触发CDN预热(将文件主动推送到边缘节点)

        preheatCDN(remotePath);

        

        // 3. 刷新旧缓存(如果需要)

        refreshCDNCache(remotePath);

    }

}


CDN缓存策略配置

# Nginx中的CDN缓存配置示例

location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {

    expires 365d;  # 缓存一年

    add_header Cache-Control "public, immutable";

    

    # 添加版本号作为查询参数

    if ($query_string ~* "^v=\d+") {

        expires max;

    }

}


适用场景

  • 静态资源:图片、CSS、JS文件

  • 软件下载包

  • 视频流媒体

  • 全球访问的网站


06 浏览器缓存:最前端的性能优化

浏览器缓存是最容易被忽视但效果最直接的缓存层级。合理利用浏览器缓存,可以大幅减少服务器压力。

HTTP缓存头详解

代码高亮:

// Spring Boot中设置HTTP缓存头

@RestController

public class ResourceController {

    

    @GetMapping("/static/{filename}")

    public ResponseEntity<Resource> getStaticFile(@PathVariable String filename) {

        Resource resource = loadResource(filename);

        

        return ResponseEntity.ok()

            .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)) // 缓存7天

            .eTag(computeETag(resource)) // ETag用于协商缓存

            .lastModified(resource.lastModified()) // 最后修改时间

            .body(resource);

    }

    

    @GetMapping("/dynamic/data")

    public ResponseEntity<Object> getDynamicData() {

        Object data = getData();

        

        // 动态数据设置较短缓存

        return ResponseEntity.ok()

            .cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS)) // 30秒

            .body(data);

    }

}

最佳实践

  1. 静态资源:设置长时间缓存(如一年),通过文件名哈希处理更新

  2. 动态数据:根据业务需求设置合理缓存时间

  3. API响应:适当使用ETag和Last-Modified


07 数据库缓存:容易被忽略的内部优化

数据库自身也有缓存机制,理解这些机制能帮助我们写出更高效的SQL。

MySQL查询缓存(已废弃但值得了解)


-- 查看查询缓存状态(MySQL 5.7及之前)

SHOW VARIABLES LIKE 'query_cache%';


-- 在8.0之前,可以通过以下方式利用查询缓存

SELECT SQL_CACHE * FROM products WHERE category_id = 10;

InnoDB缓冲池(Buffer Pool)

这是MySQL性能的关键,缓存的是数据页和索引页


-- 查看缓冲池状态

SHOW ENGINE INNODB STATUS;


-- 重要的监控指标

-- 缓冲池命中率 = (1 - (innodb_buffer_pool_reads / innodb_buffer_pool_read_requests)) * 100%

-- 命中率应尽可能接近100%

数据库级缓存最佳实践

  1. 合理设置缓冲池大小:通常是系统内存的50%-70%

  2. 优化查询:避免全表扫描,合理使用索引

  3. 预热缓存:重启后主动加载热点数据

  4. 监控命中率:持续优化


有些小伙伴可能会过度依赖应用层缓存,而忽略了数据库自身的缓存优化。

数据库缓存是最后一道防线,优化好它能让整个系统更健壮


08 综合对比与选型指南

 

实战中的多级缓存架构

在实际的高并发系统中,我们往往会采用多级缓存策略

// 多级缓存示例:本地缓存 + Redis

@Component

public class MultiLevelCacheService {

    @Autowired

    private RedisTemplate<String, Object> redisTemplate;

    

    // 一级缓存:本地缓存

    private Cache<Long, Product> localCache = Caffeine.newBuilder()

        .maximumSize(1000)

        .expireAfterWrite(30, TimeUnit.SECONDS) // 本地缓存时间短

        .build();

    

    // 二级缓存:Redis

    private static final Duration REDIS_TTL = Duration.ofMinutes(10);

    

    public Product getProductWithMultiCache(Long id) {

        // 1. 查本地缓存

        Product product = localCache.getIfPresent(id);

        if (product != null) {

            return product;

        }

        

        // 2. 查Redis

        String redisKey = "product:" + id;

        product = (Product) redisTemplate.opsForValue().get(redisKey);

        if (product != null) {

            // 回填本地缓存

            localCache.put(id, product);

            return product;

        }

        

        // 3. 查数据库

        product = productDao.findById(id);

        if (product != null) {

            // 写入Redis

            redisTemplate.opsForValue().set(redisKey, product, REDIS_TTL);

            // 写入本地缓存

            localCache.put(id, product);

        }

        

        return product;

    }

}


09 缓存常见问题与解决方案

在使用缓存的过程中,我们不可避免地会遇到一些问题:

1. 缓存穿透

问题:大量请求查询不存在的数据,绕过缓存直接击穿数据库。

解决方案

代码高亮:

// 缓存空值方案

public Product getProductSafe(Long id) {

    String key = "product:" + id;

    String nullKey = "product:null:" + id;

    

    // 检查空值标记

    if (redisTemplate.hasKey(nullKey)) {

        return null;

    }

    

    Product product = (Product) redisTemplate.opsForValue().get(key);

    if (product != null) {

        return product;

    }

    

    product = productDao.findById(id);

    if (product == null) {

        // 缓存空值,短时间过期

        redisTemplate.opsForValue().set(nullKey, "", Duration.ofMinutes(5));

        return null;

    }

    

    redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30));

    return product;

}


2. 缓存雪崩

问题:大量缓存同时过期,请求全部打到数据库。

解决方案

// 差异化过期时间

private Duration getRandomTTL() {

    // 基础30分钟 + 随机0-10分钟

    long baseMinutes = 30;

    long randomMinutes = ThreadLocalRandom.current().nextLong(0, 10);

    return Duration.ofMinutes(baseMinutes + randomMinutes);

}


3. 缓存击穿

问题:热点Key过期瞬间,大量并发请求同时查询数据库。

解决方案

// 使用互斥锁(分布式锁)

public Product getProductWithLock(Long id) {

    String key = "product:" + id;

    Product product = (Product) redisTemplate.opsForValue().get(key);

    

    if (product == null) {

        // 尝试获取分布式锁

        String lockKey = "lock:product:" + id;

        boolean locked = redisTemplate.opsForValue()

            .setIfAbsent(lockKey, "1", Duration.ofSeconds(10));

        

        if (locked) {

            try {

                // 双重检查

                product = (Product) redisTemplate.opsForValue().get(key);

                if (product == null) {

                    product = productDao.findById(id);

                    if (product != null) {

                        redisTemplate.opsForValue()

                            .set(key, product, Duration.ofMinutes(30));

                    }

                }

            } finally {

                // 释放锁

                redisTemplate.delete(lockKey);

            }

        } else {

            // 未获取到锁,等待后重试

            try { Thread.sleep(50); } 

            catch (InterruptedException e) { Thread.currentThread().interrupt(); }

            return getProductWithLock(id); // 递归重试

        }

    }

    

    return product;

}


10 总结

通过这篇文章,我们系统地探讨了工作中最常用的六种缓存技术。

每种缓存都有其独特的价值和应用场景:

  1. 本地缓存:适合进程内、变化不频繁的只读数据

  2. Redis:功能丰富的分布式缓存,适合大多数共享缓存场景

  3. Memcached:简单高效的分布式缓存,适合纯Key-Value场景

  4. CDN缓存:加速静态资源,提升全球访问速度

  5. 浏览器缓存:最前端的优化,减少不必要的网络请求

  6. 数据库缓存:最后一道防线,优化数据库访问性能


缓存使用的核心原则可以总结为以下几点

  • 分级缓存:合理利用多级缓存架构

  • 合适粒度:根据业务特点选择缓存粒度

  • 及时更新:设计合理的缓存更新策略

  • 监控告警:建立完善的缓存监控体系


有些小伙伴在工作中使用缓存时,容易陷入两个极端:要么过度设计,所有数据都加缓存;要么忽视缓存,让数据库承受所有压力。

我们需要懂得在合适的地方使用合适的缓存,在性能和复杂性之间找到最佳平衡点


记住,缓存不是银弹,而是工具箱中的一件利器。


参考文章:原文链接


该文章在 2026/1/4 15:50:09 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved