Redis+Caffeine兩級(jí)緩存的實(shí)現(xiàn)
在高性能的服務(wù)架構(gòu)設(shè)計(jì)中,緩存是一個(gè)不可或缺的環(huán)節(jié)。在實(shí)際的項(xiàng)目中,我們通常會(huì)將一些熱點(diǎn)數(shù)據(jù)存儲(chǔ)到Redis或MemCache這類緩存中間件中,只有當(dāng)緩存的訪問(wèn)沒(méi)有命中時(shí)再查詢數(shù)據(jù)庫(kù)。在提升訪問(wèn)速度的同時(shí),也能降低數(shù)據(jù)庫(kù)的壓力。
隨著不斷的發(fā)展,這一架構(gòu)也產(chǎn)生了改進(jìn),在一些場(chǎng)景下可能單純使用Redis類的遠(yuǎn)程緩存已經(jīng)不夠了,還需要進(jìn)一步配合本地緩存使用,例如Guava cache或Caffeine,從而再次提升程序的響應(yīng)速度與服務(wù)性能。于是,就產(chǎn)生了使用本地緩存作為一級(jí)緩存,再加上遠(yuǎn)程緩存作為二級(jí)緩存的兩級(jí)緩存架構(gòu)。
在先不考慮并發(fā)等復(fù)雜問(wèn)題的情況下,兩級(jí)緩存的訪問(wèn)流程可以用下面這張圖來(lái)表示:
優(yōu)點(diǎn)與問(wèn)題
那么,使用兩級(jí)緩存相比單純使用遠(yuǎn)程緩存,具有什么優(yōu)勢(shì)呢?
- 本地緩存基于本地環(huán)境的內(nèi)存,訪問(wèn)速度非常快,對(duì)于一些變更頻率低、實(shí)時(shí)性要求低的數(shù)據(jù),可以放在本地緩存中,提升訪問(wèn)速度;
- 使用本地緩存能夠減少和Redis類的遠(yuǎn)程緩存間的數(shù)據(jù)交互,減少網(wǎng)絡(luò)I/O開(kāi)銷,降低這一過(guò)程中在網(wǎng)絡(luò)通信上的耗時(shí) ;
但是在設(shè)計(jì)中,還是要考慮一些問(wèn)題的,例如數(shù)據(jù)一致性問(wèn)題。首先,兩級(jí)緩存與數(shù)據(jù)庫(kù)的數(shù)據(jù)要保持一致,一旦數(shù)據(jù)發(fā)生了修改,在修改數(shù)據(jù)庫(kù)的同時(shí),本地緩存、遠(yuǎn)程緩存應(yīng)該同步更新。
另外,如果是分布式環(huán)境下,一級(jí)緩存之間也會(huì)存在一致性問(wèn)題,當(dāng)一個(gè)節(jié)點(diǎn)下的本地緩存修改后,需要通知其他節(jié)點(diǎn)也刷新本地緩存中的數(shù)據(jù),否則會(huì)出現(xiàn)讀取到過(guò)期數(shù)據(jù)的情況,這一問(wèn)題可以通過(guò)類似于Redis中的發(fā)布/訂閱功能解決。
此外,緩存的過(guò)期時(shí)間、過(guò)期策略以及多線程訪問(wèn)的問(wèn)題也都需要考慮進(jìn)去,不過(guò)我們今天暫時(shí)先不考慮這些問(wèn)題,先看一下如何簡(jiǎn)單高效的在代碼中實(shí)現(xiàn)兩級(jí)緩存的管理。
準(zhǔn)備工作
在簡(jiǎn)單梳理了一下要面對(duì)的問(wèn)題后,下面開(kāi)始兩級(jí)緩存的代碼實(shí)戰(zhàn),我們整合號(hào)稱最強(qiáng)本地緩存的Caffeine作為一級(jí)緩存、性能之王的Redis作為二級(jí)緩存。首先建一個(gè)springboot項(xiàng)目,引入緩存要用到的相關(guān)的依賴:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.8.1</version> </dependency>
在application.yml中配置Redis的連接信息:
spring: redis: host: 127.0.0.1 port: 6379 database: 0 timeout: 10000ms lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0
在下面的例子中,我們將使用RedisTemplate來(lái)對(duì)redis進(jìn)行讀寫(xiě)操作,RedisTemplate使用前需要配置一下ConnectionFactory和序列化方式,這一過(guò)程比較簡(jiǎn)單就不貼出代碼了。
下面我們?cè)趩螜C(jī)環(huán)境下,將按照對(duì)業(yè)務(wù)侵入性的不同程度,分三個(gè)版本來(lái)實(shí)現(xiàn)兩級(jí)緩存的使用。
V1.0版本
我們可以通過(guò)手動(dòng)操作Caffeine中的Cache對(duì)象來(lái)緩存數(shù)據(jù),它是一個(gè)類似Map的數(shù)據(jù)結(jié)構(gòu),以key作為索引,value存儲(chǔ)數(shù)據(jù)。在使用Cache前,需要先配置一下相關(guān)參數(shù):
@Configuration public class CaffeineConfig { @Bean public Cache<String,Object> caffeineCache(){ return Caffeine.newBuilder() .initialCapacity(128)//初始大小 .maximumSize(1024)//最大數(shù)量 .expireAfterWrite(60, TimeUnit.SECONDS)//過(guò)期時(shí)間 .build(); } }
簡(jiǎn)單解釋一下Cache相關(guān)的幾個(gè)參數(shù)的意義:
- initialCapacity:初始緩存空大小;
- maximumSize:緩存的最大數(shù)量,設(shè)置這個(gè)值可以避免出現(xiàn)內(nèi)存溢出;
- expireAfterWrite:指定緩存的過(guò)期時(shí)間,是最后一次寫(xiě)操作后的一個(gè)時(shí)間,這里;
此外,緩存的過(guò)期策略也可以通過(guò)expireAfterAccess或refreshAfterWrite指定。
在創(chuàng)建完成Cache后,我們就可以在業(yè)務(wù)代碼中注入并使用它了。在沒(méi)有使用任何緩存前,一個(gè)只有簡(jiǎn)單的Service層代碼是下面這樣的,只有crud操作:
@Service @AllArgsConstructor public class OrderServiceImpl implements OrderService { private final OrderMapper orderMapper; @Override public Order getOrderById(Long id) { Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>() .eq(Order::getId, id)); return order; } @Override public void updateOrder(Order order) { orderMapper.updateById(order); } @Override public void deleteOrder(Long id) { orderMapper.deleteById(id); } }
接下來(lái),對(duì)上面的OrderService進(jìn)行改造,在執(zhí)行正常業(yè)務(wù)外再加上操作兩級(jí)緩存的代碼,先看改造后的查詢操作:
public Order getOrderById(Long id) { String key = CacheConstant.ORDER + id; Order order = (Order) cache.get(key, k -> { //先查詢 Redis Object obj = redisTemplate.opsForValue().get(k); if (Objects.nonNull(obj)) { log.info("get data from redis"); return obj; } // Redis沒(méi)有則查詢 DB log.info("get data from database"); Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>() .eq(Order::getId, id)); redisTemplate.opsForValue().set(k, myOrder, 120, TimeUnit.SECONDS); return myOrder; }); return order; }
在Cache的get方法中,會(huì)先從緩存中進(jìn)行查找,如果找到緩存的值那么直接返回。如果沒(méi)有找到則執(zhí)行后面的方法,并把結(jié)果加入到緩存中。
因此上面的邏輯就是先查找Caffeine中的緩存,沒(méi)有的話查找Redis,Redis再不命中則查詢數(shù)據(jù)庫(kù),寫(xiě)入Redis緩存的操作需要手動(dòng)寫(xiě)入,而Caffeine的寫(xiě)入由get方法自己完成。
在上面的例子中,設(shè)置Caffeine的過(guò)期時(shí)間為60秒,而Redis的過(guò)期時(shí)間為120秒,下面進(jìn)行測(cè)試,首先看第一次接口調(diào)用時(shí),進(jìn)行了數(shù)據(jù)庫(kù)的查詢:
而在之后60秒內(nèi)訪問(wèn)接口時(shí),都沒(méi)有打印打任何sql或自定義的日志內(nèi)容,說(shuō)明接口沒(méi)有查詢Redis或數(shù)據(jù)庫(kù),直接從Caffeine中讀取了緩存。
等到距離第一次調(diào)用接口進(jìn)行緩存的60秒后,再次調(diào)用接口:
可以看到這時(shí)從Redis中讀取了數(shù)據(jù),因?yàn)檫@時(shí)Caffeine中的緩存已經(jīng)過(guò)期了,但是Redis中的緩存沒(méi)有過(guò)期仍然可用。
下面再來(lái)看一下修改操作,代碼在原先的基礎(chǔ)上添加了手動(dòng)修改Redis和Caffeine緩存的邏輯:
public void updateOrder(Order order) { log.info("update order data"); String key=CacheConstant.ORDER + order.getId(); orderMapper.updateById(order); //修改 Redis redisTemplate.opsForValue().set(key,order,120, TimeUnit.SECONDS); // 修改本地緩存 cache.put(key,order); }
看一下下面圖中接口的調(diào)用、以及緩存的刷新過(guò)程??梢钥吹皆诟聰?shù)據(jù)后,同步刷新了緩存中的內(nèi)容,在之后的訪問(wèn)接口時(shí)不查詢數(shù)據(jù)庫(kù),也可以拿到正確的結(jié)果:
最后再來(lái)看一下刪除操作,在刪除數(shù)據(jù)的同時(shí),手動(dòng)移除Reids和Caffeine中的緩存:
public void deleteOrder(Long id) { log.info("delete order"); orderMapper.deleteById(id); String key= CacheConstant.ORDER + id; redisTemplate.delete(key); cache.invalidate(key); }
我們?cè)趧h除某個(gè)緩存后,再次調(diào)用之前的查詢接口時(shí),又會(huì)出現(xiàn)重新查詢數(shù)據(jù)庫(kù)的情況:
簡(jiǎn)單地演示到此為止,可以看到上面這種使用緩存的方式,雖然看起來(lái)沒(méi)什么大問(wèn)題,但是對(duì)代碼的入侵性比較強(qiáng)。在業(yè)務(wù)處理的過(guò)程中要由我們頻繁地操作兩級(jí)緩存,會(huì)給開(kāi)發(fā)人員帶來(lái)很大的負(fù)擔(dān)。那么,有什么方法能夠簡(jiǎn)化這一過(guò)程呢?我們看看下一個(gè)版本。
V2.0版本
在spring項(xiàng)目中,提供了CacheManager接口和一些注解,允許讓我們通過(guò)注解的方式來(lái)操作緩存。先來(lái)看一下常用的幾個(gè)注解說(shuō)明:
- @Cacheable:根據(jù)鍵從緩存中取值,如果緩存存在,那么獲取緩存成功之后,直接返回這個(gè)緩存的結(jié)果。如果緩存不存在,那么執(zhí)行方法,并將結(jié)果放入緩存中。
- @CachePut:不管之前的鍵對(duì)應(yīng)的緩存是否存在,都執(zhí)行方法,并將結(jié)果強(qiáng)制放入緩存。
- @CacheEvict:執(zhí)行完方法后,會(huì)移除掉緩存中的數(shù)據(jù)。
如果要使用上面這幾個(gè)注解管理緩存的話,我們就不需要配置V1版本中的那個(gè)類型為Cache的Bean了,而是需要配置spring中的CacheManager的相關(guān)參數(shù),具體參數(shù)的配置和之前一樣:
@Configuration public class CacheManagerConfig { @Bean public CacheManager cacheManager(){ CaffeineCacheManager cacheManager=new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .initialCapacity(128) .maximumSize(1024) .expireAfterWrite(60, TimeUnit.SECONDS)); return cacheManager; } }
然后在啟動(dòng)類上再添加上@EnableCaching注解,就可以在項(xiàng)目中基于注解來(lái)使用Caffeine的緩存支持了。下面,再次對(duì)Service層代碼進(jìn)行改造。
首先,還是改造查詢方法,在方法上添加@Cacheable注解:
@Cacheable(value = "order",key = "#id") //@Cacheable(cacheNames = "order",key = "#p0") public Order getOrderById(Long id) { String key= CacheConstant.ORDER + id; //先查詢 Redis Object obj = redisTemplate.opsForValue().get(key); if (Objects.nonNull(obj)){ log.info("get data from redis"); return (Order) obj; } // Redis沒(méi)有則查詢 DB log.info("get data from database"); Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>() .eq(Order::getId, id)); redisTemplate.opsForValue().set(key,myOrder,120, TimeUnit.SECONDS); return myOrder; }
@Cacheable注解的屬性多達(dá)9個(gè),好在我們?nèi)粘J褂脮r(shí)只需要配置兩個(gè)常用的就可以了。其中value和cacheNames互為別名關(guān)系,表示當(dāng)前方法的結(jié)果會(huì)被緩存在哪個(gè)Cache上,應(yīng)用中通過(guò)cacheName來(lái)對(duì)Cache進(jìn)行隔離,每個(gè)cacheName對(duì)應(yīng)一個(gè)Cache實(shí)現(xiàn)。value和cacheNames可以是一個(gè)數(shù)組,綁定多個(gè)Cache。
而另一個(gè)重要屬性key,用來(lái)指定緩存方法的返回結(jié)果時(shí)對(duì)應(yīng)的key,這個(gè)屬性支持使用SpringEL表達(dá)式。通常情況下,我們可以使用下面幾種方式作為key:
#參數(shù)名 #參數(shù)對(duì)象.屬性名 #p參數(shù)對(duì)應(yīng)下標(biāo)
在上面的代碼中,我們看到添加了@Cacheable注解后,在代碼中只需要保留原有的業(yè)務(wù)處理邏輯和操作Redis部分的代碼即可,Caffeine部分的緩存就交給spring處理了。
下面,我們?cè)賮?lái)改造一下更新方法,同樣,使用@CachePut注解后移除掉手動(dòng)更新Cache的操作:
@CachePut(cacheNames = "order",key = "#order.id") public Order updateOrder(Order order) { log.info("update order data"); orderMapper.updateById(order); //修改 Redis redisTemplate.opsForValue().set(CacheConstant.ORDER + order.getId(), order, 120, TimeUnit.SECONDS); return order; }
注意,這里和V1版本的代碼有一點(diǎn)區(qū)別,在之前的更新操作方法中,是沒(méi)有返回值的void類型,但是這里需要修改返回值的類型,否則會(huì)緩存一個(gè)空對(duì)象到緩存中對(duì)應(yīng)的key上。當(dāng)下次執(zhí)行查詢操作時(shí),會(huì)直接返回空對(duì)象給調(diào)用方,而不會(huì)執(zhí)行方法中查詢數(shù)據(jù)庫(kù)或Redis的操作。
最后,刪除方法的改造就很簡(jiǎn)單了,使用@CacheEvict注解,方法中只需要?jiǎng)h除Redis中的緩存即可:
@CacheEvict(cacheNames = "order",key = "#id") public void deleteOrder(Long id) { log.info("delete order"); orderMapper.deleteById(id); redisTemplate.delete(CacheConstant.ORDER + id); }
可以看到,借助spring中的CacheManager和Cache相關(guān)的注解,對(duì)V1版本的代碼經(jīng)過(guò)改進(jìn)后,可以把全手動(dòng)操作兩級(jí)緩存的強(qiáng)入侵代碼方式,改進(jìn)為本地緩存交給spring管理,Redis緩存手動(dòng)修改的半入侵方式。那么,還能進(jìn)一步改造,使之成為對(duì)業(yè)務(wù)代碼完全無(wú)入侵的方式嗎?
V3.0版本
模仿spring通過(guò)注解管理緩存的方式,我們也可以選擇自定義注解,然后在切面中處理緩存,從而將對(duì)業(yè)務(wù)代碼的入侵降到最低。
首先定義一個(gè)注解,用于添加在需要操作緩存的方法上:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DoubleCache { String cacheName(); String key(); //支持springEl表達(dá)式 long l2TimeOut() default 120; CacheType type() default CacheType.FULL; }
我們使用cacheName + key作為緩存的真正key(僅存在一個(gè)Cache中,不做CacheName隔離),l2TimeOut為可以設(shè)置的二級(jí)緩存Redis的過(guò)期時(shí)間,type是一個(gè)枚舉類型的變量,表示操作緩存的類型,枚舉類型定義如下:
public enum CacheType { FULL, //存取 PUT, //只存 DELETE //刪除 }
因?yàn)橐筴ey支持springEl表達(dá)式,所以需要寫(xiě)一個(gè)方法,使用表達(dá)式解析器解析參數(shù):
public static String parse(String elString, TreeMap<String,Object> map){ elString=String.format("#{%s}",elString); //創(chuàng)建表達(dá)式解析器 ExpressionParser parser = new SpelExpressionParser(); //通過(guò)evaluationContext.setVariable可以在上下文中設(shè)定變量。 EvaluationContext context = new StandardEvaluationContext(); map.entrySet().forEach(entry-> context.setVariable(entry.getKey(),entry.getValue()) ); //解析表達(dá)式 Expression expression = parser.parseExpression(elString, new TemplateParserContext()); //使用Expression.getValue()獲取表達(dá)式的值,這里傳入了Evaluation上下文 String value = expression.getValue(context, String.class); return value; }
參數(shù)中的elString對(duì)應(yīng)的就是注解中key的值,map是將原方法的參數(shù)封裝后的結(jié)果。簡(jiǎn)單進(jìn)行一下測(cè)試:
public void test() { String elString="#order.money"; String elString2="#user"; String elString3="#p0"; TreeMap<String,Object> map=new TreeMap<>(); Order order = new Order(); order.setId(111L); order.setMoney(123D); map.put("order",order); map.put("user","Hydra"); String val = parse(elString, map); String val2 = parse(elString2, map); String val3 = parse(elString3, map); System.out.println(val); System.out.println(val2); System.out.println(val3); }
執(zhí)行結(jié)果如下,可以看到支持按照參數(shù)名稱、參數(shù)對(duì)象的屬性名稱讀取,但是不支持按照參數(shù)下標(biāo)讀取,暫時(shí)留個(gè)小坑以后再處理。
123.0
Hydra
null
至于Cache相關(guān)參數(shù)的配置,我們沿用V1版本中的配置即可。準(zhǔn)備工作做完了,下面我們定義切面,在切面中操作Cache來(lái)讀寫(xiě)Caffeine的緩存,操作RedisTemplate讀寫(xiě)Redis緩存。
@Slf4j @Component @Aspect @AllArgsConstructor public class CacheAspect { private final Cache cache; private final RedisTemplate redisTemplate; @Pointcut("@annotation(com.cn.dc.annotation.DoubleCache)") public void cacheAspect() { } @Around("cacheAspect()") public Object doAround(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); //拼接解析springEl表達(dá)式的map 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 annotation = method.getAnnotation(DoubleCache.class); String elResult = ElParser.parse(annotation.key(), treeMap); String realKey = annotation.cacheName() + CacheConstant.COLON + elResult; //強(qiáng)制更新 if (annotation.type()== CacheType.PUT){ Object object = point.proceed(); redisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS); cache.put(realKey, object); return object; } //刪除 else if (annotation.type()== CacheType.DELETE){ redisTemplate.delete(realKey); cache.invalidate(realKey); return point.proceed(); } //讀寫(xiě),查詢Caffeine Object caffeineCache = cache.getIfPresent(realKey); if (Objects.nonNull(caffeineCache)) { log.info("get data from caffeine"); return caffeineCache; } //查詢Redis Object redisCache = redisTemplate.opsForValue().get(realKey); if (Objects.nonNull(redisCache)) { log.info("get data from redis"); cache.put(realKey, redisCache); return redisCache; } log.info("get data from database"); Object object = point.proceed(); if (Objects.nonNull(object)){ //寫(xiě)入Redis redisTemplate.opsForValue().set(realKey, object,annotation.l2TimeOut(), TimeUnit.SECONDS); //寫(xiě)入Caffeine cache.put(realKey, object); } return object; } }
切面中主要做了下面幾件工作:
- 通過(guò)方法的參數(shù),解析注解中key的springEl表達(dá)式,組裝真正緩存的key。
- 根據(jù)操作緩存的類型,分別處理存取、只存、刪除緩存操作。
- 刪除和強(qiáng)制更新緩存的操作,都需要執(zhí)行原方法,并進(jìn)行相應(yīng)的緩存刪除或更新操作。
- 存取操作前,先檢查緩存中是否有數(shù)據(jù),如果有則直接返回,沒(méi)有則執(zhí)行原方法,并將結(jié)果存入緩存。
修改Service層代碼,代碼中只保留原有業(yè)務(wù)代碼,再添加上我們自定義的注解就可以了:
@DoubleCache(cacheName = "order", key = "#id", type = CacheType.FULL) public Order getOrderById(Long id) { Order myOrder = orderMapper.selectOne(new LambdaQueryWrapper<Order>() .eq(Order::getId, id)); return myOrder; } @DoubleCache(cacheName = "order",key = "#order.id", type = CacheType.PUT) public Order updateOrder(Order order) { orderMapper.updateById(order); return order; } @DoubleCache(cacheName = "order",key = "#id", type = CacheType.DELETE) public void deleteOrder(Long id) { orderMapper.deleteById(id); }
到這里,基于切面操作緩存的改造就完成了,Service的代碼也瞬間清爽了很多,讓我們可以繼續(xù)專注于業(yè)務(wù)邏輯處理,而不用費(fèi)心去操作兩級(jí)緩存了。
總結(jié)本文按照對(duì)業(yè)務(wù)入侵的遞減程度,依次介紹了三種管理兩級(jí)緩存的方法。至于在項(xiàng)目中是否需要使用二級(jí)緩存,需要考慮自身業(yè)務(wù)情況,如果Redis這種遠(yuǎn)程緩存已經(jīng)能夠滿足你的業(yè)務(wù)需求,那么就沒(méi)有必要再使用本地緩存了。畢竟實(shí)際使用起來(lái)遠(yuǎn)沒(méi)有那么簡(jiǎn)單,本文中只是介紹了最基礎(chǔ)的使用,實(shí)際中的并發(fā)問(wèn)題、事務(wù)的回滾問(wèn)題都需要考慮,還需要思考什么數(shù)據(jù)適合放在一級(jí)緩存、什么數(shù)據(jù)適合放在二級(jí)緩存等等的其他問(wèn)題。
到此這篇關(guān)于Redis+Caffeine兩級(jí)緩存的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis Caffeine兩級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Redis實(shí)現(xiàn)分布式鎖以及任務(wù)隊(duì)列
這篇文章主要介紹了基于Redis實(shí)現(xiàn)分布式鎖以及任務(wù)隊(duì)列,需要的朋友可以參考下2015-11-11Redis實(shí)現(xiàn)數(shù)據(jù)的交集、并集、補(bǔ)集的示例
本文主要介紹了Redis實(shí)現(xiàn)數(shù)據(jù)的交集、并集、補(bǔ)集的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08如何在centos中安裝redis插件bloom-filter
布隆過(guò)濾器在第一次add的時(shí)候自動(dòng)創(chuàng)建基于默認(rèn)參數(shù)的過(guò)濾器,Redis還提供了自定義參數(shù)的布隆過(guò)濾器,下面這篇文章主要給大家介紹了關(guān)于如何在centos中安裝redis插件bloom-filter的相關(guān)資料,需要的朋友可以參考下2021-11-11Redis?異常?read?error?on?connection?的解決方案
這篇文章主要介紹了Redis異常read?error?on?connection的解決方案,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08Redis 使用跳表實(shí)現(xiàn)有序集合的方法
Redis有序集合底層為什么使用跳表而非其他數(shù)據(jù)結(jié)構(gòu)如平衡樹(shù)、紅黑樹(shù)或B+樹(shù)的原因在于其特殊的設(shè)計(jì)和應(yīng)用場(chǎng)景,跳表提供了與平衡樹(shù)類似的效率,同時(shí)實(shí)現(xiàn)更簡(jiǎn)單,調(diào)試和修改也更加容易,感興趣的朋友一起看看吧2024-09-09Redis過(guò)期鍵與內(nèi)存淘汰策略深入分析講解
因?yàn)閞edis數(shù)據(jù)是基于內(nèi)存的,然而內(nèi)存是非常寶貴的資源,然后我們就會(huì)對(duì)一些不常用或者只用一次的數(shù)據(jù)進(jìn)行存活時(shí)間設(shè)置,這樣才能提高內(nèi)存的使用效率,下面這篇文章主要給大家介紹了關(guān)于Redis中過(guò)期鍵與內(nèi)存淘汰策略,需要的朋友可以參考下2022-11-11Redis調(diào)用Lua腳本及使用場(chǎng)景快速掌握
Redis?是一種非常流行的內(nèi)存數(shù)據(jù)庫(kù),常用于數(shù)據(jù)緩存與高頻數(shù)據(jù)存儲(chǔ)。大多數(shù)開(kāi)發(fā)人員可能聽(tīng)說(shuō)過(guò)redis可以運(yùn)行?Lua?腳本,但是可能不知道redis在什么情況下需要使用到Lua腳本2022-03-03python腳本實(shí)現(xiàn)Redis未授權(quán)批量提權(quán)
這篇文章主要給大家介紹了關(guān)于利用python腳本實(shí)現(xiàn)redis未授權(quán)批量提權(quán)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09