Redis與本地緩存的結(jié)合實(shí)現(xiàn)
前言
- 我們開發(fā)中經(jīng)常用到Redis作為緩存,將高頻數(shù)據(jù)放在Redis中能夠提高業(yè)務(wù)性能,降低MySQL等關(guān)系型數(shù)據(jù)庫壓力,甚至一些系統(tǒng)使用Redis進(jìn)行數(shù)據(jù)持久化,Redis松散的文檔結(jié)構(gòu)非常適合業(yè)務(wù)系統(tǒng)開發(fā),在精確查詢,數(shù)據(jù)統(tǒng)計(jì)業(yè)務(wù)有著很大的優(yōu)勢。但是高頻數(shù)據(jù)流處理系統(tǒng)中,Redis的壓力也會(huì)很大,同時(shí)I/0開銷才是耗時(shí)的主要原因,這時(shí)候?yàn)榱私档蚏edis讀寫壓力我們可以用到本地緩存,Guava為我們提供了優(yōu)秀的本地緩存API,包含了過期策略等等,編碼難度低,個(gè)人非常推薦。
設(shè)計(jì)示例
Redis懶加載緩存
數(shù)據(jù)在新增到MySQL不進(jìn)行緩存,在精確查找進(jìn)行緩存,做到查詢即緩存,不查詢不緩存
流程圖
代碼示例
// 偽代碼示例 Xx代表你的的業(yè)務(wù)對象 如User Goods等等 public class XxLazyCache { @Autowired private RedisTemplate<String, Xx> redisTemplate; @Autowired private XxService xxService;// 你的業(yè)務(wù)service /** * 查詢 通過查詢緩存是否存在驅(qū)動(dòng)緩存加載 建議在前置業(yè)務(wù)保證id對應(yīng)數(shù)據(jù)是絕對存在于數(shù)據(jù)庫中的 */ public Xx getXx(int id) { // 1.查詢緩存里面有沒有數(shù)據(jù) Xx xxCache = getXxFromCache(id); if(xxCache != null) { return xxCache;// 衛(wèi)語句使代碼更有利于閱讀 } // 2.查詢數(shù)據(jù)庫獲取數(shù)據(jù) 我們假定到業(yè)務(wù)這一步,傳過來的id都在數(shù)據(jù)庫中有對應(yīng)數(shù)據(jù) Xx xx = xxService.getXxById(id); // 3.設(shè)置緩存、這一步相當(dāng)于Redis緩存懶加載,下次再查詢此id,則會(huì)走緩存 setXxFromCache(xx); return xx; } } /** * 對xx數(shù)據(jù)進(jìn)行修改或者刪除操作 操作數(shù)據(jù)庫成功后 刪除緩存 * 刪除請求 - 刪除數(shù)據(jù)庫數(shù)據(jù) 刪除緩存 * 修改請求 - 更新數(shù)據(jù)庫數(shù)據(jù) 刪除緩存 下次在查詢時(shí)候就會(huì)從數(shù)據(jù)庫拉取新的數(shù)據(jù)到緩存中 */ public void deleteXxFromCache(long id) { String key = "Xx:" + xx.getId(); redisTemplate.delete(key); } private void setXxFromCache(Xx xx) { String key = "Xx:" + xx.getId(); redisTemplate.opsForValue().set(key, xx); } private Xx getXxFromCache(int id) { // 通過緩存前綴拼裝唯一主鍵作為緩存Key 如Xxx信息 就是Xxx:id String key = "Xx:" + id; return redisTemplate.opsForValue().get(key); } } // 業(yè)務(wù)類 public class XxServie { @Autowired private XxLazyCache xxLazyCache; // 查詢數(shù)據(jù)庫 public Xx getXxById(long id) { // 省略實(shí)現(xiàn) return xx; } public void updateXx(Xx xx) { // 更新MySQL數(shù)據(jù) 省略 // 刪除緩存 xxLazyCache.deleteXxFromCache(xx.getId()); } public void deleteXx(long id) { // 刪除MySQL數(shù)據(jù) 省略 // 刪除緩存 xxLazyCache.deleteXxFromCache(xx.getId()); } } // 實(shí)體類 @Data public class Xx { // 業(yè)務(wù)主鍵 private Long id; // ...省略 } 復(fù)制代碼
優(yōu)點(diǎn)
- 保證最小的緩存量滿足精確查詢業(yè)務(wù),避免冷數(shù)據(jù)占用寶貴的內(nèi)存空間
- 對增刪改查業(yè)務(wù)入侵小、刪除即同步
- 可插拔,對于老系統(tǒng)升級(jí),歷史數(shù)據(jù)無需在啟動(dòng)時(shí)初始化緩存
缺點(diǎn)
- 數(shù)據(jù)量需可控,在無限增長業(yè)務(wù)場景不適用
- 在微服務(wù)場景不利于全局緩存應(yīng)用
總結(jié)
- 空間最小化
- 滿足精確查詢場景
- 總數(shù)據(jù)量可控推薦使用
- 微服務(wù)場景不適用
Redis結(jié)合本地緩存
微服務(wù)場景下,多個(gè)微服務(wù)使用一個(gè)大緩存,流數(shù)據(jù)業(yè)務(wù)下,高頻讀取緩存對Redis壓力很大,我們使用本地緩存結(jié)合Redis緩存使用,降低Redis壓力,同時(shí)本地緩存沒有連接開銷,性能更優(yōu)
流程圖
業(yè)務(wù)場景
在流處數(shù)處理過程中,微服務(wù)對多個(gè)設(shè)備上傳的數(shù)據(jù)進(jìn)行處理,每個(gè)設(shè)備有一個(gè)code,流數(shù)據(jù)的頻率高,在消息隊(duì)列發(fā)送過程中使用分區(qū)發(fā)送,我們需要為設(shè)備code生成對應(yīng)的自增號(hào),用自增號(hào)對kafka中topic分區(qū)數(shù)進(jìn)行取模,這樣如果有10000臺(tái)設(shè)備,自增號(hào)就是0~9999,在取模后就進(jìn)行分區(qū)發(fā)送就可以做到每個(gè)分區(qū)均勻分布,這個(gè)自增號(hào)我們使用redis的自增數(shù)生成,生成后放到redis的hash結(jié)構(gòu)進(jìn)行緩存,每次來一個(gè)設(shè)備,我們就去這個(gè)hash緩存中取,沒有取到就使用自增數(shù)生成一個(gè),然后放到redis的hash緩存中,這時(shí)候每個(gè)設(shè)備的自增數(shù)一經(jīng)生成是不會(huì)再發(fā)生改變的,我們就想到使用本地緩存進(jìn)行優(yōu)化,避免高頻的調(diào)用redis去獲取,降低redis壓力,下面鏈接為我寫的關(guān)于kafka分區(qū)消費(fèi)的文章,大家可以去看看 Kafka分區(qū)發(fā)送及消費(fèi)實(shí)戰(zhàn)
代碼示例
/** * 此緩存演示如何結(jié)合redis自增數(shù) hash 本地緩存使用進(jìn)行設(shè)備自增數(shù)的生成、緩存、本地緩存 * 本地緩存使用Guava Cache */ public class DeviceIncCache { /** * 本地緩存 */ private Cache<String, Integer> localCache = CacheBuilder.newBuilder() .concurrencyLevel(16) // 并發(fā)級(jí)別 .initialCapacity(1000) // 初始容量 .maximumSize(10000) // 緩存最大長度 .expireAfterAccess(1, TimeUnit.HOURS) // 緩存1小時(shí)沒被使用就過期 .build(); @Autowired private RedisTemplate<String, Integer> redisTemplate; /** * redis自增數(shù)緩存的key */ private static final String DEVICE_INC_COUNT = "device_inc_count"; /** * redis設(shè)備編碼對應(yīng)自增數(shù)的hash緩存key */ private static final String DEVICE_INC_VALUE = "device_inc_value"; /** * 獲取設(shè)備自增數(shù) */ public int getInc(String deviceCode){ // 1.從本地緩存獲取 Integer inc = localCache.get(deviceCode); if(inc != null) { return inc; } // 2.本地緩存未命中,從redis的hash緩存獲取 inc = (Integer)redisTemplate.opsForHash().get(DEVICE_INC_VALUE, deviceCode); // 3. redis的hash緩存中沒有,說明是新設(shè)備,先為設(shè)備生成一個(gè)自增號(hào) if(inc == null) { inc = redisTemplate.opsForValue().increment(DEVICE_INC_COUNT).intValue; // 添加到redis hash緩存 redisTemplate.opsForHash().put(DEVICE_INC_VALUE, deviceCode, inc); } // 4.添加到本地緩存 localCache.put(deviceCode, inc); // 4.返回自增數(shù) return inc; } }
優(yōu)點(diǎn)
- redis保證數(shù)據(jù)可持久,本地緩存保證超高的讀取性能,微服務(wù)共用redis大緩存的場景能有效降低redis壓力
- guava作為本地緩存,提供了豐富的api,過期策略,最大容量,保證服務(wù)內(nèi)存可控,冷數(shù)據(jù)不會(huì)長期占據(jù)內(nèi)存空間
- 服務(wù)重啟導(dǎo)致的本地緩存清空不會(huì)影響業(yè)務(wù)進(jìn)行
- 微服務(wù)及分布式場景使用,分布式情況下每個(gè)服務(wù)實(shí)例只會(huì)緩存自己接入的那一部分設(shè)備的自增號(hào),本地內(nèi)存空間最優(yōu)
- 在示例業(yè)務(wù)中,自增數(shù)滿足了分布區(qū)發(fā)送的均勻分布需求,也可以滿足統(tǒng)計(jì)設(shè)備接入數(shù)目的業(yè)務(wù)場景,一舉兩得
缺點(diǎn)
- 增加編碼復(fù)雜度,不直接
- 只適用于緩存內(nèi)容只增不改的場景
總結(jié)
- 本地緩存空間可控,過期策略優(yōu)
- 適用于微服務(wù)及分布式場景
- 緩存內(nèi)容不能發(fā)生改變
- 性能優(yōu)
后記
redis提供了豐富的數(shù)據(jù)類型及api,非常適合業(yè)務(wù)系統(tǒng)開發(fā),統(tǒng)計(jì)計(jì)數(shù)(increment,decrement),標(biāo)記位(bitmap),松散數(shù)據(jù)(hash),先進(jìn)先出、隊(duì)列式讀取(list);guava緩存作為本地緩存,能夠高效的讀取的同時(shí),提供了大量api方便我們控制本地緩存的數(shù)據(jù)量及冷數(shù)據(jù)淘汰;我們充分的學(xué)習(xí)這些特性能夠幫助我們在業(yè)務(wù)開發(fā)中更加輕松靈活,在空間與時(shí)間上找到一個(gè)平衡點(diǎn)。
到此這篇關(guān)于Redis與本地緩存的結(jié)合實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Redis本地緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring boot注解方式使用redis緩存操作示例
- Spring AOP如何整合redis(注解方式)實(shí)現(xiàn)緩存統(tǒng)一管理詳解
- Spring Boot 基于注解的 Redis 緩存使用詳解
- Java自定義注解實(shí)現(xiàn)Redis自動(dòng)緩存的方法
- spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
- 配置Spring4.0注解Cache+Redis緩存的用法
- redis緩存數(shù)據(jù)庫中數(shù)據(jù)的方法
- Redis緩存穿透/擊穿工具類的封裝
- Redis?緩存淘汰策略和事務(wù)實(shí)現(xiàn)樂觀鎖詳情
- 使用注解實(shí)現(xiàn)Redis緩存功能
相關(guān)文章
MyBatis緩存和二級(jí)緩存整合Redis的解決方案
這篇文章主要介紹了MyBatis緩存和二級(jí)緩存整合Redis,將MyBatis緩存和二級(jí)緩存整合Redis,可以提高查詢效率,同時(shí)也能保證數(shù)據(jù)的可靠性和一致性,需要的朋友可以參考下2023-07-07Redis簡單動(dòng)態(tài)字符串SDS的實(shí)現(xiàn)示例
Redis沒有直接復(fù)用C語言的字符串,而是新建了SDS,本文主要介紹了Redis簡單動(dòng)態(tài)字符串SDS的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08Redis實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能的實(shí)現(xiàn)
這篇文章主要介紹了Redis實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08Redis實(shí)現(xiàn)好友關(guān)注的示例代碼
本文主要介紹了Redis實(shí)現(xiàn)好友關(guān)注的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01redis中zSet實(shí)現(xiàn)排行榜的使用示例
在工作中,有時(shí)候需要實(shí)現(xiàn)排行榜功能,本文主要介紹了redis中zSet實(shí)現(xiàn)排行榜的使用示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10