日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java开发 - Redis初体验

發(fā)布時(shí)間:2024/3/13 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java开发 - Redis初体验 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

es我們已經(jīng)在前文中有所了解,和es有相似功能的是Redis,他們都不是純粹的數(shù)據(jù)庫(kù)。兩者使用場(chǎng)景也是存在一定的差異的,本文目的并不重點(diǎn)說(shuō)明他們之間的差異,但會(huì)簡(jiǎn)要說(shuō)明,重點(diǎn)還是在對(duì)Redis的了解和學(xué)習(xí)上。學(xué)完本篇,你將了解Redis的特點(diǎn)和作用,掌握Redis的基礎(chǔ)用法,這將有助于你在后續(xù)的項(xiàng)目中更好的使用Redis。建議大家都動(dòng)手和博主一起實(shí)操,莫要養(yǎng)成眼高手低的毛病,下面,讓我們提起精神,一起開(kāi)始這場(chǎng)Redis盛宴吧。

Redis

什么是Redis

Redis全名Remote Dictionary Server,即遠(yuǎn)程字典服務(wù),是一個(gè)開(kāi)源的使用ANSI?C語(yǔ)言編寫(xiě)、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫(kù),并提供多種語(yǔ)言的API。

其官網(wǎng):Redis

雖然Redis也是數(shù)據(jù)庫(kù),但又有別于我們所知的mysql等關(guān)系型數(shù)據(jù)庫(kù),Redis是一款基于內(nèi)存的NoSQL數(shù)據(jù)存儲(chǔ)服務(wù),也就是非關(guān)系型的數(shù)據(jù)庫(kù),這點(diǎn)要搞搞清楚。

為什么使用Redis

相信大家在學(xué)習(xí)SQL的時(shí)候都有過(guò)這樣的經(jīng)歷,往數(shù)據(jù)庫(kù)插入50w條數(shù)據(jù),然后去條件檢索某些數(shù)據(jù),效率如何?大家心里多少還是有點(diǎn)x數(shù)的,試想,這種暴力型操作要是出現(xiàn)在我們常用的一些網(wǎng)站和應(yīng)用上,且每次請(qǐng)求到這么做,那該是何等的瘋狂?

說(shuō)到這里,其實(shí)還沒(méi)有說(shuō)出Redis的主要應(yīng)用場(chǎng)景,每每想到這個(gè),總是會(huì)跳出另一個(gè)東西:ES。算了,咱們看下面的對(duì)比吧,有對(duì)比才有傷害啊!

Redis和ES的區(qū)別

Redis使用場(chǎng)景

因?yàn)镽edis是基于內(nèi)存運(yùn)行的,說(shuō)起內(nèi)存,你應(yīng)該知道,其運(yùn)行效率遠(yuǎn)高于和硬盤(pán)的交互。這也就導(dǎo)致了Redis運(yùn)行效率非常高。

Redis同樣支持將數(shù)據(jù)存儲(chǔ)在硬盤(pán)上,支持主從和分布式使用,但在事務(wù)上有著嚴(yán)重不足,所以在關(guān)系比較復(fù)雜的地方就不適合使用Redis,但卻可以配合關(guān)系型數(shù)據(jù)庫(kù)做緩存,也就是通過(guò)復(fù)雜SQL查找到數(shù)據(jù)緩存在Redis。

此類(lèi)緩存數(shù)據(jù),我準(zhǔn)備用一個(gè)絕對(duì)一些的詞,必須是穩(wěn)定型,通用型,高頻次數(shù)據(jù),比如類(lèi)別,商品信息,資訊信息等。如果每次請(qǐng)求都會(huì)變,每個(gè)人又都不一樣的數(shù)據(jù),不太建議存儲(chǔ)在Redis,不是不行,只是不建議,因?yàn)闀?huì)占用大量的內(nèi)存,造成數(shù)據(jù)的冗余,還不利于做數(shù)據(jù)同步。

ES使用場(chǎng)景

ES是非關(guān)系型數(shù)據(jù)庫(kù),我們通常說(shuō)他是一個(gè)引擎,實(shí)時(shí)搜索引擎,他是把數(shù)據(jù)按照一定的規(guī)律存儲(chǔ)起來(lái),達(dá)到比關(guān)系型數(shù)據(jù)庫(kù)查詢(xún)效率更高的目的。

ES擴(kuò)展容易,前文中曾使用了ik插件,ES同樣支持主從,由于其存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)特點(diǎn),所以其查詢(xún)效率非常高,在微服務(wù)這種大數(shù)據(jù)形態(tài)下的表現(xiàn)尤為優(yōu)秀,可以快速實(shí)現(xiàn)數(shù)據(jù)的整合,對(duì)于日志和數(shù)據(jù)分析非常友好,對(duì)于實(shí)時(shí)狀態(tài)的高也并發(fā)有著極強(qiáng)的適應(yīng)能力,且延遲也很低。

想了解ES的童鞋可以點(diǎn)擊下面鏈接前往查看:Java開(kāi)發(fā) - Elasticsearch初體驗(yàn)

Redis安裝

由于博主是Mac電腦,這里就以Mac為例,Windows沒(méi)試過(guò),Windows的童鞋可自行百度安裝。

  • 打開(kāi)終端,輸入:brew install redis
  • 測(cè)試安裝成功輸入:redis-server,看到Redis 的啟動(dòng)日志則說(shuō)明安裝成功,通過(guò)Ctrl-C可停止此redis
  • 使用 launchd 啟動(dòng)Redis:brew services start redis 暫停Redis:brew services stop redis?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 查看Redis信息:brew services info redis

其實(shí)博主覺(jué)得這么做挺麻煩的,有個(gè)東西叫:Docker Desktop

直接安裝這個(gè),Mac電腦省去了安裝虛擬機(jī)的麻煩,直接在此軟件內(nèi)安裝各種服務(wù),不要太爽:

數(shù)據(jù)庫(kù),nacos,senta等都可以在這里安裝并且一鍵啟動(dòng),推薦大家去裝一個(gè),就不用每一個(gè)東西都要自己裝,太麻煩了。

還有個(gè)Redis的客戶(hù)端也推薦大家裝一下 :

此軟件在Mac上App Store是收費(fèi)的,推薦安裝方式如下:

brew install --cask another-redis-desktop-manager

安裝后就可以找到啟動(dòng)圖標(biāo)了,Windows可在網(wǎng)上自行搜索,其工作頁(yè)面如下:

可看到目前我們是啟動(dòng)狀態(tài),客戶(hù)端連接數(shù)是1。

最好弄好了這些東西后再來(lái)跟著學(xué)下去,這種可視化工具可以在我們調(diào)用接口時(shí)看到存儲(chǔ)在Redis中的數(shù)據(jù),非常方便。

Redis緩存

緩存淘汰

Redis幫助我們解決了一些三高的問(wèn)題,但在訪問(wèn)量非常大的時(shí)候,Redis要在同一時(shí)間保存大量的數(shù)據(jù),但Redis內(nèi)存并不是無(wú)限的,一旦內(nèi)存占滿(mǎn),可能會(huì)發(fā)生什么?降速?阻塞?宕機(jī)?都有可能,所以在連續(xù)不斷的存入新數(shù)據(jù)的同時(shí),還要將不使用的老數(shù)據(jù)及時(shí)的從內(nèi)存中刪除,這就需要一個(gè)淘汰策略。

好在Redis提供了這種機(jī)制,我們看看有哪些淘汰策略:

noeviction:返回錯(cuò)誤**(默認(rèn))**
allkeys-random:所有數(shù)據(jù)中隨機(jī)刪除數(shù)據(jù)
volatile-random:所有過(guò)期時(shí)間的數(shù)據(jù)庫(kù)中隨機(jī)刪除數(shù)據(jù)
volatile-ttl:刪除剩余有效時(shí)間最少的數(shù)據(jù)
allkeys-lru:所有數(shù)據(jù)中刪除上次使用時(shí)間最久的數(shù)據(jù)
volatile-lru:所有過(guò)期時(shí)間的數(shù)據(jù)中刪除上次使用時(shí)間最久的數(shù)據(jù)
allkeys-lfu:所有數(shù)據(jù)中刪除使用頻率最少的
volatile-lfu:所有過(guò)期時(shí)間的數(shù)據(jù)中刪除使用頻率最少的

通過(guò)合理的選擇以上參數(shù)配置Redis,可以有效解決這個(gè)問(wèn)題,但也需要時(shí)刻監(jiān)測(cè)Redis內(nèi)存情況,現(xiàn)在的云服務(wù)做得都很好,可以提前預(yù)警通知。

緩存穿透

我們?cè)谑褂肦edis時(shí),將數(shù)據(jù)庫(kù)中查詢(xún)出來(lái)的數(shù)據(jù)保存在Redis中,但Redis有自己的淘汰策略,所以這些數(shù)據(jù)并不會(huì)無(wú)限期保存。

正常來(lái)說(shuō),訪問(wèn)的請(qǐng)求會(huì)先去Redis中拿數(shù)據(jù),拿不到才會(huì)去數(shù)據(jù)庫(kù)中查找,再將查到的數(shù)據(jù)存儲(chǔ)到Redis中,一旦這樣的請(qǐng)求數(shù)量非常多的時(shí)候,數(shù)據(jù)庫(kù)的壓力就會(huì)變大,我們可以認(rèn)為,其表現(xiàn)的現(xiàn)象即為Redis失效,沒(méi)有工作,當(dāng)然算是失效了,而這種情況,我們稱(chēng)之為緩存穿透。

開(kāi)發(fā)中當(dāng)然要避免緩存穿透,簡(jiǎn)單點(diǎn),可以將查詢(xún)回來(lái)為空的數(shù)據(jù)在Redis中存為null,防止Redis被反復(fù)穿透,但這也有缺點(diǎn),比如反復(fù)更換查詢(xún)關(guān)鍵字,反復(fù)穿透依然存在,當(dāng)然,這只是特例,雖然實(shí)際中發(fā)生的概率不會(huì)太高,但還是要防范利用此情況攻擊服務(wù)器的可能。

最好的做法是通過(guò)增加布隆過(guò)濾器來(lái)解決此問(wèn)題,在業(yè)務(wù)進(jìn)入時(shí),提前判斷用戶(hù)查詢(xún)的信息是否存在于數(shù)據(jù)庫(kù)中,如果沒(méi)有,直接返回,不再走完整的路徑。

緩存擊穿

緩存穿透和緩存擊穿很類(lèi)似,我們正常的流程是先訪問(wèn)Redis,Redis沒(méi)有就去數(shù)據(jù)庫(kù)查詢(xún),這種情況,數(shù)據(jù)庫(kù)是可以查到數(shù)據(jù)的,此種現(xiàn)象就叫擊穿,而少量的擊穿并不是問(wèn)題。

緩存雪崩

上面的擊穿在同一時(shí)間大量發(fā)生,就變成了雪崩,數(shù)據(jù)庫(kù)短時(shí)間內(nèi)出現(xiàn)很多新的查詢(xún)請(qǐng)求,就會(huì)發(fā)生性能問(wèn)題。

這是由于Redis緩存淘汰策略把過(guò)期的數(shù)據(jù)大批量清空導(dǎo)致的,它本身不算異常,只是我們要避免同一時(shí)間大量的過(guò)期情況出現(xiàn),所以在設(shè)置過(guò)期時(shí)間時(shí),在基礎(chǔ)時(shí)間上增加10分鐘或30分鐘以?xún)?nèi)的隨機(jī)時(shí)間來(lái)解決這個(gè)問(wèn)題,時(shí)間你可以自己定。

Redis持久化

存儲(chǔ)特點(diǎn)

Redis是在內(nèi)存中運(yùn)行的,這和我們所有的軟件都是一樣的,內(nèi)存可以保存,但Redis保存的數(shù)據(jù)卻并不是在內(nèi)存上,試想我們的電腦手機(jī),關(guān)機(jī)后再打開(kāi)還能恢復(fù)打開(kāi)時(shí)的樣子嗎?這自然是不能的。

所以,為了解決斷電重啟等問(wèn)題,Redis支持了持久化,將需要保存的數(shù)據(jù)保存在服務(wù)器硬盤(pán)上。

針對(duì)以上硬盤(pán)保存數(shù)據(jù)的特點(diǎn),Redis在重新啟動(dòng)后恢復(fù)數(shù)據(jù)的方式有兩種,我們來(lái)看看是哪兩種。

RDB

RDB全稱(chēng)Redis Database Backup,中文名叫數(shù)據(jù)庫(kù)快照,它可以將Redis數(shù)據(jù)庫(kù)數(shù)據(jù)轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)保存在硬盤(pán)上,生成一個(gè)dump.rdb的文件,想使用此恢復(fù)模式需要提前在Redis安裝程序的配置文件中進(jìn)行配置才能生效。

基于此模式,由于是整體Redis數(shù)據(jù)的二進(jìn)制格式,所以數(shù)據(jù)恢復(fù)是整體恢復(fù)的,非常方便。但也因此存在了一個(gè)大文件的通病:讀寫(xiě)效率不高。快照的備份不能實(shí)時(shí)進(jìn)行,所以斷電重啟恢復(fù)只能恢復(fù)最后一次生成的rdb文件數(shù)據(jù)。可能會(huì)造成短時(shí)間的數(shù)據(jù)丟失。

AOF

AOF全稱(chēng)Append Only File,它的策略不是緩存數(shù)據(jù),而是將所有命令日志備份下來(lái),在數(shù)據(jù)丟失后,可以根據(jù)運(yùn)行過(guò)的日志恢復(fù)為斷電前的狀態(tài),注意一點(diǎn):這種保存日志的策略也不是實(shí)時(shí)的,數(shù)據(jù)量比較大時(shí)會(huì)分批分次進(jìn)行緩存。

實(shí)際中,我們一般設(shè)置1s發(fā)送一次日志,斷電最多丟失1s數(shù)據(jù)。為了降低日志對(duì)內(nèi)存的占用,AOF支持AOF rewrite,也就是說(shuō),如果你是刪除數(shù)據(jù),那完全沒(méi)有留日志的必要,但默認(rèn)時(shí)有日志的,所以,可以將這些刪除操作的日志刪除。

存儲(chǔ)原理

存儲(chǔ)原理博主簡(jiǎn)單給大家說(shuō)說(shuō),想要深入了解的推薦這篇博客:Redis存儲(chǔ)原理深入剖析 - 墨天輪

也可以自行查找。

Redis將內(nèi)存劃分為16384個(gè)槽,類(lèi)似哈希槽,將要存儲(chǔ)的數(shù)據(jù)的key通過(guò)CRC16算法處理,得到一個(gè)0~16383之間的值,然后將這條數(shù)據(jù)存儲(chǔ)到對(duì)應(yīng)的槽中,下次查找的時(shí)候也是通過(guò)CRC16算法處理過(guò)的數(shù)字去對(duì)應(yīng)槽中查找當(dāng)前key是否存在,因?yàn)橛锌赡苤苯右淮尉驼业綄?duì)應(yīng)key,所以這種存儲(chǔ)查找方式效率非常高。這也是一種散列算法,和數(shù)據(jù)庫(kù)主鍵查找的原理很類(lèi)似。推薦讀一下博主這篇博客:Java開(kāi)發(fā) - 數(shù)據(jù)庫(kù)索引的數(shù)據(jù)結(jié)構(gòu)

Redis集群

Redis我們一般說(shuō)起來(lái)都會(huì)說(shuō)Redis服務(wù)器,所以,Redis本質(zhì)上也是一臺(tái)服務(wù)器,Redis即服務(wù)器,服務(wù)器即Redis。服務(wù)器宕機(jī),Redis肯定也好不到哪里去。如果只有一臺(tái)Redis服務(wù)器,那將會(huì)面臨一定的風(fēng)險(xiǎn),比如系統(tǒng)崩潰。

主從

為了解決單Redis服務(wù)器可能存在的問(wèn)題,我們一般會(huì)使用一臺(tái)備用機(jī),這就叫做主從。主從狀態(tài)下,備用機(jī)Redis會(huì)實(shí)時(shí)同步主機(jī)Redis的數(shù)據(jù),如果主機(jī)掉線,備用機(jī)就可以起到預(yù)備隊(duì)的效果。但這也存在一定的問(wèn)題,主機(jī)正常工作時(shí),從機(jī)就在那里歇著,主機(jī)累的喘不過(guò)來(lái)氣,肯定不愿意,從機(jī)的錢(qián)不是也白花了嗎?如下圖:

讀寫(xiě)分離

為了解決從機(jī)不干活的問(wèn)題,我們一般會(huì)將讀寫(xiě)分離,主機(jī)可讀可寫(xiě),從機(jī)也可以讀取,這樣不僅讓從機(jī)干活,還減輕了主機(jī)的壓力,提高了項(xiàng)目運(yùn)行的流暢度。如下圖:

哨兵

此時(shí),還存在一個(gè)問(wèn)題,主機(jī)宕機(jī)后,需要人手動(dòng)切換到從機(jī),要是及時(shí)發(fā)現(xiàn)還好,要是不及時(shí),將會(huì)造成嚴(yán)重的后果。這時(shí)候,我們需要一個(gè)可以自動(dòng)切換到從機(jī)的機(jī)制:哨兵模式。如下圖:

哨兵每隔固定時(shí)間向主從節(jié)點(diǎn)發(fā)送請(qǐng)求,如果節(jié)點(diǎn)正常相應(yīng),則說(shuō)明節(jié)點(diǎn)正常工作,否則,將視為節(jié)點(diǎn)異常,啟將自動(dòng)切換備用機(jī)。

有時(shí)候,因?yàn)榫W(wǎng)絡(luò)問(wèn)題或者其他因素會(huì)導(dǎo)致哨兵接受請(qǐng)求返回異常而切換備用機(jī),這不僅沒(méi)起到保護(hù)的作用,還降低了Redis的工作效率,這時(shí),有兩種方式來(lái)解決:一是多次請(qǐng)求后都返回異常再切換備用機(jī),二是采用多個(gè)哨兵的形式,當(dāng)多臺(tái)哨兵都認(rèn)為某臺(tái)機(jī)器存在異常,再切換到備用機(jī)。但是切記一點(diǎn),哨兵不能和Redis在同一臺(tái)服務(wù)器上,否則服務(wù)器異常哨兵也將離線。

?哨兵的配置推薦看看這篇博客:Redis中的哨兵模式 - 簡(jiǎn)書(shū)?

Redis基本使用

下面,我們就來(lái)看看Redis有哪些API,具體該怎么使用。在原來(lái)微服務(wù)項(xiàng)目中,上一篇Quartz是在stock子項(xiàng)目下運(yùn)行的,Redis我們也在stock子項(xiàng)目下添加吧,你也可以選擇其他模塊,或者獨(dú)立建一個(gè)項(xiàng)目也是可以的。

添加依賴(lài)

<!-- Spring Boot Data Redis:緩存 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>

請(qǐng)注意,博主依賴(lài)中沒(méi)有添加version信息是因?yàn)橹鞴こ讨袑?duì)版本進(jìn)行了管理,如果你單獨(dú)創(chuàng)建的項(xiàng)目是需要加上版本的,其他依賴(lài)在需要時(shí)按需添加。

添加配置

spring:redis:database: 0host: localhostport: 6379password:jedis:pool:#最大連接數(shù)max-active: 8#最大阻塞等待時(shí)間(負(fù)數(shù)表示沒(méi)限制)max-wait: -1#最大空閑max-idle: 8#最小空閑min-idle: 0#連接超時(shí)時(shí)間timeout: 10000cache:redis:time-to-live: 360000000

推薦這篇博客:SpringDataRedis知識(shí)概括,可以去了解下Redis配置相關(guān)的詳細(xì)信息,博主覺(jué)得寫(xiě)得還是很全面的。

創(chuàng)建配置類(lèi)

操作Redis需要使用RedisTemplate對(duì)象,我們?cè)赾onfig包下創(chuàng)建RedisConfiguration類(lèi):

package com.codingfire.cloud.stock.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer;import java.io.Serializable;@Configuration public class RedisConfiguration {@Beanpublic RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(RedisSerializer.string());redisTemplate.setValueSerializer(RedisSerializer.json());return redisTemplate;} }

固定模式,也沒(méi)啥好說(shuō)的,只是需要使用這樣一個(gè)實(shí)例來(lái)操作Redis。

編寫(xiě)測(cè)試方法?

由于我們之前刪除了用于測(cè)試的文件夾,還是需要新建的,選擇src,新建,file:

選擇test/java,新建測(cè)試類(lèi):

package com.codingfire.cloud.stock;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest public class CloudStockApplicationTests {}

?包名,路徑和格式注意下,不要錯(cuò)了。下面我們來(lái)添加Redis的測(cè)試方法。

存儲(chǔ)普通字符串:

package com.codingfire.cloud.stock;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.junit.jupiter.api.Test;import java.io.Serializable; import java.util.concurrent.TimeUnit;@SpringBootTest class CloudStockApplicationTests {@AutowiredRedisTemplate<String, Serializable> redisTemplate;@Testvoid testSetValue() {redisTemplate.opsForValue().set("name", "codingfire");}}

運(yùn)行測(cè)試方法,成功后我們?nèi)edis客戶(hù)端看看有沒(méi)有什么變化:

這個(gè)就是我們測(cè)試方法中存入的數(shù)據(jù),表示我們已經(jīng)將此字符串存入Redis中。有問(wèn)題的童鞋看看自己的Redis有沒(méi)有啟動(dòng)連接。

有存就有取,我們?nèi)グ褎偛糯孢M(jìn)去的字符串取出來(lái):

@Testvoid testGetValue() {// 當(dāng)key存在時(shí),可獲取到有效值// 當(dāng)key不存在時(shí),獲取到的結(jié)果將是nullSerializable name = redisTemplate.opsForValue().get("name");System.out.println("get value --> " + name);}

運(yùn)行測(cè)試方法,查看控制臺(tái)輸出:

get value --> codingfire

成功從Redis取到了我們存入的數(shù)據(jù)。

不知道你注意到?jīng)],我們前面的配置類(lèi)里面對(duì)Redis的存取類(lèi)型做了限制:

redis存儲(chǔ)類(lèi)型很有限,不過(guò)好在string和json幾乎能滿(mǎn)足所有的需要,下面我們來(lái)存取對(duì)象類(lèi)型試試:

@Testvoid testSetAdminValue() {AdminLoginDTO adminLoginDTO = new AdminLoginDTO();adminLoginDTO.setUsername("codingfire");adminLoginDTO.setPassword("123456");redisTemplate.opsForValue().set("user", adminLoginDTO);}@Testvoid testGetAdminValue() {// 當(dāng)key存在時(shí),可獲取到有效值// 當(dāng)key不存在時(shí),獲取到的結(jié)果將是nullSerializable user = redisTemplate.opsForValue().get("user");System.out.println("get value --> " + user);if (user != null) {AdminLoginDTO adminLoginDTO = (AdminLoginDTO) user;System.out.println("get value --> " + adminLoginDTO);}}

分別運(yùn)行以上存取對(duì)象類(lèi)型方法,看看Redis客戶(hù)端和控制臺(tái)會(huì)輸出什么。

存儲(chǔ)對(duì)象后Redis客戶(hù)端:

獲取對(duì)象后控制臺(tái)輸出:

兩次輸出一樣為什么還要強(qiáng)轉(zhuǎn)呢?不強(qiáng)轉(zhuǎn)你就不知道獲取的對(duì)象類(lèi)型,也無(wú)法直接使用序列化后對(duì)象進(jìn)行數(shù)據(jù)的操作。

有添加就有刪除,那么刪除Redis中數(shù)據(jù)該怎么做呢:

@Testvoid testDeleteKey() {// 刪除key時(shí),將返回“是否成功刪除”// 當(dāng)key存在時(shí),將返回true// 當(dāng)key不存在時(shí),將返回falseBoolean result = redisTemplate.delete("name");System.out.println("result --> " + result);}

?運(yùn)行此測(cè)試方法查看結(jié)果:

由于Redis中存在名為name的參數(shù),所以result為true,對(duì)象類(lèi)型的刪除也是一樣的操作。

接下來(lái)我們?cè)O(shè)置Redis的過(guò)期時(shí)間,時(shí)間到了就自動(dòng)刪除,我們通過(guò)查看源碼得知,set有三個(gè)參數(shù),第二個(gè)為過(guò)期時(shí)間,第三個(gè)為時(shí)間單位:

下面我來(lái)寫(xiě)代碼:

@Testvoid testSetValueTimeout() {redisTemplate.opsForValue().set("name", "codingfire",20, TimeUnit.SECONDS);}

?運(yùn)行測(cè)試方法,在Redis中能看到存入的數(shù)據(jù),20s后,數(shù)據(jù)自動(dòng)刪除:

在客戶(hù)端中能看到TTL,就是倒計(jì)時(shí)的意思,打開(kāi)自動(dòng)刷新功能,你能看見(jiàn)數(shù)字是在變化的。

在存儲(chǔ)數(shù)據(jù)時(shí)還有一個(gè)特殊情況,比如分頁(yè)數(shù)據(jù),是一個(gè)數(shù)組,這該怎么存呢?Redis中ops是操作器,opsForValue可以操作普通字符串或?qū)ο箢?lèi)型,既然是對(duì)象,為什么不能操作數(shù)組?數(shù)組也是對(duì)象啊,博主也不死心,我們來(lái)試試看:

?好像成功了?為了對(duì)比這兩種方式存儲(chǔ)數(shù)組的能力我們來(lái)試試ospForList存數(shù)組后是否一樣:

@Testvoid testRightPushList() {// 存入List時(shí),需要redisTemplate.opsForList()得到針對(duì)List的操作器// 通過(guò)rightPush()可以向Redis中的List追加數(shù)據(jù)// 每次調(diào)用rightPush()時(shí)使用的key必須是同一個(gè),才能把多個(gè)數(shù)據(jù)放到同一個(gè)List中List<AdminLoginDTO> list = new ArrayList<>();for (int i = 1; i <= 5; i++) {AdminLoginDTO adminLoginDTO = new AdminLoginDTO();adminLoginDTO.setUsername("name" + i);list.add(adminLoginDTO);}String key = "UserList";for (AdminLoginDTO adminLoginDTO : list) {redisTemplate.opsForList().rightPush(key, adminLoginDTO);}}

運(yùn)行測(cè)試代碼后查看Redis客戶(hù)端數(shù)據(jù):

呀!數(shù)據(jù)展示的形式不一樣啊,我們來(lái)獲取并輸出一下這兩組數(shù)據(jù),看看輸出是否一樣:

@Testvoid testGetListValue() {// 調(diào)用opsForList()后再調(diào)用range(String key, long start, long end)方法取出List中的若干個(gè)數(shù)據(jù),將得到List// long start:起始下標(biāo)(結(jié)果中將包含)// long end:結(jié)束下標(biāo)(結(jié)果中將包含),如果需要取至最后一個(gè)元素,可使用-1作為此參數(shù)值List<Serializable> rangeList = redisTemplate.opsForList().range("UserList", 0, -1);Serializable listSer = redisTemplate.opsForValue().get("list");List<Serializable> list = (List<Serializable>) listSer;System.out.println(rangeList);System.out.println(list);for (Serializable serializable : rangeList) {System.out.println(serializable);}for (Serializable serializable : list) {System.out.println(serializable);}}

?運(yùn)行測(cè)試方法查看控制臺(tái)輸出:

從數(shù)據(jù)庫(kù),其實(shí)并沒(méi)有什么太大的差別 ,但是請(qǐng)仔細(xì)看博主獲取兩個(gè)list的代碼,秘密就藏在里面,也就是說(shuō):怎么存,怎么取,不通過(guò)數(shù)組專(zhuān)用方法的需要強(qiáng)轉(zhuǎn)。這就是最終結(jié)論。大家不用自己試了,博主都一一試過(guò)了,opsForxxxxx用的不對(duì)就報(bào)錯(cuò)了。

最后再補(bǔ)充兩個(gè)方法,一個(gè)是獲取數(shù)組長(zhǎng)度,一個(gè)是獲取Redis中所有key的方法,注意:獲取數(shù)組長(zhǎng)度的方法必須是通過(guò)opsForList方法存進(jìn)去的,否則此方法無(wú)效,且報(bào)錯(cuò),看代碼:

@Testvoid testListSize() {// 獲取List的長(zhǎng)度,即List中的元素?cái)?shù)量String key = "UserList";Long size = redisTemplate.opsForList().size(key);System.out.println("size --> " + size);}

運(yùn)行結(jié)果:

獲取所有Redis的key:

@Testvoid testKeys() {// 調(diào)用keys()方法可以找出匹配模式的所有key// 在模式中,可以使用星號(hào)作為通配符Set<String> keys = redisTemplate.keys("*");for (String key : keys) {System.out.println(key);}}

?此方法對(duì)ops無(wú)影響,只獲取key,查看運(yùn)行結(jié)果:

對(duì)比Redis客戶(hù)端中所有key值:

完全一致,測(cè)試成功。

最后,關(guān)于Key的使用,通常建議使用冒號(hào)區(qū)分多層次,類(lèi)似URL的設(shè)計(jì)方式,例如:

  • 用戶(hù)列表的Key:users:list或users
  • 某個(gè)用戶(hù)id(001)對(duì)應(yīng)用戶(hù)信息的Key:users:userId:001

但也不是絕對(duì),可根據(jù)自己需要選擇合適的組合方式,目的是使key不重復(fù),且好理解。

到這里,Redis基礎(chǔ)方法使用就講解完了,但這畢竟只是基礎(chǔ)方法,在實(shí)戰(zhàn)中該怎么用還是個(gè)問(wèn)題。下面,我們將在真實(shí)項(xiàng)目中去使用Redis。

Redis實(shí)戰(zhàn)

開(kāi)始前的思考

使用Redis可以提高查詢(xún)效率,降低數(shù)據(jù)庫(kù)的壓力。基本上是用在高頻查詢(xún)的數(shù)據(jù)或是幾乎不太會(huì)改變的數(shù)據(jù)上。所以,有些比較精密的經(jīng)常需要變動(dòng)的數(shù)據(jù)就不能使用Redis,比如購(gòu)物類(lèi)應(yīng)用的創(chuàng)建訂單,庫(kù)存都屬于此類(lèi)數(shù)據(jù)。

所以在開(kāi)始前確定哪些數(shù)據(jù)使用Redis,Redis中的數(shù)據(jù)從哪來(lái)是很重要的。關(guān)于Redis的調(diào)用,是寫(xiě)在業(yè)務(wù)邏輯層還是做一個(gè)單獨(dú)的組件獨(dú)立出來(lái),這也是一個(gè)問(wèn)題。

如果直接將訪問(wèn)Redis的代碼寫(xiě)在Service中,首次開(kāi)發(fā)時(shí)會(huì)很省事,但卻不利于后期的維護(hù)。

如果將訪問(wèn)Redis的代碼寫(xiě)的新的組件中,首次開(kāi)發(fā)時(shí)會(huì)更麻煩,但有利于后期的維護(hù)。

所以你會(huì)怎么選呢?我們首先要知道,訪問(wèn)Redis的API都很簡(jiǎn)單,上面的基礎(chǔ)使用大家基本應(yīng)該是掌握了,也沒(méi)什么難的,自定義組件雖然有利于后期維護(hù),但代碼量可能會(huì)很少,這個(gè)需要我們?nèi)ズ饬靠傮w的工作量和后期的維護(hù)情況。

每一個(gè)項(xiàng)目都不一樣,甚至有些小的項(xiàng)目根本不使用Redis都有可能,這并不是危言聳聽(tīng),這個(gè)就教給大家自己選擇了,今天,博主的目的是教會(huì)大家在項(xiàng)目中使用Redis。

創(chuàng)建Redis模塊

博主準(zhǔn)備以passport為基礎(chǔ),在其上使用Redis,雖然實(shí)際中不會(huì)在這么簡(jiǎn)單的模塊用,不過(guò)該有的功能,博主是一步都不會(huì)省略的,照葫蘆畫(huà)瓢,其他的模塊參照此模塊就可以移植。passport是做單點(diǎn)登錄的模塊:Java開(kāi)發(fā) - 單點(diǎn)登錄初體驗(yàn)(Spring Security + JWT)

沒(méi)有此模塊的童鞋可以先學(xué)此篇,也可以先看看博主代碼,新建一個(gè)模塊,照著往別的模塊上面搬。

創(chuàng)建調(diào)用Redis接口

在passport包下創(chuàng)建repository包,repository下創(chuàng)建IPassportRedisRepository接口:

package com.codingfire.cloud.passport.repository;import com.codingfire.cloud.commons.pojo.passport.vo.AdminLoginVO;public interface IPassportRedisRepository {String KEY_ADMIN_ITEM_PREFIX = "admins:item:";// 將用戶(hù)信息存入到Redis中void save(AdminLoginVO adminLoginVO);// 根據(jù)用戶(hù)id獲取用戶(hù)信息AdminLoginVO getAdminDetailsById(Long id); }

創(chuàng)建Redis實(shí)現(xiàn)類(lèi)

在這一步之前,請(qǐng)大家添加Redis的依賴(lài),并將上面代碼中Redis的配置類(lèi)RedisConfiguration復(fù)制到passport的config包下,在repository包下新建impl包,包下建PassportRedisRepositoryImpl實(shí)現(xiàn)類(lèi):

package com.codingfire.cloud.passport.repository.impl;import com.codingfire.cloud.commons.pojo.passport.vo.AdminLoginVO; import com.codingfire.cloud.passport.repository.IPassportRedisRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; import java.io.Serializable;@Repository public class PassportRedisRepositoryImpl implements IPassportRedisRepository {@Autowiredprivate RedisTemplate<String, Serializable> redisTemplate;@Overridepublic void save(AdminLoginVO adminLoginVO) {String key = KEY_ADMIN_ITEM_PREFIX + adminLoginVO();redisTemplate.opsForValue().set(key, adminLoginVO);}@Overridepublic AdminLoginVO getAdminDetailsById(Long id) {String key = KEY_ADMIN_ITEM_PREFIX + id;Serializable result = redisTemplate.opsForValue().get(key);if (result == null) {return null;} else {AdminLoginVO adminLoginVO = (AdminLoginVO) result;return adminLoginVO;}} }

測(cè)試以上代碼

下面在test文件夾下depassport包下新建一個(gè)測(cè)試類(lèi)PassportRedisRepositoryTests:

package com.codingfire.cloud.passport;import com.codingfire.cloud.commons.pojo.passport.dto.AdminLoginDTO; import com.codingfire.cloud.commons.pojo.passport.vo.AdminLoginVO; import com.codingfire.cloud.passport.repository.IPassportRedisRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest class PassportRedisRepositoryTests {@AutowiredIPassportRedisRepository repository;@Testvoid testGetAdminDetailsByIdSuccessfully() {testSave();Long id = 2L;AdminLoginDTO admin = repository.getAdminDetailsById(id);System.out.println(admin);}@Testvoid testGetAdminDetailsByIdReturnNull() {Long id = -1L;AdminLoginVO adminLoginVO = repository.getAdminDetailsById(id);Assertions.assertNull(adminLoginVO);}private void testSave() {AdminLoginVO adminLoginVO = new AdminLoginVO();adminLoginVO.setId(2L);adminLoginVO.setUsername("codeliu");adminLoginVO.setPassword("123456");repository.save(adminLoginVO);} }

?運(yùn)行第一個(gè)方法,先存儲(chǔ),后查找:

控制器輸出我們存入的數(shù)據(jù),看看Redis客戶(hù)端有沒(méi)有數(shù)據(jù):

可以看到室友層級(jí)的key,這種key的命名方式我們?cè)谏厦嬉呀?jīng)講過(guò),有利于數(shù)據(jù)分層,便于觀察。

讓Redis在業(yè)務(wù)中體現(xiàn)調(diào)用邏輯

業(yè)務(wù)邏輯思考

下面,我們結(jié)合接口的調(diào)用來(lái)做個(gè)修改,在接口的調(diào)用過(guò)程中,我們?nèi)ナ褂肦edis。我們打開(kāi)IAdminService類(lèi),在里面添加一個(gè)新的方法:

AdminLoginVO getAdminDetailsById(Long id);

接著打開(kāi)AdminServiceImpl類(lèi),在里面實(shí)現(xiàn)接口方法:

@Overridepublic AdminLoginVO getAdminDetailsById(Long id) {// ===== 以下是原有代碼,只從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù) ===== // AdminLoginVO adminLoginVO = adminMapper.getLoginInfoByUserId(id); // if (adminLoginVO == null) { // throw new CloudServiceException(ResponseCode.ERR_INTERNAL_SERVER_ERROR, // "獲取用戶(hù)信息失敗,嘗試訪問(wèn)的數(shù)據(jù)不存在!"); // } // return adminLoginVO;// ===== 以下是新的業(yè)務(wù),將從Redis中獲取數(shù)據(jù) =====// 從repsotiroy中調(diào)用方法,根據(jù)id獲取緩存的數(shù)據(jù)// 判斷緩存中是否存在與此id對(duì)應(yīng)的key// 有:表示明確的存入過(guò)某數(shù)據(jù),此數(shù)據(jù)可能是有效數(shù)據(jù),也可能是null// -- 判斷此key對(duì)應(yīng)的數(shù)據(jù)是否為null// -- 是:表示明確的存入了null值,則此id對(duì)應(yīng)的數(shù)據(jù)確實(shí)不存在,則拋出異常// -- 否:表示明確的存入了有效數(shù)據(jù),則返回此數(shù)據(jù)即可// 無(wú):表示從未向緩存中寫(xiě)入此id對(duì)應(yīng)的數(shù)據(jù),在數(shù)據(jù)庫(kù)中,此id可能存在數(shù)據(jù),也可能不存在// 從mapper中調(diào)用方法,根據(jù)id獲取數(shù)據(jù)庫(kù)的數(shù)據(jù)// 判斷從數(shù)據(jù)庫(kù)中獲取的結(jié)果是否為null// 是:數(shù)據(jù)庫(kù)也沒(méi)有此數(shù)據(jù),先向緩存中寫(xiě)入錯(cuò)誤數(shù)據(jù)(null),再拋出異常// 將從數(shù)據(jù)庫(kù)中查詢(xún)到的結(jié)果存入到緩存中// 返回查詢(xún)結(jié)果return null;}

大家看看實(shí)現(xiàn)的邏輯,這個(gè)很重要。

看完后,為了保證項(xiàng)目能運(yùn)行,我們還有兩步需要做。

添加調(diào)用SQL的方法

AdminMapper添加如下方法:

AdminLoginVO getLoginInfoByUserId(Long id);

添加SQL

AdminMapper.xml添加如下SQL:

<select id="getLoginInfoByUserId" resultMap="LoginInfoResultMap">select<include refid="LoginInfoQueryFields" />from adminleft join admin_roleon admin.id = admin_role.admin_idleft join role_permissionon admin_role.role_id = role_permission.role_idleft join permissionon role_permission.permission_id = permission.idwhere id=#{id} </select>?

避免緩存穿透

在IPassportRedisRepository接口中添加如下方法:

/*** 判斷是否存在id對(duì)應(yīng)的緩存數(shù)據(jù)** @param id 類(lèi)別id* @return 存在則返回true,否則返回false*/boolean exists(Long id);/*** 向緩存中寫(xiě)入某id對(duì)應(yīng)的空數(shù)據(jù)(null),此方法主要用于解決緩存穿透問(wèn)題** @param id 類(lèi)別id*/void saveEmptyValue(Long id);

在PassportRedisRepositoryImpl類(lèi)中添加實(shí)現(xiàn)方法如下:

@Overridepublic boolean exists(Long id) {String key = KEY_ADMIN_ITEM_PREFIX + id;return redisTemplate.hasKey(key);}@Overridepublic void saveEmptyValue(Long id) {String key = KEY_ADMIN_ITEM_PREFIX + id;redisTemplate.opsForValue().set(key, null);}

其實(shí)我們?cè)谏厦嬲f(shuō)過(guò),這種設(shè)置null的方法能一定程度上防止緩存反復(fù)穿透,但卻并不是最好的解決辦法,常規(guī)做法應(yīng)該是通過(guò)布隆過(guò)濾器來(lái)做。

業(yè)務(wù)實(shí)現(xiàn)

@Overridepublic AdminLoginVO getAdminDetailsById(Long id) { // ===== 以下是原有代碼,只從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù) ===== // AdminLoginVO adminLoginVO = adminMapper.getLoginInfoByUserId(id); // if (adminLoginVO == null) { // throw new CloudServiceException(ResponseCode.ERR_INTERNAL_SERVER_ERROR, // "獲取用戶(hù)信息失敗,嘗試訪問(wèn)的數(shù)據(jù)不存在!"); // } // return adminLoginVO;// ===== 以下是新的業(yè)務(wù),將從Redis中獲取數(shù)據(jù) =====log.debug("根據(jù)id({})獲取用戶(hù)詳情……", id);// 從repository中調(diào)用方法,根據(jù)id獲取緩存的數(shù)據(jù)// 判斷緩存中是否存在與此id對(duì)應(yīng)的keyboolean exists = redisRepository.exists(id);if (exists) {// 有:表示明確的存入過(guò)某數(shù)據(jù),此數(shù)據(jù)可能是有效數(shù)據(jù),也可能是null// -- 判斷此key對(duì)應(yīng)的數(shù)據(jù)是否為nullAdminLoginVO cacheResult = redisRepository.getAdminDetailsById(id);if (cacheResult == null) {// -- 是:表示明確的存入了null值,則此id對(duì)應(yīng)的數(shù)據(jù)確實(shí)不存在,則拋出異常log.warn("在緩存中存在此id()對(duì)應(yīng)的Key,卻是null值,則拋出異常", id);throw new CloudServiceException(ResponseCode.ERR_INTERNAL_SERVER_ERROR,"獲取用戶(hù)詳情失敗,嘗試訪問(wèn)的數(shù)據(jù)不存在!");} else {// -- 否:表示明確的存入了有效數(shù)據(jù),則返回此數(shù)據(jù)即可return cacheResult;}}// 緩存中沒(méi)有此id匹配的數(shù)據(jù)// 從mapper中調(diào)用方法,根據(jù)id獲取數(shù)據(jù)庫(kù)的數(shù)據(jù)log.debug("沒(méi)有命中緩存,則從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù)……");AdminLoginVO dbResult = adminMapper.getAdminInfoByUserId(id);// 判斷從數(shù)據(jù)庫(kù)中獲取的結(jié)果是否為nullif (dbResult == null) {// 是:數(shù)據(jù)庫(kù)也沒(méi)有此數(shù)據(jù),先向緩存中寫(xiě)入錯(cuò)誤數(shù)據(jù),再拋出異常log.warn("數(shù)據(jù)庫(kù)中也無(wú)此數(shù)據(jù)(id={}),先向緩存中寫(xiě)入錯(cuò)誤數(shù)據(jù)", id);redisRepository.saveEmptyValue(id);log.warn("拋出異常");throw new CloudServiceException(ResponseCode.ERR_INTERNAL_SERVER_ERROR,"獲取用戶(hù)信息失敗,嘗試訪問(wèn)的數(shù)據(jù)不存在!");}// 將從數(shù)據(jù)庫(kù)中查詢(xún)到的結(jié)果存入到緩存中l(wèi)og.debug("已經(jīng)從數(shù)據(jù)庫(kù)查詢(xún)到匹配的數(shù)據(jù),將數(shù)據(jù)存入緩存……");redisRepository.save(dbResult);// 返回查詢(xún)結(jié)果log.debug("返回查詢(xún)到數(shù)據(jù):{}", dbResult);return dbResult;}

到這里,基于業(yè)務(wù)調(diào)用的Redis業(yè)務(wù)調(diào)用流程代碼就結(jié)束了,你可以在controller中添加新的方法來(lái)調(diào)用此接口完成測(cè)試,博主不再寫(xiě)了。

但此時(shí)還有一個(gè)問(wèn)題,我們不能讓每次數(shù)據(jù)查詢(xún)的時(shí)候再去存Redis,否則第一次查詢(xún)的時(shí)候Redis是空的。基于此,我們需要在系統(tǒng)啟動(dòng)時(shí)就把數(shù)據(jù)存入Redis中,此法叫做緩存預(yù)熱。

緩存預(yù)熱

創(chuàng)建預(yù)熱類(lèi)

緩存預(yù)熱需要確定哪些數(shù)據(jù)在系統(tǒng)啟動(dòng)時(shí)就存入數(shù)據(jù),我們?cè)赟pring Boot內(nèi)自定義一個(gè)組件,他需要實(shí)現(xiàn)實(shí)現(xiàn)ApplicationRunner,我們和啟動(dòng)類(lèi)平級(jí)建一個(gè)這樣的類(lèi),名字叫CachePreLoad:

package com.codingfire.cloud.passport;import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner;public class CachePreLoad implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("CachePreLoad.run()");} }

此類(lèi)用于啟動(dòng)項(xiàng)目時(shí)提前預(yù)熱資源,run方法是重寫(xiě)的啟動(dòng)類(lèi)方法,啟動(dòng)時(shí)此方法會(huì)被調(diào)用,可在這里寫(xiě)存儲(chǔ)Redis相關(guān)的內(nèi)容。

但卻不可將業(yè)務(wù)實(shí)現(xiàn)直接寫(xiě)在這個(gè)類(lèi)中,為了項(xiàng)目代碼整體的一致性,我們還將預(yù)熱的業(yè)務(wù)以接口和實(shí)現(xiàn)的形式寫(xiě)在Redis單獨(dú)的Repository組件內(nèi)。

在寫(xiě)之前,為了防止童鞋們迷惑,博主需要解釋一個(gè)問(wèn)題,我個(gè)人也感覺(jué)使用用戶(hù)模塊做Redis不太恰當(dāng),應(yīng)該是使用店鋪列表,商品列表,品牌列表這樣不太會(huì)變的數(shù)據(jù)做Redis,考慮到再加入新的表增加大家學(xué)習(xí)難度,所以才在用戶(hù)表上面做文章,好在這個(gè)用戶(hù)表也很簡(jiǎn)單,大家在真實(shí)場(chǎng)景中可以根據(jù)這里的代碼轉(zhuǎn)嫁到其他的業(yè)務(wù)下即可,萬(wàn)不可鉆牛角尖。此處數(shù)據(jù)只做案例講解使用,并非真實(shí)使用場(chǎng)景,但代碼絕對(duì)是業(yè)務(wù)級(jí)別的。下面,我們來(lái)在Redis組件中增加預(yù)熱的代碼。

添加Redis操作接口

在IPassportRedisRepository接口中增加以下接口:

String KEY_ADMIN_LIST = "admins:list";/*** 將用戶(hù)的列表存入到Redis中** @param admins 用戶(hù)列表*/void save(List<AdminLoginVO> admins);/*** 刪除Redis中各獨(dú)立存儲(chǔ)的用戶(hù)數(shù)據(jù)*/void deleteAllItem();/*** 刪除Redis中的用戶(hù)列表* @return 如果成功刪除,則返回true,否則返回false*/Boolean deleteList();

實(shí)現(xiàn)Redis操作接口

接口增加完了,實(shí)現(xiàn)類(lèi)報(bào)錯(cuò),需要實(shí)現(xiàn)新增加的方法:

@Overridepublic void save(List<AdminLoginVO> admins) {for (AdminLoginVO admin : admins) {redisTemplate.opsForList().rightPush(KEY_ADMIN_LIST, admin);}}@Overridepublic void deleteAllItem() {Set<String> keys = redisTemplate.keys(KEY_ADMIN_ITEM_PREFIX + "*");redisTemplate.delete(keys);}@Overridepublic Boolean deleteList() {return redisTemplate.delete(KEY_ADMIN_LIST);}

添加調(diào)用SQL方法

在IAdminMapper接口中增加方法:

@Select("select * from admin")List<AdminLoginVO> list();

由于比較簡(jiǎn)單,就把SQL直接寫(xiě)在注解里來(lái)。

添加預(yù)熱調(diào)用接口

在IAdminService接口中增加預(yù)熱方法:

void preloadCache();

實(shí)現(xiàn)預(yù)熱方法

在AdminServiceImpl實(shí)現(xiàn)類(lèi)中實(shí)現(xiàn)上面的接口方法:

@Overridepublic void preloadCache() {log.debug("刪除緩存中的用戶(hù)列表……");redisRepository.deleteList();log.debug("刪除緩存中的各獨(dú)立的用戶(hù)數(shù)據(jù)……");redisRepository.deleteAllItem();log.debug("從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)列表……");List<AdminLoginVO> list = adminMapper.list();for (AdminLoginVO admin : list) {log.debug("查詢(xún)結(jié)果:{}", admin);log.debug("將當(dāng)前用戶(hù)存入到Redis:{}", admin);redisRepository.save(admin);}log.debug("將用戶(hù)列表寫(xiě)入到Redis……");redisRepository.save(list);log.debug("將用戶(hù)列表寫(xiě)入到Redis完成!");}

啟動(dòng)時(shí)預(yù)熱類(lèi)調(diào)用預(yù)熱方法?

最后一步,在預(yù)熱緩存類(lèi)中調(diào)用此預(yù)熱方法:

package com.codingfire.cloud.passport;import com.codingfire.cloud.passport.service.IAdminService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component;@Component @Slf4j public class CachePreLoad implements ApplicationRunner {@Autowiredprivate IAdminService adminService;@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("CachePreLoad.run()");log.debug("準(zhǔn)備執(zhí)行緩存預(yù)熱……");adminService.preloadCache();log.debug("緩存預(yù)熱完成!");} }

測(cè)試緩存預(yù)熱

其實(shí)這一步測(cè)試是最簡(jiǎn)單的,我們什么都不需要做,直接啟動(dòng)項(xiàng)目就可以,項(xiàng)目啟動(dòng)后,我們?cè)诳刂婆_(tái)會(huì)看到預(yù)熱方法運(yùn)行的輸出:

?下面是啟動(dòng)前,Redis客戶(hù)端內(nèi)的參數(shù)列表:

?下面是項(xiàng)目啟動(dòng)后,Redis客戶(hù)端內(nèi)的參數(shù)列表:

?到這一步,若果你的測(cè)試結(jié)果和博主一樣,那么恭喜你,你已經(jīng)完成了Redis的基本學(xué)習(xí),趕快到自己的項(xiàng)目中使用吧。

結(jié)語(yǔ)

Redis學(xué)習(xí)到這里就結(jié)束了,美中不足是少了布隆過(guò)濾器對(duì)Redis做的一個(gè)防止緩存穿透的操作,建議大家可以自己寫(xiě)寫(xiě),后期博主會(huì)做個(gè)補(bǔ)充,建議大家不要等,自己動(dòng)動(dòng)手,也不算難。總體上,Redis整體上還算是比較簡(jiǎn)單的,用過(guò)幾次其實(shí)就熟練了,很多東西都是調(diào)用API,再配合我們的業(yè)務(wù)邏輯來(lái)寫(xiě)。行吧,結(jié)語(yǔ)也不知道該寫(xiě)點(diǎn)啥,就是跟大家訴訴苦,太累了,坐的腰酸背疼,熬夜熬的眼疼,覺(jué)得寫(xiě)的不錯(cuò),三連支持一下。

總結(jié)

以上是生活随笔為你收集整理的Java开发 - Redis初体验的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。