Redis(入门)
文章目錄
- 一、 Redis簡(jiǎn)介
- 二、 基于Docker安裝Redis單機(jī)版
- 三、 Redis常用命令
- 1 Key操作
- 2 字符串值(String)(值的長(zhǎng)度不超過(guò)512MB)
- 3 哈希表(Hash)
- 4 列表(List)
- 5 集合(Set)
- 6 有序集合(Sorted Set)
- 四、 Redis持久化策略
- 1 RDB
- 2 AOF
- 五、 Redis主從復(fù)制
- 六、 哨兵(Sentinel)
- 七、Redis集群(Cluster)
- 八、 Jedis(了解)
- 九、 使用SpringBoot整合SpringDataRedis操作redis
- 十、 高并發(fā)下Redis可能存在的問(wèn)題及解決方案
- 1 緩存擊穿
- 2 緩存雪崩
- 3 緩存穿透(查詢不存在數(shù)據(jù))
- 4 邊路緩存
- 5 Redis腦裂
- 6 Redis 緩存淘汰策略/當(dāng)內(nèi)存不足時(shí)如何回收數(shù)據(jù)/保證Redis中數(shù)據(jù)不出現(xiàn)內(nèi)存溢出情況
一、 Redis簡(jiǎn)介
1 NoSQL簡(jiǎn)介
目前市場(chǎng)主流數(shù)據(jù)存儲(chǔ)都是使用關(guān)系型數(shù)據(jù)庫(kù)。每次操作關(guān)系型數(shù)據(jù)庫(kù)時(shí)都是I/O操作,I/O操作是主要影響程序執(zhí)行性能原因之一,連接數(shù)據(jù)庫(kù)關(guān)閉數(shù)據(jù)庫(kù)都是消耗性能的過(guò)程。盡量減少對(duì)數(shù)據(jù)庫(kù)的操作,能夠明顯的提升程序運(yùn)行效率。
針對(duì)上面的問(wèn)題,市場(chǎng)上就出現(xiàn)了各種NoSQL(Not Only SQL,不僅僅可以使用關(guān)系型數(shù)據(jù)庫(kù))數(shù)據(jù)庫(kù),它們的宣傳口號(hào):不是什么樣的場(chǎng)景都必須使用關(guān)系型數(shù)據(jù)庫(kù),一些特定的場(chǎng)景使用NoSQL數(shù)據(jù)庫(kù)更好。
常見(jiàn)NoSQL數(shù)據(jù)庫(kù):
memcached :鍵值對(duì),內(nèi)存型數(shù)據(jù)庫(kù),所有數(shù)據(jù)都在內(nèi)存中。
Redis:和Memcached類似,還具備持久化能力。
HBase:以列作為存儲(chǔ)。
MongoDB:以Document做存儲(chǔ)。
2 Redis簡(jiǎn)介
Redis是以Key-Value形式進(jìn)行存儲(chǔ)的NoSQL數(shù)據(jù)庫(kù)。
Redis是使用C語(yǔ)言進(jìn)行編寫(xiě)的。
平時(shí)操作的數(shù)據(jù)都在內(nèi)存中,效率特高,讀的效率110000/s,寫(xiě)81000/s,所以多把Redis當(dāng)做緩存工具使用。
Redis以solt(槽)作為數(shù)據(jù)存儲(chǔ)單元,每個(gè)槽中可以存儲(chǔ)N多個(gè)鍵值對(duì)。Redis中固定具有16384。理論上可以實(shí)現(xiàn)一個(gè)槽是一個(gè)Redis。每個(gè)向Redis存儲(chǔ)數(shù)據(jù)的key都會(huì)進(jìn)行crc16算法得出一個(gè)值后對(duì)16384取余就是這個(gè)key存放的solt位置。
同時(shí)通過(guò)Redis Sentinel提供高可用,通過(guò)Redis Cluster提供自動(dòng)分區(qū)。
二、 基于Docker安裝Redis單機(jī)版
1 拉取鏡像
docker pull redis:6.2.12 創(chuàng)建并啟動(dòng)容器
docker run -d --name redis -p 6379:6379 --restart always redis:6.2.13 客戶端測(cè)試
docker exec -it redis bash docker exec -it redis redis-cli 在任意目錄在輸入redis-cli 即可進(jìn)入redis命令行。三、 Redis常用命令
Redis中數(shù)據(jù)是key-value形式,key為字符串類型,value可取類型如下:
? String 字符串
? Hash 哈希表
? List 列表
? Set 集合
? Sorted Set 有序集合
Redis命令相關(guān)手冊(cè)有很多,下面為其中比較好用的兩個(gè)
https://www.redis.net.cn/order/
http://doc.redisfans.com/
1 Key操作
1.1 exists
判斷key是否存在。
語(yǔ)法:exists key名稱
返回值:存在返回?cái)?shù)字,不存在返回0
1.2 expire
設(shè)置key的過(guò)期時(shí)間,單位秒
語(yǔ)法:expire key 秒數(shù)
返回值:成功返回1,失敗返回0
1.3 ttl
查看key的剩余過(guò)期時(shí)間
語(yǔ)法:ttl key
返回值:返回剩余時(shí)間,如果不過(guò)期返回-1,如key不存在返回-2
1.4 del
根據(jù)key刪除鍵值對(duì)。
語(yǔ)法:del key [key…]
返回值:被刪除key的數(shù)量
1.5 keys
查看當(dāng)前redis中的key數(shù)據(jù)
語(yǔ)法: keys 表達(dá)式 如: keys *
返回值:符合表達(dá)式的key列表
2 字符串值(String)(值的長(zhǎng)度不超過(guò)512MB)
應(yīng)?場(chǎng)景:
驗(yàn)證碼、計(jì)數(shù)器、訂單重復(fù)提交、?戶登錄信息、商品詳情、分布式鎖
2.1 set(mset批量設(shè)置)
設(shè)置指定key的值
語(yǔ)法:set key value
返回值:成功OK
2.2 get(mget批量獲取)
獲取指定key的值
語(yǔ)法:get key
返回值:key的值。不存在返回nil
2.3 setnx
當(dāng)且僅當(dāng)key不存在時(shí)才新增。恒新增,無(wú)修改功能。
語(yǔ)法:setnx key value
返回值:不存在時(shí)返回1,存在返回0
底層:
setnx具備分布式鎖能力。在編寫(xiě)代碼時(shí)如果調(diào)用setnx,時(shí)會(huì)對(duì)代碼進(jìn)行加鎖。直到刪除該key時(shí)會(huì)解鎖。
setnx();// 加鎖
// 代碼
del();//解鎖。
如果在并發(fā)訪問(wèn)時(shí)第一個(gè)線程setnx()時(shí)發(fā)現(xiàn)沒(méi)有指定key會(huì)正常向下運(yùn)行。其他線程在執(zhí)行setnx()時(shí)發(fā)現(xiàn)有這個(gè)key就會(huì)等待,等待第一個(gè)線程刪除key時(shí)才會(huì)繼續(xù)向下執(zhí)行。
常見(jiàn)的鎖
鎖:在Java中可以通過(guò)鎖,讓多線程執(zhí)行時(shí)某個(gè)代碼塊或方法甚至類是線程安全的。通俗點(diǎn)說(shuō):一個(gè)線程訪問(wèn),別的線程需要等待。
線程鎖:同一個(gè)應(yīng)用。多線程訪問(wèn)時(shí)添加的鎖。synchronized(自動(dòng)釋放)或Lock(手動(dòng)釋放)
進(jìn)程鎖:不同進(jìn)程(一個(gè)進(jìn)程就是一個(gè)應(yīng)用)需要訪問(wèn)同一個(gè)資源時(shí),可以通過(guò)添加進(jìn)程鎖進(jìn)行實(shí)現(xiàn)。
分布式鎖:在分布式項(xiàng)目中不同項(xiàng)目訪問(wèn)同一個(gè)資源時(shí),可以通過(guò)添加分布式鎖保證線程安全。常見(jiàn)的分布式鎖有兩種:Redis的分布式鎖和Zookeeper的分布式鎖(通過(guò)調(diào)用Zookeeper的API給Zookeeper集群添加一個(gè)節(jié)點(diǎn)。如果節(jié)點(diǎn)能添加繼續(xù)向下執(zhí)行,執(zhí)行結(jié)束刪除該節(jié)點(diǎn)。如果其他線程發(fā)現(xiàn)該節(jié)點(diǎn)已經(jīng)添加,會(huì)阻塞等待該節(jié)點(diǎn)刪除才繼續(xù)向下執(zhí)行。)。
2.4 setex
設(shè)置key的存活時(shí)間,無(wú)論是否存在指定key都能新增,如果存在key覆蓋舊值。同時(shí)必須指定過(guò)期時(shí)間。
語(yǔ)法:setex key seconds value
返回值:OK
2.5 incr
對(duì)key的值加一,返回新值
語(yǔ)法:incr key
返回值:新值
2.5 incrby
對(duì)key的值加increment,返回新值,若key不存在,操作之前key置為0
語(yǔ)法:incr key increment
返回值:新值
2.5 getset
對(duì)key設(shè)置新值,返回舊值(set返回為ok)
語(yǔ)法:getset key aa
返回值:key的舊值
3 哈希表(Hash)
應(yīng)用場(chǎng)景:
購(gòu)物?、?戶個(gè)人信息、商品詳情
3.1 hset
給key中field設(shè)置值。
語(yǔ)法:hset key field value
返回值:成功1,失敗0
3.2 hget
獲取key中某個(gè)field的值
語(yǔ)法:hget key field
返回值:返回field的內(nèi)容
3.3 hmset
給key中多個(gè)filed設(shè)置值
語(yǔ)法:hmset key field value field value
返回值:成功OK
3.4 hmget
一次獲取key中多個(gè)field的值
語(yǔ)法:hmget key field field
返回值:value列表
3.5 hkeys
獲取key中所有的field的值
語(yǔ)法: hkeys key
返回值: field 列表
3.6 hvals
獲取key中所有value的值
語(yǔ)法:hvals key
返回值:value列表
3.7 hgetall
獲取所有field和value
語(yǔ)法:hgetall key
返回值:field和value交替顯示列表
3.8 hdel
刪除key中任意個(gè)field
語(yǔ)法:hdel key field field
返回值:成功刪除field的數(shù)量,當(dāng)刪除key中所有的field,key自動(dòng)刪除。
4 列表(List)
應(yīng)?場(chǎng)景:
簡(jiǎn)單隊(duì)列、最新評(píng)論列表、非實(shí)時(shí)排行榜:定時(shí)計(jì)算榜單(如手機(jī)日銷榜單)
4.1 rpush
向列表末尾中插入一個(gè)或多個(gè)值
語(yǔ)法;rpush key value value
返回值:列表長(zhǎng)度
4.2 lrange
返回列表中指定區(qū)間內(nèi)的值。可以使用-1代表列表末尾
語(yǔ)法:lrange list 0 -1
返回值:查詢到的值
4.3 lpush
將一個(gè)或多個(gè)值插入到列表前面
語(yǔ)法:lpush key value value
返回值:列表長(zhǎng)度
4.4 llen
獲取列表長(zhǎng)度
語(yǔ)法:llen key
返回值:列表長(zhǎng)度
4.5 lrem
刪除列表中元素。count為正數(shù)表示從左往右刪除的數(shù)量。負(fù)數(shù)從右往左刪除的數(shù)量。
語(yǔ)法:lrem key count value
返回值:刪除數(shù)量。
4.6 lpop rpop
移除并獲取最后一個(gè)元素,并返回該元素
語(yǔ)法:lpop key rpop key
返回值:刪除元素。
4.6 brpop
移除并獲取最后一個(gè)元素,沒(méi)有元素會(huì)阻塞列表直到超時(shí)或發(fā)現(xiàn)有可彈出元素
語(yǔ)法:brpop key timeout
返回值:刪除元素。
5 集合(Set)
應(yīng)用場(chǎng)景:
去重
社交應(yīng)用關(guān)注、粉絲、共同好友
統(tǒng)計(jì)網(wǎng)站的PV、uV、IP
大數(shù)據(jù)里面的用戶畫(huà)像標(biāo)簽集合
5.1 sadd
向集合中添加內(nèi)容。不允許重復(fù)。
語(yǔ)法:sadd key value value value
返回值:本次命令新增數(shù)據(jù)個(gè)數(shù)
5.2 scard
返回集合元素?cái)?shù)量
語(yǔ)法:scard key
返回值:集合長(zhǎng)度
5.3 smembers
查看集合中元素內(nèi)容
語(yǔ)法:smembers key
返回值:集合中元素
5.4 srem
刪除集合中的元素
語(yǔ)法: srem key member [member…]
返回值:刪除元素個(gè)數(shù)
6 有序集合(Sorted Set)
應(yīng)用場(chǎng)景:
實(shí)時(shí)排行榜:商品熱銷榜、
體育類應(yīng)用熱門球隊(duì)、積分榜優(yōu)先級(jí)任務(wù)、隊(duì)列
朋友圈文章點(diǎn)贊-取消,邏輯:用戶只能點(diǎn)贊或取消,統(tǒng)計(jì)一篇文章被點(diǎn)贊了多少次,可以直接取里面有多少個(gè)成員
數(shù)據(jù)結(jié)構(gòu)介紹:
使?HashMap+跳表skipList保證數(shù)據(jù)存儲(chǔ)和有序
跳躍表性能堪?紅?樹(shù),?且實(shí)現(xiàn)起來(lái)?紅?樹(shù)簡(jiǎn)單很多
有序集合中每個(gè)value都有一個(gè)分?jǐn)?shù)(score),根據(jù)分?jǐn)?shù)進(jìn)行排序。
6.1 zadd
向有序集合中添加一個(gè)或者多個(gè)數(shù)據(jù)
語(yǔ)法:zadd key score value score value
返回值:新增的元素個(gè)數(shù)
6.2 zrange
返回區(qū)間內(nèi)容,從小到大,withscores表示帶有分?jǐn)?shù)
語(yǔ)法:zrange key start stop [withscores]
返回值:值列表
6.3 zrem
刪除集合內(nèi)容
語(yǔ)法: zrem key member [member …]
返回值: 刪除元素個(gè)數(shù)
6.4 zcard
獲取有序集合成員數(shù)
語(yǔ)法: zcard key
返回值: 個(gè)數(shù)
6.5 zcount
獲取有序集合指定區(qū)間內(nèi)的成員數(shù)
語(yǔ)法: zcard key min max
返回值: 個(gè)數(shù)
6.6 zincrby
給指定元素增加increment分?jǐn)?shù)
語(yǔ)法: zincrby key increment member
返回值: 增量后的分?jǐn)?shù)
6.2 zrevange
返回區(qū)間內(nèi)容,從大到小,withscores表示帶有分?jǐn)?shù)
語(yǔ)法:zrange key start stop [withscores]
返回值:值列表
四、 Redis持久化策略
Redis不僅僅是一個(gè)內(nèi)存型數(shù)據(jù)庫(kù),還具備持久化能力。
1 RDB
rdb模式是默認(rèn)模式,可以在指定的時(shí)間間隔內(nèi)生成數(shù)據(jù)快照(snapshot),默認(rèn)保存到dump.rdb文件中。當(dāng)redis重啟后會(huì)自動(dòng)加載dump.rdb文件中內(nèi)容到內(nèi)存中。
用戶可以使用SAVE(同步)或BGSAVE(異步)手動(dòng)保存數(shù)據(jù)。
可以設(shè)置服務(wù)器配置的save選項(xiàng),讓服務(wù)器每隔一段時(shí)間自動(dòng)執(zhí)行一次BGSAVE命令,可以通過(guò)save選項(xiàng)設(shè)置多個(gè)保存條件,但只要其中任意一個(gè)條件被滿足,服務(wù)器就會(huì)執(zhí)行BGSAVE命令。
例如:
save 900 1
save 300 10
save 60 10000
那么只要滿足以下三個(gè)條件中的任意一個(gè),BGSAVE命令就會(huì)被執(zhí)行
服務(wù)器在900秒之內(nèi),對(duì)數(shù)據(jù)庫(kù)進(jìn)行了至少1次修改
服務(wù)器在300秒之內(nèi),對(duì)數(shù)據(jù)庫(kù)進(jìn)行了至少10次修改
服務(wù)器在60秒之內(nèi),對(duì)數(shù)據(jù)庫(kù)進(jìn)行了至少10000次修改
1.1 優(yōu)點(diǎn)
rdb文件是一個(gè)緊湊文件,直接使用rdb文件就可以還原數(shù)據(jù)。
數(shù)據(jù)保存會(huì)由一個(gè)子進(jìn)程進(jìn)行保存,不影響父進(jìn)程。
恢復(fù)數(shù)據(jù)的效率要高于aof
1.2 缺點(diǎn)
每次保存點(diǎn)之間,因redis不可意料的關(guān)閉,可能會(huì)導(dǎo)致丟失數(shù)據(jù)。
由于每次保存數(shù)據(jù)都需要fork()子進(jìn)程,在數(shù)據(jù)量比較大時(shí)可能會(huì)比較耗費(fèi)性能。
2 AOF
AOF默認(rèn)是關(guān)閉的,需要在配置文件中開(kāi)啟AOF。Redis支持AOF和RDB同時(shí)生效,如果同時(shí)存在,AOF優(yōu)先級(jí)高于RDB(Redis重新啟動(dòng)時(shí)會(huì)使用AOF進(jìn)行數(shù)據(jù)恢復(fù))
監(jiān)聽(tīng)執(zhí)行的命令,如果發(fā)現(xiàn)執(zhí)行了修改數(shù)據(jù)的操作,同時(shí)直接同步到數(shù)據(jù)庫(kù)文件中。
2.1 優(yōu)點(diǎn)
相對(duì)RDB數(shù)據(jù)更加安全。
2.2 缺點(diǎn)
相同數(shù)據(jù)集AOF要大于RDB。
相對(duì)RDB可能會(huì)慢一些。
2.3 開(kāi)啟辦法
修改redis.conf中。
appendonly yes 開(kāi)啟aof
appendfilename 設(shè)置aof數(shù)據(jù)文件,名稱隨意。
五、 Redis主從復(fù)制
Redis支持集群功能。為了保證單一節(jié)點(diǎn)可用性,redis支持主從復(fù)制功能。每個(gè)節(jié)點(diǎn)有N個(gè)復(fù)制品(replica),其中一個(gè)復(fù)制品是主(master),另外N-1個(gè)復(fù)制品是從(Slave),也就是說(shuō)Redis支持一主多從。
一個(gè)主可有多個(gè)從,而一個(gè)從又可以看成主,它還可以有多個(gè)從。
1 主從優(yōu)點(diǎn)
增加單一節(jié)點(diǎn)的健壯性,從而提升整個(gè)集群的穩(wěn)定性。(Redis中當(dāng)超過(guò)1/2節(jié)點(diǎn)不可用時(shí),整個(gè)集群不可用)
從節(jié)點(diǎn)可以對(duì)主節(jié)點(diǎn)數(shù)據(jù)備份,提升容災(zāi)能力。
讀寫(xiě)分離。在redis主從中,主節(jié)點(diǎn)一般用作寫(xiě)(具備讀的能力),從節(jié)點(diǎn)只能讀,利用這個(gè)特性實(shí)現(xiàn)讀寫(xiě)分離,寫(xiě)用主,讀用從。
2 基于Docker一主多從搭建
2.1 拉取redis鏡像
2.2 創(chuàng)建并運(yùn)行三個(gè)Docker容器
先停止單機(jī)版Redis。單機(jī)版Redis端口6379
三個(gè)容器分別占用系統(tǒng)的6479、6480、6481端口
2.3 在從中指定主的ip和端口
設(shè)定redis1容器為主機(jī)(master)。redis2和redis3容器為從機(jī)(slave)
進(jìn)入redis2容器內(nèi)部設(shè)置主的ip和端口,連接從機(jī),指定主機(jī)端口
進(jìn)入redis2容器內(nèi)部設(shè)置主的ip和端口
# docker exec -it redis3 redis-cli # slaveof 192.168.108.128 6379 # exit2.4 測(cè)試主從效果
進(jìn)入redis1容器內(nèi)部,新增key-value
分別進(jìn)入redis2和redis3容器,查看是否有name鍵
# docker exec -it redis1 redis-cli # get name六、 哨兵(Sentinel)
在redis主從默認(rèn)是只有主具備寫(xiě)的能力,而從只能讀。如果主宕機(jī),整個(gè)節(jié)點(diǎn)不具備寫(xiě)能力。但是如果這是讓一個(gè)從變成主,整個(gè)節(jié)點(diǎn)就可以繼續(xù)工作。即使之前的主恢復(fù)過(guò)來(lái)也當(dāng)做這個(gè)節(jié)點(diǎn)的從即可。
Redis的哨兵就是幫助監(jiān)控整個(gè)節(jié)點(diǎn)的,當(dāng)節(jié)點(diǎn)主宕機(jī)等情況下,幫助重新選取主。
Redis中哨兵支持單哨兵和多哨兵。單哨兵是只要這個(gè)哨兵發(fā)現(xiàn)master宕機(jī)了,就直接選取另一個(gè)master。而多哨兵是根據(jù)我們?cè)O(shè)定,達(dá)到一定數(shù)量哨兵認(rèn)為master宕機(jī)后才會(huì)進(jìn)行重新選取主。我們以多哨兵演示。
1 沒(méi)有哨兵下主從效果
只要?dú)⒌糁?#xff0c;整個(gè)節(jié)點(diǎn)無(wú)法在寫(xiě)數(shù)據(jù),從身份不會(huì)變化,主的信息還是以前的信息。
七、Redis集群(Cluster)
1 集群原理
a) 集群搭建完成后由集群節(jié)點(diǎn)平分(不能平分時(shí),前幾個(gè)節(jié)點(diǎn)多一個(gè)槽)16384個(gè)槽。
b) 客戶端可以訪問(wèn)集群中任意節(jié)點(diǎn)。所以在寫(xiě)代碼時(shí)都是需要把集群中所有節(jié)點(diǎn)都配置上。
c) 當(dāng)向集群中新增或查詢一個(gè)鍵值對(duì)時(shí),會(huì)對(duì)Key進(jìn)行Crc16算法得出一個(gè)小于16384值,這個(gè)值就是放在哪個(gè)槽中,在判斷槽在哪個(gè)節(jié)點(diǎn)上,然后就操作哪個(gè)節(jié)點(diǎn)。
集群:集群中所有節(jié)點(diǎn)都安裝在不同服務(wù)器上。
偽集群:所有節(jié)點(diǎn)都安裝在一臺(tái)服務(wù)器上,通過(guò)不同端口號(hào)進(jìn)行區(qū)分不同節(jié)點(diǎn)。
當(dāng)集群中超過(guò)或等于1/2節(jié)點(diǎn)不可用時(shí),整個(gè)集群不可用。為了搭建穩(wěn)定集群,都采用奇數(shù)節(jié)點(diǎn)。
Redis每個(gè)節(jié)點(diǎn)都支持一主多從。會(huì)有哨兵監(jiān)控主的狀態(tài)。如果出現(xiàn)(配置文件中配置當(dāng)多少個(gè)哨兵認(rèn)為主失敗時(shí))哨兵發(fā)現(xiàn)主不可用時(shí)會(huì)進(jìn)行投票,投票選舉一個(gè)從當(dāng)作主,如果后期主恢復(fù)了,主當(dāng)作從加入節(jié)點(diǎn)。在搭建redis集群時(shí),內(nèi)置哨兵策略。
演示時(shí):創(chuàng)建3個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)搭建一主一從。一共需要有6個(gè)redis。
2 Redis集群安裝步驟
2.1 新建配置模板文件
紅色I(xiàn)P部分需要修改為自己的Linux虛擬機(jī)IP
port ${PORT} protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 192.168.137.128 cluster-announce-port ${PORT} cluster-announce-bus-port 1${PORT} appendonly yes2.2 使用Shell腳本創(chuàng)建6個(gè)目錄
for port in `seq 7000 7005`; do \mkdir -p ./${port}/conf \&& PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \&& mkdir -p ./${port}/data; \ done2.3 創(chuàng)建橋連網(wǎng)絡(luò)
# docker network create redis-net查看網(wǎng)絡(luò)是否創(chuàng)建成功
# docker network ls2.4 創(chuàng)建并啟動(dòng)6個(gè)容器
for port in `seq 7000 7005`; do \docker run -d -ti -p ${port}:${port} -p 1${port}:1${port} \-v /usr/local/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \-v /usr/local/redis-cluster/${port}/data:/data \--restart no --name redis-${port} --net redis-net \--sysctl net.core.somaxconn=1024 redis:5.0.5 redis-server /usr/local/etc/redis/redis.conf; \ done2.5 查看6個(gè)容器ip及端口
# docker inspect redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 | grep IPAddress2.6 執(zhí)行集群腳本
進(jìn)入6個(gè)容器中任意一個(gè)。示例中以redis-7000舉例
執(zhí)行創(chuàng)建腳本命令。 --cluster-relicas 1表示每個(gè)主有1個(gè)從。
redis-cli --cluster create \ 172.18.0.2:7000 \ 172.18.0.3:7001 \ 172.18.0.4:7002 \ 172.18.0.5:7003 \ 172.18.0.6:7004 \ 172.18.0.7:7005 \ --cluster-replicas 1輸入后給出集群信息,輸入yes后創(chuàng)建集群
2.7 驗(yàn)證集群
在任意Redis容器內(nèi)部,進(jìn)入Redis客戶端工具。
示例中還是以Redis-7000舉例。
八、 Jedis(了解)
Redis給Java語(yǔ)言提供了客戶端API,稱之為Jedis。
Jedis API和Redis 命令幾乎是一樣的。
例如:Redis對(duì)String值新增時(shí)set命令,Jedis中也是set方法。所以本課程中沒(méi)有重點(diǎn)把所有方法進(jìn)行演示,重要演示Jedis如何使用。
Jedis API特別簡(jiǎn)單,基本上都是創(chuàng)建對(duì)象調(diào)用方法即可。由于Jedis不具備把對(duì)象轉(zhuǎn)換為字符串的能力,所以每次都需要借助Json轉(zhuǎn)換工具進(jìn)行轉(zhuǎn)換,這個(gè)功能在Spring Data Redis中已經(jīng)具備,推薦使用Spring Data Redis。
九、 使用SpringBoot整合SpringDataRedis操作redis
1 Spring Data簡(jiǎn)介
Spring Data是Spring公司的頂級(jí)項(xiàng)目,里面包含了N多個(gè)二級(jí)子項(xiàng)目,這些子項(xiàng)目都是相對(duì)獨(dú)立的項(xiàng)目。每個(gè)子項(xiàng)目是對(duì)不同API的封裝。
所有Spring Boot整合Spring Data xxxx的啟動(dòng)器都叫做spring-boot-starter-data-xxxx
Spring Data 好處很方便操作對(duì)象類型(基于POJO模型)。
只要是Spring Data 的子項(xiàng)目被Spring Boot整合后都會(huì)有一個(gè)XXXXTemplate示實(shí)例。
把Redis不同值得類型放到一個(gè)opsForXXX方法中。
opsForValue : String值(最常用),如果存儲(chǔ)Java對(duì)象或Java中集合時(shí)就需要使用序列化器,進(jìn)行序列化成JSON字符串。
opsForList : 列表List
opsForHash: 哈希表Hash
opsForZSet: 有序集合Sorted Set
opsForSet : 集合
2 Spring Data Redis序列化器介紹
經(jīng)常需要向Redis中保存Java中Object或List等類型,這個(gè)時(shí)候就需要通過(guò)序列化器把Java中對(duì)象轉(zhuǎn)換為字符串進(jìn)行存儲(chǔ)。
2.1 JdkSerializationRedisSerializer
是RedisTemplate類默認(rèn)的序列化方式。JdkSerializationRedisSerializer使用JDK自帶的序列化方式。要求被序列化的對(duì)象必須實(shí)現(xiàn)java.io.Serializable接口,而且存儲(chǔ)的內(nèi)容為二進(jìn)制數(shù)據(jù),這對(duì)開(kāi)發(fā)者是不友好的。會(huì)出現(xiàn)雖然不影響使用,但是直接使用Redis客戶端查詢Redis中數(shù)據(jù)時(shí)前面出現(xiàn)亂碼問(wèn)題。
2.2 OxmSerializer
以字符串格式的xml存儲(chǔ)。解析起來(lái)也比較復(fù)雜,效率也比較低。已經(jīng)很少有人在使用該序列化器。
2.3 StringRedisSerializer
只能對(duì)String類型序列化操作。
2.4 GenericToStringSerializer
需要調(diào)用者給傳遞一個(gè)對(duì)象到字符串互轉(zhuǎn)的Converter(轉(zhuǎn)換器),使用比較麻煩。
2.5 Jackson2JsonRedisSerializer
該序列化器可以將對(duì)象自動(dòng)轉(zhuǎn)換為Json的形式存儲(chǔ),效率高且對(duì)調(diào)用者友好。
優(yōu)點(diǎn):
速度快,序列化后的字符串短小精悍,不需要實(shí)現(xiàn)Serializable接口。
缺點(diǎn):
此類的構(gòu)造函數(shù)中有一個(gè)類型參數(shù),必須提供要序列化對(duì)象的類型信息(.class對(duì)象)。如果存儲(chǔ)List等帶有泛型的類型,此序列化器是無(wú)法識(shí)別泛型的,會(huì)直接把泛型固定設(shè)置為L(zhǎng)inkedHashMap。
例如:存儲(chǔ)時(shí)List , 取出時(shí)是List
2.6 GenericJackson2JsonRedisSerializer
與Jackson2JsonRedisSerializer功能相似。底層依然使用Jackson工具包。相比Jackson2JsonRedisSerializer多了_class列,列里面存儲(chǔ)類(新增時(shí)類型)的全限定路徑,從Redis取出時(shí)根據(jù)_class類型進(jìn)行轉(zhuǎn)換,解決了泛型問(wèn)題。
該序列化器不需要指定對(duì)象類型信息(.class對(duì)象)使用Object作為默認(rèn)類型。目前都使用這個(gè)序列化器。
3 代碼步驟
基于單元測(cè)試演示
3.1 添加依賴
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0-M3</version> </parent><dependencies><!-- 為了要在項(xiàng)目中jackson工具包 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> </dependencies>3.2 配置配置文件
spring.redis.host=localhost 默認(rèn)值
spring.redis.port=6379 端口號(hào)默認(rèn)值
如果連接Redis集群,不需要配置host,配置spring.redis.cluster.nodes,取值為redis集群所在ip:port,ip:port。由于word排版問(wèn)題nodes后面取值沒(méi)有和nodes在一行。
3.3 編寫(xiě)配置類
@Configuration public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);//配置key和value的序列化器。不適用默認(rèn)的jdk序列化,使用json序列化redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//如果使用hash數(shù)據(jù)類型。可以提供格外的序列化器redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return redisTemplate;} }3.4 編寫(xiě)代碼
package com.bjsxt;import com.bjsxt.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.test.context.junit4.SpringRunner;import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;@SpringBootTest public class TestDataRedis {/*** Spring Data Redis提供的客戶端對(duì)象。這個(gè)對(duì)象* 是spring-boot-starter-data-redis構(gòu)建的。* 類型是RedisTemplate* 默認(rèn)創(chuàng)建的客戶端泛型是RedisTemplate<Object, Object>* 代表,當(dāng)前客戶端對(duì)象在訪問(wèn)Redis的時(shí)候,對(duì)key的類型約束是Object,對(duì)value的類型約束是Object** RedisTemplate中,提供了key和value的序列化器。* 默認(rèn)提供的序列化器都是JDKSerializer,基于Serializable實(shí)現(xiàn)的序列化。* 數(shù)據(jù)讀寫(xiě)的時(shí)候,RedisTemplate先把參數(shù)key和value,用序列化器轉(zhuǎn)換成字節(jié)數(shù)組。* 在實(shí)現(xiàn)讀寫(xiě)操作。** RedisTemplate是基于模板設(shè)計(jì)模式開(kāi)發(fā)的類型。其中也包含很多其他的設(shè)計(jì)模式,包括不限于:* 工廠方法設(shè)計(jì)模式,構(gòu)建器設(shè)計(jì)模式,模板方法設(shè)計(jì)模式,裝飾模式等。* RedisTemplate基于工廠方法,為不同的數(shù)據(jù)類型,準(zhǔn)備了不同的訪問(wèn)客戶端。* 如:字符串操作,ValueOperations,通過(guò)redisTemplate.opsForValue()方法獲取。* 如:hash操作,HashOperations,通過(guò)redisTemplate.opsForHash()獲取。* RedisTemplate中也有大量的直接訪問(wèn)Redis服務(wù)器的方法,這些方法都是操作key的或者管理服務(wù)器的。* 如:刪除鍵值對(duì);查詢有效時(shí)間;刪除有效時(shí)間;設(shè)置有效時(shí)間等。*/@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Testpublic void testKeys(){redisTemplate.delete("user1");redisTemplate.expire("users", 1L, TimeUnit.MINUTES);System.out.println(redisTemplate.getExpire("users"));redisTemplate.persist("users");System.out.println(redisTemplate.getExpire("users"));}@Testpublic void testUsers(){List<User> user = new ArrayList<>();for(int i = 0; i < 3; i++){user.add(new User(i, "name"+i, "male"));}redisTemplate.opsForValue().set("users", user);}@Testpublic void testList(){redisTemplate.opsForList().rightPushAll("test-list", "1", "2", "3");System.out.println(redisTemplate.opsForList().range("test-list", 0, -1));}@Testpublic void testHash(){redisTemplate.opsForHash().put("test-hash1", "f1", "v1");System.out.println(redisTemplate.opsForHash().get("test-hash1", "f1"));}@Testpublic void testSetUser(){User user = new User(1, "張三", "男");ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();valueOps.set("user3", user);System.out.println(valueOps.get("user3"));/*redisTemplate.opsForValue().set("user2", user);System.out.println(redisTemplate.opsForValue().get("user2"));*/}// 新增字符串?dāng)?shù)據(jù)@Testpublic void testSet(){redisTemplate.opsForValue().set("data-k2", "data-v2");Object value = redisTemplate.opsForValue().get("data-k2");System.out.println(value);System.out.println(redisTemplate.opsForValue().get("data-k1"));}@Testpublic void testClient(){System.out.println(redisTemplate);} }十、 高并發(fā)下Redis可能存在的問(wèn)題及解決方案
1 緩存擊穿
緩存中沒(méi)有但數(shù)據(jù)庫(kù)中有的數(shù)據(jù),假如是熱點(diǎn)數(shù)據(jù),那key在緩存過(guò)期的?刻,同時(shí)有?量的請(qǐng)求,這些請(qǐng)求都會(huì)擊穿到DB,造成瞬時(shí)DB請(qǐng)求量?、壓?增?。和緩存雪崩的區(qū)別在于這?針對(duì)某?key緩存,后者則是很多key。
解決辦法:
設(shè)置熱點(diǎn)數(shù)據(jù)不過(guò)期,定時(shí)任務(wù)定時(shí)更新緩存
設(shè)置互斥鎖
1.1 ReentrantLock(重入鎖)
JDK對(duì)對(duì)于并發(fā)訪問(wèn)處理的內(nèi)容都放入了java.util.concurrent中
ReentrantLock性能和synchronized沒(méi)有區(qū)別的,但是API使用起來(lái)更加方便。
@SpringBootTest public class MyTest {@Testpublic void test(){new Thread(){@Overridepublic void run() {test2("第一個(gè)線程111111");}}.start();new Thread(){@Overridepublic void run() {test2("第二個(gè)線程222222");}}.start();try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}}ReentrantLock lock = new ReentrantLock();public void test2(String who){lock.lock();if(lock.isLocked()) {System.out.println("開(kāi)始執(zhí)行:" + who);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("執(zhí)行完:" + who);lock.unlock();}} }1.2 解決緩存擊穿實(shí)例代碼
只有在第一次訪問(wèn)時(shí)和Key過(guò)期時(shí)才會(huì)訪問(wèn)數(shù)據(jù)庫(kù)。對(duì)于性能來(lái)說(shuō)沒(méi)有過(guò)大影響,因?yàn)槠綍r(shí)都是直接訪問(wèn)redis。
private ReentrantLock lock = new ReentrantLock(); @Override public Item selectByid(Integer id) {String key = "item:"+id;if(redisTemplate.hasKey(key)){return (Item) redisTemplate.opsForValue().get(key);}lock.lock();if(lock.isLocked()) {Item item = itemDubboService.selectById(id);// 由于設(shè)置了有效時(shí)間,就可能出現(xiàn)緩存擊穿問(wèn)題redisTemplate.opsForValue().set(key, item, 7, TimeUnit.DAYS);lock.unlock();return item;}// 如果加鎖失敗,為了保護(hù)數(shù)據(jù)庫(kù),直接返回nullreturn null; }SpringCache解決?案:
緩存的同步 ,sync 可以指示底層將緩存鎖住,使只有?個(gè)線程可以進(jìn)?計(jì)算,?其他線程堵塞,直到返回結(jié)果更新到緩存中
@Cacheable(value = {"product"},key ="#root.args[0]", cacheManager ="customCacheManager", sync=true)2 緩存雪崩
緩存雪崩 (多個(gè)熱點(diǎn)key都過(guò)期)?量的key設(shè)置了相同的過(guò)期時(shí)間,導(dǎo)致在緩存在同?時(shí)刻全部失效,造成瞬時(shí)DB請(qǐng)求量?、壓?驟增,引起雪崩
預(yù)防:
存數(shù)據(jù)的過(guò)期時(shí)間設(shè)置隨機(jī),防?同?時(shí)間?量數(shù)據(jù)過(guò)期現(xiàn)象發(fā)?
設(shè)置熱點(diǎn)數(shù)據(jù)永遠(yuǎn)不過(guò)期,定時(shí)任務(wù)定時(shí)更新
SpringCache解決?案
1 設(shè)置差別的過(guò)時(shí)時(shí)間,?如CacheManager配置多個(gè)過(guò)期時(shí)間維度
2 配置?件 time-to-live 配置
3 緩存穿透(查詢不存在數(shù)據(jù))
查詢?個(gè)不存在的數(shù)據(jù),由于緩存是不命中的,并且出于容錯(cuò)考慮,如發(fā)起為id為“-1”不存在的數(shù)據(jù)如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫(xiě)?緩存這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都要到存儲(chǔ)層去查詢,失去了緩存的意義。存在?量查詢不存在的數(shù)據(jù),可能DB就掛掉了,這也是?客利?不存在的key頻繁攻擊應(yīng)?的?種?式。
預(yù)防
接?層增加校驗(yàn),數(shù)據(jù)合理性校驗(yàn)
緩存取不到的數(shù)據(jù),在數(shù)據(jù)庫(kù)中也沒(méi)有取到,這時(shí)也可以將key-value對(duì)寫(xiě)為key-null,設(shè)置短點(diǎn)的過(guò)期時(shí)間,防?同個(gè)key被?直攻擊
SpringCache解決?案
空結(jié)果也緩存,默認(rèn)不配置condition或者unless就?
spring:cache:type: redis#過(guò)時(shí)時(shí)間redis:time-to-live: 3600000# 開(kāi)啟前綴,默以為trueuse-key-prefix: true# 鍵的前綴,默認(rèn)就是緩存名cacheNameskey-prefix: XD_CACHE# 是否緩存空結(jié)果,防?緩存穿透,默以為truecache-null-values: true4 邊路緩存
cache aside pattern 邊路緩存問(wèn)題。其實(shí)是一種指導(dǎo)思想,思想中包含:
5 Redis腦裂
Redis腦裂主要是指因?yàn)橐恍┚W(wǎng)絡(luò)原因?qū)е翿edis Master和Redis Slave和Sentinel集群處于不同的網(wǎng)絡(luò)分區(qū)。Sentinel連接不上Master就會(huì)重新選擇Master,此時(shí)就會(huì)出現(xiàn)兩個(gè)不同Master,好像一個(gè)大腦分裂成兩個(gè)一樣。
Redis集群中不同節(jié)點(diǎn)存儲(chǔ)不同的數(shù)據(jù),腦裂會(huì)導(dǎo)致大量數(shù)據(jù)丟失。
解決Redis腦裂只需要在Redis配置文件中配置兩個(gè)參數(shù)
min-slaves-to-write 3 //連接到master的最小slave數(shù)量
min-slaves-max-lag 10 //slave連接到master的最大延遲時(shí)間
6 Redis 緩存淘汰策略/當(dāng)內(nèi)存不足時(shí)如何回收數(shù)據(jù)/保證Redis中數(shù)據(jù)不出現(xiàn)內(nèi)存溢出情況
Redis中數(shù)據(jù)都放入到內(nèi)存中。如果沒(méi)有淘汰策略將會(huì)導(dǎo)致內(nèi)存中數(shù)據(jù)越來(lái)越多,最終導(dǎo)致內(nèi)存溢出。在Redis5中內(nèi)置了緩存淘汰策略。在配置文件中有如下配置
# maxmemory-policy noeviction 默認(rèn)策略noevication # maxmemory <bytes> 緩存最大閾值 # volatile-lru -> 在設(shè)置過(guò)期key集中選擇使用數(shù)最小的。 # allkeys-lru -> 在所有key中選擇使用最小的。 # volatile-lfu -> 在設(shè)置過(guò)期時(shí)間key集中采用lfu算法。 # allkeys-lfu -> 在所有key中采用lfu算法。 # volatile-random -> 在設(shè)置過(guò)期key集中隨機(jī)刪除。 # allkeys-random -> 在所有key中隨機(jī)刪除。 # volatile-ttl -> 在設(shè)置了過(guò)期時(shí)間key中刪除最早過(guò)期時(shí)間的。 # noeviction -> 不刪除key,超過(guò)時(shí)報(bào)錯(cuò)。6.1 Lru和lfu算法
6.1.1 LRU
LRU (Least recently used) 最近最少使用,如果數(shù)據(jù)最近被訪問(wèn)過(guò),那么將來(lái)被訪問(wèn)的幾率也更高。LRU算法實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行時(shí)性能也良好,被廣泛的使用在緩存/內(nèi)存淘汰中。
? 新數(shù)據(jù)插入到鏈表頭部
? 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn)),則將數(shù)據(jù)移到鏈表頭部
? 當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄
6.1.2 LFU
Least Frequently Used(最近最不經(jīng)常使用)如果一個(gè)數(shù)據(jù)在最近一段時(shí)間很少被訪問(wèn)到,那么可以認(rèn)為在將來(lái)它被訪問(wèn)的可能性也很小。因此,當(dāng)空間滿時(shí),最小頻率訪問(wèn)的數(shù)據(jù)最先被淘汰。
6.1.3 LRU和LFU的區(qū)別
LRU淘汰時(shí)淘汰的是鏈表最末尾的數(shù)據(jù)。而LFU是一段時(shí)間內(nèi)訪問(wèn)次數(shù)最少的。
6.2 何時(shí)淘汰數(shù)據(jù)
在Redis中每次新增數(shù)據(jù)都會(huì)判斷是否超過(guò)閾值。如果超過(guò)了,就會(huì)按照淘汰策略刪除一些key。
6.3 每次刪除多少
淘汰數(shù)據(jù)量和新增數(shù)據(jù)量進(jìn)行判斷。
總結(jié)
- 上一篇: 手机为什么不能root
- 下一篇: Redis(案例一:注册登录-图形验证码