解讀緩存db redis local的取舍之道
前言
讓我們來聊一下數(shù)據(jù)緩存,它是如何為我們帶來快速的數(shù)據(jù)響應(yīng)的。
你知道嗎,為了提高數(shù)據(jù)的讀取速度,我們通常會引入數(shù)據(jù)緩存。
但是,你知道嗎,不是所有的數(shù)據(jù)都適合緩存,有些數(shù)據(jù)更適合直接從數(shù)據(jù)庫查詢。
現(xiàn)在,我們就來一起討論一下,什么樣的數(shù)據(jù)適合直接從數(shù)據(jù)庫查詢,什么樣的數(shù)據(jù)適合從緩存中讀取。
這將有助于我們更好地利用緩存,提高系統(tǒng)的性能。讓我們開始吧!
一、影響因素
當(dāng)涉及到數(shù)據(jù)查詢和緩存時(shí),有幾個(gè)因素可以考慮來確定什么樣的數(shù)據(jù)適合直接從數(shù)據(jù)庫查詢,什么樣的數(shù)據(jù)適合從緩存中讀取。
- 訪問頻率:如果某個(gè)數(shù)據(jù)被頻繁訪問,且對實(shí)時(shí)性要求不高,那么將其緩存在內(nèi)存中會顯著提高響應(yīng)速度。這樣的數(shù)據(jù)可以是經(jīng)常被查詢的熱點(diǎn)數(shù)據(jù),比如網(wǎng)站的熱門文章、商品信息等。
- 數(shù)據(jù)更新頻率:如果某個(gè)數(shù)據(jù)經(jīng)常發(fā)生更新,那么將其緩存可能導(dǎo)致緩存和數(shù)據(jù)庫中的數(shù)據(jù)不一致。對于這種情況,最好直接從數(shù)據(jù)庫中查詢最新數(shù)據(jù)。比如用戶個(gè)人信息、訂單狀態(tài)等經(jīng)常變動(dòng)的數(shù)據(jù)。
- 數(shù)據(jù)大?。狠^大的數(shù)據(jù)對象,如圖片、視頻等,由于其體積較大,將其緩存到內(nèi)存中可能會占用大量資源。這種情況下,可以將這些數(shù)據(jù)存儲在分布式文件系統(tǒng)或云存儲中,并通過緩存存儲其訪問路徑或標(biāo)識符。
- 數(shù)據(jù)一致性:一些數(shù)據(jù)在不同地方的多個(gè)副本可能會導(dǎo)致一致性問題。對于需要保持強(qiáng)一致性的數(shù)據(jù),建議直接從數(shù)據(jù)庫查詢。而對于可以容忍一定程度的數(shù)據(jù)不一致的場景,可以考慮將數(shù)據(jù)緩存。
- 查詢復(fù)雜度:某些復(fù)雜的查詢操作可能會消耗大量的計(jì)算資源和時(shí)間,如果這些查詢結(jié)果需要頻繁訪問,可以將其緩存,避免重復(fù)計(jì)算,提高響應(yīng)速度。
需要注意的是,數(shù)據(jù)緩存并非適用于所有情況。緩存的使用需要謹(jǐn)慎,需要權(quán)衡數(shù)據(jù)的實(shí)時(shí)性、一致性和存儲成本等方面的需求。此外,對于緩存數(shù)據(jù)的更新和失效策略也需要考慮,以確保緩存數(shù)據(jù)的準(zhǔn)確性和及時(shí)性。
綜上所述,數(shù)據(jù)適合直接從數(shù)據(jù)庫查詢還是緩存讀取,取決于數(shù)據(jù)的訪問頻率、更新頻率、大小、一致性要求和查詢復(fù)雜度等因素。在實(shí)際應(yīng)用中,需要根據(jù)具體情況進(jìn)行綜合考慮和合理選擇。
二、db or redis or local
1.db
- 查詢復(fù)雜度低
- 字段少
- sql執(zhí)行效率高
- 實(shí)時(shí)性高
通常數(shù)據(jù)庫適合查詢字典類型數(shù)據(jù),如類似 key value 鍵值對,數(shù)據(jù)更新頻繁,實(shí)時(shí)性高的數(shù)據(jù)。
對于sql效率高的查詢,redis查詢不一定比db查詢快。
2.redis
- 查詢復(fù)雜度高
- 字段相對不多
- 實(shí)時(shí)性低
Redis適合查詢復(fù)雜度較高、實(shí)時(shí)性要求較低的數(shù)據(jù)。當(dāng)SQL查詢效率較低,或者需要進(jìn)行字段code和value的轉(zhuǎn)換存儲時(shí),Redis可以提供更高效的查詢方式。
不過,需要注意的是,Redis的主要瓶頸在于數(shù)據(jù)的序列化和反序列化過程。如果數(shù)據(jù)量較大,包含大量字段或者數(shù)據(jù)量巨大,那么Redis的查詢速度可能不一定比數(shù)據(jù)庫快,當(dāng)然此時(shí)數(shù)據(jù)庫本身執(zhí)行效率也低。
在這種情況下,我們需要綜合考慮數(shù)據(jù)的復(fù)雜度、實(shí)時(shí)性要求以及數(shù)據(jù)量的大小,選擇最適合的查詢方式。
有時(shí)候,可能需要在數(shù)據(jù)庫和Redis之間進(jìn)行權(quán)衡和折中,以找到最佳的性能和效率平衡點(diǎn)。因此,為了提高查詢速度,我們需要根據(jù)具體的業(yè)務(wù)需求和數(shù)據(jù)特性,選擇合適的存儲和查詢方案。
3. local
- 查詢復(fù)雜度高
- 字段多
- 實(shí)時(shí)性低
本地緩存通常是最快的。它可以在內(nèi)存中直接讀取數(shù)據(jù),速度非???。然而,由于受限于內(nèi)存大小,本地緩存的數(shù)據(jù)量是有限的。
對于那些數(shù)據(jù)庫和Redis難以處理的大型數(shù)據(jù),我們可以考慮使用本地緩存。通過將一部分頻繁訪問的數(shù)據(jù)存儲在本地緩存中,可以大大提高系統(tǒng)的響應(yīng)速度。
這樣,我們可以在不犧牲太多內(nèi)存資源的情況下,快速獲取到需要的數(shù)據(jù)。當(dāng)然,需要注意的是,由于本地緩存的數(shù)據(jù)是存儲在內(nèi)存中的,所以在服務(wù)器重啟或緩存過期時(shí),需要重新從數(shù)據(jù)庫或Redis中加載數(shù)據(jù)到本地緩存中。
因此,在使用本地緩存時(shí),需要權(quán)衡數(shù)據(jù)的大小、更新頻率以及內(nèi)存資源的限制,以獲得最佳的性能和可用性。
三、redisson 和 CaffeineCache 封裝
提供緩存查詢封裝,查詢不到時(shí)直接查數(shù)據(jù)庫后存入緩存。
3.1 redisson
- 3.1.1 maven
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> </dependency>
- 3.1.2 封裝
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.cuzue.common.core.exception.BusinessException; import com.cuzue.dao.cache.redis.RedisClient; import org.redisson.api.RBucket; import org.redisson.api.RKeys; import org.redisson.api.RedissonClient; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public class RedisCacheProvider { private static RedissonClient redissonClient; public RedisCacheProvider(RedissonClient redissonClient) { this.redissonClient = redissonClient; } /** * 從redissonClient緩存中取數(shù)據(jù),如果沒有,查數(shù)據(jù)后存入 * * @param key redis key * @param dataFetcher 獲取數(shù)據(jù) * @param ttl 緩存時(shí)間 * @param timeUnit 緩存時(shí)間單位 * @param <T> * @return 數(shù)據(jù) */ public <T> List<T> getCachedList(String key, Supplier<List<T>> dataFetcher, long ttl, TimeUnit timeUnit) { if (ObjectUtil.isNotNull(redissonClient)) { // 嘗試從緩存中獲取數(shù)據(jù) List<T> cachedData = redissonClient.getList(key); if (cachedData.size() > 0) { // 緩存中有數(shù)據(jù),直接返回 return cachedData; } else { // 緩存中沒有數(shù)據(jù),調(diào)用數(shù)據(jù)提供者接口從數(shù)據(jù)庫中獲取 List<T> data = dataFetcher.get(); cachedData.clear(); cachedData.addAll(data); // 將數(shù)據(jù)存入緩存,并設(shè)置存活時(shí)間 // 獲取 bucket 對象,為了設(shè)置過期時(shí)間 RBucket<List<T>> bucket = redissonClient.getBucket(key); // 為整個(gè)列表設(shè)置過期時(shí)間 bucket.expire(ttl, timeUnit); // 返回新獲取的數(shù)據(jù) return data; } } else { throw new BusinessException("redissonClient has not initialized"); } } /** * 刪除緩存 * * @param key redis key */ public void deleteCachedList(String systemName, String key) { if (ObjectUtil.isNotNull(redissonClient)) { RKeys keys = redissonClient.getKeys(); keys.deleteByPattern(key); } else { throw new BusinessException("redis client has not initialized"); } } }
- 3.1.3 使用
啟動(dòng)類添加:@Import({RedissonConfig.class})
直接引用:
@Resource private RedissonClient redissonClient; //緩存數(shù)據(jù)獲取 public List<MatMaterialsResp> listCache(ListQO qo) { RedisCacheProvider cache = new RedisCacheProvider(redissonClient); List<MatMaterialsResp> resps = cache.getCachedList("testList", () -> { // 緩存數(shù)據(jù)查詢 }, 20, TimeUnit.SECONDS); return resps; }
3.2 CaffeineCache
也可以使用hashMap
- 3.1.1 maven
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.0.5</version> </dependency>
- 3.1.2 封裝
CaffeineCache<K, V>
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Weigher; import java.util.concurrent.TimeUnit; import java.util.function.Function; public class CaffeineCache<K, V> { private final Cache<K, V> cache; /** * 不過期緩存 * * @param maxSize 緩存條目數(shù)量 注意對象大小不要超過jvm內(nèi)存 */ public CaffeineCache(long maxSize) { this.cache = Caffeine.newBuilder() .maximumSize(maxSize) .build(); } /** * 初始化Caffeine * * @param maxSize * @param expireAfterWriteDuration * @param unit */ public CaffeineCache(long maxSize, long expireAfterWriteDuration, TimeUnit unit) { this.cache = Caffeine.newBuilder() .maximumSize(maxSize) .expireAfterWrite(expireAfterWriteDuration, unit) .build(); } /** * 初始化Caffeine 帶權(quán)重 * * @param maxSize * @param weigher 權(quán)重 * @param expireAfterWriteDuration * @param unit */ public CaffeineCache(long maxSize, Weigher weigher, long expireAfterWriteDuration, TimeUnit unit) { this.cache = Caffeine.newBuilder() .maximumSize(maxSize) .weigher(weigher) .expireAfterWrite(expireAfterWriteDuration, unit) .build(); } public V get(K key) { return cache.getIfPresent(key); } public void put(K key, V value) { cache.put(key, value); } public void remove(K key) { cache.invalidate(key); } public void clear() { cache.invalidateAll(); } // 如果你需要一個(gè)加載功能(當(dāng)緩存miss時(shí)自動(dòng)加載值),你可以使用這個(gè)方法 public V get(K key, Function<? super K, ? extends V> mappingFunction) { return cache.get(key, mappingFunction); } // 添加獲取緩存統(tǒng)計(jì)信息的方法 public String stats() { return cache.stats().toString(); } }
LocalCacheProvider
import cn.hutool.core.util.ObjectUtil; import com.cuzue.dao.cache.localcache.CaffeineCache; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; /** * 本地緩存 */ public class LocalCacheProvider { private static CaffeineCache cache; /** * 無過期時(shí)間 * @param maxSize 緩存最大條數(shù) */ public LocalCacheProvider(long maxSize) { cache = new CaffeineCache(maxSize); } /** * 帶過期時(shí)間 * @param maxSize 緩存最大條數(shù) * @param ttl 過期時(shí)間 * @param timeUnit 時(shí)間單位 */ public LocalCacheProvider(long maxSize, long ttl, TimeUnit timeUnit) { cache = new CaffeineCache(maxSize, ttl, timeUnit); } public static <T> List<T> getCachedList(String key, Supplier<List<T>> dataFetcher) { if (ObjectUtil.isNotNull(cache.get(key))) { return (List<T>) cache.get(key); } else { List<T> data = dataFetcher.get(); cache.put(key, data); return data; } } public static <T> List<T> getCachedList(String key, Function<String, List<T>> dataFetcher) { return (List<T>) cache.get(key, dataFetcher); } /** * 刪除緩存 * * @param key redis key */ public void deleteCachedList(String key) { cache.remove(key); } }
- 3.1.3 使用
//初始化caffeine對象 LocalCacheProvider cache = new LocalCacheProvider(5000, 20, TimeUnit.SECONDS); //緩存數(shù)據(jù)獲取 public List<MatMaterialsResp> listLocalCache(ListQO qo) { List<MatMaterialsResp> resps = cache.getCachedList("testList", (s) -> { // 緩存數(shù)據(jù)查詢 }); return resps; }
注意:Caffeine 實(shí)現(xiàn)的緩存占用 JVM 內(nèi)存,小心 OutOfMemoryError
解決場景:
- 1.本地緩存適用不限制緩存大小,導(dǎo)致OOM,適合緩存小對象
- 2.本地緩存長時(shí)間存在,未及時(shí)清除無效緩存,導(dǎo)致內(nèi)存占用資源浪費(fèi)
- 3.防止人員api濫用, 未統(tǒng)一管理隨意使用,導(dǎo)致維護(hù)性差等等
總結(jié)
從前的無腦經(jīng)驗(yàn),db查詢慢,redis緩存起來,redis真不一定快!
一個(gè)簡單性能測試:(測試響應(yīng)時(shí)間均為二次查詢的大概時(shí)間)
1.前置條件: 一條數(shù)據(jù)轉(zhuǎn)換需要200ms,共5條數(shù)據(jù),5個(gè)字段項(xiàng),數(shù)據(jù)量大小463 B
db > 1s redis > 468ms local > 131ms
2.去除轉(zhuǎn)換時(shí)間,直接響應(yīng)
db > 208ms redis > 428ms local > 96ms
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
windows下使用redis requirepass認(rèn)證不起作用的解決方法
今天小編就為大家分享一篇windows下使用redis requirepass認(rèn)證不起作用的解決方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05redis數(shù)據(jù)類型及應(yīng)用場景知識點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是關(guān)于2020-02-02Redis如何在項(xiàng)目中合理使用經(jīng)驗(yàn)分享
這篇文章主要給大家介紹了關(guān)于Redis如何在項(xiàng)目中合理使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn)
本文主要介紹了Redis6 主從復(fù)制及哨兵機(jī)制的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Redis如何使用zset處理排行榜和計(jì)數(shù)問題
Redis的ZSET數(shù)據(jù)結(jié)構(gòu)非常適合處理排行榜和計(jì)數(shù)問題,它可以在高并發(fā)的點(diǎn)贊業(yè)務(wù)中高效地管理點(diǎn)贊的排名,并且由于ZSET的排序特性,可以輕松實(shí)現(xiàn)根據(jù)點(diǎn)贊數(shù)實(shí)時(shí)排序的功能2025-02-02