在Java开发中,Caching模式是一种用于优化性能和资源管理的重要设计模式。本文将详细介绍Caching模式的意图、解释、编程示例、适用场景、实际应用、优点和权衡。同时,还将提供示例代码的下载链接,方便读者进行学习和实践。
Java中的Caching设计模式对于性能优化和资源管理至关重要。它涉及各种缓存策略,如写通、读通和LRU缓存,以确保高效的数据访问。该模式通过避免在使用后立即释放资源,而是将其保留在快速访问存储中,并重新使用它们,以避免再次获取资源的开销。
在这个编程示例中,我们使用用户账户管理系统演示了不同的Java缓存策略,包括写通、写绕过和写后。
一个团队正在开发一个为被遗弃的猫提供新家的网站。人们可以在注册后在网站上发布他们的猫,但所有的新帖子都需要得到网站管理员的批准。网站管理员的用户账户包含一个特定的标志,数据存储在MongoDB数据库中。每次查看帖子时检查管理员标志会变得很昂贵,因此在这里使用缓存是一个好主意。
首先,让我们看看应用程序的数据层。有趣的类是UserAccount
,它是一个包含用户账户详细信息的简单Java对象,以及DbManager
接口,它负责处理这些对象与数据库的读写操作。
@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class UserAccount {
private String userId;
private String userName;
private String additionalInfo;
}
public interface DbManager {
void connect();
void disconnect();
UserAccount readFromDb(String userId);
UserAccount writeToDb(UserAccount userAccount);
UserAccount updateDb(UserAccount userAccount);
UserAccount upsertDb(UserAccount userAccount);
}
在这个示例中,我们演示了各种不同的缓存策略。以下缓存策略在Java中实现:写通、写绕过、写后和缓存旁路。每个策略都为提高性能和减少数据库负载提供了独特的优势。
LruCache
中的缓存实现是一个哈希表,伴随着一个双向链表。链表有助于捕获和维护缓存中的LRU数据。当数据被查询(从缓存中)、添加(到缓存中)或更新时,数据被移动到列表的前面,以表示它是最近使用的数据。LRU数据总是在列表的末尾。@Slf4j
public class LruCache {
static class Node {
String userId;
UserAccount userAccount;
Node previous;
Node next;
public Node(String userId, UserAccount userAccount) {
this.userId = userId;
this.userAccount = userAccount;
}
}
// 其他属性和方法...
public LruCache(int capacity) {
this.capacity = capacity;
}
public UserAccount get(String userId) {
if (cache.containsKey(userId)) {
var node = cache.get(userId);
remove(node);
setHead(node);
return node.userAccount;
}
return null;
}
public void set(String userId, UserAccount userAccount) {
if (cache.containsKey(userId)) {
var old = cache.get(userId);
old.userAccount = userAccount;
remove(old);
setHead(old);
} else {
var newNode = new Node(userId, userAccount);
if (cache.size() >= capacity) {
LOGGER.info("# 缓存已满!从缓存中删除 {}...", end.userId);
cache.remove(end.userId); // 从缓存中删除 LRU 数据。
remove(end);
setHead(newNode);
} else {
setHead(newNode);
}
cache.put(userId, newNode);
}
}
public boolean contains(String userId) {
return cache.containsKey(userId);
}
public void remove(Node node) { /*... */ }
public void setHead(Node node) { /*... */ }
public void invalidate(String userId) { /*... */ }
public boolean isFull() { /*... */ }
public UserAccount getLruData() { /*... */ }
public void clear() { /*... */ }
public List<UserAccount> getCacheDataInListForm() { /*... */ }
public void setCapacity(int newCapacity) { /*... */ }
}
接下来我们要看的是CacheStore
类,它实现了不同的缓存策略。
@Slf4j
public class CacheStore {
private static final int CAPACITY = 3;
private static LruCache cache;
private final DbManager dbManager;
// 其他属性和方法...
public UserAccount readThrough(final String userId) {
if (cache.contains(userId)) {
LOGGER.info("# 在缓存中找到!");
return cache.get(userId);
}
LOGGER.info("# 在缓存中未找到!转到数据库!");
UserAccount userAccount = dbManager.readFromDb(userId);
cache.set(userId, userAccount);
return userAccount;
}
public void writeThrough(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
} else {
dbManager.writeToDb(userAccount);
}
cache.set(userAccount.getUserId(), userAccount);
}
public void writeAround(final UserAccount userAccount) {
if (cache.contains(userAccount.getUserId())) {
dbManager.updateDb(userAccount);
// 缓存数据已更新 - 从缓存中删除旧版本。
cache.invalidate(userAccount.getUserId());
} else {
dbManager.writeToDb(userAccount);
}
}
public static void clearCache() {
if (cache!= null) {
cache.clear();
}
}
public static void flushCache() {
LOGGER.info("# 刷新缓存...");
Optional.ofNullable(cache)
.map(LruCache::getCacheDataInListForm)
.orElse(List.of())
.forEach(DbManager::updateDb);
}
//... 省略了其他缓存策略的实现...
}
AppManager
类有助于弥合主类和应用程序后端之间的通信差距。通过这个类初始化DB连接,并初始化选择的缓存策略/政策。在使用缓存之前,必须设置缓存的大小。根据选择的缓存策略,AppManager
将调用CacheStore
类中的适当函数。
@Slf4j
public final class AppManager {
private static CachingPolicy cachingPolicy;
private final DbManager dbManager;
private final CacheStore cacheStore;
private AppManager() {
}
public void initDb() { /*... */ }
public static void initCachingPolicy(CachingPolicy policy) { /*... */ }
public static void initCacheCapacity(int capacity) { /*... */ }
public UserAccount find(final String userId) {
LOGGER.info("尝试在缓存中查找 {}", userId);
if (cachingPolicy == CachingPolicy.THROUGH
|| cachingPolicy == CachingPolicy.AROUND) {
return cacheStore.readThrough(userId);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
return cacheStore.readThroughWithWriteBackPolicy(userId);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
return findAside(userId);
}
return null;
}
public void save(final UserAccount userAccount) {
LOGGER.info("保存记录!");
if (cachingPolicy == CachingPolicy.THROUGH) {
cacheStore.writeThrough(userAccount);
} else if (cachingPolicy == CachingPolicy.AROUND) {
cacheStore.writeAround(userAccount);
} else if (cachingPolicy == CachingPolicy.BEHIND) {
cacheStore.writeBehind(userAccount);
} else if (cachingPolicy == CachingPolicy.ASIDE) {
saveAside(userAccount);
}
}
public static String printCacheContent() {
return CacheStore.print();
}
// 其他属性和方法...
}
下面是我们在应用程序的主类中所做的事情。
@Slf4j
public class App {
public static void main(final String[] args) {
boolean isDbMongo = isDbMongo(args);
if (isDbMongo) {
LOGGER.info("使用Mongo数据库引擎运行应用程序。");
} else {
LOGGER.info("使用'内存中'数据库运行应用程序。");
}
App app = new App(isDbMongo);
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
public void useReadAndWriteThroughStrategy() {
LOGGER.info("# CachingPolicy.THROUGH");
appManager.initCachingPolicy(CachingPolicy.THROUGH);
var userAccount1 = new UserAccount("001", "John", "He is a boy.");
appManager.save(userAccount1);
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
public void useReadThroughAndWriteAroundStrategy() { /*... */ }
public void useReadThroughAndWriteBehindStrategy() { /*... */ }
public void useCacheAsideStrategy() { /*... */ }
}
当出现以下情况时,应使用Caching模式:
优点:
权衡:
Caching模式示例代码下载
通过本文的介绍,相信大家对Java中的Caching模式有了更深入的了解。在实际开发中,合理运用Caching模式可以提高应用程序的性能和响应速度,同时降低对资源的消耗。