Redis:Key-value的NoSQL數(shù)據(jù)庫
一.Reids簡介
目前市場主流數(shù)據(jù)庫存儲都是使用關(guān)系型數(shù)據(jù)庫.每次操作關(guān)系型數(shù)據(jù)庫時(shí)都是I/O操作,I/O操作是主要影響程序執(zhí)行性能原因之一,連接數(shù)據(jù)庫關(guān)閉數(shù)據(jù)庫都是消耗性能的過程.盡量減少對數(shù)據(jù)庫的操作,能夠明顯的提升程序運(yùn)行效率.
針對上面的問題,市場上就出現(xiàn)了各種NoSQL(Not Only SQL,不僅僅可以使用關(guān)系型數(shù)據(jù)庫)數(shù)據(jù)庫,它們的宣傳口號:不是什么樣的場景都必須使用關(guān)系型數(shù)據(jù)庫,一些特定的場景使用NoSQL數(shù)據(jù)庫更好
1.常見NoSQL數(shù)據(jù)庫:
memcached:鍵值對,內(nèi)存型數(shù)據(jù)庫,所有數(shù)據(jù)都在內(nèi)存中
Redis和Memcached類似,還具備持久化能力
HBase:以列作為存儲
MongoDB:以Document做存儲
2.Redis簡介
Redis是以Key-Value形式進(jìn)行存儲的NoSQL數(shù)據(jù)庫
Redis是使用C語言進(jìn)行編寫的
平時(shí)操作的數(shù)據(jù)都在內(nèi)從中,效率特高,讀的效率110000/s,寫81000/s,所以多把Redis當(dāng)做緩存工具使用
Redis以slot(槽)作為存儲單元.每個(gè)曹中可以存儲N多個(gè)鍵值對.Redis中固定具有16384.理論上可以實(shí)現(xiàn)一個(gè)槽是一個(gè)Redis.每個(gè)向Redis存儲數(shù)據(jù)的key都會進(jìn)行crc16算法得出一個(gè)值后對16384取余就是這個(gè)key存放的slot位置
同時(shí)通過Redis Sentinel(哨兵)提供高可用,通過Redis Cluster(集群)提供自動分區(qū)
二.使用Redis作為緩存工具時(shí)流程(寫代碼時(shí)思路)
1.應(yīng)用程序向Redis查詢數(shù)據(jù)
2.判斷Key是否存在
3.是否存在
1)存在
a.把結(jié)果查詢出來
b.返回?cái)?shù)據(jù)給應(yīng)用程序
2).不存在
a.向MySQL查詢數(shù)據(jù)
b.把數(shù)據(jù)返回給應(yīng)用程序
c.把結(jié)果緩存到Redis中
三.Redis單機(jī)版安裝
四.Redis數(shù)據(jù)類型(面試問題)
Redis中數(shù)據(jù)是key-value形式.不同類型Value是由不同的命令進(jìn)行操作.key為字符串類型,value常用類型:
String字符串
Hash哈希表
List列表
Set集合
Sorted Set有序集合(zSet)
Redis命令相關(guān)手冊很多,下面為其中比較好用的兩個(gè)
https://www.redis.net.cn/order/
http://doc.redisfans.com/
Redis中命令有很多,抽取出部分進(jìn)行講解
1.Key操作
1.1exists
判斷key是否存在
語法:exists key名稱
返回值:存在返回?cái)?shù)字,不存在返回0
1.2expire
設(shè)置key的過期時(shí)間,單位秒
語法:expire key秒數(shù)
返回值:成功返回1,失敗返回0
1.3ttl
查看key的剩余過期時(shí)間
語法:ttl key
返回值:返回剩余時(shí)間,如果不過期返回-1
1.4persist
刪除key的有效時(shí)長
語法:persist key
返回值:返回刪除有效時(shí)長的key的個(gè)數(shù)
1.5del
根據(jù)key刪除鍵值對
語法:del key
返回值:被刪除key的數(shù)量
1.6keys
根據(jù)表達(dá)式,顯示redis中的key
語法:keys表達(dá)式 [如:keys*]
返回值:redis中的key列表
2.字符串值(String)
2.1set
設(shè)置指定key的值.如果key不存在是新增效果,如果key存在是修改效果.鍵值對是永久存在的
語法:set key value
返回值:成功OK
2.2get
獲取指定key的值
語法:get key
返回值:key的值.不存在返回null
2.3setnx
當(dāng)且僅當(dāng)key不存在時(shí)才新增.恒新增,無修改功能.
語法:setnx key value
返回值:不存在時(shí)返回1,存在返回0
2.4setex
設(shè)置key的存活時(shí)間,無論是否存在指定key都能新增,如果存在key覆蓋舊值.同時(shí)必須指定過期時(shí)間
語法:setex key seconds value
返回值:OK
3.哈希表(Hash)
Hash類型的值中包含多組field value
3.1hset
給key中field設(shè)置值
語法:hset key field value
返回值:成功1,失敗0
3.2hget
獲取key中某個(gè)field的值
語法:hget key field
返回值:返回field的內(nèi)容
3.3hmset
給key中多個(gè)field設(shè)置值
語法:hmset key field value field value
返回值:成功OK
3.4hmget
一次獲取key中多個(gè)field的值
語法:hmget key field field
返回值:value列表
3.5hkeys
獲取key中所有的field名稱
語法:hkeys key
返回值:field名稱列表
3.6hvals
獲取key中所有field的值
語法:hvals key
返回值:value列表
3.7hgetall
獲取所有field和value
語法:hgetall key
返回值:field和value交替顯示列表
3.8hdel
刪除key中任意個(gè)field
語法:hdel key field field
返回值:成功刪除field的數(shù)量,當(dāng)key對應(yīng)的所有field全部刪除,自動刪除key
4.列表(List)
4.1Rpush
向列表末尾中插入一個(gè)或多個(gè)值
語法:rpush key value value
返回值:列表長度
4.2lrange
返回列表中指定區(qū)域內(nèi)的值.可以使用-1代表列表末尾
語法:lrange list 0 -1
返回值:查詢到的值
4.3lpush
將一個(gè)或多個(gè)值插入到列表前面
語法:lpush key value value
返回值:列表長度
4.4llen
獲取列表長度
語法:llen key
返回值:列表長度
4.5lren
刪除雷彪中元素.count為正數(shù)表示從左往右的數(shù)量.負(fù)數(shù)從右往左刪除的數(shù)量.
語法:lren key count value
返回值:刪除數(shù)量
5.集合(Set)
set和java中集合一樣
5.1sadd
向集合中添加內(nèi)容.不允許重復(fù)
語法:sadd key value value value
返回值:成功新增元素個(gè)數(shù)
5.2scard
返回集合元素?cái)?shù)量
語法:scard key
返回值:集合長度
5.3smembers
查看集合中元素內(nèi)容
語法:smembers key
返回值:集合中元素
5.4srem
刪除集合中的元素
語法:srem key member [member...]
返回值;刪除的有效數(shù)據(jù)個(gè)數(shù)
6.有序集合(Sorted Set)
有序集合中每個(gè)value都有一個(gè)分?jǐn)?shù)(score),根據(jù)分?jǐn)?shù)進(jìn)行排序
6.1zadd
向有序集合中添加數(shù)據(jù)
語法:zadd key score value score value
返回值:長度
6.2zrange
返回區(qū)間內(nèi)容,withscores表示帶有分?jǐn)?shù)
語法:zrange key區(qū)間[withscores]
返回值:值列表
6.3zrem
刪除元素
語法:zrem key memeber[member...]
返回:刪除的元素個(gè)數(shù)
6.4zcard查看zset集合長度
語法:zcard key
返回:集合長度
五.Redis持久化策略(面試問題)
Redis不僅僅是一個(gè)內(nèi)存型數(shù)據(jù)庫,還具備持久化能力
Redis每次啟動時(shí)都會從硬盤存儲文件中把數(shù)據(jù)讀取到內(nèi)存中.運(yùn)行過程中操作的數(shù)量都是內(nèi)存的數(shù)據(jù)
一共包含兩種持久化策略:RDB 和AOF
1.RDB(RedisDataBase)
rdb模式是默認(rèn)模式,可以在指定的時(shí)間間隔內(nèi)生成數(shù)據(jù)快照(snapshot),默認(rèn)保存到dump.rdb文件中.當(dāng)redis重啟后會自動加載dump.rdb文件中內(nèi)容到內(nèi)存中.
用戶可以使用SAVE(同步)或BGSAVE(異步)手動保存數(shù)據(jù)
可以設(shè)置服務(wù)器配置的save選項(xiàng),讓服務(wù)器每個(gè)一段時(shí)間自動執(zhí)行一次BGSAVE命令,可以通過save選項(xiàng)設(shè)置多個(gè)保存條件,單只要其中任意一個(gè)條件被滿足,服務(wù)器就會執(zhí)行BGSAVE命令
例如:
save900 1
save300 10
save60 10000
那么只要滿足以下三個(gè)條件中的任意一個(gè),BGSAVE命令就會被執(zhí)行.計(jì)時(shí)單位是必須要執(zhí)行的時(shí)間,save900 1,每900秒檢測一次.在并發(fā)量越高的項(xiàng)目中Redis的時(shí)間參數(shù)設(shè)置的值要越小
服務(wù)器在900秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少1次修改
服務(wù)器在300秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少10次修改
服務(wù)器在60秒之內(nèi),對數(shù)據(jù)庫進(jìn)行了至少10000次修改
1.1優(yōu)點(diǎn)
rdb文件是一個(gè)緊湊文件,直接使用rdb文件就可以還原數(shù)據(jù)
數(shù)據(jù)保存會由一個(gè)子進(jìn)程進(jìn)行保存,不影響父進(jìn)程做其他事情
恢復(fù)數(shù)據(jù)的效率要高于aof
總結(jié):性能要高于AOF
1.2缺點(diǎn)
每次保存點(diǎn)之間導(dǎo)致redis不可意料的關(guān)閉,可能會丟失數(shù)據(jù)
由于每次保存數(shù)據(jù)都需要fork()子進(jìn)程,在數(shù)據(jù)量比較大時(shí)可能會比較耗費(fèi)性能
2.AOF(AppendOnly File)
AOF默認(rèn)是關(guān)閉的appendonly no,需要在配置文件redis.conf中開啟AOF.Redis支持AOF和RDB同時(shí)生效,如果同時(shí)存在,AOF優(yōu)先級高于RDB(Redis重新啟動時(shí)會使用AOF進(jìn)行數(shù)據(jù)恢復(fù))
AOF原理:監(jiān)聽執(zhí)行的命令,如果發(fā)現(xiàn)執(zhí)行了修改數(shù)據(jù)的操作,同時(shí)直接同步到數(shù)據(jù)庫文件中,同時(shí)會把命令記錄到日志中.即使突然出現(xiàn)問題,由于日志文件中已經(jīng)記錄命令,下一次啟動時(shí)也可以按照日志進(jìn)行恢復(fù)數(shù)據(jù),由于內(nèi)存數(shù)據(jù)和硬盤數(shù)據(jù)實(shí)時(shí)同步,即使出現(xiàn)意外情況也需要擔(dān)心
2.1優(yōu)點(diǎn)
相對RDB數(shù)據(jù)更加安全
2.2缺點(diǎn)
相同數(shù)據(jù)集AOF要大于RDB
相對RDB可能會慢一些
2.3開啟辦法
修改redis.conf中
appendonlu yes 開啟aof
appendfilename設(shè)置aof數(shù)據(jù)文件,名稱隨意.
# 默認(rèn)no
appendonly yes
# aof 文件名
appendfilename "appendonly.aof"
六.Redis主從復(fù)制
Redis支持集群功能.為了保證單一節(jié)點(diǎn)可用性,redis支持主從復(fù)制功能.每個(gè)節(jié)點(diǎn)有N個(gè)復(fù)制品(replica),其中一個(gè)復(fù)制品是主(master),另外N-1一個(gè)復(fù)制品從(Slave),也就是說Redis支持一主多從
一個(gè)主可有多個(gè)從,而一個(gè)從又可以看成主,它還可以有多個(gè)從.
1.主從優(yōu)點(diǎn)
增加單一節(jié)點(diǎn)的健壯性,從而提升整個(gè)集群的穩(wěn)定性(Redis中當(dāng)超過1/2節(jié)點(diǎn)不可用時(shí),整個(gè)集群不可用)
從節(jié)點(diǎn)可以對主節(jié)點(diǎn)數(shù)據(jù)備份,提升容災(zāi)能力
讀寫分離.在redis主從中,主節(jié)點(diǎn)一般用作寫(具備讀的能力),從節(jié)點(diǎn)只能讀,利用這個(gè)特性實(shí)現(xiàn)讀寫分離,寫用主,讀用從.
2.一主多從搭建
在已經(jīng)搭建的單機(jī)版redis基礎(chǔ)上進(jìn)行操作
并且關(guān)閉redis單機(jī)版
2.1新建目錄
# mkdir /usr/local/replica
2.2復(fù)制目錄
把之前安裝的redis單機(jī)版中bin目錄復(fù)制三份,分別叫做:master,slave1,slave2
# cp -r /usr/local/redis/bin /usr/local/replica/master
# cp -r /usr/local/redis/bin /usr/local/replica/slave1
# cp -r /usr/local/redis/bin /usr/local/replica/slave2
2.3修改從的配置文件
修改2個(gè)從的redis.conf,指定主節(jié)點(diǎn)ip和端口.并修改自身端口號防止和其他redis沖突
# vim /usr/local/replica/slave1/redis.conf
指定主節(jié)點(diǎn)ip和端口
replicaof 192.168.232.132 6379
修改自己端口
port 6380
# vim /usr/local/replica/slave2/redis.conf
指定主節(jié)點(diǎn)ip和端口
replicaof 192.168.232.132 6379
修改自己端口
port 6381
2.4啟動三個(gè)redis實(shí)例
注意:一定要關(guān)閉單機(jī)的redis,否則端口沖突.master的端口是6379,單機(jī)版redis端口也是6379
# cd /usr/local/replica
# vim startup.sh
在文件中添加下面內(nèi)容,一點(diǎn)要先點(diǎn)擊鍵盤i鍵盤,啟用編輯狀態(tài)后在粘貼.退出編輯狀態(tài)下輸入gg=G進(jìn)行排版
cd /usr/local/replica/master/
./redis-server redis.conf
cd /usr/local/replica/slave1
./redis-server redis.conf
授權(quán)
# chmod a+x startup.sh
# ./startup.sh
2.5查看啟動狀態(tài)
# ps aux | grep redis
2.6測試
# cd /usr/local/replica/master/
# ./redis-cli -p 端口號
在客戶端命令模式下,添加一條數(shù)據(jù):
進(jìn)入slave查看數(shù)據(jù)是否同步
# cd /usr/local/replica/slave1
必須帶有-p,否則默認(rèn)進(jìn)入6379端口,相當(dāng)于連接的是master
# ./redis-cli -p 6380
七.哨兵(Sentinel)
在redis主從模型是只有主(Master)具備寫的能力,而從(Slave)只能讀.如果主宕機(jī),整個(gè)節(jié)點(diǎn)不具備寫能力.但是如果這時(shí)讓一個(gè)從變成主,整個(gè)節(jié)點(diǎn)就可以繼續(xù)工作.即使之前的主恢復(fù)過來也當(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ù)我們設(shè)定,達(dá)到一定數(shù)量哨兵認(rèn)為master宕機(jī)后才會進(jìn)行重新選取主.我們以丟多哨兵演示
1.沒有哨兵下主從效果
只要?dú)⒌糁?整個(gè)節(jié)點(diǎn)無法在寫數(shù)據(jù),從身份不會變化,主的信息還是以前的信息
2.搭建多哨兵
前提:安裝了單機(jī)的redis
2.1新建目錄
# mkdir /usr/local/sentinel
2.2復(fù)制redis
# cp -r /usr/local/redis/bin/* /usr/local/sentinel
2.3復(fù)制配置文件
從redis解壓目錄中復(fù)制sentinel配置文件
# cd /usr/local/tmp/redis-5.0.5/
# cp sentinel.conf /usr/local/sentinel/
2.4修改配置文件
# cd /usr/local/sentinel
# vim sentinel.conf
sentinel monitor命令
mymaster主的名稱
192.168.232.132主的ip
6379主的端口號
2.多少個(gè)哨兵發(fā)現(xiàn)主宕機(jī)時(shí)重新選擇
port 26379
daemonize yes
logfile "/usr/local/sentinel/26379.log"
sentinel monitor mymaster 192.168.232.132 6379 2
復(fù)制sentinel.conf,命名為sentinel-26380.conf
# cp sentinel.conf sentinel-26380.conf
# vim sentinel-26380.conf
port 26380
daemonize yes
logfile "/usr/local/sentinel/26380.log"
sentinel monitor mymaster 192.168.232.132 6379 2
復(fù)制sentinel.conf,命名為sentinel-26381.conf
# cp sentinel.conf sentinel-26381.conf
# vim sentinel-26381.conf
port 26381
daemonize yes
logfile "/usr/local/sentinel/26381.log"
sentinel monitor mymaster 192.168.232.132 6379 2
2.5啟動主從
如果已經(jīng)啟動狀態(tài),忽略下面命令.如果啟動部分,全部kill后重新啟動
使用kill殺死全部redis
# ps aux | grep redis
# kill -9 進(jìn)程號
啟動redis主從
# cd /usr/local/replica
# ./startup.sh
2.6啟動三個(gè)哨兵
# cd /usr/local/sentinel
# ./redis-sentinel sentinel.conf
# ./redis-sentinel sentinel-26380.conf
# ./redis-sentinel sentinel-26381.conf
2.7查看日志
# cat 26379.log
2.8測試宕機(jī)
查看redis進(jìn)程號
# ps aux | grep redis
殺死主進(jìn)程號
# kill -9 進(jìn)程號
查看日志,短暫延遲后發(fā)現(xiàn),出現(xiàn)新的主.觀察日志中新增內(nèi)容
# tail -f 26379.log
進(jìn)入到6381(可能是6380)的客戶端,查看主從信息
# cd /usr/local/replica
# ./redis-cli -p 6381
八.集群(Cluster)
1.集群原理
a)集群搭建完成后有集群節(jié)點(diǎn)平分(不能平分時(shí),前幾個(gè)節(jié)點(diǎn)多一個(gè)槽)16384個(gè)槽
b)客戶端可以訪問集群中任意節(jié)點(diǎn).所以在寫代碼時(shí)都是需要把集群中所有節(jié)點(diǎn)都配置上
c)當(dāng)向集群中新增或查詢一個(gè)鍵值對時(shí),會對Key進(jìn)行Crc16算法得出一個(gè)小于16384的值,判斷值在那個(gè)節(jié)點(diǎn)上,然后就操作哪個(gè)節(jié)點(diǎn)
前提:已經(jīng)安裝好redis單機(jī)版
當(dāng)集群中超過或等于1/2節(jié)點(diǎn)不可用時(shí),整個(gè)集群不可用.為了搭建穩(wěn)定集群,都采用奇數(shù)節(jié)點(diǎn)
演示時(shí):創(chuàng)建3個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)搭建一主一從
1.復(fù)制redis
在/usr/local下新建redis-cluster
# mkdir /usr/local/redis-cluster
把單機(jī)redis復(fù)制到redis-cluster中
# cd /usr/local/redis
# cp -r bin /usr/local/redis-cluster/redis1
2.修改redis.conf
# cd /usr/local/redis-cluster/redis1
# vim redis.conf
需要修改如下
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
# appendonly yes如果開啟aof默認(rèn),需要修改為yes.如果使用rdb,此處不需要修改
daemonize yes
protected-mode no
pidfile /var/run/redis_7001.pid
3.復(fù)制redis
把redis1復(fù)制5份
# 傳遞/usr/local/redis-cluster
執(zhí)行之前一定要先刪除dump.rdb,清空Redis中數(shù)據(jù)
# rm -f redis1/dump.rdb
# cp -r redis1 redis2
# cp -r redis1 redis3
# cp -r redis1 redis4
# cp -r redis1 redis5
# cp -r redis1 redis6
新復(fù)制的5個(gè)redis的配置文件redis.conf都需要修改三處
# vim redis2/redis.conf
# vim redis3/redis.conf
# vim redis4/redis.conf
# vim redis5/redis.conf
# vim redis6/redis.conf
例如redis2/redis.conf中需要把所有7001都換成7002
可以使用:%/7001/7002/g進(jìn)行全局修改
port 7002
cluster-config-file node-7002.conf
pidfile /var/run/redis_7002.pid
4.啟動6個(gè)redis
# vim startup.sh
先啟用編輯狀態(tài),在粘貼下面內(nèi)容
cd redis1
./redis-server redis.conf
cd ..
cd redis2
./redis-server redis.conf
cd ..
cd redis3
./redis-server redis.conf
cd ..
cd redis4
./redis-server redis.conf
cd ..
cd redis5
./redis-server redis.conf
cd ..
cd redis6
./redis-server redis.conf
cd ..
# chmod a+x startup.sh
# ./startup.sh
5.查看啟動狀態(tài)
ps aux | grep redis
6.建立集群
在redis3de1時(shí)候需要借助ruby腳本實(shí)現(xiàn)集群.在redis5中可以使用自帶的redis-cli實(shí)現(xiàn)集群功能,比redis3的時(shí)候更加方便了
建議配置靜態(tài)ip,ip改變集群失效
# cd redis1
# ./redis-cli --cluster create 192.168.232.132:7001?192.168.232.132:7002?192.168.232.132:7003?192.168.232.132:7004?192.168.232.132:7005?192.168.232.132:7006 --cluster-replicas 1
執(zhí)行完命令后,詢問是否按照當(dāng)前配置創(chuàng)建集群,輸入yes而不是y
7.測試
集群測試時(shí),千萬不要忘記最后一個(gè)-c參數(shù)
# ./redis-cli -p 7001 -c
# set age 18
8.編寫關(guān)閉腳本
# cd /usr/local/redis-cluster
# vim stop.sh
# chmod a+x stop.sh
./redis1/redis-cli -p 7001 shutdown
./redis2/redis-cli -p 7002 shutdown
./redis3/redis-cli -p 7003 shutdown
./redis4/redis-cli -p 7004 shutdown
./redis5/redis-cli -p 7005 shutdown
./redis6/redis-cli -p 7006 shutdown
九.Jedis
Redis給Java語言提供了客戶端API,稱之為Jedis
JedisAPI和Redis命令幾乎是一樣的
例如:Redis對String值新增時(shí)set命令,Jedis中也是set方法.所以本課程中沒有重點(diǎn)把所有方法進(jìn)行演示,重要演示Jedis如何使用
JedisAPI特別簡單,基本上都是創(chuàng)建對象調(diào)用方法即可.由于Jedis不具備把對象轉(zhuǎn)換為字符串的能力,所以每次都需要借助Hson轉(zhuǎn)換工具進(jìn)行轉(zhuǎn)換,這個(gè)功能在Spring Data Redis中已經(jīng)具備,推薦使用Spring Data Redis
1.添加依賴
<dependencies>
? ? <dependency>
? ? ? ? <groupId>redis.clients</groupId>
? ? ? ? <artifactId>jedis</artifactId>
? ? ? ? <version>3.3.0</version>
????</dependency>
? ? <dependency>
? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? <artifactId>Jedis</artifactId>
? ? ? ? <version>3.3.0</version>
????</dependency>
? ? <dependency>
? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? ? <scope>test</scope>
? ? ? ? <version>2.2.6.RELEASE</version>
? ? ? ? <exclusions>
? ? ? ? ? ? <groupId>org.junit.vintage</groupId>
? ? ? ? ? ? <artifactId>junit-vintage-engine</artifactId>
????????</exclusion>
????</dependency>
</dependencies>
2.單機(jī)版
public void testStandalone(){
? ? Jedis jedis = new Jedis("192.168.232.132",6379);
? ? jedis.set("name","root-standalone");
? ? String value = jedis.get("name");
????System.out.println(value);
}
3.帶有連接池
public void testPool(){
? ? JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
????jedisPoolConfig.setMaxTotal(20);
????jedisPoolConfig.setMaxIdle(5);
????jedisPoolConfig.setMinIdle(3);
????JedisPool jedisPool = new?
????JedisPool(jedisPoolConfig,"192.168.232.132",6379);
????Jedis jedis = jedisPool.getResource();
????jedis.set("name","root-pool");
????String value = jedis.get("name");
????System.out.println(value);
}
4.集群
public void testCluster(){
? ? Set<HostAndPort> set = new HashSet<>();
? ? set.add(new HostAndPort("192.168.232.132",7001));
? ? set.add(new HostAndPort("192.168.232.132",7002));
? ? set.add(new HostAndPort("192.168.232.132",7003));
? ? set.add(new HostAndPort("192.168.232.132",7004));
? ? set.add(new HostAndPort("192.168.232.132",7005));
? ? set.add(new HostAndPort("192.168.232.132",7006));
? ? JedisCluster jedisCluster = new JedisCluster(set);
? ? jedisCluster.set("name","root");
? ? String value = jedisCluster.get("name");
? ? System.out.println(value);
}
十.使用SpringBoot整合SpringDataRedis操作redis
1.SpringDataRedis簡介
Spring Data Redis好處很方便操作對象類型(基于POJO模型)
把Redis不同值得類型放到一個(gè)opsForXXX方法中
opsForValue:String值(最常用),如果存儲Java對象或Java中時(shí)就需要使用序列化器,進(jìn)行序列化
opsForList:列表List
opsForHash:哈希表Hash
opsForZSet:有序集合Sorted Set
opsForSet:集合
2.Spring Data Redis序列化器介紹
經(jīng)常需要向Redis中保存Java中Object或List等類型,這個(gè)時(shí)候就需要通過序列化器把Java中對象轉(zhuǎn)換為字符串進(jìn)行存儲
2.1JdkSerializationRedisSerializer
是RedisTemplate類默認(rèn)的序列化方式.JdkSerializationRedisSerializer使用JDK自帶的序列化方式.要求被序列化的對象必須實(shí)現(xiàn)java.io.Serializable接口,而且存儲的內(nèi)容為二進(jìn)制數(shù)據(jù),這對開發(fā)者是不友好的.會出現(xiàn)雖然不影響使用,但是直接使用Redis客戶端查詢Redis中數(shù)據(jù)時(shí)前面出現(xiàn)亂碼問題
2.2OxmSerializer
以字符串格式的xml存儲.解析起來也比較復(fù)雜,效率也比較低.已經(jīng)很少有人在使用該序列化器.
2.3StringRedisSerializer
只能對String類型序列化操作.常用在key的序列化上
2.4GenericToStringSerializer
需要調(diào)用者給傳遞一個(gè)對象到字符串互轉(zhuǎn)的Converter(轉(zhuǎn)換器),使得比較麻煩
2.5Jackson2JsonRedisSerializer
該序列化器可以將對象自動轉(zhuǎn)換為Json的形式存儲,效率高且對調(diào)用者友好
優(yōu)點(diǎn):
速度快,序列化后的字符串短小精悍,不需要實(shí)現(xiàn)Serializable接口
缺點(diǎn):
此類的構(gòu)造函數(shù)中有一個(gè)類型參數(shù),必須提供要序列化對象的類型信息(.class對象).如果存儲List等帶有泛型的類型,次序列化器是無法識別泛型的,會直接把泛型固定設(shè)置為LinkedHashMap
例如:存儲時(shí)List<People>,取出時(shí)是List<LinkedHashMap>
2.6GenericJackson2JsonRedisSerializer
與JacksonJsonRedisSerializer功能相似.底層依然使用Jackson工具包.相比Jackson2JsonRedisSerializer多了_class列,列里面存儲類(新增時(shí)類型)的全限定路徑,從Redis取出時(shí)根據(jù)_class類型進(jìn)行轉(zhuǎn)換,解決了泛型問題
該序列化器不需要指定對象類型信息(.class對象)使用Object最為默認(rèn)類型.目前都使用這個(gè)序列化器
3.代碼步驟
基于SpringTest單元測試演示
3.1添加依賴
<parent>
? ? <groupId>org.springframework.boot</groupId>
? ? <artifactId>spring-boot-starter-parent</artifactId>
? ? <version>2.2.6RELEASE</version>
</parent>
<dependencies>
? ? <!--為了要愛項(xiàng)目中jackson工具包-->
? ? <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端口號默認(rèn)值
如果連接Redis集群,不需要配置host,配置spring.redis.cluster.nodes,取值為redis集群所在ip:port,ip:port.由于word排版問題nodes后面取值沒有和nodes在一行
spring:
? ? redis:
? ? ? ? host: 192.168.232.132
# cluster:
# nodes: 192.168.232.132:7001,192.168.232.132:7002,192.168.232.132:7003,192.168.232.132:7004,192.168.232.132:7005,192.168.232.132:7006
3.3編寫配置類
@Configuration
public class RedisConfig{
? ? @Bean
? ? public RedisTemplate<String,Object>redisTemplate(RedisConnectionFactory factory){
? ? ? ? RedisTemplate<String ,Object>redisTemplate = new RedisTemplate<>();
? ? ? ? redisTemplate.setConnectionFactory(factory);
? ? ? ? redisTempalte.setKeySerializer(new StringRedisSerializer());
? ? ? ? redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
? ? ? ? return redisTemplate;
????}
}
3.4編寫代碼
3.4.1編寫對象新增
@Autowired
private RedisTemplate<String,Object>redisTemplate;
@Test
public void testString(){
? ? People peo = new People(1,"張三");
? ? redisTemplate.opsForValue().set("peo1",peo);
}
3.4.2.編寫對象獲取
@Test
public void testGetString(){
? ? People peo = (People)redisTemplate.opsForValue().get("peo1");
? ? System.out.println(peo);
}
3.4.3編寫List
@Test
public void testList(){
? ? List<People> list = new ArrayList<>();
? ? list.add(new People(1,"張三"));
? ? list.add(new People(2,"李四"));
? ? redisTemplate.opsForValue().set("list2",list);
}
3.4.4編寫List取值
@Test
public void testGetList(){
? ? List<People> list2 = (ListPeople)redisTemplate.opsForValue().get("list2");
? ? System.out.println(list2);
}
3.4.5判斷Key是否存在
上面講解Redis作為緩存工具時(shí),需要判斷key是否存在,通過hasKey進(jìn)行判斷
@Test
public void hasKey(){
? ? Boolean result = redisTemplate.hasKey("name");
? ? System.out.println(result);
}?
3.4.6 根據(jù)key刪除數(shù)據(jù)
如果key-value正常被刪除,返回true,如果key不存在返回false
@Test
public void del(){
? ? Boolean result = redisTemplate.delete("name");
? ? System.out.println(result);
}
十一.高并發(fā)下Redis可能存在的問題及解決方案
1.緩存穿透(面試問題)
在實(shí)際開發(fā)中,添加緩存工具的目的,減少對數(shù)據(jù)庫的訪問次數(shù),增加訪問效率.
肯定會出現(xiàn)Redis中不存在的緩存數(shù)據(jù).例如:訪問id=-1的數(shù)據(jù).可能出現(xiàn)繞過redis依然頻繁訪問數(shù)據(jù)庫的情況,稱為緩存穿透,多出現(xiàn)在查詢?yōu)閚ull的情況不被緩存時(shí).
解決辦法:
如果查詢出來為null數(shù)據(jù),把null數(shù)據(jù)依然放入到redis緩存中,同時(shí)設(shè)置這個(gè)key的有效時(shí)間比正常有效時(shí)間更短一些
if(list==null){
? ? //key value有效時(shí)間 時(shí)間單位
?????redisTemplate.opsForValue().set(navKey,null,10,TimeUnit.MINUTES);
}else{
????redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS);
}
2.緩存擊穿(面試問題)
實(shí)際開發(fā)中,考慮redis所在服務(wù)器中內(nèi)存壓力,都會設(shè)置key的有效時(shí)間.一定會出現(xiàn)鍵值對過期的情況.如果正好key過期了,此時(shí)出現(xiàn)大量并發(fā)訪問,這些訪問都會去訪問數(shù)據(jù)庫,這種情況稱為緩存擊穿
解決辦法:
永久數(shù)據(jù).
加鎖.防止出現(xiàn)數(shù)據(jù)庫的并發(fā)訪問
2.1ReentrantLock(重入鎖)
JDK對于并發(fā)訪問處理的內(nèi)容都放入了java.util.concurrent中
java.util.concurrent
ReentrantLock性能和synchronized沒有區(qū)別的,但是API使用起來更加方便
@SpringBootTest
public class MyTest{
? ? @Test
? ? public void test(){
? ? ? ? new Thread(){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? test2("第一個(gè)線程11111");
????????????}
????????}.start();
? ? ? ? new Thread(){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run(){
????????????????test2("第二個(gè)線程22222");
????????????}
????????}.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("開始執(zhí)行:"+who);
? ? ? ? ? ? try{
? ? ? ? ? ? ? ? Thread.sleep(5000);
????????????}catch(InterruptedException e){
? ? ? ? ? ? ? ? e.printStackTrace();
????????????}
? ? ? ? ? ? System.out.println("執(zhí)行完:"+who);
? ? ? ? ? ? lock.unlock();
????????}
????}
}
2.2解決緩存擊穿實(shí)例代碼
只有在第一次訪問時(shí)和key過期時(shí)才會訪問數(shù)據(jù)庫.對于性能來說沒有過大影響,因?yàn)槠綍r(shí)都是直接訪問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)緩存擊穿問題
? ? ? ? ????????redisTemplate.opsForValue().set(key,item,7,TimeUnit.DAYS);
????????lock.unlock();
????????return item;
????}
? ? //如果加鎖失敗,為了保護(hù)數(shù)據(jù)庫,直接返回null
? ? return null;
}
3.緩存雪奔(面試問題)
在一段時(shí)間內(nèi)容,出現(xiàn)大量緩存數(shù)據(jù)失效,這段時(shí)間內(nèi)容數(shù)據(jù)庫的訪問評率驟增,這種情況稱為緩存雪奔
解決辦法:
永久生效
自動以算法.例如:隨機(jī)有效時(shí)間.讓所有key盡量避開同一時(shí)間段
int seconds = random.nextInt(10000);
redisTemplate.opsForValue().set(key,item,100+seconds,TimeUnit.SECONDS);