旁路缓存模式、读写穿透模式和异步缓存写入模式是三种常见的缓存使用模式,以下是对三种经典缓存使用模式在缓存与数据库数据一致性方面更全面的分析:
1.数据读取流程
2.数据写入流程
3.一致性分析
4.代码实例
import redis.clients.jedis.Jedis;
import java.sql.*;
public class CacheAsidePattern {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String DB_URL = "jdbc:mysql://localhost:3306/testdb";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
// 创建表
createTable(conn);
// 插入示例数据
insertSampleData(conn);
// 测试读取
String userName = getUser(jedis, conn, 1);
System.out.println("用户姓名: " + userName);
// 测试更新
updateUser(jedis, conn, 1, "Bob");
userName = getUser(jedis, conn, 1);
System.out.println("更新后用户姓名: " + userName);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void createTable(Connection conn) throws SQLException {
String createTableSQL = "CREATE TABLE IF NOT EXISTS users (" +
"id INT PRIMARY KEY, " +
"name VARCHAR(255))";
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(createTableSQL);
}
}
private static void insertSampleData(Connection conn) throws SQLException {
String insertSQL = "INSERT IGNORE INTO users (id, name) VALUES (1, 'Alice')";
try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
pstmt.executeUpdate();
}
}
private static String getUser(Jedis jedis, Connection conn, int userId) {
String key = "user:" + userId;
String user = jedis.get(key);
if (user != null) {
System.out.println("从缓存中获取数据");
return user;
} else {
try (PreparedStatement pstmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
user = rs.getString("name");
jedis.set(key, user);
System.out.println("从数据库中获取数据并写入缓存");
return user;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
private static void updateUser(Jedis jedis, Connection conn, int userId, String newName) {
String key = "user:" + userId;
try (PreparedStatement pstmt = conn.prepareStatement("UPDATE users SET name = ? WHERE id = ?")) {
pstmt.setString(1, newName);
pstmt.setInt(2, userId);
pstmt.executeUpdate();
jedis.del(key);
System.out.println("数据库更新并删除缓存");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
1.数据读取流程
2.数据写入流程
3.一致性分析
4.代码实例
import redis.clients.jedis.Jedis;
import java.sql.*;
public class WriteThroughPattern {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String DB_URL = "jdbc:mysql://localhost:3306/testdb";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
// 创建表
createTable(conn);
// 插入示例数据
insertSampleData(conn);
// 测试读取
String userName = getUser(jedis, conn, 1);
System.out.println("用户姓名: " + userName);
// 测试更新
updateUser(jedis, conn, 1, "Bob");
userName = getUser(jedis, conn, 1);
System.out.println("更新后用户姓名: " + userName);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void createTable(Connection conn) throws SQLException {
String createTableSQL = "CREATE TABLE IF NOT EXISTS users (" +
"id INT PRIMARY KEY, " +
"name VARCHAR(255))";
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(createTableSQL);
}
}
private static void insertSampleData(Connection conn) throws SQLException {
String insertSQL = "INSERT IGNORE INTO users (id, name) VALUES (1, 'Alice')";
try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
pstmt.executeUpdate();
}
}
private static String getUser(Jedis jedis, Connection conn, int userId) {
String key = "user:" + userId;
String user = jedis.get(key);
if (user != null) {
System.out.println("从缓存中获取数据");
return user;
} else {
try (PreparedStatement pstmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
user = rs.getString("name");
jedis.set(key, user);
System.out.println("从数据库中获取数据并写入缓存");
return user;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
private static void updateUser(Jedis jedis, Connection conn, int userId, String newName) {
String key = "user:" + userId;
try {
conn.setAutoCommit(false);
jedis.set(key, newName);
try (PreparedStatement pstmt = conn.prepareStatement("UPDATE users SET name = ? WHERE id = ?")) {
pstmt.setString(1, newName);
pstmt.setInt(2, userId);
pstmt.executeUpdate();
}
conn.commit();
System.out.println("缓存和数据库同时更新");
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
System.out.println("更新失败: " + e.getMessage());
}
}
}
1.数据读取流程
2.数据写入流程
3.一致性分析
4.代码实例
import redis.clients.jedis.Jedis;
import java.sql.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class WriteBehindCachingPattern {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String DB_URL = "jdbc:mysql://localhost:3306/testdb";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
private static final int FLUSH_INTERVAL = 5; // 每 5 秒刷写一次
public static void main(String[] args) {
try (Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
// 创建表
createTable(conn);
// 插入示例数据
insertSampleData(conn);
// 启动异步刷写任务
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(() -> flushCacheToDB(jedis, conn), 0, FLUSH_INTERVAL, TimeUnit.SECONDS);
// 测试读取
String userName = getUser(jedis, conn, 1);
System.out.println("用户姓名: " + userName);
// 测试更新
updateUser(jedis, conn, 1, "Bob");
userName = getUser(jedis, conn, 1);
System.out.println("更新后用户姓名: " + userName);
// 关闭线程池
executor.shutdown();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void createTable(Connection conn) throws SQLException {
String createTableSQL = "CREATE TABLE IF NOT EXISTS users (" +
"id INT PRIMARY KEY, " +
"name VARCHAR(255))";
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(createTableSQL);
}
}
private static void insertSampleData(Connection conn) throws SQLException {
String insertSQL = "INSERT IGNORE INTO users (id, name) VALUES (1, 'Alice')";
try (PreparedStatement pstmt = conn.prepareStatement(insertSQL)) {
pstmt.executeUpdate();
}
}
private static String getUser(Jedis jedis, Connection conn, int userId) {
String key = "user:" + userId;
String user = jedis.get(key);
if (user != null) {
System.out.println("从缓存中获取数据");
return user;
} else {
try (PreparedStatement pstmt = conn.prepareStatement("SELECT name FROM users WHERE id = ?")) {
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
user = rs.getString("name");
jedis.set(key, user);
System.out.println("从数据库中获取数据并写入缓存");
return user;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
private static void updateUser(Jedis jedis, Connection conn, int userId, String newName) {
String key = "user:" + userId;
jedis.set(key, newName);
System.out.println("数据写入缓存,等待异步刷写数据库");
}
private static void flushCacheToDB(Jedis jedis, Connection conn) {
try {
conn.setAutoCommit(false);
// 模拟获取所有用户缓存数据
// 实际应用中需要根据业务逻辑获取待刷写的数据
String keyPattern = "user:*";
for (String key : jedis.keys(keyPattern)) {
int userId = Integer.parseInt(key.split(":")[1]);
String userName = jedis.get(key);
try (PreparedStatement pstmt = conn.prepareStatement("UPDATE users SET name = ? WHERE id = ?")) {
pstmt.setString(1, userName);
pstmt.setInt(2, userId);
pstmt.executeUpdate();
}
}
conn.commit();
System.out.println("缓存数据刷写到数据库");
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
System.out.println("刷写失败: " + e.getMessage());
}
}
}
三种经典缓存使用模式在缓存与数据库数据一致性方面各有优劣。在实际应用中,需要根据业务对数据一致性的严格程度、读写操作的频率和性能要求等因素,综合权衡选择合适的缓存模式,并通过相应的技术手段和策略来最大程度地保障数据一致性。