提升接口性能之缓存

缓存策略: 从本地到分布式, 再到 HTTP 缓存的全面解析

引言

在当今的软件开发中, 缓存技术扮演着至关重要的角色. 它能够显著提升系统性能, 降低数据库负载, 提高用户体验. 本文将详细介绍三种常见的缓存策略: 本地缓存, 分布式缓存以及 HTTP 缓存, 并探讨如何在实际应用中合理运用它们.

一, 本地缓存

1.1 Caffeine

Caffeine 是一个基于 Java 8 的高性能本地缓存库. 它结合了多种优秀的缓存算法, 具有极高的性能和可扩展性.

1.1.1 引入依赖

在 Maven 项目中, 只需添加以下依赖:

xml 复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.3</version>
</dependency>
1.1.2 使用示例
java 复制代码
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

public class CaffeineExample {
    public static void main (String [] args) {
        Cache<String, String> cache = Caffeine.newBuilder ()
              .expireAfterWrite (10, TimeUnit.MINUTES)
              .maximumSize (100)
              .build ();

        cache.put ("key1", "value1");
        String value = cache.getIfPresent ("key1");
        System.out.println (value);
    }
}

在上述代码中, 我们设置了缓存项在写入 10 分钟后过期, 并设置了最大缓存容量为 100. 当缓存达到最大容量时, 会根据 LRU (最近最少使用) 策略淘汰旧的缓存项.

1.2 Guava Cache

Guava Cache 是 Google Guava 库中的一部分, 提供了功能强大的本地缓存实现.

1.2.1 引入依赖
xml 复制代码
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.1-jre</version>
</dependency>
1.2.2 使用示例
java 复制代码
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 ()
              .expireAfterWrite (5, TimeUnit.MINUTES)
              .maximumSize (50)
              .build ();

        cache.put ("key2", "value2");
        String value = cache.getIfPresent ("key2");
        System.out.println (value);
    }
}

这里同样设置了缓存项在写入 5 分钟后过期, 最大缓存容量为 50, 同样采用 LRU 淘汰策略.

二, 分布式缓存

2.1 Redis

Redis 是一个开源的, 基于内存的数据结构存储系统, 常用作分布式缓存. 它支持多种数据结构, 如字符串, 哈希表, 列表等, 并且具有高可用性和高性能.

2.1.1 缓存预热

在系统启动时, 可以通过编写脚本将热点数据提前加载到 Redis 中. 例如, 使用 Python 的 redis - py 库:

python 复制代码
import redis

r = redis.StrictRedis (host='localhost', port=6379, db = 0)

# 假设热点数据存储在一个列表中
hot_data = [('key3', 'value3'), ('key4', 'value4')]

for key, value in hot_data:
    r.set (key, value)
2.1.2 防雪崩策略

为了防止大量缓存同时过期导致数据库压力瞬间增大, 可以为每个缓存项设置随机的过期时间. 例如, 在 Java 中使用 Jedis 操作 Redis:

java 复制代码
import redis.clients.jedis.Jedis;
import java.util.Random;

public class RedisAntiAvalancheExample {
    public static void main (String [] args) {
        Jedis jedis = new Jedis ("localhost", 6379);
        Random random = new Random ();
        int baseExpireTime = 3600; // 基础过期时间 1 小时
        int maxRandomOffset = 600; // 最大随机偏移 10 分钟

        for (int i = 0; i < 10; i++) {
            String key = "key" + i;
            String value = "value" + i;
            int expireTime = baseExpireTime + random.nextInt (maxRandomOffset);
            jedis.setex (key, expireTime, value);
        }
        jedis.close ();
    }
}

2.2 Memcached

Memcached 是一个高性能的分布式内存对象缓存系统, 主要用于动态 Web 应用以减轻数据库负载. 它的使用方式与 Redis 类似, 但数据结构相对简单, 主要以键值对形式存储.

2.2.1 缓存预热

在 Python 中, 可以使用 pymemcache 库进行缓存预热:

python 复制代码
from pymemcache.client import base

client = base.Client (('localhost', 11211))

hot_data = [('key5', 'value5'), ('key6', 'value6')]

for key, value in hot_data:
    client.set (key, value)
2.2.2 防雪崩策略

在 Java 中使用 XMemcached 设置随机过期时间:

java 复制代码
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.utils.AddrUtil;
import java.util.Random;

public class MemcachedAntiAvalancheExample {
    public static void main (String [] args) throws Exception {
        MemcachedClientBuilder builder = new XMemcachedClientBuilder (AddrUtil.getAddresses ("localhost:11211"));
        MemcachedClient client = builder.build ();
        Random random = new Random ();
        int baseExpireTime = 3600;
        int maxRandomOffset = 600;

        for (int i = 0; i < 10; i++) {
            String key = "key" + i;
            String value = "value" + i;
            int expireTime = baseExpireTime + random.nextInt (maxRandomOffset);
            client.set (key, expireTime, value);
        }
        client.shutdown ();
    }
}

三, HTTP 缓存

3.1 Cache - Control

Cache - Control 是 HTTP/1.1 中用于控制缓存的通用首部字段. 通过设置不同的指令, 可以精确控制缓存行为.

3.1.1 示例

在 Java 的 Spring Boot 项目中, 可以通过如下方式设置 Cache - Control 头:

java 复制代码
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class HttpCacheExample {
    @GetMapping ("/staticResource")
    public ResponseEntity<String> getStaticResource () {
        CacheControl cacheControl = CacheControl.maxAge (3600, TimeUnit.SECONDS).cachePublic ();
        return ResponseEntity.ok ()
              .cacheControl (cacheControl)
              .body ("This is a static resource");
    }
}

上述代码设置了缓存有效期为 1 小时, 并且允许公共缓存 (如 CDN) 缓存该资源.

3.2 ETag

ETag (实体标签) 是一种用于缓存验证的机制. 服务器为每个资源生成一个唯一的标识符, 客户端在后续请求中携带该标识符, 服务器通过比较标识符来判断资源是否发生变化.

3.2.1 示例

在 Java 的 Servlet 中, 可以如下生成和验证 ETag:

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@WebServlet ("/etagResource")
public class ETagServlet extends HttpServlet {
    @Override
    protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String resource = "This is a resource with ETag";
        String etag = generateETag (resource);

        String ifNoneMatch = request.getHeader ("If - None - Match");
        if (ifNoneMatch!= null && ifNoneMatch.equals (etag)) {
            response.setStatus (HttpServletResponse.SC_NOT_MODIFIED);
            return;
        }

        response.setHeader ("ETag", etag);
        PrintWriter out = response.getWriter ();
        out.println (resource);
    }

    private String generateETag (String resource) {
        try {
            MessageDigest digest = MessageDigest.getInstance ("MD5");
            byte [] hash = digest.digest (resource.getBytes ());
            StringBuilder hexString = new StringBuilder ();
            for (byte b : hash) {
                hexString.append (String.format ("%02x", b));
            }
            return hexString.toString ();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException (e);
        }
    }
}

四, 总结

本地缓存适用于减少单个应用内的重复计算和数据库查询, Caffeine 和 Guava Cache 提供了便捷的实现方式. 分布式缓存如 Redis 和 Memcached 则用于缓存全局共享数据, 通过缓存预热和防雪崩策略能有效降低数据库压力. HTTP 缓存则通过控制客户端和 CDN 对静态资源的缓存, 进一步提升了用户访问速度. 合理运用这三种缓存策略, 可以构建出高性能, 高可用的应用系统.

相关推荐
千宇宙航5 小时前
闲庭信步使用图像验证平台加速FPGA的开发:第九课——图像插值的FPGA实现
图像处理·计算机视觉·缓存·fpga开发
全栈凯哥6 小时前
20.缓存问题与解决方案详解教程
java·spring boot·redis·后端·缓存
Hellyc13 小时前
用户查询优惠券之缓存击穿
java·redis·缓存
鼠鼠我捏,要死了捏15 小时前
缓存穿透与击穿多方案对比与实践指南
redis·缓存·实践指南
汤姆大聪明1 天前
Redis 持久化机制
数据库·redis·缓存
kk在加油1 天前
Redis数据安全性分析
数据库·redis·缓存
hcvinh2 天前
CANDENCE 17.4 进行元器件缓存更新
学习·缓存
墨着染霜华2 天前
Caffeine的tokenCache与Spring的CaffeineCacheManager缓存区别
java·spring·缓存
weixin_438335402 天前
Redis:分组与设备在 Redis 中缓存存储设计
redis·缓存·bootstrap
秋也凉2 天前
redis的命令集合
数据库·redis·缓存