解讀緩存db redis local的取舍之道
前言
讓我們來聊一下數據緩存,它是如何為我們帶來快速的數據響應的。
你知道嗎,為了提高數據的讀取速度,我們通常會引入數據緩存。
但是,你知道嗎,不是所有的數據都適合緩存,有些數據更適合直接從數據庫查詢。
現在,我們就來一起討論一下,什么樣的數據適合直接從數據庫查詢,什么樣的數據適合從緩存中讀取。
這將有助于我們更好地利用緩存,提高系統(tǒng)的性能。讓我們開始吧!
一、影響因素
當涉及到數據查詢和緩存時,有幾個因素可以考慮來確定什么樣的數據適合直接從數據庫查詢,什么樣的數據適合從緩存中讀取。
- 訪問頻率:如果某個數據被頻繁訪問,且對實時性要求不高,那么將其緩存在內存中會顯著提高響應速度。這樣的數據可以是經常被查詢的熱點數據,比如網站的熱門文章、商品信息等。
- 數據更新頻率:如果某個數據經常發(fā)生更新,那么將其緩存可能導致緩存和數據庫中的數據不一致。對于這種情況,最好直接從數據庫中查詢最新數據。比如用戶個人信息、訂單狀態(tài)等經常變動的數據。
- 數據大?。狠^大的數據對象,如圖片、視頻等,由于其體積較大,將其緩存到內存中可能會占用大量資源。這種情況下,可以將這些數據存儲在分布式文件系統(tǒng)或云存儲中,并通過緩存存儲其訪問路徑或標識符。
- 數據一致性:一些數據在不同地方的多個副本可能會導致一致性問題。對于需要保持強一致性的數據,建議直接從數據庫查詢。而對于可以容忍一定程度的數據不一致的場景,可以考慮將數據緩存。
- 查詢復雜度:某些復雜的查詢操作可能會消耗大量的計算資源和時間,如果這些查詢結果需要頻繁訪問,可以將其緩存,避免重復計算,提高響應速度。
需要注意的是,數據緩存并非適用于所有情況。緩存的使用需要謹慎,需要權衡數據的實時性、一致性和存儲成本等方面的需求。此外,對于緩存數據的更新和失效策略也需要考慮,以確保緩存數據的準確性和及時性。
綜上所述,數據適合直接從數據庫查詢還是緩存讀取,取決于數據的訪問頻率、更新頻率、大小、一致性要求和查詢復雜度等因素。在實際應用中,需要根據具體情況進行綜合考慮和合理選擇。
二、db or redis or local
1.db
- 查詢復雜度低
- 字段少
- sql執(zhí)行效率高
- 實時性高
通常數據庫適合查詢字典類型數據,如類似 key value 鍵值對,數據更新頻繁,實時性高的數據。
對于sql效率高的查詢,redis查詢不一定比db查詢快。
2.redis
- 查詢復雜度高
- 字段相對不多
- 實時性低
Redis適合查詢復雜度較高、實時性要求較低的數據。當SQL查詢效率較低,或者需要進行字段code和value的轉換存儲時,Redis可以提供更高效的查詢方式。
不過,需要注意的是,Redis的主要瓶頸在于數據的序列化和反序列化過程。如果數據量較大,包含大量字段或者數據量巨大,那么Redis的查詢速度可能不一定比數據庫快,當然此時數據庫本身執(zhí)行效率也低。
在這種情況下,我們需要綜合考慮數據的復雜度、實時性要求以及數據量的大小,選擇最適合的查詢方式。
有時候,可能需要在數據庫和Redis之間進行權衡和折中,以找到最佳的性能和效率平衡點。因此,為了提高查詢速度,我們需要根據具體的業(yè)務需求和數據特性,選擇合適的存儲和查詢方案。
3. local
- 查詢復雜度高
- 字段多
- 實時性低
本地緩存通常是最快的。它可以在內存中直接讀取數據,速度非??臁H欢?,由于受限于內存大小,本地緩存的數據量是有限的。
對于那些數據庫和Redis難以處理的大型數據,我們可以考慮使用本地緩存。通過將一部分頻繁訪問的數據存儲在本地緩存中,可以大大提高系統(tǒng)的響應速度。
這樣,我們可以在不犧牲太多內存資源的情況下,快速獲取到需要的數據。當然,需要注意的是,由于本地緩存的數據是存儲在內存中的,所以在服務器重啟或緩存過期時,需要重新從數據庫或Redis中加載數據到本地緩存中。
因此,在使用本地緩存時,需要權衡數據的大小、更新頻率以及內存資源的限制,以獲得最佳的性能和可用性。
三、redisson 和 CaffeineCache 封裝
提供緩存查詢封裝,查詢不到時直接查數據庫后存入緩存。
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緩存中取數據,如果沒有,查數據后存入
*
* @param key redis key
* @param dataFetcher 獲取數據
* @param ttl 緩存時間
* @param timeUnit 緩存時間單位
* @param <T>
* @return 數據
*/
public <T> List<T> getCachedList(String key, Supplier<List<T>> dataFetcher, long ttl, TimeUnit timeUnit) {
if (ObjectUtil.isNotNull(redissonClient)) {
// 嘗試從緩存中獲取數據
List<T> cachedData = redissonClient.getList(key);
if (cachedData.size() > 0) {
// 緩存中有數據,直接返回
return cachedData;
} else {
// 緩存中沒有數據,調用數據提供者接口從數據庫中獲取
List<T> data = dataFetcher.get();
cachedData.clear();
cachedData.addAll(data);
// 將數據存入緩存,并設置存活時間
// 獲取 bucket 對象,為了設置過期時間
RBucket<List<T>> bucket = redissonClient.getBucket(key);
// 為整個列表設置過期時間
bucket.expire(ttl, timeUnit);
// 返回新獲取的數據
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 使用
啟動類添加:@Import({RedissonConfig.class})
直接引用:
@Resource
private RedissonClient redissonClient;
//緩存數據獲取
public List<MatMaterialsResp> listCache(ListQO qo) {
RedisCacheProvider cache = new RedisCacheProvider(redissonClient);
List<MatMaterialsResp> resps = cache.getCachedList("testList", () -> {
// 緩存數據查詢
}, 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 緩存條目數量 注意對象大小不要超過jvm內存
*/
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 帶權重
*
* @param maxSize
* @param weigher 權重
* @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();
}
// 如果你需要一個加載功能(當緩存miss時自動加載值),你可以使用這個方法
public V get(K key, Function<? super K, ? extends V> mappingFunction) {
return cache.get(key, mappingFunction);
}
// 添加獲取緩存統(tǒng)計信息的方法
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;
/**
* 無過期時間
* @param maxSize 緩存最大條數
*/
public LocalCacheProvider(long maxSize) {
cache = new CaffeineCache(maxSize);
}
/**
* 帶過期時間
* @param maxSize 緩存最大條數
* @param ttl 過期時間
* @param timeUnit 時間單位
*/
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);
//緩存數據獲取
public List<MatMaterialsResp> listLocalCache(ListQO qo) {
List<MatMaterialsResp> resps = cache.getCachedList("testList", (s) -> {
// 緩存數據查詢
});
return resps;
}
注意:Caffeine 實現的緩存占用 JVM 內存,小心 OutOfMemoryError
解決場景:
- 1.本地緩存適用不限制緩存大小,導致OOM,適合緩存小對象
- 2.本地緩存長時間存在,未及時清除無效緩存,導致內存占用資源浪費
- 3.防止人員api濫用, 未統(tǒng)一管理隨意使用,導致維護性差等等
總結
從前的無腦經驗,db查詢慢,redis緩存起來,redis真不一定快!
一個簡單性能測試:(測試響應時間均為二次查詢的大概時間)
1.前置條件: 一條數據轉換需要200ms,共5條數據,5個字段項,數據量大小463 B
db > 1s redis > 468ms local > 131ms
2.去除轉換時間,直接響應
db > 208ms redis > 428ms local > 96ms
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
windows下使用redis requirepass認證不起作用的解決方法
今天小編就為大家分享一篇windows下使用redis requirepass認證不起作用的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05

