Redis+Caffeine實(shí)現(xiàn)高效兩級緩存架構(gòu)的詳細(xì)指南
引言
在現(xiàn)代高并發(fā)系統(tǒng)中,緩存是提升系統(tǒng)性能的關(guān)鍵組件之一。傳統(tǒng)的單一緩存方案往往難以同時滿足高性能和高可用性的需求。本文將介紹如何結(jié)合 Redis 和 Caffeine 構(gòu)建一個高效的兩級緩存系統(tǒng),并通過三個版本的演進(jìn)展示如何逐步優(yōu)化代碼結(jié)構(gòu)。
兩級緩存架構(gòu)概述
兩級緩存通常由本地緩存(如 Caffeine)和分布式緩存(如 Redis)組成:
- 本地緩存(Caffeine):基于內(nèi)存,訪問速度極快,但容量有限且無法跨進(jìn)程共享
- 分布式緩存(Redis):可跨進(jìn)程共享,容量更大,但訪問速度相對較慢
通過結(jié)合兩者優(yōu)勢,我們可以構(gòu)建一個既快速又具備一致性的緩存系統(tǒng)。
兩級緩存的優(yōu)勢
性能優(yōu)勢
| 緩存類型 | 平均延遲 | 延遲波動范圍 |
|---|---|---|
| 本地緩存 | 0.05-1ms | 穩(wěn)定 |
| 遠(yuǎn)程緩存 | 1-10ms | 受網(wǎng)絡(luò)影響大 |
| 數(shù)據(jù)庫查詢 | 10-100ms | 取決于SQL復(fù)雜度 |
典型案例:某電商平臺商品詳情頁采用兩級緩存后:
- 單純Redis方案:P99響應(yīng)時間8ms
- 兩級緩存方案:P99響應(yīng)時間降至2ms
本地緩存的延遲是最低的,遠(yuǎn)遠(yuǎn)低于redis等遠(yuǎn)程緩存,而且本地緩存不受網(wǎng)絡(luò)的影響,所以延遲的波動范圍也是最穩(wěn)定的。所以,二級緩存在性能上有極大的優(yōu)勢。
系統(tǒng)穩(wěn)定性
1.抗流量洪峰能力
假如電商環(huán)境中出現(xiàn)了秒殺場景,或者促銷活動。會有大量的訪問到同一個商品或者優(yōu)惠券,以下是兩種情景:
純Redis方案,所有請求直達(dá)Redis,容易導(dǎo)致:
- 連接池耗盡
- 帶寬被打滿
- Redis CPU飆升
兩級緩存方案:
- 80%以上請求被本地緩存攔截
- Redis負(fù)載降低5-10倍
- 系統(tǒng)整體更平穩(wěn)
2.故障容忍度
由于Redis等遠(yuǎn)程緩存需要通過網(wǎng)絡(luò)連接,如果網(wǎng)絡(luò)出現(xiàn)異常,很容易出現(xiàn)訪問不到數(shù)據(jù)的情況。本地緩存則不存在網(wǎng)絡(luò)問題,所以對故障的容忍度是非常高的。
網(wǎng)絡(luò)分區(qū)場景測試:
模擬 機(jī)房網(wǎng)絡(luò)抖動(丟包率30%):
- 純Redis方案:錯誤率飆升到85%
- 兩級緩存方案:核心接口仍保持92%成功率
Caffeine簡介
Caffeine 是一個高性能的 Java 本地緩存庫,可以理解為 Java 版的"內(nèi)存臨時儲物柜"。它的核心特點(diǎn)可以用日常生活中的例子來理解:
就像一個智能的文件柜:
- 自動整理 - 會自己清理不常用的文件(基于大小或時間)
- 快速查找 - 比去檔案室(數(shù)據(jù)庫)找資料快100倍
- 空間管理 - 只保留最常用的1000份文件(可配置)
技術(shù)特點(diǎn):
基于 Google Guava 緩存改進(jìn)而來
讀寫性能接近 HashMap(O(1)時間復(fù)雜度)
提供多種淘汰策略:
// 按數(shù)量淘汰(保留最近使用的1000個) Caffeine.newBuilder().maximumSize(1000) // 按時間淘汰(數(shù)據(jù)保存1小時) Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS)
典型使用場景:
// 創(chuàng)建緩存(相當(dāng)于準(zhǔn)備一個儲物柜)
Cache<String, User> cache = Caffeine.newBuilder()
.maximumSize(100) // 最多存100個用戶
.expireAfterWrite(10, TimeUnit.MINUTES) // 10分鐘不用就清理
.build();
// 存數(shù)據(jù)(往柜子里放東西)
cache.put("user101", new User("張三"));
// 取數(shù)據(jù)(從柜子拿東西)
User user = cache.getIfPresent("user101");
// 取不到時自動加載(柜子沒有就去倉庫找)
User user = cache.get("user101", key -> userDao.getUser(key));
優(yōu)勢對比:
- 比 HashMap:支持自動清理和過期
- 比 Redis:快100倍(無需網(wǎng)絡(luò)IO)
- 比 Guava Cache:內(nèi)存效率更高,并發(fā)性能更好
注意事項:
- 僅適用于單機(jī)(不同服務(wù)器間的緩存不共享)
- 適合緩存不易變的數(shù)據(jù)(如系統(tǒng)配置)
- JVM重啟后數(shù)據(jù)會丟失(如需持久化需配合Redis)
版本演進(jìn)
版本1:直接侵入Service代碼
在第一個版本中,我們直接在 Service 層實(shí)現(xiàn)了兩級緩存邏輯:
@Override
public Order getOrderById(Integer id) {
String key = CacheConstant.ORDER + id;
return (Order) orderCache.get(key, k -> {
// 先查詢 Redis
Object obj = redisTemplate.opsForValue().get(key);
if (obj != null) {
log.info("get data from redis");
if (obj instanceof Order) {
return (Order) obj;
} else {
log.warn("Unexpected type from Redis, expected Order but got {}", obj.getClass());
}
}
// Redis沒有或類型不匹配則查詢 DB
log.info("get data from database");
Order myOrder = orderMapper.getOrderById(id);
redisTemplate.opsForValue().set(key, myOrder, 120, TimeUnit.SECONDS);
return myOrder;
});
}
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡單直接
- 緩存邏輯清晰可見
缺點(diǎn):
- 緩存代碼與業(yè)務(wù)代碼高度耦合
- 難以復(fù)用緩存邏輯
- 代碼重復(fù)率高
版本2:使用Spring Cache注解
在spring項目中,提供了CacheManager接口和一些注解,允許讓我們通過注解的方式來操作緩存。先來看一下常用的幾個注解說明:
1.@Cacheable- 緩存查詢
作用:將方法的返回值緩存起來,下次調(diào)用時直接返回緩存數(shù)據(jù),避免重復(fù)計算或查詢數(shù)據(jù)庫。
適用場景:
- 查詢方法(如
getUserById、findProduct) - 計算結(jié)果穩(wěn)定的方法
示例:
@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) {
// 如果緩存中沒有,才執(zhí)行此方法
return userRepository.findById(userId).orElse(null);
}
參數(shù)說明:
value/cacheNames:緩存名稱(如"users")key:緩存鍵(支持 SpEL 表達(dá)式,如#userId)condition:條件緩存(如condition = "#userId > 100")unless:排除某些返回值(如unless = "#result == null")
2.@CachePut- 更新緩存
作用:方法執(zhí)行后,更新緩存(通常用于 insert 或 update 操作)。
適用場景:
- 新增或修改數(shù)據(jù)后同步緩存
- 避免緩存與數(shù)據(jù)庫不一致
示例:
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user); // 更新數(shù)據(jù)庫后,自動更新緩存
}
注意:
與 @Cacheable 不同,@CachePut 一定會執(zhí)行方法,并更新緩存。
3.@CacheEvict- 刪除緩存
作用:方法執(zhí)行后,刪除緩存(適用于 delete 操作)。
適用場景:
- 數(shù)據(jù)刪除后清理緩存
- 緩存失效策略
示例:
@CacheEvict(value = "users", key = "#userId")
public void deleteUser(Long userId) {
userRepository.deleteById(userId); // 刪除數(shù)據(jù)庫數(shù)據(jù)后,自動刪除緩存
}
參數(shù)擴(kuò)展:
allEntries = true:清空整個緩存(如@CacheEvict(value = "users", allEntries = true))beforeInvocation = true:在方法執(zhí)行前刪除緩存(避免方法異常導(dǎo)致緩存未清理)
第二個版本利用了 Spring 的緩存注解來簡化代碼,如果要使用上面這幾個注解管理緩存的話,我們就不需要配置V1版本中的那個類型為Cache的Bean了,而是需要配置spring中的CacheManager的相關(guān)參數(shù),具體參數(shù)的配置和之前一樣。
注意,在改進(jìn)更新操作的時,這里和V1版本的代碼有一點(diǎn)區(qū)別,在之前的更新操作方法中,是沒有返回值的void類型,但是這里需要修改返回值的類型,否則會緩存一個空對象到緩存中對應(yīng)的key上。當(dāng)下次執(zhí)行查詢操作時,會直接返回空對象給調(diào)用方,而不會執(zhí)行方法中查詢數(shù)據(jù)庫或Redis的操作。
@Cacheable(value = "order", key = "#id")
@Override
public Order getOrderById(Integer id) {
String key = CacheConstant.ORDER + id;
// 先查詢 Redis
Object obj = redisTemplate.opsForValue().get(key);
if (obj != null) {
log.info("get data from redis");
if (obj instanceof Order) {
return (Order) obj;
} else {
log.warn("Unexpected type from Redis, expected Order but got {}", obj.getClass());
}
}
// Redis沒有或類型不匹配則查詢 DB
log.info("get data from database");
Order myOrder = orderMapper.getOrderById(id);
redisTemplate.opsForValue().set(key, myOrder, 120, TimeUnit.SECONDS);
return myOrder;
}
@Override
@CachePut(cacheNames = "order",key = "#order.id")
public Order updateOrder(Order order) {
log.info("update order data");
orderMapper.updateOrderById(order);
//修改 Redis
redisTemplate.opsForValue().set(CacheConstant.ORDER + order.getId(),
order, 120, TimeUnit.SECONDS);
return order;
}
@Override
@CacheEvict(cacheNames = "order",key = "#id")
public void deleteOrderById(Integer id) {
log.info("delete order");
orderMapper.deleteOrderById(id);
redisTemplate.delete(CacheConstant.ORDER + id);
}
改進(jìn)點(diǎn):
- 使用
@Cacheable注解管理 Caffeine 緩存 - 減少了部分重復(fù)代碼
- 緩存配置更加集中
遺留問題:
- Redis 操作仍需手動編寫
- 兩級緩存的同步邏輯仍需在業(yè)務(wù)代碼中處理
版本3:自定義注解+AOP實(shí)現(xiàn)
如果單純只是使用Cache注解進(jìn)行緩存,還是無法把Redis功能實(shí)現(xiàn)從server模塊中剝離出去。如果按照spring對cache注解的思路,我們可以自定義注解再利用AOP切片操作,把對應(yīng)的緩存功能切入到service的代碼中,就能實(shí)現(xiàn)二者之間的解耦。
首先,需要定義一個注解:
/**
* 雙緩存注解,用于標(biāo)記需要使用雙緩存(通常為本地緩存和遠(yuǎn)程緩存)的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoubleCache {
/**
* 指定緩存的名稱
* @return 緩存名稱
*/
String cacheName();
/**
* 指定緩存的鍵,支持Spring EL表達(dá)式
* @return 緩存鍵
*/
String key(); //支持springEl表達(dá)式
/**
* 指定二級緩存的超時時間,單位默認(rèn)根據(jù)實(shí)現(xiàn)確定(通常為秒)
* 默認(rèn)值為120
* @return 二級緩存超時時間
*/
long l2TimeOut() default 120;
/**
* 指定緩存類型
* 默認(rèn)值為 CacheType.FULL
* @return 緩存類型
*/
CacheType type() default CacheType.FULL;
}
定義一個枚舉類型的變量,表示緩存操作的類型:
public enum CacheType {
FULL, //存取
PUT, //只存
DELETE //刪除
}
如果要支持springEL的表達(dá)式,還需要一個工具類來解析springEI的表達(dá)式:
public class SpelExpressionUtils {
/**
* 解析 SpEL 表達(dá)式并替換變量
* @param elString 表達(dá)式(如 "user.name")
* @param map 變量鍵值對
* @return 解析后的字符串
*/
public static String parse(String elString, TreeMap<String, Object> map) {
// 將輸入的表達(dá)式包裝為 SpEL 表達(dá)式格式
elString = String.format("#{%s}", elString);
// 創(chuàng)建 SpEL 表達(dá)式解析器
ExpressionParser parser = new SpelExpressionParser();
// 創(chuàng)建標(biāo)準(zhǔn)的評估上下文,用于存儲變量
EvaluationContext context = new StandardEvaluationContext();
// 將傳入的變量鍵值對設(shè)置到評估上下文中
map.forEach(context::setVariable);
// 使用解析器解析表達(dá)式,使用模板解析上下文
Expression expression = parser.parseExpression(elString, new TemplateParserContext());
// 在指定上下文中計算表達(dá)式的值,并將結(jié)果轉(zhuǎn)換為字符串返回
return expression.getValue(context, String.class);
}
}
定義切片,在切片操作中來實(shí)現(xiàn)Caffeine和Redis的緩存操作:
@Slf4j
@Component
@Aspect
@AllArgsConstructor
public class CacheAspect {
private final Cache<String, Object> cache;
private final RedisTemplate<String, Object> redisTemplate;
/**
* 定義切點(diǎn),匹配使用了 @DoubleCache 注解的方法
*/
@Pointcut("@annotation(com.example.redis_caffeine.annonation.DoubleCache)")
public void cacheAspect() {}
/**
* 環(huán)繞通知,處理緩存的讀寫、更新和刪除操作
*
* @param point 切入點(diǎn)對象,包含方法執(zhí)行的相關(guān)信息
* @return 方法執(zhí)行的返回結(jié)果
* @throws Throwable 方法執(zhí)行過程中可能拋出的異常
*/
@Around("cacheAspect()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
try {
// 獲取方法簽名和方法對象
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 解析參數(shù),將參數(shù)名和參數(shù)值存入 TreeMap 中
String[] paramNames = signature.getParameterNames();
Object[] args = point.getArgs();
TreeMap<String, Object> treeMap = new TreeMap<>();
for (int i = 0; i < paramNames.length; i++) {
treeMap.put(paramNames[i], args[i]);
}
// 獲取方法上的 @DoubleCache 注解
DoubleCache annotation = method.getAnnotation(DoubleCache.class);
// 解析 SpEL 表達(dá)式,得到最終的 key 片段
String elResult = SpelExpressionUtils.parse(annotation.key(), treeMap);
// 拼接完整的緩存 key
String realKey = annotation.cacheName() + CacheConstant.ORDER + elResult;
// 處理強(qiáng)制更新操作
if (annotation.type() == CacheType.PUT) {
// 執(zhí)行目標(biāo)方法
Object object = point.proceed();
// 將結(jié)果存入 Redis,并設(shè)置過期時間
redisTemplate.opsForValue().set(realKey, object, annotation.l2TimeOut(), TimeUnit.SECONDS);
// 將結(jié)果存入 Caffeine 緩存
cache.put(realKey, object);
return object;
}
// 處理刪除操作
if (annotation.type() == CacheType.DELETE) {
// 從 Redis 中刪除緩存
redisTemplate.delete(realKey);
// 從 Caffeine 緩存中刪除緩存
cache.invalidate(realKey);
return point.proceed();
}
// 優(yōu)先從 Caffeine 緩存中獲取數(shù)據(jù)
Object caffeineCache = cache.getIfPresent(realKey);
if (caffeineCache != null) {
log.info("get data from caffeine");
return caffeineCache;
}
// 其次從 Redis 中獲取數(shù)據(jù)
Object redisCache = redisTemplate.opsForValue().get(realKey);
if (redisCache != null) {
log.info("get data from redis");
// 將從 Redis 中獲取的數(shù)據(jù)存入 Caffeine 緩存
cache.put(realKey, redisCache);
return redisCache;
}
// 最后查詢數(shù)據(jù)庫
log.info("get data from database");
Object object = point.proceed();
if (object != null) {
// 將數(shù)據(jù)庫查詢結(jié)果存入 Redis,并設(shè)置過期時間
redisTemplate.opsForValue().set(realKey, object, annotation.l2TimeOut(), TimeUnit.SECONDS);
// 將數(shù)據(jù)庫查詢結(jié)果存入 Caffeine 緩存
cache.put(realKey, object);
}
return object;
} catch (Exception e) {
// 記錄緩存切面處理過程中的錯誤
log.error("Cache aspect error", e);
throw e;
}
}
}
以上操作的主要工作總結(jié)下來是:
- 定義切點(diǎn):匹配使用
@DoubleCache注解的方法 - 參數(shù)解析與鍵生成:提取方法參數(shù),解析 SpEL 表達(dá)式生成緩存鍵
- 緩存更新策略:PUT 類型執(zhí)行方法后同步更新 Redis 和本地緩存
- 緩存刪除策略:DELETE 類型先刪除 Redis 和本地緩存,再執(zhí)行方法
- 多級查詢策略:優(yōu)先查本地緩存 → Redis → 數(shù)據(jù)庫,查詢結(jié)果寫入兩級緩存
執(zhí)行操作流程,以查詢操作為例:
攔截被 @DoubleCache 標(biāo)記的目標(biāo)方法
生成緩存鍵 realKey
依次查詢Caffeine → Redis → 數(shù)據(jù)庫
將數(shù)據(jù)庫結(jié)果寫入兩級緩存并返回
若觸發(fā)更新/刪除操作,則同步清理或更新緩存
/**
* 根據(jù)訂單ID獲取訂單信息
* 使用 @DoubleCache 注解,類型為 FULL,會執(zhí)行完整的緩存操作邏輯
* @param id 訂單ID
* @return 訂單對象
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.FULL)
public Order getOrderById(Integer id) {
return orderMapper.getOrderById(id);
}
/**
* 更新訂單信息
* 使用 @DoubleCache 注解,類型為 PUT,會執(zhí)行緩存更新操作
* @param order 訂單對象
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.PUT)
public void updateOrder(Order order) {
orderMapper.updateOrderById(order);
}
/**
* 根據(jù)訂單ID刪除訂單信息
* 使用 @DoubleCache 注解,類型為 DELETE,會執(zhí)行緩存刪除操作
* @param id 訂單ID
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.DELETE)
public void deleteOrderById(Integer id) {
orderMapper.deleteOrderById(id);
}
核心注解:
@DoubleCache:自定義注解,用于標(biāo)記需要兩級緩存的方法CacheType:枚舉,定義緩存操作類型(FULL, PUT, DELETE)
AOP實(shí)現(xiàn)要點(diǎn):
- 解析注解參數(shù)
- 根據(jù)操作類型執(zhí)行不同的緩存邏輯
- 處理緩存穿透、雪崩等問題
- 保證兩級緩存的一致性
優(yōu)勢:
- 業(yè)務(wù)代碼完全專注于業(yè)務(wù)邏輯
- 緩存邏輯集中管理,便于維護(hù)
- 注解配置靈活,可適應(yīng)不同場景
- 代碼簡潔,可讀性高
關(guān)鍵配置
Caffeine 配置
@Configuration
@EnableCaching
public class CaffeineConfig {
//-----------------------------V1------V3-----------------------------------
@Bean
public Cache<String, Object> orderCache() {
return Caffeine.newBuilder()
.initialCapacity(128)
.maximumSize(1024)
.expireAfterWrite(60, TimeUnit.SECONDS)
.build();
}
//-----------------------------V2------------------------------------------
@Bean
public CacheManager cacheManager(){
CaffeineCacheManager cacheManager=new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(128)
.maximumSize(1024)
.expireAfterWrite(60, TimeUnit.SECONDS));
return cacheManager;
}
}
Redis 配置
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 創(chuàng)建 ObjectMapper 實(shí)例,用于 JSON 序列化和反序列化
ObjectMapper objectMapper = new ObjectMapper();
// 注冊 JavaTimeModule,用于支持 Java 8 日期時間類型的序列化和反序列化
objectMapper.registerModule(new JavaTimeModule());
// 禁用將日期寫成時間戳的功能
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 啟用默認(rèn)類型信息,用于處理多態(tài)類型的序列化和反序列化
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
// 創(chuàng)建 GenericJackson2JsonRedisSerializer 實(shí)例,使用配置好的 ObjectMapper
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
性能優(yōu)化建議
合理設(shè)置緩存過期時間:
- 本地緩存過期時間應(yīng)短于 Redis 緩存
- 根據(jù)數(shù)據(jù)更新頻率調(diào)整過期策略
緩存穿透防護(hù):
- 對空結(jié)果也進(jìn)行緩存
- 使用布隆過濾器
緩存雪崩防護(hù):
- 設(shè)置隨機(jī)過期時間
- 實(shí)現(xiàn)熔斷機(jī)制
一致性保證:
- 考慮使用消息隊列同步多節(jié)點(diǎn)本地緩存
- 對于關(guān)鍵數(shù)據(jù),可采用"先更新數(shù)據(jù)庫,再刪除緩存"策略
總結(jié)
通過三個版本的演進(jìn),我們實(shí)現(xiàn)了一個從強(qiáng)耦合到完全解耦的兩級緩存系統(tǒng)。最終版本利用自定義注解和 AOP 技術(shù),既保持了代碼的簡潔性,又提供了強(qiáng)大的緩存功能。這種架構(gòu)特別適合讀多寫少、對性能要求較高的場景。
在實(shí)際應(yīng)用中,還需要根據(jù)具體業(yè)務(wù)特點(diǎn)調(diào)整緩存策略,并做好監(jiān)控和指標(biāo)收集,以便持續(xù)優(yōu)化緩存效果。Redis + Caffeine 實(shí)現(xiàn)高效的兩級緩存架構(gòu)
以上就是Redis+Caffeine實(shí)現(xiàn)高效兩級緩存架構(gòu)的詳細(xì)指南的詳細(xì)內(nèi)容,更多關(guān)于Redis Caffeine兩級緩存的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis 緩存實(shí)現(xiàn)存儲和讀取歷史搜索關(guān)鍵字的操作方法
這篇文章主要介紹了Redis 緩存實(shí)現(xiàn)存儲和讀取歷史搜索關(guān)鍵字,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
Redis?鍵值對(key-value)數(shù)據(jù)庫實(shí)現(xiàn)方法
Redis 的鍵值對中的 key 就是字符串對象,而 value 可以是字符串對象,也可以是集合數(shù)據(jù)類型的對象,比如 List 對象,Hash 對象、Set 對象和 Zset 對象,這篇文章主要介紹了Redis?鍵值對數(shù)據(jù)庫是怎么實(shí)現(xiàn)的,需要的朋友可以參考下2024-05-05
Redis源碼解析sds字符串實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Redis源碼解析sds字符串實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Redis 7持久化RDB和AOF的原理機(jī)制講解(圖文教程)
Redis是一個基于內(nèi)存的數(shù)據(jù)庫,由于內(nèi)存的易失性(斷電后數(shù)據(jù)會丟失),Redis提供了持久化機(jī)制:將內(nèi)存中的數(shù)據(jù)保存到磁盤中,確保數(shù)據(jù)在Redis服務(wù)重啟或崩潰后能夠恢復(fù),通過持久化,可以避免數(shù)據(jù)丟失,提高數(shù)據(jù)的可靠性,Redis提供兩種持久化方式:RDB和AOF2026-01-01

