高級框架第五天Redis

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單機(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);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市炮叶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異聘裁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弓千,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門衡便,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洋访,你說我怎么就攤上這事镣陕。” “怎么了姻政?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵呆抑,是天一觀的道長。 經(jīng)常有香客問我汁展,道長鹊碍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任善镰,我火速辦了婚禮妹萨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘炫欺。我一直安慰自己乎完,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布品洛。 她就那樣靜靜地躺著树姨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪桥状。 梳的紋絲不亂的頭發(fā)上帽揪,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音辅斟,去河邊找鬼转晰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的查邢。 我是一名探鬼主播蔗崎,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扰藕!你這毒婦竟也來了缓苛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤邓深,失蹤者是張志新(化名)和其女友劉穎未桥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芥备,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冬耿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了门躯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淆党。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讶凉,靈堂內(nèi)的尸體忽然破棺而出染乌,到底是詐尸還是另有隱情,我是刑警寧澤懂讯,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布荷憋,位于F島的核電站,受9級特大地震影響褐望,放射性物質(zhì)發(fā)生泄漏勒庄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一瘫里、第九天 我趴在偏房一處隱蔽的房頂上張望实蔽。 院中可真熱鬧,春花似錦谨读、人聲如沸局装。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铐尚。三九已至,卻和暖如春哆姻,著一層夾襖步出監(jiān)牢的瞬間宣增,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工矛缨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爹脾,地道東北人帖旨。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像灵妨,于是被迫代替她去往敵國和親碉就。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354