概要
1)Redis的常用指令及業(yè)務(wù)應(yīng)用場(chǎng)景
2)使用Java客戶端操作Redis
簡(jiǎn)介:redis是一個(gè)開(kāi)源的使用C語(yǔ)言編寫说贝、支持網(wǎng)絡(luò)榆综、可基于內(nèi)存也可以持久化的日志型Key-Value數(shù)據(jù)庫(kù)糠爬,并提供多種語(yǔ)言的API楣号。
本質(zhì):客戶端-服務(wù)端應(yīng)用程序
特點(diǎn):使用簡(jiǎn)單叼耙、性能強(qiáng)大腕窥、應(yīng)用場(chǎng)景豐富
1.1 通用指令
指令 | 說(shuō)明 |
---|---|
del key | key存在時(shí),將key刪除 |
dump key | 序列化給定key筛婉,并返回被序列化的值 |
exists key | 檢查給定key是否存在 |
expire key seconds | 為給定key設(shè)置過(guò)期時(shí)間簇爆,以秒計(jì) |
ttl key | 以秒為單位炸宵,返回給定key的剩余生存時(shí)間(TTL-time to live) |
type key | 返回key所存儲(chǔ)的值得類型 |
dbsize | 返回對(duì)應(yīng)DB的大小 |
help @對(duì)應(yīng)的數(shù)據(jù)類型 | 查看對(duì)應(yīng)的數(shù)據(jù)類型的所有指令 |
help cmd | 查看對(duì)應(yīng)的指令的用法 |
示例
連接到服務(wù)端:
[docker@docker-node1 redis-all]$ ./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379>
簡(jiǎn)單指令演示:
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name vander
OK
127.0.0.1:6379[1]> exists name
(integer) 1
127.0.0.1:6379[1]> ttl name
(integer) -1
127.0.0.1:6379[1]> type name
string
127.0.0.1:6379[1]> set name vander ex 10
OK
127.0.0.1:6379[1]> ttl name
(integer) 5
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> get name
(nil)
127.0.0.1:6379[1]> set name1 jason
OK
127.0.0.1:6379[1]> keys *
1) "name1"
127.0.0.1:6379[1]>
1.2 五種基本數(shù)據(jù)結(jié)構(gòu)
Redis的數(shù)據(jù)結(jié)構(gòu)的命令可以從官方文檔查找:https://redis.io/commands
1.2.1 數(shù)據(jù)結(jié)構(gòu)-String
簡(jiǎn)介:string數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單的key-value類型,value不僅是string腊徙,還可以是數(shù)字
應(yīng)用場(chǎng)景:微博數(shù)酷含、粉絲數(shù)等常規(guī)計(jì)數(shù)、分布式鎖(Redis執(zhí)行Lua腳本保證原子性)哨毁、Session共享枫甲、全局序列號(hào)
字符串常用操作
指令 | 說(shuō)明 |
---|---|
set key value | 設(shè)置指定key的值 |
get key | 獲取指定key的值 |
mset key value [<key value> ...] | 批量存儲(chǔ)多個(gè)鍵值對(duì) |
mget key [key ...] | 獲取所有給定key的值 |
原子加減
指令 | 說(shuō)明 |
---|---|
incr key | 將key中存儲(chǔ)的數(shù)字值增1 |
decr key | 將key中存儲(chǔ)的數(shù)字值減1 |
incrby key increment | 將key存儲(chǔ)的數(shù)字值加上increment |
decrby key decrement | 將key存儲(chǔ)的數(shù)字值減去decrement |
設(shè)置Key-Value的有效時(shí)間:
127.0.0.1:6379[1]> set name vander ex 10
OK
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) 5
127.0.0.1:6379[1]> get name
"vander"
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> get name
(nil)
設(shè)置對(duì)Value進(jìn)行遞增或者遞減
127.0.0.1:6379[1]> set counter 10
OK
127.0.0.1:6379[1]> incr counter
(integer) 11
127.0.0.1:6379[1]> decr counter
(integer) 10
127.0.0.1:6379[1]>
應(yīng)用場(chǎng)景
## 單值緩存或者是使用對(duì)象序列化器存儲(chǔ)<key,Object>
127.0.0.1:6379> set name jason
OK
127.0.0.1:6379> get name
"jason"
127.0.0.1:6379>
## 分布式鎖的應(yīng)用
# 假設(shè)是第一個(gè)線程執(zhí)行這個(gè)指令
127.0.0.1:6379> setnx product:iPhone true
(integer) 1
# 另一個(gè)線程執(zhí)行此指令就失敗了
127.0.0.1:6379> setnx product:iPhone true
(integer) 0
# 第一個(gè)線程獲取設(shè)置成功后表示獲取到了鎖,等用完之后把鎖釋放掉
127.0.0.1:6379> del product:iPhone
(integer) 1
## 計(jì)數(shù)器場(chǎng)景
# 例如記錄文章被讀取的次數(shù)
127.0.0.1:6379> incr article:read_count:001
(integer) 1
127.0.0.1:6379> incr article:read_count:001
(integer) 2
127.0.0.1:6379> incr article:read_count:001
(integer) 3
127.0.0.1:6379> get article:read_count:001
"3"
## Web集群的Session共享
Spring Session + Redis 實(shí)現(xiàn)Session共享
## 分布式系統(tǒng)全局序列號(hào)
#為了保證性能每個(gè)客戶端批量獲取一批序列號(hào)扼褪,用完之后再到Redis里重新取一批
incrby orderid 1000
1.2.2 數(shù)據(jù)結(jié)構(gòu)-List
簡(jiǎn)介:即鏈表
可實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu):
Stack(棧) = LPUSH+LPOP = FILO
Queue(隊(duì)列)= LPUSH+RPOP = FIFO
Blocking Queue(阻塞隊(duì)列)= LPUSH + BRPOP
使用場(chǎng)景:微博的關(guān)注列表想幻,粉絲列表
指令 | 說(shuō)明 |
---|---|
lpush key value1 value2 ... | 將一個(gè)或多個(gè)值插入到列表頭部 |
rpush key value1 value2 ... | 在列表中添加一個(gè)或多個(gè)值 |
lpop key | 移除并獲取列表的第一個(gè)元素,返回值為移除的元素 |
rpop key | 移除并獲取列表的最后一個(gè)元素话浇,返回值為移除的元素 |
lrange | 獲取所有給定key的值 |
實(shí)現(xiàn)棧(后進(jìn)先出)的功能:
127.0.0.1:6379[1]> rpush teachers Miss.Xu Miss.Yu
(integer) 2
127.0.0.1:6379[1]> rpop teachers
"Miss.Yu"
127.0.0.1:6379[1]> rpop teachers
"Miss.Xu"
127.0.0.1:6379[1]> rpush teachers Miss.Xu
(integer) 1
127.0.0.1:6379[1]> rpush teachers Miss.Yu
(integer) 2
127.0.0.1:6379[1]> rpop teachers
"Miss.Yu"
127.0.0.1:6379[1]> rpop teachers
"Miss.Xu"
127.0.0.1:6379[1]>
實(shí)現(xiàn)隊(duì)列(先進(jìn)先出)功能:
127.0.0.1:6379[1]> lpush students stu1 stu2
(integer) 2
127.0.0.1:6379[1]> rpop students
"stu1"
127.0.0.1:6379[1]> rpop students
"stu2"
127.0.0.1:6379[1]>
場(chǎng)景模擬
指令 | 說(shuō)明 |
---|---|
lpush key value1 value2 ... | 將一個(gè)或多個(gè)值插入到列表頭部 |
rpush key value1 value2 ... | 在列表中添加一個(gè)或多個(gè)值 |
lpop key | 移除并獲取列表的第一個(gè)元素脏毯,返回值為移除的元素 |
rpop key | 移除并獲取列表的最后一個(gè)元素,返回值為移除的元素 |
lrange | 獲取所有給定key的值 |
blpop key [key ...] timeout | 從key列表頭部彈出一個(gè)元素幔崖,若列表沒(méi)有元素食店,阻塞等待timeout秒,如果timeout=0赏寇,則會(huì)一直阻塞等待 |
brpop key [key ...] timeout | 從key列表尾部彈出一個(gè)元素吉嫩,若列表沒(méi)有元素,阻塞等待timeout秒蹋订,如果timeout=0率挣,則會(huì)一直阻塞等待 |
##微博或微信公眾號(hào)消息,如Vander關(guān)注了"財(cái)經(jīng)旗艦"露戒、“大眾點(diǎn)評(píng)”兩個(gè)公眾號(hào)
#001為用戶Vander的UserID
#模擬財(cái)經(jīng)旗艦發(fā)送了一個(gè)消息椒功,消息編號(hào)為1001
127.0.0.1:6379> lpush msg:001 msg_1001
(integer) 1
#模擬大眾點(diǎn)評(píng)發(fā)送了一個(gè)消息,消息編號(hào)為1005
127.0.0.1:6379> lpush msg:001 msg_1005
(integer) 2
#查看待閱列表
127.0.0.1:6379> lrange msg:001 0 2
1) "msg_1005"
2) "msg_1001"
1.2.3 數(shù)據(jù)結(jié)構(gòu)-Set
簡(jiǎn)介:即集合智什,存儲(chǔ)一堆不重復(fù)值的組合
使用場(chǎng)景:抽獎(jiǎng)动漾、微信微博點(diǎn)贊、收藏荠锭、標(biāo)簽
Set常用操作
指令 | 說(shuō)明 |
---|---|
sadd key member [member ...] | 將一個(gè)或多個(gè)值插入到列表頭部 |
srem key member [member ...] | 從集合key中刪除元素 |
spop key [count] | 移除并獲取集合中隨機(jī)獲取count個(gè)元素旱眯,返回值為移除的元素 |
srandmember key [count] | 獲取集合中隨機(jī)獲取count個(gè)元素,不會(huì)移除元素 |
sismembers key member | 判斷member元素是否存在集合key中 |
smembers key | 返回集合中的所有成員 |
sunion | 返回給定集合的并集 |
往集合中添加刪除元素:
127.0.0.1:6379[1]> sadd students a_stu b_stu
(integer) 2
127.0.0.1:6379[1]> spop students
"b_stu"
127.0.0.1:6379[1]> sadd students c_stu
(integer) 1
127.0.0.1:6379[1]> spop students
"c_stu"
127.0.0.1:6379[1]> sadd students 0_stus
(integer) 1
127.0.0.1:6379[1]> spop students
"a_stu"
127.0.0.1:6379[1]> sadd students a_stu b_stu
(integer) 2
127.0.0.1:6379[1]> smembers students
1) "b_stu"
2) "0_stus"
3) "a_stu"
127.0.0.1:6379[1]> spop students 3
1) "b_stu"
2) "0_stus"
3) "a_stu"
將三個(gè)集合取并集:
127.0.0.1:6379[1]> sadd stuGroup1 stu_a stu_b stu_c
(integer) 3
127.0.0.1:6379[1]> sadd stuGroup2 stu_k stu_a stu_b
(integer) 3
127.0.0.1:6379[1]> sadd stuGroup3 stu_x stu_c stu_b
(integer) 3
127.0.0.1:6379[1]> sunion stuGroup1 stuGroup2 stuGroup3
1) "stu_x"
2) "stu_c"
3) "stu_k"
4) "stu_a"
5) "stu_b"
127.0.0.1:6379[1]>
場(chǎng)景模擬
##模擬抽獎(jiǎng)
# 添加抽獎(jiǎng)用戶
127.0.0.1:6379> sadd draw_user Vander Jason Panda Susan
(integer) 4
# 查看所有參與抽獎(jiǎng)用戶
127.0.0.1:6379> smembers draw_user
1) "Susan"
2) "Jason"
3) "Panda"
4) "Vander"
# 抽取一個(gè)用戶放回的(不放回的用spop draw_user 1)
127.0.0.1:6379> srandmember draw_user 1
1) "Susan"
127.0.0.1:6379> srandmember draw_user 1
1) "Panda"
127.0.0.1:6379> srandmember draw_user 1
1) "Jason"
##模擬點(diǎn)贊取消點(diǎn)贊证九、收藏删豺、標(biāo)簽
# 點(diǎn)贊,001-消息ID愧怜、1001-用戶ID呀页,說(shuō)明1001用戶對(duì)001消息點(diǎn)贊
127.0.0.1:6379> sadd like:001 1001
(integer) 1
# 1001用戶對(duì)002消息點(diǎn)贊
127.0.0.1:6379> sadd like:002 1001
(integer) 1
# 1002用戶對(duì)001消息點(diǎn)贊
127.0.0.1:6379> sadd like:001 1002
(integer) 1
# 檢查1002用戶是否對(duì)001消息點(diǎn)贊
127.0.0.1:6379> sismember like:001 1002
(integer) 1
# 獲取對(duì)001消息點(diǎn)贊的用戶列表
127.0.0.1:6379> smembers like:001
1) "1001"
2) "1002"
# 獲取對(duì)001消息點(diǎn)贊的用戶數(shù)
127.0.0.1:6379> scard like:001
(integer) 2
# 1001用戶取消對(duì)001消息點(diǎn)贊
127.0.0.1:6379> srem like:001 1001
(integer) 1
Set集合操作
指令 | 說(shuō)明 |
---|---|
sinter key [key ...] | 交集運(yùn)算 |
sinterstore dest key [key...] | 交集運(yùn)算并把結(jié)果存到新的集合dest中 |
sunion key [key ...] | 并集運(yùn)算 |
sunionstore dest key [key ...] | 并集運(yùn)算并把結(jié)果存到新的集合dest中 |
sdiff key [key ...] | 差集運(yùn)算 |
sdiffstore dest key [key ...] | 差集運(yùn)算并把結(jié)果存到新的集合dest中 |
集合運(yùn)算應(yīng)用場(chǎng)景:共同關(guān)注、共同喜好拥坛、電商商品篩選
假設(shè)上圖中的集合Set1是A同學(xué)的關(guān)注列表蓬蝶,Set2是B同學(xué)的關(guān)注列表尘分,Set3是C同學(xué)的關(guān)注列表
A同學(xué)和B同學(xué)的共用關(guān)注:Set1∩Set2 -> sinter set1 set2
A同學(xué)關(guān)注的人關(guān)注了他:即A同學(xué)關(guān)注了B同學(xué),A同學(xué)關(guān)注了C同學(xué)丸氛,B培愁、C同學(xué)都關(guān)注了d
SISMEMBER Set2 d(即A同學(xué)關(guān)注的B同學(xué)關(guān)注了d)
SISMEMBER Set3 d(即A同學(xué)關(guān)注的C同學(xué)關(guān)注了d)
A可能認(rèn)識(shí)的人:SDIFF Set2 Set1 (即從B同學(xué)的關(guān)注列表里列出A同學(xué)沒(méi)有的)
場(chǎng)景模擬
# 添加華為p40手機(jī)
127.0.0.1:6379> sadd brand:huawei p40
(integer) 1
# 添加小米11Pro手機(jī)
127.0.0.1:6379> sadd brand:xiaomi "xiaomi 11 pro"
(integer) 1
# 添加iPhone 12 Pro手機(jī)
127.0.0.1:6379> sadd brand:apple "iPhone 12 Pro"
(integer) 1
# 添加安卓系統(tǒng)的手機(jī)
127.0.0.1:6379> sadd os:android p40 "xiaomi 11 pro"
(integer) 2
# 添加內(nèi)存8G的手機(jī)
127.0.0.1:6379> sadd ram:8G p40 "xiaomi 11 pro" "iPhone 12 Pro"
(integer) 3
# 獲取安卓系統(tǒng)且內(nèi)存為8G的手機(jī)
127.0.0.1:6379> sinter os:android ram:8G
1) "p40"
2) "xiaomi 11 pro"
1.2.4 數(shù)據(jù)結(jié)構(gòu)-Sorted Set(ZSet)
簡(jiǎn)介:即有序集合,存儲(chǔ)一堆不重復(fù)值的組合缓窜,與Set類似定续,區(qū)別在于Set不是自動(dòng)有序,Sorted Set則可以通過(guò)用戶額外提供一個(gè)優(yōu)先級(jí)(score)來(lái)為成員排序雹洗,并且是插入有序(會(huì)自動(dòng)進(jìn)行排序)
使用場(chǎng)景:排行榜香罐、按用戶投票和進(jìn)行時(shí)間排序
ZSet常用操作
指令 | 說(shuō)明 |
---|---|
zadd key <score member> [<score member> ...] | 往有序集合key中加入帶分值的元素 |
zrem key member [member ...] | 從有序集合key中刪除元素 |
zscore key member | 返回有序集合key中元素member的分值 |
zincrby key incr member | 為有序集合key中元素member的分支加上incr |
zcard key | 返回有序集合key中的元素個(gè)數(shù) |
zrange key start stop [withscores] | 正序獲取有序集合Key卧波,從start下標(biāo)到stop下標(biāo)的元素 |
zrevrange key start stop [withscores] | 倒序獲取有序集合Key时肿,從start下標(biāo)到stop下標(biāo)的元素 |
往有序集中添加元素:
127.0.0.1:6379[1]> del stuGroup1
(integer) 1
127.0.0.1:6379[1]> zadd stuGroup1 0 "stu_a" 1 "stu_b" 2 "stu_c"
(integer) 3
127.0.0.1:6379[1]> del stuGroup1
(integer) 1
127.0.0.1:6379[1]> zadd stuGroup1 0 stu_a 1 stu_b 2 stu_c
(integer) 3
127.0.0.1:6379[1]> zrange stuGroup1 0 -1 withscores
1) "stu_a"
2) "0"
3) "stu_b"
4) "1"
5) "stu_c"
6) "2"
移除并顯示有序集的元素:
127.0.0.1:6379[1]> zrem stuGroup1 stu_b
(integer) 1
127.0.0.1:6379[1]> zrange stuGroup1 0 -1 withscores
1) "stu_a"
2) "0"
3) "stu_c"
4) "2"
127.0.0.1:6379[1]> zcard stuGroup1
(integer) 2
場(chǎng)景模擬
##使用ZSet實(shí)現(xiàn)排行榜,假設(shè)當(dāng)前百度熱榜是趙麗穎結(jié)婚港粱、鄭爽棄養(yǎng)螃成、喬妹離婚
# 使用此方式登錄,否則顯示亂碼
./redis-cli --raw -h 127.0.0.1 -p 6379
# 模擬點(diǎn)擊新聞
127.0.0.1:6379> zincrby HotNews:20210201 1 鄭爽棄養(yǎng)
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 鄭爽棄養(yǎng)
"2"
127.0.0.1:6379> zincrby HotNews:20210201 1 鄭爽棄養(yǎng)
"3"
127.0.0.1:6379> zincrby HotNews:20210201 1 鄭爽棄養(yǎng)
"4"
127.0.0.1:6379> zincrby HotNews:20210201 1 趙麗穎結(jié)婚
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 趙麗穎結(jié)婚
"2"
127.0.0.1:6379> zincrby HotNews:20210201 1 喬妹離婚
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 小米11發(fā)布
"1"
127.0.0.1:6379> zincrby HotNews:20210201 1 蘋果發(fā)布會(huì)
"1"
# 展示當(dāng)日點(diǎn)擊量排行前3的新聞查坪,并將點(diǎn)擊量輸出
127.0.0.1:6379> zrevrange HotNews:20210201 0 2 withscores
鄭爽棄養(yǎng)
4
趙麗穎結(jié)婚
2
蘋果發(fā)布會(huì)
1
#新增其它兩天的點(diǎn)擊量
127.0.0.1:6379> zincrby HotNews:20210202 1 小米11發(fā)布
1
127.0.0.1:6379> zincrby HotNews:20210202 1 喬妹離婚
1
127.0.0.1:6379> zincrby HotNews:20210203 1 鄭爽棄養(yǎng)
1
127.0.0.1:6379> zincrby HotNews:20210203 1 小米11發(fā)布
1
# 3天內(nèi)的榜單計(jì)算
127.0.0.1:6379> zunionstore HotNews:20210201-20210203 3 HotNews:20210201 HotNews:20210202 HotNews:20210203
5
127.0.0.1:6379> zrange HotNews:20210201-20210203 0 -1 withscores
蘋果發(fā)布會(huì)
1
喬妹離婚
2
趙麗穎結(jié)婚
2
小米11發(fā)布
3
鄭爽棄養(yǎng)
5
# 展示3天排行前3名點(diǎn)擊量的事件
127.0.0.1:6379> zrevrange HotNews:20210201-20210203 0 2
鄭爽棄養(yǎng)
小米11發(fā)布
趙麗穎結(jié)婚
ZSet集合操作
指令 | 說(shuō)明 |
---|---|
zunionstore dest numkeys key [key ...] | 并集運(yùn)算寸宏,并存儲(chǔ)結(jié)果到dest |
zinterstore dest numkeys key [key ...] | 交集運(yùn)算,并存儲(chǔ)結(jié)果到dest |
1.2.5 數(shù)據(jù)結(jié)構(gòu)-Hash
簡(jiǎn)介:Hash是一個(gè)string類型的field和value的映射表
使用場(chǎng)景:存儲(chǔ)部分變更數(shù)據(jù)偿曙,如用戶信息
指令 | 說(shuō)明 |
---|---|
hset key field value | 將哈希表中key的字段field的值設(shè)置為value |
hget key field | 獲取存儲(chǔ)在哈希表中指定field的值 |
hmset key field value [field value ...] | 批量獲取哈希表key中對(duì)應(yīng)多個(gè)field的鍵值 |
hmget key field [field ...] | 批量獲取哈希表key中多個(gè)field鍵值 |
hsetnx key field value | 存儲(chǔ)一個(gè)不存在與hash表key的鍵值對(duì) |
hdel key field [field ...] | 刪除hash表key中的field簡(jiǎn)直 |
hlen key | 返回hash表key中field的數(shù)量 |
hget all | 獲取在哈希表中指定key的所有字段和值 |
hincrby key field increment | 為hash表key中filed鍵的值上增量increment |
往Hash表中插入元素:
127.0.0.1:6379[1]> hset Miss.Xu age 25
(integer) 1
127.0.0.1:6379[1]> hset Miss.Xu height 170
(integer) 1
127.0.0.1:6379[1]> hget Miss.Xu age
"25"
127.0.0.1:6379[1]> hgetall Miss.Xu
1) "age"
2) "25"
3) "height"
4) "170"
127.0.0.1:6379[1]>
場(chǎng)景模擬
##對(duì)象緩存
127.0.0.1:6379> hmset user:2010130110 name Vander age 18
OK
127.0.0.1:6379> hmget user:2010130110 name age
1) "Vander"
2) "18"
##電商購(gòu)物車氮凝,key-用戶ID field-商品ID value-商品數(shù)量
# 添加商品
127.0.0.1:6379> hset cart:2010130110 1001 1
(integer) 1
127.0.0.1:6379> hset cart:2010130110 1002 1
(integer) 1
127.0.0.1:6379> hset cart:2010130110 1003 2
(integer) 1
127.0.0.1:6379> hincrby cart:2010130110 1001 3
(integer) 4
# 獲取商品種類的個(gè)數(shù)
127.0.0.1:6379> hlen cart:2010130110
(integer) 3
# 刪除商品
127.0.0.1:6379> hdel cart:2010130110 1002
(integer) 1
# 獲取購(gòu)物車的所有商品
127.0.0.1:6379> hgetall cart:2010130110
1) "1001"
2) "4"
3) "1003"
4) "2"
Hash VS string
優(yōu)勢(shì):
1)同類數(shù)據(jù)歸類整合存儲(chǔ),方便數(shù)據(jù)管理
2)相比string操作消耗內(nèi)存與CPU更小
3)相比string存儲(chǔ)更省空間
劣勢(shì):
1)過(guò)期功能不能使用在field上望忆,只能用在key上
2)Redis集群架構(gòu)下不適合大規(guī)模使用
1.3 Java代碼實(shí)操
下面我們通過(guò)Spring演示以上的數(shù)據(jù)罩阵,細(xì)節(jié)請(qǐng)查看Spring官方文檔:
https://docs.spring.io/spring-data/redis/docs/2.1.8.RELEASE/reference/html/
官方文檔中有這么一句話:
Spring Redis requires Redis 2.6 or above and Spring Data Redis integrates with Lettuce and Jedis, two popular open-source Java libraries for Redis(說(shuō)明Spring的Redis客戶端有Lettuce和Jedis兩種)
1.3.1 Example :使用Jedis連接單實(shí)例的Redis服務(wù)端
@Profile(“jedis”)—@ActiveProfiles(“jedis”)成對(duì)使用,ActiveProfiles會(huì)選擇對(duì)應(yīng)的Profile進(jìn)行注入
測(cè)試類:JedisTests.java——使用Jedis操作set启摄、hash稿壁、zset、list(模擬隊(duì)列)
package szu.vander.test.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@ActiveProfiles("single") // 設(shè)置profile
@Slf4j
public class JedisTests {
@Autowired
private Jedis jedis;
// ------------------------ jedis 工具直連演示
// jedis和redis命令名稱匹配度最高歉备,最為簡(jiǎn)潔傅是,學(xué)習(xí)難度最低
// 列表~ 集合數(shù)據(jù)存儲(chǔ)~ java.util.List,java.util.Stack
// 生產(chǎn)者消費(fèi)者(簡(jiǎn)單MQ)
@Test
public void list() {
// 插入數(shù)據(jù)1 --- 2 --- 3
jedis.rpush("queue_1", "1");
jedis.rpush("queue_1", "2", "3");
List<String> strings = jedis.lrange("queue_1", 0, -1);
for (String string : strings) {
log.info(String.format("往隊(duì)列queue_1中寫入:%s", string));
}
// 消費(fèi)者線程簡(jiǎn)例
while (true) {
String item = jedis.lpop("queue_1");
if (item == null) break;
log.info(String.format("從隊(duì)列queue_1中取出:%s", item));
}
jedis.close();
}
// 類似:在redis里面存儲(chǔ)一個(gè)hashmap
// 推薦的方式蕾羊,無(wú)特殊需求是喧笔,一般的緩存都用這個(gè)
@Test
public void hashTest() {
String key = "2010130110";
jedis.hset(key, "name", "Vander");
jedis.hset(key, "age", "18");
jedis.hget(key, "name");
log.info(String.format("獲取Key=%s的所有相關(guān)屬性:%s", key, jedis.hgetAll(key).toString()));
jedis.close();
}
// 用set實(shí)現(xiàn)(交集 并集)
// 交集示例: 共同關(guān)注的好友
// 并集示例:
@Test
public void setTest() {
// 取出兩個(gè)人共同關(guān)注的好友
// 每個(gè)人維護(hù)一個(gè)set
jedis.sadd("userA", "userC", "userD", "userE");
jedis.sadd("userB", "userC", "userE", "userF");
// 取出共同關(guān)注
Set<String> intersection = jedis.sinter("userA", "userB");
log.info(String.format("獲取userA{%s}和userB{%s}的交集:%s", jedis.smembers("userA"), jedis.smembers("userB"), intersection));
// 取出共同人群
Set<String> unionSet = jedis.sunion("userA", "userB");
log.info(String.format("獲取userA{%s}和userB{%s}的并集:%s", jedis.smembers("userA"), jedis.smembers("userB"), unionSet));
jedis.close();
}
// 游戲排行榜
@Test
public void zsetTest() {
String ranksKeyName = "exam_rank";
jedis.zadd(ranksKeyName, 100.0, "stu1");
jedis.zadd(ranksKeyName, 82.0, "stu2");
jedis.zadd(ranksKeyName, 90, "stu3");
jedis.zadd(ranksKeyName, 96, "stu4");
jedis.zadd(ranksKeyName, 89, "stu5");
jedis.zadd(ranksKeyName, 29, "stu6");
Set<String> stringSet = jedis.zrevrange(ranksKeyName, 0, 2);
System.out.println("返回前三名:");
for (String s : stringSet) {
System.out.println(s);
}
long zcount = jedis.zcount(ranksKeyName, 85, 100);
System.out.println("超過(guò)85分的數(shù)量 " + zcount);
jedis.close();
}
}
JedisConfig
package szu.jason.redis.standalone.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import redis.clients.jedis.Jedis;
import szu.jason.redis.standalone.config.common.RedisStandaloneProperties;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 注入連接單節(jié)點(diǎn)的Jedis客戶端
*/
@Profile("jedis")
@Slf4j
@Configuration
public class JedisConfig {
@Autowired
private RedisStandaloneProperties properties;
@Bean
public Jedis jedis() {
log.info("注入Jedis!");
Jedis jedis = new Jedis(properties.getHostname(), properties.getPort());
jedis.select(properties.getDb());
return jedis;
}
}
User
package szu.jason.redis.standalone.model;
import com.sun.xml.internal.ws.developer.Serialization;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 此對(duì)象需要實(shí)現(xiàn)序列化接口龟再,否則JDK序列化器無(wú)法將其序列化
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private String userId;
private String username;
}
AppConfig
package szu.jason.redis.standalone.config.common;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import szu.jason.redis.standalone.model.User;
import java.util.HashMap;
import java.util.Map;
/**
* @author : Vander
* @date : 2021/3/7
* @description :
*/
@ComponentScan("szu.jason.redis.standalone")
@Configuration
public class AppConfig {
@Bean
public Map<String, User> userRepository() {
Map<String, User> userRepository = new HashMap<>();
userRepository.put("2010130110", new User("2010130110", "Jason"));
userRepository.put("2013130128", new User("2013130128", "Panda"));
return userRepository;
}
}
PropertiesConfig
package szu.jason.redis.standalone.config.common;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author : Vander
* @date : 2021/3/7
* @description :
*/
@Configuration
@EnableConfigurationProperties(RedisStandaloneProperties.class)
public class PropertiesConfig {
}
RedisStandaloneProperties
package szu.jason.redis.standalone.config.common;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author : Vander
* @date : 2021/3/7
* @description : 根據(jù)前綴注入屬性值
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "standalone.redis")
public class RedisStandaloneProperties {
private String hostname;
private int port;
private int db;
}
application-redis.properties——配置redis服務(wù)
standalone.redis.hostname=192.168.118.8
standalone.redis.port=6379
standalone.redis.db=1
Pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cache-redis-demo</artifactId>
<groupId>szu.jason</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis-standalone</artifactId>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
</dependencies>
</project>
1.3.2 Example :使用Lettuce連接單實(shí)例的Redis服務(wù)端
測(cè)試類:使用Lettuce客戶端操作Hash類型的數(shù)據(jù)
package test.redis.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import szu.jason.redis.standalone.config.common.AppConfig;
import szu.jason.redis.standalone.model.User;
import szu.jason.redis.standalone.service.LettuceExampleService;
@ActiveProfiles("lettuce")
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@PropertySource("classpath:application-redis.properties")
@ImportAutoConfiguration(classes= AppConfig.class)
public class LettuceTests {
@Autowired
private LettuceExampleService exampleService;
@Test
public void testGet() throws Exception {
User user = exampleService.findUser("2010130110");
System.out.println(user);
}
}
LettuceConfig
package szu.jason.redis.standalone.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import szu.jason.redis.standalone.config.common.RedisStandaloneProperties;
@Profile("lettuce")
@Slf4j
@EnableCaching
@Configuration
public class LettuceConfig {
@Autowired
private RedisStandaloneProperties properties;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
log.info(String.format("注入LettuceConnectionFactory书闸,當(dāng)前Host:%s:%s database:%s!",
properties.getHostname(), properties.getPort(), properties.getDb()));
RedisStandaloneConfiguration redisStandaloneConfiguration =
new RedisStandaloneConfiguration(properties.getHostname(), properties.getPort());
redisStandaloneConfiguration.setDatabase(properties.getDb());
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.info("注入RedisTemplate吸申!");
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 可以配置對(duì)象的轉(zhuǎn)換規(guī)則梗劫,比如使用json格式對(duì)object進(jìn)行存儲(chǔ)享甸。
// Object --> 序列化 --> 二進(jìn)制流 --> redis-server存儲(chǔ)
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
return redisTemplate;
}
// 配置Spring Cache注解功能
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
log.info("注入CacheManager!");
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
return cacheManager;
}
}
LettuceExampleService
package szu.jason.redis.standalone.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import szu.jason.redis.standalone.model.User;
import java.util.Map;
@Profile("lettuce")
@Slf4j
@Service
public class LettuceExampleService {
// 參數(shù)可以是任何對(duì)象梳侨,默認(rèn)由JDK序列化
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private Map<String, User> userRepository;
/**
* 對(duì)象緩存功能
*/
public User findUser(String userId) throws Exception {
User user;
// 1蛉威、 判定緩存中是否存在
user = (User) redisTemplate.opsForValue().get(userId);
if (user != null) {
log.info("從緩存中讀取到值:" + user);
return user;
}
// TODO 2、不存在則讀取數(shù)據(jù)庫(kù)或者其他地方的值
user = userRepository.get(userId);
log.info("從數(shù)據(jù)庫(kù)中讀取到值:" + user);
// 3走哺、 同步存儲(chǔ)value到緩存蚯嫌。
redisTemplate.opsForValue().set(userId, user);
return user;
}
}
AppConfig、PropertiesConfig丙躏、RedisStandaloneProperties择示、User、application-redis.properties晒旅、pom均與上個(gè)example一致
1.3.3 Example :使用Spring Cache實(shí)現(xiàn)自動(dòng)移除緩存栅盲、添加緩存、更新緩存
測(cè)試類:模擬查找废恋、更新谈秫、刪除數(shù)據(jù)庫(kù)記錄的操作,Spring Cache會(huì)自動(dòng)更新緩存
package test.redis.standalone;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import szu.jason.redis.standalone.config.common.AppConfig;
import szu.jason.redis.standalone.model.User;
import szu.jason.redis.standalone.service.SpringCacheService;
@ActiveProfiles("lettuce")
@Slf4j
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(SpringJUnit4ClassRunner.class)
@PropertySource("classpath:application-redis.properties")
@ImportAutoConfiguration(classes= AppConfig.class)
public class SpringCacheTests {
private final static String USER_ID = "2010130110";
@Autowired
private SpringCacheService springCacheService;
@Test
public void test0FindUserById() {
User user = springCacheService.findUserById(USER_ID);
System.out.println("\n");
log.info("查找到userId為{}的用戶:{}", USER_ID, user.toString());
System.out.println("\n");
}
@Test
public void test1UpdateUser() {
springCacheService.updateUser(new User(USER_ID, "Panda"));
User user = springCacheService.findUserById(USER_ID);
System.out.println("\n");
log.info("userId為{}的用戶:{}", USER_ID, user.toString());
System.out.println("\n");
}
@Test
public void test2DeleteUserById() {
User user = springCacheService.deleteUserById(USER_ID);
System.out.println("\n");
log.info("刪除userId為{}的用戶:{}", USER_ID, user.toString());
System.out.println("\n");
}
}
SpringCacheService
package szu.jason.redis.standalone.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import szu.jason.redis.standalone.model.User;
import java.util.Map;
@Profile("lettuce")
@Slf4j
@Service
public class SpringCacheService {
@Autowired
private Map<String, User> userRepository;
/**
* value的作用:寫入Redis時(shí)會(huì)自動(dòng)添加緩存前綴
* key:支持SpEL表達(dá)式
* @param userId
* @return
*/
@Cacheable(cacheManager = "cacheManager", value = "user", key = "#userId")
public User findUserById(String userId) {
// 讀取數(shù)據(jù)庫(kù)
User user = userRepository.get(userId);
System.out.println("\n");
log.info("從數(shù)據(jù)庫(kù)中讀取到數(shù)據(jù):{}", user);
System.out.println("\n");
return user;
}
@CacheEvict(cacheManager = "cacheManager", value = "user", key = "#userId")
public User deleteUserById(String userId) {
User user = userRepository.remove(userId);
System.out.println("\n");
log.info(String.format("用戶從數(shù)據(jù)庫(kù)刪除成功鱼鼓,請(qǐng)檢查緩存ID:%s是否已經(jīng)清除拟烫!", user.getUserId()));
System.out.println("\n");
return user;
}
// 如果數(shù)據(jù)庫(kù)更新成功,更新redis緩存
@CachePut(cacheManager = "cacheManager", value = "user", key = "#user.userId", condition = "#result ne null")
public User updateUser(User user) {
// 更新數(shù)據(jù)庫(kù)
userRepository.put(user.getUserId(), user);
System.out.println("\n");
log.info("數(shù)據(jù)庫(kù)進(jìn)行了更新迄本,檢查緩存是否一致");
System.out.println("\n");
return user; // 返回最新內(nèi)容硕淑,代表更新成功
}
}
AppConfig、PropertiesConfig嘉赎、RedisStandaloneProperties置媳、User、application-redis.properties曹阔、pom均與上個(gè)example一致
運(yùn)行結(jié)果:
test0FindUserById:觀察到對(duì)應(yīng)的User被加載到了緩存
test1UpdateUser:觀察到對(duì)應(yīng)的User的名稱被改為了Panda
test2DeleteUserById:觀察到對(duì)應(yīng)的Key已經(jīng)被刪除了
附錄:建議安裝教程
下載Redis半开,解壓后編譯
$ wget http://download.redis.io/releases/redis-5.0.4.tar.gz
$ tar xzf redis-5.0.4.tar.gz
$ cd redis-5.0.4
$ make
運(yùn)行Redis服務(wù)端
$ src/redis-server
You can interact with Redis using the built-in client:
運(yùn)行Redis客戶端
$ src/redis-cli
redis> set foo bar
OK
redis> get foo
"bar"
···