提升接口性能之缓存
缓存策略: 从本地到分布式, 再到 HTTP 缓存的全面解析
引言
在当今的软件开发中, 缓存技术扮演着至关重要的角色. 它能够显著提升系统性能, 降低数据库负载, 提高用户体验. 本文将详细介绍三种常见的缓存策略: 本地缓存, 分布式缓存以及 HTTP 缓存, 并探讨如何在实际应用中合理运用它们.
一, 本地缓存
1.1 Caffeine
Caffeine 是一个基于 Java 8 的高性能本地缓存库. 它结合了多种优秀的缓存算法, 具有极高的性能和可扩展性.
1.1.1 引入依赖
在 Maven 项目中, 只需添加以下依赖:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>
1.1.2 使用示例
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 引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
1.2.2 使用示例
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 库:
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:
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 库进行缓存预热:
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 设置随机过期时间:
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 头:
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:
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 对静态资源的缓存, 进一步提升了用户访问速度. 合理运用这三种缓存策略, 可以构建出高性能, 高可用的应用系统.