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

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

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

Redis(入门)

發(fā)布時(shí)間:2023/12/3 数据库 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis(入门) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

    • 一、 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.1

2 創(chuàng)建并啟動(dòng)容器

docker run -d --name redis -p 6379:6379 --restart always redis:6.2.1

3 客戶端測(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ù)文件,名稱隨意。

# 默認(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),也就是說(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鏡像

# docker pull redis:6.2.1

2.2 創(chuàng)建并運(yùn)行三個(gè)Docker容器

先停止單機(jī)版Redis。單機(jī)版Redis端口6379
三個(gè)容器分別占用系統(tǒng)的6479、6480、6481端口

# docker run --name redis1 -p 6479:6379 -v /opt/redis:/data -d redis:6.2.1 # docker run --name redis2 -p 6480:6379 -v /opt/redis:/data -d redis:6.2.1 # docker run --name redis3 -p 6481:6379 -v /opt/redis:/data -d redis:6.2.1

2.3 在從中指定主的ip和端口

設(shè)定redis1容器為主機(jī)(master)。redis2和redis3容器為從機(jī)(slave)
進(jìn)入redis2容器內(nèi)部設(shè)置主的ip和端口,連接從機(jī),指定主機(jī)端口

# docker exec -it redis2 redis-cli # slaveof 192.168.108.128 6379 # exit

進(jìn)入redis2容器內(nèi)部設(shè)置主的ip和端口

# docker exec -it redis3 redis-cli # slaveof 192.168.108.128 6379 # exit

2.4 測(cè)試主從效果
進(jìn)入redis1容器內(nèi)部,新增key-value

# docker exec -it redis1 redis-cli # set name "bjsxt" # exit

分別進(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 新建配置模板文件

# cd /usr/local # mkdir redis-cluster # cd redis-cluster # vim redis-cluster.tmpl

紅色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 yes

2.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; \ done

2.3 創(chuàng)建橋連網(wǎng)絡(luò)

# docker network create redis-net

查看網(wǎng)絡(luò)是否創(chuàng)建成功

# docker network ls

2.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; \ done

2.5 查看6個(gè)容器ip及端口

# docker inspect redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 | grep IPAddress

2.6 執(zhí)行集群腳本
進(jìn)入6個(gè)容器中任意一個(gè)。示例中以redis-7000舉例

# docker exec -it redis-7000 bash

執(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舉例。

# redis-cli -c -p 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在一行。

spring:redis:host: 192.168.52.133 # cluster: # nodes: 192.168.52.133:7001,192.168.52.133:7002,192.168.52.133:7003,192.168.52.133:7004,192.168.52.133:7005,192.168.52.133:7006

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í)更新

int seconds = random.nextInt(10000); redisTemplate.opsForValue().set(key, item, 100+ seconds, TimeUnit.SECONDS);

SpringCache解決?案

1 設(shè)置差別的過(guò)時(shí)時(shí)間,?如CacheManager配置多個(gè)過(guò)期時(shí)間維度
2 配置?件 time-to-live 配置

spring:cache:type: redis#過(guò)時(shí)時(shí)間redis:time-to-live: 3600000

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被?直攻擊

if(list==null){// key value 有效時(shí)間 時(shí)間單位redisTemplate.opsForValue().set(navKey,null,10, TimeUnit.MINUTES); }else{redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS); }

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: true

4 邊路緩存

cache aside pattern 邊路緩存問(wèn)題。其實(shí)是一種指導(dǎo)思想,思想中包含:

  • 查詢的時(shí)候應(yīng)該先查詢緩存,如果緩存不存在,在查詢數(shù)據(jù)庫(kù)
  • 修改緩存數(shù)據(jù)時(shí),應(yīng)先修改數(shù)據(jù)庫(kù),后修改緩存。
  • 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ù)

  • 消極方法(passive way):在讀取數(shù)據(jù)時(shí)先判斷是否過(guò)期,如果過(guò)期刪除他。例如:get、hget、hmget等
  • 積極方法(active way):周期性判斷是否有失效內(nèi)容,如果有就刪除。
  • 主動(dòng)刪除:當(dāng)超過(guò)閾值時(shí)會(huì)刪除。
    在Redis中每次新增數(shù)據(jù)都會(huì)判斷是否超過(guò)閾值。如果超過(guò)了,就會(huì)按照淘汰策略刪除一些key。
  • 6.3 每次刪除多少

    淘汰數(shù)據(jù)量和新增數(shù)據(jù)量進(jìn)行判斷。

    總結(jié)

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

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