本地緩存在Java中的實現(xiàn)過程
本地緩存是Java應(yīng)用中常用的性能優(yōu)化手段。如下圖所示:
在分布式系統(tǒng)中,同一個應(yīng)用部署有多個,這些應(yīng)用的本地緩存僅限于本地應(yīng)用內(nèi)部,是互不相通的,在負(fù)載均衡中,分配到處理的各個應(yīng)用讀取本地緩存的結(jié)果可能會存在不一致。
注意:本地緩存是jvm層面的緩存,一旦該應(yīng)用重啟或停止了,緩存也消失了。
1、介紹
引入緩存,主要用于實現(xiàn)系統(tǒng)的高性能,高并發(fā)。如下圖所示:
將數(shù)據(jù)庫查詢出來的數(shù)據(jù)放入緩存服務(wù)中,因為緩存是存儲在內(nèi)存中的,內(nèi)存的讀寫性能遠(yuǎn)超磁盤的讀寫性能,所以訪問的速度非常快。
注意:
但是電腦重啟后,內(nèi)存中的數(shù)據(jù)會全部清除,而磁盤中的數(shù)據(jù)雖然讀寫性能很差,但是數(shù)據(jù)不會丟失。
2、實現(xiàn)方式
2.1、HashMap
最簡單的方式是使用ConcurrentHashMap
實現(xiàn)線程安全的緩存。
代碼示例如下:
import java.util.concurrent.ConcurrentHashMap; public class SimpleCache<K, V> { private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>(); public void put(K key, V value) { cache.put(key, value); } public V get(K key) { return cache.get(key); } public void remove(K key) { cache.remove(key); } public void clear() { cache.clear(); } }
適用場景:
- 簡單的內(nèi)存緩存需求
- 緩存數(shù)據(jù)量很?。◣装贄l以內(nèi))
- 不需要過期策略或淘汰機(jī)制
- 快速原型開發(fā)
優(yōu)點:
- 零依賴
- 實現(xiàn)簡單直接
- 性能極高
缺點:
- 缺乏過期、淘汰等高級功能
- 需要手動實現(xiàn)線程安全(使用ConcurrentHashMap除外)
如下所示:
// 簡單的配置項緩存 private static final Map<String, String> CONFIG_CACHE = new ConcurrentHashMap<>(); public String getConfig(String key) { return CONFIG_CACHE.computeIfAbsent(key, k -> loadConfigFromDB(k)); }
2.2、LinkedHashMap
利用LinkedHashMap的訪問順序特性實現(xiàn)LRU(最近最少使用)緩存。
如下圖所示:
import java.util.LinkedHashMap; import java.util.Map; public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int maxSize; public LRUCache(int maxSize) { super(maxSize, 0.75f, true); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxSize; } }
適用場景:
- 需要簡單的LRU淘汰策略
- 緩存數(shù)量固定且不大
- 不想引入第三方庫
優(yōu)點:
- JDK內(nèi)置,無額外依賴
- 實現(xiàn)LRU策略簡單
缺點:
- 功能有限
- 并發(fā)性能一般
如下所示:
// 最近訪問的用戶基本信息緩存 private static final int MAX_ENTRIES = 1000; private static final Map<Long, UserInfo> USER_CACHE = Collections.synchronizedMap(new LinkedHashMap<Long, UserInfo>(MAX_ENTRIES, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; } });
2.3、Guava Cache
Guava Cache是JVM層面的緩存,服務(wù)停掉或重啟便消失了,在分布式環(huán)境中也有其局限性。
因此,比較好的緩存方案是Guava Cache+Redis雙管齊下。先查詢Guava Cache,命中即返回,未命中再查redis。
引入依賴:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>32.1.2-jre</version> <!-- 使用最新版本 --> </dependency>
代碼如下所示:
import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; public class GuavaCacheExample { public static void main(String[] args) { //建造者模式 Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 最大緩存數(shù)量 .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期 .build(); // 放入緩存 cache.put("key1", "value1"); // 獲取緩存 String value = cache.getIfPresent("key1"); System.out.println(value); // 移除緩存 cache.invalidate("key1"); } }
適用場景:
- 需要豐富的緩存特性(過期、淘汰、刷新等)
- 中等規(guī)模緩存(幾千到幾十萬條目)
- 需要良好的并發(fā)性能
- 項目已經(jīng)使用Guava庫
優(yōu)點:
- 功能全面(權(quán)重、刷新、統(tǒng)計等)
- 良好的API設(shè)計
- 中等規(guī)模的優(yōu)秀性能
缺點:
- 不如Caffeine性能高
- 大型緩存時內(nèi)存效率一般
示例如下:
// 商品詳情緩存,30分鐘自動過期,最大10000條 LoadingCache<Long, Product> productCache = CacheBuilder.newBuilder() .maximumSize(10_000) .expireAfterWrite(30, TimeUnit.MINUTES) .recordStats() .build(new CacheLoader<Long, Product>() { @Override public Product load(Long id) { return productDao.findById(id); } }); // 使用 Product product = productCache.get(123L);
2.4、Caffeine Cache
Caffeine是Guava Cache的現(xiàn)代替代品,性能更好。
引入依賴:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.8</version> <!-- 使用最新版本 --> </dependency>
代碼示例如下:
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; public class CaffeineCacheExample { public static void main(String[] args) { Cache<String, String> cache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); cache.put("key1", "value1"); String value = cache.getIfPresent("key1"); System.out.println(value); } }
適用場景:
- 高性能要求的應(yīng)用
- 大規(guī)模緩存(幾十萬以上條目)
- 需要最優(yōu)的讀寫性能
- 現(xiàn)代Java項目(JDK8+)
優(yōu)點:
- 目前性能最好的Java緩存庫
- 內(nèi)存效率高
- 豐富的特性(異步加載、權(quán)重等)
- 優(yōu)秀的并發(fā)性能
缺點:
- 較新的庫,老項目可能不適用
- API與Guava不完全兼容
示例如下:
// 高性能的秒殺商品庫存緩存 Cache<Long, AtomicInteger> stockCache = Caffeine.newBuilder() .maximumSize(100_000) .expireAfterWrite(10, TimeUnit.SECONDS) // 庫存信息短期有效 .refreshAfterWrite(1, TimeUnit.SECONDS) // 1秒后訪問自動刷新 .build(id -> new AtomicInteger(queryStockFromDB(id))); // 使用 int remaining = stockCache.get(productId).decrementAndGet();
2.5、Ehcache
Ehcache是一個成熟的Java緩存框架:功能更強(qiáng)大,支持磁盤持久化、分布式緩存等。
<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.10.8</version> </dependency>
import org.ehcache.Cache; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.config.units.MemoryUnit; import org.ehcache.core.config.DefaultConfiguration; import org.ehcache.core.spi.service.LocalPersistenceService; import org.ehcache.impl.config.persistence.DefaultPersistenceConfiguration; import org.ehcache.impl.persistence.DefaultLocalPersistenceService; public class EhcacheExample { public static void main(String[] args) { // 配置持久化到磁盤 LocalPersistenceService persistenceService = new DefaultLocalPersistenceService( new DefaultPersistenceConfiguration(new File("cache-data"))); // 創(chuàng)建緩存管理器 DefaultConfiguration config = new DefaultConfiguration( persistenceService, ResourcePoolsBuilder.heap(100).build()); Cache<String, String> cache = CacheConfigurationBuilder.newCacheConfigurationBuilder( String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(100, MemoryUnit.MB) // 堆內(nèi)內(nèi)存 .disk(1, MemoryUnit.GB) // 磁盤持久化 ).buildConfig(String.class); // 寫入數(shù)據(jù) cache.put("key1", "value1"); // 讀取數(shù)據(jù) String value = cache.get("key1"); System.out.println("Value: " + value); // 輸出 Value: value1 // 關(guān)閉資源 persistenceService.close(); } }
適用場景:
- 企業(yè)級應(yīng)用
- 需要持久化到磁盤
- 需要分布式緩存支持
- 復(fù)雜的緩存拓?fù)湫枨?/li>
優(yōu)點:
- 功能最全面(堆外、磁盤、集群等)
- 成熟的監(jiān)控和管理
- 良好的Spring集成
缺點:
- 性能不如Caffeine
- 配置較復(fù)雜
- 內(nèi)存效率一般
示例如下:
<!-- ehcache.xml --> <cache name="financialDataCache" maxEntriesLocalHeap="10000" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <persistence strategy="localTempSwap"/> </cache>
// 金融數(shù)據(jù)緩存,需要持久化 @Cacheable(value = "financialDataCache", key = "#symbol + '_' + #date.format(yyyyMMdd)") public FinancialData getFinancialData(String symbol, LocalDate date) { // 從外部API獲取數(shù)據(jù) }
2.6、使用Spring Cache注解
Spring框架提供了緩存抽象。關(guān)于cache的常用注解如下:
1、引入依賴
<dependencies> <!-- Spring Boot Starter Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- 使用Caffeine作為緩存實現(xiàn) --> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> </dependencies>
2、使用緩存配置類
import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.caffeine.CaffeineCacheManager; import java.util.concurrent.TimeUnit; @Configuration @EnableCaching public class CacheConfig { @Bean public CaffeineCacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .initialCapacity(100) .maximumSize(500) .expireAfterWrite(10, TimeUnit.MINUTES) .recordStats()); return cacheManager; } @Bean @Primary public CacheManager productCacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager("products"); cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.HOURS)); return cacheManager; } @Bean public CacheManager userCacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager("users"); cacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(500) .expireAfterAccess(30, TimeUnit.MINUTES)); return cacheManager; } }
注意:在設(shè)置緩存配置類的時候,可以配置多個。
然后在服務(wù)類中指定使用哪個緩存管理器:
@Service public class UserService { @Cacheable(value = "users", cacheManager = "userCacheManager") public User getUserById(Long id) { // ... } }
3、服務(wù)類使用緩存
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class ProductService { // 根據(jù)ID獲取產(chǎn)品,如果緩存中有則直接返回 @Cacheable(value = "products", key = "#id") public Product getProductById(Long id) { // 模擬數(shù)據(jù)庫查詢 System.out.println("查詢數(shù)據(jù)庫獲取產(chǎn)品: " + id); return findProductInDB(id); } // 更新產(chǎn)品信息,并更新緩存 @CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { // 模擬數(shù)據(jù)庫更新 System.out.println("更新數(shù)據(jù)庫中的產(chǎn)品: " + product.getId()); return updateProductInDB(product); } // 刪除產(chǎn)品,并清除緩存 @CacheEvict(value = "products", key = "#id") public void deleteProduct(Long id) { // 模擬數(shù)據(jù)庫刪除 System.out.println("從數(shù)據(jù)庫刪除產(chǎn)品: " + id); } // 清除所有產(chǎn)品緩存 @CacheEvict(value = "products", allEntries = true) public void clearAllCache() { System.out.println("清除所有產(chǎn)品緩存"); } // 模擬數(shù)據(jù)庫查詢方法 private Product findProductInDB(Long id) { // 實際項目中這里應(yīng)該是數(shù)據(jù)庫操作 return new Product(id, "產(chǎn)品" + id, 100.0); } // 模擬數(shù)據(jù)庫更新方法 private Product updateProductInDB(Product product) { // 實際項目中這里應(yīng)該是數(shù)據(jù)庫操作 return product; } }
4、實體類
public class Product { private Long id; private String name; private double price; // 構(gòu)造方法、getter和setter省略 // 實際項目中應(yīng)該包含這些方法 }
5、控制器示例:
import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/products") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } @GetMapping("/{id}") public Product getProduct(@PathVariable Long id) { return productService.getProductById(id); } @PutMapping public Product updateProduct(@RequestBody Product product) { return productService.updateProduct(product); } @DeleteMapping("/{id}") public void deleteProduct(@PathVariable Long id) { productService.deleteProduct(id); } @PostMapping("/clear-cache") public void clearCache() { productService.clearAllCache(); } }
適用場景:
- Spring/Spring Boot項目
- 需要聲明式緩存
- 可能切換緩存實現(xiàn)
- 需要與Spring生態(tài)深度集成
優(yōu)點:
- 統(tǒng)一的緩存抽象
- 注解驅(qū)動,使用簡單
- 輕松切換實現(xiàn)(Caffeine/Ehcache/Redis等)
缺點:
- 性能取決于底層實現(xiàn)
- 高級功能需要了解底層實現(xiàn)
如下所示:
// 多級緩存配置:本地緩存+Redis @Configuration @EnableCaching public class CacheConfig { // 本地一級緩存 @Bean @Primary public CacheManager localCacheManager() { CaffeineCacheManager manager = new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(30, TimeUnit.MINUTES)); return manager; } // Redis二級緩存 @Bean public CacheManager redisCacheManager(RedisConnectionFactory factory) { return RedisCacheManager.builder(factory) .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(2)) .disableCachingNullValues()) .build(); } } // 服務(wù)層使用 @Service public class ProductService { @Cacheable(cacheNames = "products", cacheManager = "localCacheManager") // 先用本地緩存 @Cacheable(cacheNames = "products", cacheManager = "redisCacheManager", unless = "#result == null") // 再用Redis緩存 public Product getProduct(Long id) { return productRepository.findById(id); } }
3、性能對比
1.合理設(shè)置緩存大小:
根據(jù)可用內(nèi)存設(shè)置上限。使用weigher
對大型對象特殊處理。
2.選擇合適的過期策略:
// 根據(jù)業(yè)務(wù)場景選擇 .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后固定時間過期 .expireAfterAccess(30, TimeUnit.MINUTES) // 訪問后延長有效期 .refreshAfterWrite(1, TimeUnit.MINUTES) // 寫入后定時刷新
3.監(jiān)控緩存命中率:
CacheStats stats = cache.stats(); double hitRate = stats.hitRate(); // 命中率 long evictionCount = stats.evictionCount(); // 淘汰數(shù)量
4.避免緩存污染:
// 不緩存null或空值 .build(key -> { Value value = queryFromDB(key); return value == null ? Optional.empty() : value; }); @Cacheable(unless = "#result == null || #result.isEmpty()")
5.考慮使用軟引用(內(nèi)存敏感場景):
.softValues() // 內(nèi)存不足時自動回收
根據(jù)您的具體業(yè)務(wù)需求、數(shù)據(jù)規(guī)模和性能要求,選擇最適合的緩存方案,并持續(xù)監(jiān)控和優(yōu)化緩存效果。
4、使用建議
簡單小規(guī)模緩存:ConcurrentHashMap
或LinkedHashMap
- 適用于配置項、簡單查詢結(jié)果緩存
- 無外部依賴,實現(xiàn)簡單
中等規(guī)模通用緩存:Guava Cache
或Caffeine
- 適用于大多數(shù)業(yè)務(wù)數(shù)據(jù)緩存
- Guava適合已有Guava依賴的項目
- Caffeine性能更好,推薦新項目使用
高性能大規(guī)模緩存:Caffeine
- 適用于高并發(fā)、高性能要求的場景
- 如秒殺系統(tǒng)、高頻交易系統(tǒng)
企業(yè)級復(fù)雜需求:Ehcache
- 需要持久化、集群等高級功能
- 已有Ehcache使用經(jīng)驗的項目
Spring項目:Spring Cache + Caffeine
- 利用Spring抽象層,方便后續(xù)擴(kuò)展
- 推薦Caffeine作為底層實現(xiàn)
多級緩存架構(gòu):Caffeine + Redis
- 本地緩存作為一級緩存
- Redis作為二級分布式緩存
- 通過Spring Cache抽象統(tǒng)一管理
總結(jié)
內(nèi)存管理:設(shè)置合理的 maximumSize 或 expireAfterWrite,避免內(nèi)存溢出(OOM)。
并發(fā)安全:Guava/Caffeine/Ehcache 均為線程安全,直接使用即可。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中RabbitMQ的幾種消息確認(rèn)機(jī)制
RabbitMQ消息確認(rèn)機(jī)制指的是在消息傳遞過程中,發(fā)送方發(fā)送消息后,接收方需要對消息進(jìn)行確認(rèn),以確保消息被正確地接收和處理,本文主要介紹了Java中RabbitMQ的幾種消息確認(rèn)機(jī)制,具有一定的參考價值,感興趣的可以了解一下2023-12-12Java設(shè)計模式之抽象工廠模式AbstractFactoryPattern詳解
這篇文章主要介紹了Java設(shè)計模式之抽象工廠模式AbstractFactoryPattern詳解,抽象工廠模式是一種軟件開發(fā)設(shè)計模式,抽象工廠模式提供了一種方式,可以將一組具有同一主題的單獨(dú)的工廠封裝起來,需要的朋友可以參考下2023-10-10