设计思路
- 数据实时性
- 读写分离更新:在写操作时,同时更新数据库和 Redis 缓存。读操作先从缓存读,若缓存不存在再读数据库,并将数据回种到缓存。
- 使用消息队列:写操作完成后,发送消息到消息队列,由消费者异步更新缓存,确保数据一致性且不影响主业务流程。
- 减少缓存穿透风险
- 布隆过滤器:在查询缓存前,先通过布隆过滤器判断数据是否存在。若不存在,直接返回,避免查询数据库。布隆过滤器存在一定误判率,但可通过合理设置参数降低。
- 缓存空值:当数据库查询结果为空时,在缓存中设置一个空值,并设置较短过期时间,防止后续相同查询穿透到数据库。
- 减少缓存雪崩风险
- 随机过期时间:为缓存设置随机的过期时间,避免大量缓存同时过期。例如,原本过期时间为 60 秒,可设置为 55 - 65 秒之间的随机值。
- 多级缓存:采用本地缓存(如 Guava Cache)和 Redis 缓存结合。先查本地缓存,不存在再查 Redis 缓存。当 Redis 缓存大量失效时,本地缓存可分担部分压力。
- 服务降级:当系统压力过大时,对非核心业务进行服务降级,直接返回默认值或提示信息,避免因大量请求压垮数据库。
关键代码实现要点
- 读写分离更新
import redis.clients.jedis.Jedis;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class CacheAndDBUpdate {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String DB_URL = "jdbc:mysql://localhost:3306/yourdatabase";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
try {
// 写操作
String key = "exampleKey";
String value = "exampleValue";
// 更新数据库
Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
String updateSql = "UPDATE your_table SET column =? WHERE some_condition";
PreparedStatement updateStmt = connection.prepareStatement(updateSql);
updateStmt.setString(1, value);
updateStmt.executeUpdate();
// 更新缓存
jedis.set(key, value);
// 读操作
String cachedValue = jedis.get(key);
if (cachedValue == null) {
String selectSql = "SELECT column FROM your_table WHERE some_condition";
PreparedStatement selectStmt = connection.prepareStatement(selectSql);
ResultSet resultSet = selectStmt.executeQuery();
if (resultSet.next()) {
cachedValue = resultSet.getString(1);
jedis.set(key, cachedValue);
}
}
System.out.println("Value from cache or db: " + cachedValue);
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
}
- 布隆过滤器
- Java 示例(使用 Google Guava 的 BloomFilter)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterExample {
private static final int EXPECTED_ELEMENTS = 10000;
private static final double FALSE_POSITIVE_RATE = 0.01;
private static BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(), EXPECTED_ELEMENTS, FALSE_POSITIVE_RATE);
public static void main(String[] args) {
// 添加数据到布隆过滤器
bloomFilter.put("exampleKey1");
bloomFilter.put("exampleKey2");
// 查询数据
boolean mightContain1 = bloomFilter.mightContain("exampleKey1");
boolean mightContain2 = bloomFilter.mightContain("exampleKey3");
System.out.println("exampleKey1 might contain: " + mightContain1);
System.out.println("exampleKey3 might contain: " + mightContain2);
}
}
- 缓存空值
import redis.clients.jedis.Jedis;
public class CacheNullValue {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final int NULL_CACHE_EXPIRE = 60; // 60 秒
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
String key = "nonexistentKey";
String value = jedis.get(key);
if (value == null) {
// 假设数据库查询结果也为空
jedis.setex(key, NULL_CACHE_EXPIRE, "nullValue");
value = "nullValue";
}
System.out.println("Value from cache: " + value);
jedis.close();
}
}
- 随机过期时间
import redis.clients.jedis.Jedis;
import java.util.Random;
public class RandomExpiration {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final int BASE_EXPIRE = 60; // 基础过期时间 60 秒
private static final int RANDOM_RANGE = 10; // 随机范围 10 秒
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
String key = "exampleKey";
String value = "exampleValue";
Random random = new Random();
int randomExpire = BASE_EXPIRE + random.nextInt(RANDOM_RANGE * 2) - RANDOM_RANGE;
jedis.setex(key, randomExpire, value);
System.out.println("Set key with random expiration: " + randomExpire + " seconds");
jedis.close();
}
}