Redis(長篇) 從頭到尾

NoSQL(NoSQL = Not Only SQL )煮盼,意即“不僅僅是SQL”
泛指非關(guān)系型的數(shù)據(jù)庫遣蚀。隨著互聯(lián)網(wǎng)web2.0網(wǎng)站的興起,傳統(tǒng)的關(guān)系數(shù)據(jù)庫在應(yīng)付web2.0網(wǎng)站,特別是超大規(guī)模和高并發(fā) 的SNS類型的web2.0純動態(tài)網(wǎng)站已經(jīng)顯得力不從心,暴露了很多難以克服的問題政溃,而非關(guān)系型的數(shù)據(jù)庫則由于其本身的特 點(diǎn)得到了非常迅速的發(fā)展。NoSQL數(shù)據(jù)庫的產(chǎn)生就是為了解決大規(guī)模數(shù)據(jù)集合多重?cái)?shù)據(jù)種類帶來的挑戰(zhàn)态秧,尤其是大數(shù)據(jù)應(yīng) 用難題董虱,包括超大規(guī)模數(shù)據(jù)的存儲。 (例如谷歌或Facebook每天為他們的用戶收集萬億比特的數(shù)據(jù))申鱼。這些類型的數(shù)據(jù)存儲不需要固定的模式愤诱,無需多余操作 就可以橫向擴(kuò)展。

NoSQL數(shù)據(jù)庫種類繁多捐友,但是一個(gè)共同的特點(diǎn)都是去掉關(guān)系數(shù)據(jù)庫的關(guān)系型特性淫半。數(shù)據(jù)之間無關(guān)系,這樣就非常容易擴(kuò)展匣砖。也無形之間科吭,在架構(gòu)的層面上帶來了可擴(kuò)展的能力。

NoSQL數(shù)據(jù)庫都具有非常高的讀寫性能猴鲫,尤其在大數(shù)據(jù)量下对人,同樣表現(xiàn)優(yōu)秀。
這得益于它的無關(guān)系性拂共,數(shù)據(jù)庫的結(jié)構(gòu)簡單牺弄。

一般MySQL使用Query Cache,每次表的更新Cache就失效,是一種大粒度的Cache, 在針對web2.0的交互頻繁的應(yīng)用匣缘,Cache性 能不高猖闪。而NoSQL 的Cache是記錄級的鲜棠, 是一種細(xì)粒度的Cache,所以NoSQL在這個(gè)層面上來說就要性能高很多了

RDBMS (關(guān)系型數(shù)據(jù)庫)
-高度組織化結(jié)構(gòu)化數(shù)據(jù)
-結(jié)構(gòu)化查詢語言(SQL)
-數(shù)據(jù)和關(guān)系都存儲在單獨(dú)的表中培慌。
-數(shù)據(jù)操縱語言豁陆,數(shù)據(jù)定義語言
-嚴(yán)格的一致性
-基礎(chǔ)事務(wù)

NoSQL (非關(guān)系型的數(shù)據(jù)庫)
-代表著不僅僅是SQL
-沒有聲明性查詢語言
-沒有預(yù)定義的模式
-鍵-值對存儲,列存儲吵护,文檔存儲盒音,圖形數(shù)據(jù)庫
-最終一致性,而非ACID屬性
-非結(jié)構(gòu)化和不可預(yù)知的數(shù)據(jù)
-高性能馅而,高可用性和可伸縮性

Reiids

Redis(Remote Dictionary Server )祥诽,即遠(yuǎn)程字典服務(wù),是一個(gè)開源的使用ANSI C語言編寫瓮恭、支持網(wǎng)絡(luò)雄坪、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫屯蹦,并提供多種語言的API维哈。從2010年3月15日起,Redis的開發(fā)工作由VMware主持登澜。從2013年5月開始阔挠,Redis的開發(fā)由Pivotal贊助。

Redis作用

  • 內(nèi)存存儲脑蠕,持久化(rdb购撼,aof)
  • 效率高,可以用于高速緩存
  • 發(fā)布訂閱
  • 地圖信息分析
  • 計(jì)時(shí)器谴仙,計(jì)數(shù)器

Redis特性

  • 多樣的數(shù)據(jù)類型
  • 持久化
  • 集群
  • 事務(wù)

Redis是單線程的

Redis是很快的,官方表示, Redis是基于內(nèi)存操作, CPU不是Redis性能瓶頸, Redis的瓶頸是根據(jù)機(jī)器的內(nèi)存和網(wǎng)絡(luò)帶寬迂求,既然可以使用單線程來實(shí)現(xiàn),就使用單線程了!所有就使用了單線程了!

  • 核心:redis將所有的數(shù)據(jù)全部放入內(nèi)存里面,所以使用單線程去操作效率就是最高的狞甚,對于內(nèi)存系統(tǒng)來說锁摔,如果沒有上下文切換效率就是最高的,多次讀寫都是在一個(gè)cpu上的哼审,在內(nèi)存情況下這個(gè)就是最佳方案

五大數(shù)據(jù)類型以常用命令

首先Redis默認(rèn)有16個(gè)數(shù)據(jù)庫 0-15 ,默認(rèn)使用第 0 個(gè)孕豹,可以使用select進(jìn)行切換



move key db 把key移除到數(shù)據(jù)庫
exists key 判斷當(dāng)前key是否存在
expire name seconds 設(shè)置當(dāng)前key的過期時(shí)間
ttl key 查看當(dāng)前key的剩余時(shí)間
tyoe 判斷當(dāng)前key的類型

String

set key
get key
del key
exists key
Append key value 追加字符 如果不存在創(chuàng)建
strlen 獲得長度
incr 自增1
decr 自減1
incrby 設(shè)置步長 指定增量
decrby 指定減量
getrange key index index 截取
setrange key index value 替換指定位置的字符串
setex(set with expire) 設(shè)置過期時(shí)間
setnx(set if not exists) 不存在再設(shè)置 存在則創(chuàng)建失敗 分布式鎖中經(jīng)常使用
mest 批量增加
mget 批量獲取

mset&mget

Msetnx: 原子操作 都不存在再設(shè)置 存在任意一個(gè)值則創(chuàng)建失敗

設(shè)置一個(gè)json字符串來保存對象
set user:y1 {name:y1,age:1}


set user:y1 {name:y1,age:1}

還可以用mset巧妙設(shè)計(jì)key 對象:id:屬性

mset&mget

比如設(shè)置當(dāng)前文章瀏覽量 對象:id:屬性的格式
set article:1:views 0

getset 如果不存在值返回null設(shè)置新的值 如果存在就獲取原來的值并設(shè)置新的值


getset

String 類似的使用場景:(value除了是字符串還可以是數(shù)字)
計(jì)數(shù)器
統(tǒng)計(jì)多單位的數(shù)量
粉絲數(shù)
對象緩存存儲


List

在redis里面 可以把List當(dāng)成 棧 和隊(duì)列 阻塞隊(duì)列
所有的list 命令都是以 “ l ” 開頭的

List是先進(jìn)后出的


List

lpush key value 將一個(gè)值或者多個(gè)值插入列表頭部
rpush key value 將一個(gè)值或者多個(gè)值插入列表尾部
lrange key index index 返回隊(duì)列的指定index之間的值

lpush 1 ———> 1 list 4 <——— 4 rpush
lpush 2 ———> 2 1 list 4 5 <——— 5 rpush
lpush 3 ———> 3 2 1 list 4 5 6 <——— 6 rpush

lrange list 0 -1
3 2 1 4 5 6

lpop key 左移
rpop key 右移

lpop 3 <——— 3 2 1 list 4 5 6 ———> 6 rpop
lpop 2 <——— 2 1 list 4 5 ———> 5 rpop
lpop 1 <——— 1 list 4 ———> 4 rpop

lindex key index 返回index 下標(biāo)的值
lien key 獲得list的長度

lrem key 1 value 移除一個(gè)值
lrem key n value 移除n個(gè)value

trim key index index 截取index下標(biāo)指定的長度,list被改變,只剩下截取后的

rpoplpush key newKey 移除列表最后一個(gè)元素到新的列表
Exists Listkey 是否存在list
lset key index element 替換key的index下標(biāo)的位置的值 如果key不存在報(bào)錯

linsert key before/after element value 往key的element前面或者后面插入 value

list實(shí)際上是一個(gè)鏈表 before node after窟勃,left right 都可以插入
如果key不存在斗搞,創(chuàng)建新的鏈表
如果key存在,新增內(nèi)容
如果移除了所有值叶眉,空鏈表址儒,也代表不存在
在兩邊插入或者改動效率最高芹枷,中間元素效率偏低

你可以把List消看成息隊(duì)列,消息排隊(duì)
lpush rpop 隊(duì)列
lpush lpop 棧

Set

Set中的值不能重復(fù) 無序
sadd key value set存入值
smembers key set查看所有值
sismember key value 判斷是否set中存在value return 1 & 0

scard key 獲取set中元素個(gè)數(shù)

srem key value 移除set中value元素

srandmember key 隨機(jī)抽取一個(gè)元素莲趣。(做抽獎)
srandmember key num 隨機(jī)抽取num個(gè)元素

spop key num 隨機(jī)彈出 num個(gè)元素

smove key1 ket2 member 將member從key1移動到key2. 成功返回1失敗0(當(dāng)有相同的元素時(shí)失斣Т取)

數(shù)字集合類
差集 sdiff key1 key2
交集 sinter key1 key2
并集 suniom key1 key2

應(yīng)用
共同關(guān)注(并集)
共同關(guān)注 A用戶將所有關(guān)注的人放在一個(gè)集合當(dāng)中 將他的粉絲關(guān)注的人放在一個(gè)集合中
公共愛好 共同好友 推薦好友

Hash

k-map k-<key-value>

hset key field value 設(shè)置一個(gè)名字為key的hash,字段名(key)是 field 值是value 如果存在field就替換
hget key field field 取出key里面的 field對應(yīng)的 value
hgetall key 獲取全部key里面的鍵值對
hdel key field 刪除hash指定的字段

hlen key 獲取hash里面鍵值對的數(shù)量
hexists key field 判斷hash的指定字段是否存在 return 1,0

hkeys key 只獲得所有field
hvals 只獲得所有的value

hincrby key Field num key里面的field字段加num
hsetnx key field value 如果key里面不存在field子段就創(chuàng)建并且設(shè)置value 如果存在返回0不能設(shè)置

hash作為用戶信息 變更信息保存
user 當(dāng)成 hash-key喧伞,name age 作為field
string更適合字符串存儲

ZSet(有序集合)

在set的基礎(chǔ)上 (sadd k1 v1) 增加了一個(gè)值
zadd k1 score1 v1 score2 v2 添加一個(gè)值或者多個(gè)值
zrange key index index 查看index區(qū)間內(nèi)的值

zrevrange Key 0 -1 從大到小排序
zrevrange Key 0 -1 withscores 從大到小排序附帶score
zrevrange Key 0 num withscores 從大到num排序附帶score

zrangebyscore key -inf +inf 顯示所有的值 從小到大排序
zrangebyscore key -inf +inf withscores 顯示所有的值 從小到大排序 并且附帶score
zrangebyscore key -inf num withscores 顯示所有的值 從小到num排序 并且附帶score

zrem key member 移除一個(gè)指定元素 member
zcard key 獲取有序集合中的元素?cái)?shù)量

zcount key min max 獲取指定區(qū)間的members數(shù)量

案例 存儲班級成績 工資
消息權(quán)重 score分?jǐn)?shù)權(quán)重判斷
排行榜 實(shí)現(xiàn)

geospatial (地理位置)

六個(gè)命令
GEOADD
GEODIST
GEOHASH
GEOPOS
GEORADIUS
GEORADIUSBYMEMBER

GEOADD 添加地理位置
規(guī)則兩級無法添加走芋,一般下載城市數(shù)據(jù),直接通過java程序一次性導(dǎo)入
參數(shù)潘鲫。key 值(經(jīng)度翁逞,緯度,名稱)
GEOADD key 經(jīng)度 緯度 名稱

geospatial

GEOPOS key 名稱
獲得當(dāng)前定位溉仑,一定是一個(gè)坐標(biāo)值

兩人之間的距離(直線距離)
單位
m km ml英里 ft英尺
GEODIST key member member (單位) 默認(rèn)是米

GEORADIUS 以給定的經(jīng)度緯度為中心挖函,找出半徑內(nèi)的元素
附近的人實(shí)現(xiàn)
獲得所有附近的人的地址,手機(jī)定位 浊竟,通過半徑來查詢
GEORADIUS key 經(jīng)度 緯度 半徑 單位 (參數(shù))

帶了參數(shù)的

GEORADIUSBYMEMBER 根指定元素為中心 查找半徑內(nèi)的城市
GEORADIUSBYMEMBER key member 半徑 單位 (參數(shù))

GEOHASH 返回一個(gè)或者多個(gè)位置元素的GEOHASH表示 11個(gè)長度的GEOHASH 字符串 字符串越像表示距離越近
GEOHASH key member


GEO 底層實(shí)現(xiàn)原理其實(shí)就是zset 可以使用zset來操作GEO
查看地圖中所有元素


移除北京的定位(移除一個(gè)元素)

hyperloglog

什么是基數(shù)
基數(shù) :一個(gè)集合內(nèi)不重復(fù)的元素個(gè)數(shù)
redis 2.8.9版本就更新了 hyperloglog 基數(shù)統(tǒng)計(jì)算法
網(wǎng)頁 uv(一個(gè)人訪問一個(gè)網(wǎng)站多次怨喘,但是還是算做一個(gè)人)
傳統(tǒng)方式,set保存用戶id逐沙,統(tǒng)計(jì)set中元素?cái)?shù)量
很多分布式id很長所以保存起來比較麻煩哲思,我們目的是計(jì)數(shù),為不是保存用戶id

hyperloglog優(yōu)點(diǎn) 占用內(nèi)存固定 比如 放入 2^64不同的元素的基數(shù)吩案,只需要12kb內(nèi)存 如果從內(nèi)存角度比較的話 hyperloglog首選棚赔。錯誤率 0.81%

使用

pfadd key [value,value…..] 創(chuàng)建一組元素
pfcount key 統(tǒng)計(jì)元素?cái)?shù)量
pfcount key key2 統(tǒng)計(jì)key,key2 元素并集數(shù)量
pfmerge newkey key1 key2 合并key1 key2 的并集到newkey



不允許容錯 就用set

Bitmap 位存儲

比如統(tǒng)計(jì)疫情感染人數(shù):0 0 0 0 0 0 0 0 0 1 0 1 0 1 0
0未感染 1感染

統(tǒng)計(jì)用戶信息 活躍 不活躍 登錄 未登錄 365打卡
兩個(gè)狀態(tài)的都可以使用 bitmap
bitmaps位圖 數(shù)據(jù)結(jié)構(gòu)都是操作2進(jìn)制位來進(jìn)行記錄
1字節(jié)=8bit 一個(gè)人一年365bit 約等于 46字節(jié) 效率高 省內(nèi)存

0-6 表示 周一到周六 0未打卡 1打卡



查看某一天是否打卡



統(tǒng)計(jì)打卡天數(shù)
Bitcount key start end

事務(wù)

事務(wù)的本質(zhì):一組命令的集合 一個(gè)事務(wù)中的所有命令都會被序列化 在事務(wù)執(zhí)行的過程中徘郭,會按照順序執(zhí)行
一次性靠益,順序性,排他性執(zhí)行一系列命令 [set,set,set]
redis事務(wù)沒有隔離級別的概念 所有命令在入隊(duì)的時(shí)候并沒有被執(zhí)行 只有發(fā)起執(zhí)行命令的時(shí)候才會被執(zhí)行 Exec

redis事務(wù):
開啟事務(wù) multi
命令入隊(duì) 命令
執(zhí)行事務(wù) exec

正常執(zhí)行事務(wù)



命令報(bào)錯事物停止 命令不會執(zhí)行 (編譯型異常)



運(yùn)行時(shí)異常 運(yùn)行的時(shí)候其他命令可以執(zhí)行

放棄事物 discard 隊(duì)列中的命令都不會執(zhí)行


監(jiān)控

redis單條命令保證原子性残揉,但是事務(wù)不保證原子性

redis的監(jiān)視測試 redis可以實(shí)現(xiàn)樂觀鎖



正常執(zhí)行成功 事物正常結(jié)束 數(shù)據(jù)沒有發(fā)生變動 watch 監(jiān)視money對象

開啟兩個(gè)進(jìn)程終端

第一個(gè)不做執(zhí)行



第二個(gè)進(jìn)程直修改money數(shù)據(jù)



此時(shí)執(zhí)行第一個(gè)進(jìn)程 修改失敗

使用watch可以當(dāng)做樂觀鎖

如果事務(wù)執(zhí)行失敗先解鎖 獲取最新的值 再次監(jiān)視 比對監(jiān)視的值是否發(fā)生變化 如果沒變化可以執(zhí)行成功



jedis

要使用java來操作redis
什么是jedis:
官方推薦的java連接開發(fā)工具 使用java操作redis的中間件

1 導(dǎo)入對應(yīng)的依賴

<!--    導(dǎo)入jedis的包-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>

2編碼測試
連接--->操作--->結(jié)束


public class TestPing
{
    public static void main(String[] args)
    {
        //new一個(gè)jedis對象
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //所有的命令就是我們上面的所有指令
        System.out.println("測試鏈接:"+jedis.ping());
        System.out.println("清空當(dāng)前數(shù)據(jù)庫數(shù)據(jù):"+jedis.flushDB());
        System.out.println("判斷某個(gè)鍵是否存在:"+jedis.exists("username"));
        System.out.println("新增'username'胧后,'Y1'的鍵值對:"+jedis.set("username","Y1"));
        System.out.println("新增'password','Y1'的鍵值對:"+jedis.set("password","Y1"));
        System.out.println("系統(tǒng)中所有的鍵如下:"+"\n"+jedis.keys("*"));
        System.out.println("刪除鍵password:"+jedis.del("password"));
        System.out.println("判斷鍵password是否存在:"+jedis.exists("password"));
        System.out.println("查看username所儲存值的類型:"+jedis.type("username"));
        System.out.println("隨機(jī)返回key空間的一個(gè):"+jedis.randomKey());
        System.out.println("重命名key:"+jedis.rename("username","name"));
        System.out.println("取出改后的name:"+jedis.get("name"));
        System.out.println("選擇0號數(shù)據(jù)庫:"+jedis.select(0));
        System.out.println("刪除當(dāng)前庫所有keys:"+jedis.flushDB());
        System.out.println("返回當(dāng)前庫keys數(shù)量:"+jedis.dbSize());
        System.out.println("刪除所有keys:"+jedis.flushAll());

        System.out.println("增加:"+jedis.set("k1","v1"));
        System.out.println("增加:"+jedis.set("k2","v2"));
        System.out.println("增加:"+jedis.set("k3","v3"));
        System.out.println("刪除k2:"+jedis.del("k2"));
        System.out.println("獲取k2:"+jedis.get("k2"));
        System.out.println("修改k1:"+jedis.set("k1","newV1"));
        System.out.println("獲取k1:"+jedis.get("k1"));
        System.out.println("在k3后面增加:"+jedis.append("k3","append"));
        System.out.println("獲取k3:"+jedis.get("k3"));
        System.out.println("增加多個(gè)鍵值對:"+jedis.mset("k4","v4","k5","v5","k6","v6"));
        System.out.println("獲取多個(gè)鍵值對:"+jedis.mget("k4","kk5","k6"));
        System.out.println("刪除多個(gè)鍵值對:"+jedis.del("k4","k5"));
        System.out.println("獲取多個(gè)鍵值對:"+jedis.mget("k4","kk5","k6"));
        System.out.println("清庫:"+jedis.flushAll());
        System.out.println("==========分割線==========");
        System.out.println("新增防止覆蓋之前的:");
        System.out.println("k1:"+jedis.setnx("k1","v1"));
        System.out.println("k2:"+jedis.setnx("k2","v2"));
        System.out.println("k2:"+jedis.setnx("k2","v3"));
        System.out.println("獲取k1:"+jedis.get("k1"));
        System.out.println("獲取k2:"+jedis.get("k2"));
        System.out.println("==========分割線==========");
        System.out.println("新增鍵值對設(shè)置有效時(shí)間");
        System.out.println("set k1:"+jedis.setex("k1",2,"2s"));
        System.out.println("get k1:"+jedis.get("k1"));
        try
        {
            TimeUnit.SECONDS.sleep(3);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("get k1:"+jedis.get("k1"));
        System.out.println("獲取原值更新為新值");
        System.out.println("getset k2:"+jedis.getSet("k2","getset k2"));
        System.out.println("get k2:"+jedis.get("k2"));
        System.out.println("獲取k2的剪切字串:"+jedis.getrange("k2",2,4));
    }
}

所有api都是上面的命令 一個(gè)都沒有變化 全部一樣

Jedis事務(wù)

public class TestTX
{
    public static void main(String[] args)
    {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","Y1");

        jedis.flushDB();
        //jedis.watch("user1","user2");
        //開啟事物
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();

        try
        {
            multi.set("user1",result);
            multi.set("user2",result);
            //代碼跑出編譯異常事務(wù)執(zhí)行失敗
            int i =1/0;
            //執(zhí)行事物
            multi.exec();
        }
        catch (Exception e)
        {
            //放棄事物
            multi.discard();
            e.printStackTrace();
        }
        finally
        {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

SpringBoot整合

SpringBoot操作數(shù)據(jù):
SpringData:jpa jdbc mongodb redis
說明在springboot2.X之后我們原來的用的jedis被換成了 lettuce
jedis : 采用直接連接抱环,多個(gè)線程操作的話壳快,是不安全的,如果想要避免不安全镇草,使用jedis 的 pool 連接池 BIO
lettuce:采用netty眶痰,實(shí)例可以在多個(gè)線程中共享,不存在線程不安全的情況梯啤,可以減少線程數(shù)量竖伯,更像 NIO

源碼分析:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

   @Bean
   @ConditionalOnMissingBean(name = "redisTemplate”) //我們可以自己定一個(gè)redisTemplate 替換默認(rèn)的
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
         throws UnknownHostException {
    //默認(rèn)的redisTemplate 沒有過多的設(shè)置, redis的對象保存 都是需要序列化的
    //兩個(gè)范性都是object類型后面使用需要強(qiáng)轉(zhuǎn)<String, Object>
      RedisTemplate<Object, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }

   @Bean
   @ConditionalOnMissingBean。//由于String類型是redis最最常用的一個(gè)類型七婴,所以單獨(dú)提出來一個(gè)bean
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
         throws UnknownHostException {
      StringRedisTemplate template = new StringRedisTemplate();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }
}

整合測試一下

springboot 所有的配置 都有一個(gè)自動配置類 autoconfigure
自動配置類會綁定一個(gè) properties 配置文件

1祟偷,導(dǎo)入依賴,配置連接打厘,測試

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  redis:
    host: 127.0.0.1
    port: 6379

@SpringBootTest
class RedisSpringbootApplicationTests
{
    @Resource
    RedisTemplate redisTemplate;

    @Test
    void contextLoads()
    {
        // 操作字符串
        redisTemplate.opsForValue();

        //操作list
        redisTemplate.opsForList();

        //hash
        redisTemplate.opsForHash();

        //set
        redisTemplate.opsForSet();

        //zset
        redisTemplate.opsForZSet();

        //geo
        redisTemplate.opsForGeo();

        //hyperloglog
        redisTemplate.opsForHyperLogLog();

        //bitmap
        redisTemplate.opsForValue().setBit("key",new Long(12345678),true);

        //連接對象
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        connection.flushAll();
        connection.flushDb();
    }

}

值的序列化


默認(rèn)的jdk序列化方式(字符串會轉(zhuǎn)譯) 我會要使用json來序列化



所有的對象需要序列化 不然會報(bào)錯



關(guān)于對象的保存:
Pojo 類都會序列化
redis 里實(shí)現(xiàn) Serializable 接口

默認(rèn)是jdk序列化
我們要自己實(shí)現(xiàn)序列化方式



源碼的序列化方式 默認(rèn)使用jdk序列化方式

自己的redistemplate序列化配置模版
@Configuration
public class RedisConfig
{
    //編寫我們自己的 ridesTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException
    {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        
        //序列化配置
        //使用json去解析任意的對象 用json做序列化
        Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //用obojectmapper進(jìn)行轉(zhuǎn)譯
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(objectMapper);
        //string的序列化
        StringRedisSerializer stringRedisTemplate = new StringRedisSerializer();
        
        // key 采用string的序列化方式
        template.setKeySerializer(stringRedisTemplate);
        // hash 的key 也采用string的序列化方式
        template.setHashKeySerializer(stringRedisTemplate);
        //value的序列化采用json的方式
        template.setValueSerializer(jsonRedisSerializer);
        //hash的value序列化采用json的方式
        template.setHashValueSerializer(jsonRedisSerializer);
        //把properties set 進(jìn)去
        template.afterPropertiesSet();
        //返回template
        return template;
    }
}

序列化之后的 redis-cli 正常顯示key



Redis config 的配置

啟動的時(shí)候修肠,就通過配置文件來啟動

單位 配置文件 對單位大小寫不敏感


網(wǎng)絡(luò)

端口

通用配置

快照


主從復(fù)置

安全


驗(yàn)證

通過命令來設(shè)置


客戶端



1、volatile-lru:只對設(shè)置了過期時(shí)間的key進(jìn)行LRU(默認(rèn)值)
2婚惫、allkeys-lru : 刪除lru算法的key
3氛赐、volatile-random:隨機(jī)刪除即將過期key
4、allkeys-random:隨機(jī)刪除
5先舷、volatile-ttl : 刪除即將過期的
6艰管、noeviction : 永不過期,返回錯誤
AOF 配置


一些小小的配置可以改變性能=āI蟆!

持久化



在指定的時(shí)間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤,也就是行話講的Snapshot快照,它恢復(fù)時(shí)是將快照文件直接讀到內(nèi)存里捺球。Redis會單獨(dú)創(chuàng)建( fork ) 一個(gè)子進(jìn)程來進(jìn)行持久化,會先將數(shù)據(jù)寫入到一個(gè)臨時(shí)文件中,待持久化過程都結(jié)束了,再用這個(gè)臨時(shí)文 件替換上次持久化好的文件缸浦。整個(gè)過程中,主進(jìn)程是不進(jìn)行任何I0操作的。這就確保了極高的性能氮兵。如果需要進(jìn)行大規(guī)模數(shù)據(jù)的恢復(fù)裂逐,且對于數(shù)據(jù)恢復(fù)的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺點(diǎn)是最后一次持久化后的數(shù)據(jù)可能丟失泣栈。
一般情況下 不需要修改配置
Rdb保存的文件是 dump.rdb



自定義 rdb 機(jī)制 60s 內(nèi)修改了 5次 觸發(fā)快照策略

觸發(fā)機(jī)制
1 save的規(guī)則滿足的情況下 會自動觸發(fā)rdb規(guī)則 生成dump.rdb文件
2執(zhí)行了flushall命令 會出發(fā)rdb規(guī)則 生成dump.rdb文件
3退出redis的時(shí)候 也會觸發(fā)rdb規(guī)則 生成dump.rdb文件

如何恢復(fù)rdb文件
只需要將rbd文件放入redis的啟動目錄就可以了卜高,redis啟動的時(shí)候會自動檢查dump.rdb文件 恢復(fù)其中數(shù)據(jù)
查看dump文件存放的位置



默認(rèn)配置基本夠用

優(yōu)點(diǎn):適合大規(guī)模數(shù)據(jù)恢復(fù),對于數(shù)據(jù)恢復(fù)的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效

缺點(diǎn):需要一定的時(shí)間間隔操作南片,如果意外宕機(jī)最后一次修改數(shù)據(jù)丟失掺涛,fork進(jìn)程會占用一定的內(nèi)存空間

生產(chǎn)環(huán)境會將rdb備份

AOF 記錄所有的命令,恢復(fù)的時(shí)候就把這個(gè)文件全部的再執(zhí)行一遍


以日志的形式來記錄每個(gè)寫操作疼进,將Reids執(zhí)行過的寫入指令記錄下來薪缆,只許追加文件但不可以改寫文件,redis啟動之初會讀取aof文件重新構(gòu)建數(shù)據(jù)伞广,換言之拣帽,redis重啟的話就根據(jù)日志文件的內(nèi)容將寫指令全部執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。
如果aof文件被惡心修改嚼锄、損壞诞外,redisg是不能啟動的,此時(shí)可以使用redis-check-aof來進(jìn)行修復(fù)

默認(rèn)不開啟需要手動配置



若aof文件大于指定值(64mb)灾票,會fork一個(gè)新子進(jìn)程將文件重寫
重啟redis aof生效 生成appendonly.aof文件 以日志級別記錄寫操作



如果aof文件出錯 redis無法啟動 需要修復(fù)文件

但是修復(fù)后,可能會丟失一些數(shù)據(jù)
優(yōu)點(diǎn)
每一次修改都同步茫虽,確保文件的完整性
每秒同步一次刊苍,可能會丟失一秒的數(shù)據(jù)
從不同步既们,效率最高

但是修復(fù)后,可能會丟失一些數(shù)據(jù)
優(yōu)點(diǎn)
每一次修改都同步正什,確保文件的完整性
每秒同步一次啥纸,可能會丟失一秒的數(shù)據(jù)
從不同步,效率最高

缺點(diǎn)
相對于數(shù)據(jù)文件來說婴氮,aof遠(yuǎn)遠(yuǎn)大于rdb斯棒,修復(fù)速度比rdb慢
aof運(yùn)行效率要比rdb慢(涉及到大量的IO操作)

拓展
1.Redis能對AOF文件進(jìn)行重寫,使得AOF文件體積不至于過大
2.只做緩存主经,只希望數(shù)據(jù)在服務(wù)器運(yùn)行的時(shí)候存在荣暮,也可以不做任何持久化
3.同時(shí)開啟兩種持久化方式:
redis重啟會優(yōu)先載入AOF文件來恢復(fù)原始數(shù)據(jù),因?yàn)橥ǔG闆r下AOF文件保存的數(shù)據(jù)比RDB要完整
RDB數(shù)據(jù)不實(shí)時(shí)罩驻,更適合用于備份數(shù)據(jù)庫(AOF在不斷變化不好備份)穗酥,快速重啟,而且不會有AOF可能潛在的bug

1惠遏、RDB持久化方式能夠在指定的時(shí)間間隔內(nèi)對你的數(shù)據(jù)進(jìn)行快照存儲
2砾跃、AOF持久化方式記錄每次對服務(wù)器寫的操作,當(dāng)服務(wù)器重啟的時(shí)候會重新執(zhí)行這些命令來恢復(fù)原始的數(shù)據(jù), AOF命令以Redis協(xié)議追加保存每次寫的操作到文件末尾,Redis還能對AOF文件進(jìn)行后臺重寫,使得AOF文件的體積不至于過大节吮。
3抽高、只做緩存,如果你只希望你的數(shù)據(jù)在服務(wù)器運(yùn)行的時(shí)候存在,你也可以不使用任何持久化
4、同時(shí)開啟兩種持久化方式
●在這種情況下,當(dāng)redis重啟的時(shí)候會優(yōu)先載入AOF文件來恢復(fù)原始的數(shù)據(jù),因?yàn)樵谕ǔG闆r下AOF文件保存的數(shù)據(jù)集要比RDB 文件保存的數(shù)據(jù)集要完整透绩。
●RDB的數(shù)據(jù)不實(shí)時(shí),同時(shí)使用兩者時(shí)服務(wù)器重啟也只會找AOF文件,那要不要只使用AOF呢?作者建議不要,因?yàn)镽DB更適合 用于備份數(shù)據(jù)庫( AOF在不斷變化不好備份) ,快速重啟,而且不會有AOF可能潛在的Bug ,留著作為一個(gè)萬一的手段翘骂。
5、性能建議
●因?yàn)镽DB文件只用作后備用途,建議只在Slave.上持久化RDB文件,而且只要15分鐘備份一次就夠了
●如果Enable AOF ,好處是在最惡劣情況下也只會丟失不超過兩秒數(shù)據(jù),啟動腳本較簡單只load自己的AOF文件就可以了,代價(jià) 一是帶來了持續(xù)的IO ,二是AOF rewrite的最后將rewrite過程中產(chǎn)生的新數(shù)據(jù)寫到新文件造成的阻塞幾乎是不可避免的渺贤。只要 硬盤許可,應(yīng)該盡量減少AOF rewrite的頻率, AOF重寫的基礎(chǔ)大小默認(rèn)值64M太小了,可以設(shè)到5G以上,默認(rèn)超過原大小 100%大小重寫可以改到適當(dāng)?shù)臄?shù)值雏胃。
●如果不Enable AOF,僅靠Master-Slave Repllcation實(shí)現(xiàn)高可用性也可以,能省掉一大筆IO志鞍,也減少了rewrite時(shí)帶來的系統(tǒng)波動瞭亮。代價(jià)是如果Master/Slave 同時(shí)倒掉,會丟失十幾分鐘的數(shù)據(jù)固棚,啟動腳本也要比較兩個(gè)Master/Slave中的RDB文件,載入較新的那個(gè)统翩,微博就是這種架構(gòu)。


發(fā)布訂閱




發(fā)送

訂閱

原理
Redis是使用C實(shí)現(xiàn)的,通過分析Redis 源碼里的pubsub.c文件此洲,了解發(fā)布和訂閱機(jī)制的底層實(shí)現(xiàn),籍此加深對Redis 的理解厂汗。
Redis通過PUBLISH、SUBSCRIBE 和PSUBSCRIBE等命令實(shí)現(xiàn)發(fā)布和訂閱功能呜师。

通過SUBSCRIBE命令訂閱某頻道后, redis-server 里維護(hù)了一個(gè)字典,字典的鍵就是一個(gè)個(gè)channel , 而字典的值則是一個(gè)鏈 表娶桦,鏈表中保存了所有訂閱這個(gè)channel的客戶端。SUBSCRIBE 命令的關(guān)鍵,就是將客戶端添加到給定channel的訂閱鏈表中。

通過PUBLISH命令向訂閱者發(fā)送消息, redis-server會使用給定的頻道作為鍵,在它所維護(hù)的channel字典中查找記錄了訂閱這個(gè)頻道的所有客戶端的鏈表,遍歷這個(gè)鏈表,將消息發(fā)布給所有訂閱者衷畦。

Pub/Sub從字面上理解就是發(fā)布( Publish )與訂閱( Subscribe ) , 在Redis中,你可以設(shè)定對某-個(gè)key值進(jìn)行消息發(fā)布及消息訂 閱栗涂,當(dāng)一個(gè)key值上進(jìn)行了消息發(fā)布后,所有訂閱它的客戶端都會收到相應(yīng)的消息。這一功能最明顯的用法就是用作實(shí)時(shí)消息系 統(tǒng)祈争,比如普通的即時(shí)聊天,群聊等功能斤程。

簡單應(yīng)用有:
實(shí)時(shí)消息系統(tǒng)
聊天室
關(guān)注系統(tǒng)

主從復(fù)制

概念
主從復(fù)制,是指將一臺Redis服務(wù)器的數(shù)據(jù),復(fù)制到其他的Redis服務(wù)器。前者稱為主節(jié)點(diǎn)(master/leader) ,后者稱為從節(jié)點(diǎn)(slave/follower) ;數(shù)據(jù)的復(fù)制是單向的,只能由主節(jié)點(diǎn)到從節(jié)點(diǎn)菩混。Master以寫為主, Slave以讀為主忿墅。

默認(rèn)情況下,每臺Redis服務(wù)器都是主節(jié)點(diǎn)
且一個(gè)主節(jié)點(diǎn)可以有多個(gè)從節(jié)點(diǎn)(或沒有從節(jié)點(diǎn)) ,但一個(gè)從節(jié)點(diǎn)只能有一個(gè)主節(jié)點(diǎn)。

主從復(fù)制的作用主要包括:
1沮峡、數(shù)據(jù)冗余:主從復(fù)制實(shí)現(xiàn)了數(shù)據(jù)的熱備份,是持久化之外的一種數(shù)據(jù)冗余方式疚脐。
2、故障恢復(fù):當(dāng)主節(jié)點(diǎn)出現(xiàn)問題時(shí),可以由從節(jié)點(diǎn)提供服務(wù),實(shí)現(xiàn)快速的故障恢復(fù);實(shí)際上是一種服務(wù)的冗余帖烘。
3亮曹、負(fù)載均衡:在主從復(fù)制的基礎(chǔ)上,配合讀寫分離,可以由主節(jié)點(diǎn)提供寫服務(wù),由從節(jié)點(diǎn)提供讀服務(wù)(即寫Redis數(shù)據(jù)時(shí)應(yīng)用連接 主節(jié)點(diǎn),讀Redis數(shù)據(jù)時(shí)應(yīng)用連接從節(jié)點(diǎn)) , 分擔(dān)服務(wù)器負(fù)載;尤其是在寫少讀多的場景下,通過多個(gè)從節(jié)點(diǎn)分擔(dān)讀負(fù)載,可以大 大提高Redis服務(wù)器的并發(fā)量。
4秘症、高可用基石:除了上述作用以外照卦,主從復(fù)制還是哨兵和集群能夠?qū)嵤┑幕A(chǔ),因此說主從復(fù)制是Redis高可用的基礎(chǔ)。 一般來說,要將Redis運(yùn)用于工程項(xiàng)目中,只使用一臺Redis是萬萬不能的,原因如下:
1乡摹、從結(jié)構(gòu)上,單個(gè)Redis服務(wù)器會發(fā)生單點(diǎn)故障,并且一臺服務(wù)器需要處理所有的請求負(fù)載,壓力較大;
2役耕、從容量上,單個(gè)Redis服務(wù)器內(nèi)存容量有限,就算一臺Redis服務(wù)器內(nèi)存 容量為256G ,也不能將所有內(nèi)存用作Redis存儲內(nèi)存, 一般來說 ,單臺Redis最大使用內(nèi)存不應(yīng)該超過20G。 電商網(wǎng)站上的商品聪廉,一般都是一次上傳,無數(shù)次瀏覽的,說專業(yè)點(diǎn)也就是"多讀少寫"瞬痘。


主從復(fù)制,讀寫分離板熊,80%的情況下都是在進(jìn)行讀操作框全,減緩服務(wù)器壓力,架構(gòu)中經(jīng)常使用干签,一主二從
只要在公司中津辩,主從復(fù)置必須要使用,因?yàn)樵谡鎸?shí)的項(xiàng)目中不可能單機(jī)使用redis

環(huán)境配置

只配置從庫容劳,不配置主庫

查看當(dāng)前庫的信息喘沿,redis默認(rèn)是主庫


修改
端口 pid名字 log文件名 dump.rdb名字 開啟后臺啟動

修改主機(jī)的配置文件
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "6379.log"
dbfilename dump6379.rdb

修改從機(jī)的配置文件
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump6380.rdb

通過配置文件開啟服務(wù)


一主二從
開啟三個(gè)服務(wù)發(fā)現(xiàn)默認(rèn)都是主機(jī)

一般情況下只用配置從機(jī)就可以了
6379為主機(jī)

80 81 為從機(jī)

這個(gè)是暫時(shí)的配置
真實(shí)的配置是應(yīng)該修改配置文件的

??
主機(jī)寫 從機(jī)讀


主機(jī)宕機(jī)從機(jī)還是可以拿到值,只是沒有了寫操作
主機(jī)從新開機(jī)竭贩,從機(jī)仍然可以獲取主機(jī)的寫入

?? 用命令行配置的主從
從機(jī)宕機(jī) 主機(jī)在宕機(jī)期間寫入的從機(jī)取不到值
因?yàn)閿嚅_以后重啟 讀取未修改的配置文件默認(rèn)還是主節(jié)點(diǎn)啟動

復(fù)制原理
Slave啟動成功連接到master后會發(fā)送一個(gè)sync同步命令

Master接到命令,啟動后臺的存盤進(jìn)程,同時(shí)收集所有接收到的用于修改數(shù)據(jù)集命令,在后臺進(jìn)程執(zhí)行完畢之后, master將傳送 整個(gè)數(shù)據(jù)文件到slave ,并完成一次完全同步蚜印。

全量復(fù)制:slave服務(wù)在接收到數(shù)據(jù)庫文件數(shù)據(jù)后,將其存盤并加載到內(nèi)存中。

增量復(fù)制: Master繼續(xù)將新的所有收集到的修改命令依次傳給slave ,完成同步

但是只要是重新連接master , 一次完全同步(全量復(fù)制)將被自動執(zhí)行

主機(jī)斷開連接使用這個(gè)命令是自己變?yōu)橹鳈C(jī)
SLAVEOF no one


哨兵模式



哨兵配置

配置哨兵配置文件 vim sentinel.conf



配置遠(yuǎn)遠(yuǎn)不止這么少

啟動哨兵
redis-sentinel sentinel.conf



開啟三個(gè)redis服務(wù) 79 80 81
設(shè)置主從后 哨兵日志更新



模擬主機(jī)宕機(jī)

failover 故障轉(zhuǎn)移

哨兵選舉6380為新的主機(jī) (投票算法)



主機(jī)從新鏈接了也只能當(dāng)作是6380的從機(jī) (哨兵模式規(guī)則)

優(yōu)點(diǎn):
1留量、哨兵集群,基于主從復(fù)制模式,所有的主從配置優(yōu)點(diǎn),它全有
2窄赋、主從可以切換,故障可以轉(zhuǎn)移,系統(tǒng)的可用性就會更好
3哟冬、哨兵模式就是主從模式的升級,手動到自動,更加健壯!

缺點(diǎn):
1、Redis 不好在線擴(kuò)容的,集群容量一但到達(dá)上限,在線擴(kuò)容就十分麻煩!
2寝凌、實(shí)現(xiàn)哨兵模式的配置其實(shí)是很麻煩的,里面有很多選擇!

基礎(chǔ)配置
protected-mode no #關(guān)閉保護(hù)模式
port 26479 #端口
daemonize yes #使用后臺模式啟動
pidfile "/var/run/redis-sentinel_26479.pid" #進(jìn)程id文件
logfile "/usr/local/redis/sentinel/sentinel_26479.log" #日志文件
dir "/usr/local/redis/sentinel"               #工作目錄

核心配置
1柒傻、sentinel monitor <master-name> <ip> <port> <quorum>
master-name:redis主節(jié)點(diǎn)昵稱。
ip:redis主機(jī)ip较木。
port:redis主機(jī)端口。
quorum:哨兵判斷主節(jié)點(diǎn)是否發(fā)生故障的票數(shù)青柄。如果設(shè)置為2伐债,表示2個(gè)哨兵節(jié)點(diǎn)認(rèn)為主節(jié)點(diǎn)發(fā)生了故障,一般設(shè)置為:哨兵節(jié)點(diǎn)數(shù)/2+1致开。
2峰锁、sentinel down-after-milliseconds <master-name> <times>
哨兵會定期的向redis節(jié)點(diǎn)發(fā)送ping命令來判斷redis是否可達(dá),若超過指定的times毫秒內(nèi)還未得到pong回復(fù)双戳,則判讀該redis不可達(dá)虹蒋。
3、sentinel parallel-syncs <master-name> <nums>
當(dāng)redis主節(jié)點(diǎn)掛了后飒货,哨兵會選出新的master魄衅,此時(shí),剩余的slave會向新的master發(fā)起同步數(shù)據(jù)塘辅,這個(gè)設(shè)置表示允許并行同步的slave個(gè)數(shù)晃虫。
4、sentinel failover-timeout <master-name> <times>
進(jìn)行故障轉(zhuǎn)移時(shí)扣墩,如果超過設(shè)置的times毫秒哲银,表示故障轉(zhuǎn)移失敗。
5呻惕、sentinel auth-pass <master-name> <password>
如果redis主節(jié)點(diǎn)設(shè)置了密碼荆责,則需要進(jìn)行這個(gè)配置。

*****備注配置redis主從復(fù)制亚脆、讀寫分離*******
配置思路:master配置文件不需要動做院,修改slave的配置文件。
1型酥、添加一行:replicaof <masterip> <masterport>
2山憨、如果master配置有密碼,則需要配置這一行
  masterauth <master-password>
3弥喉、replica-read-only yes    #表示slave中的數(shù)據(jù)是只讀的

*****springboot整合redis哨兵模式*******
添加yml配置文件
spring:
  redis:
    database: 0
    password: 12345678
    sentinel:
      master: mymaster
      nodes: 192.168.0.1:26379,192.168.0.1:26479,192.168.0.1:26579


雪崩 擊穿 穿透

1郁竟、緩存處理流程

接收到查詢數(shù)據(jù)請求時(shí),優(yōu)先從緩存中查詢由境,若緩存中有數(shù)據(jù)棚亩,則直接返回蓖议,若緩存中查不到則從DB中查詢,將查詢的結(jié)果更新到緩存中讥蟆,并返回查詢結(jié)果勒虾,若DB中查不到,則返回空數(shù)據(jù)


緩存穿透

當(dāng)緩存與數(shù)據(jù)庫中都不存在該數(shù)據(jù)時(shí)瘸彤,由于當(dāng)數(shù)據(jù)庫查詢不到數(shù)據(jù)就不會寫入緩存修然,這個(gè)時(shí)候如果用戶不斷的惡意發(fā)起請求,就會導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請求都會查詢DB质况,請求量大的情況下愕宋,就會導(dǎo)致DB壓力過大,直接掛掉结榄。

解決方案:

1中贝、當(dāng)查詢返回一個(gè)空數(shù)據(jù)時(shí),直接將這個(gè)空數(shù)據(jù)存到緩存中臼朗,過期時(shí)間不宜設(shè)置過長邻寿,建議不超過5分鐘

2、采用布隆過濾器:將所有可能存在數(shù)據(jù)视哑,分別通過多個(gè)哈希函數(shù)生成多個(gè)哈希值绣否,然后將這些哈希值存到一個(gè)足夠大的bitmap中,此時(shí)一個(gè)一定不存在的數(shù)據(jù)就會被這個(gè)bitmap攔截黎炉,從而減少了數(shù)據(jù)庫的查詢壓力枝秤。

參考鏈接:http://www.reibang.com/p/2104d11ee0a2

緩存擊穿

某一個(gè)數(shù)據(jù)緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù)(一般是緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多慷嗜,同時(shí)讀緩存沒讀到數(shù)據(jù)淀弹,又同時(shí)去數(shù)據(jù)庫去取數(shù)據(jù),引起數(shù)據(jù)庫壓力瞬間增大庆械,嚴(yán)重情況下會直接掛掉薇溃。

解決方案:

1、添加互斥鎖:

  • ReentrantLock公平鎖
  • 根據(jù)key值加鎖缭乘,這樣線程之間會不影響沐序,不會因?yàn)槟骋粋€(gè)線程獲取了鎖,其它線程就處于等待時(shí)間堕绩,也就是線程A從數(shù)據(jù)庫取key1的數(shù)據(jù)并不妨礙線程B取key2的數(shù)據(jù)
    2策幼、設(shè)置熱點(diǎn)數(shù)據(jù)永不過期(物理上的不過期、“邏輯上”的不過期(緩存到期動態(tài)構(gòu)建緩存))
    簡單的互斥鎖例子:
  @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private Jedis               jedis;
    private final String        MUTEX_KEY = "MUTEX_";

    public String getData(String key) throws InterruptedException {
        String value = stringRedisTemplate.opsForValue().get(key);
        //緩存失效
        if (StringUtils.isBlank(value)) {
            //設(shè)置分布式鎖奴紧,只允許一個(gè)線程去查詢DB特姐,同時(shí)指定過期時(shí)間為1min,防止del操作失敗黍氮,導(dǎo)致死鎖唐含,緩存過期無法加載DB數(shù)據(jù)
            if (tryLock(MUTEX_KEY + key, 60L)) {
                //從數(shù)據(jù)庫查詢數(shù)據(jù),將查詢的結(jié)果緩存起來
                value = getValueFromDB();
                stringRedisTemplate.opsForValue().set(key, value);

                //釋放分布式鎖
                stringRedisTemplate.delete(MUTEX_KEY + key);
            } else {
                //當(dāng)鎖被占用時(shí)浅浮,睡眠5s繼續(xù)調(diào)用獲取數(shù)據(jù)請求
                Thread.sleep(5000);
                getData(key);}
        }
        return value;
    }

    /**
     * redis實(shí)現(xiàn)分布式事務(wù)鎖 嘗試獲取鎖
     * 
     * @param lockName  鎖
     * @param expireTime 過期時(shí)間
     * @return
     */
    public Boolean tryLock(String lockName, long expireTime) {
        //RedisCallback redis事務(wù)管理,將redis操作命令放到事務(wù)中處理捷枯,保證執(zhí)行的原子性
        String result = stringRedisTemplate.opsForValue().getOperations().execute(new RedisCallback<String>() {

            /**
             * @param key 使用key來當(dāng)鎖滚秩,因?yàn)閗ey是唯一的。
             * @param value 請求標(biāo)識淮捆,可通過UUID.randomUUID().toString()生成,解鎖時(shí)通value參數(shù)可識別出是哪個(gè)請求添加的鎖
             * @param nx 表示SET IF NOT EXIST郁油,即當(dāng)key不存在時(shí),我們進(jìn)行set操作争剿;若key已經(jīng)存在已艰,則不做任何操作
             * @param ex 表示過期時(shí)間的單位是秒
             * @param time 表示過期時(shí)間
             */
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                return jedis.set(lockName, UUID.randomUUID().toString(), "NX", "EX", expireTime);
            }
        });

        if ("OK".equals(result)) {
            return true;
        }
        return false;
    }

    public String getValueFromDB() {
        return "";
    }

緩存雪崩

緩存中大批量的數(shù)據(jù)都到了過期時(shí)間,從而導(dǎo)致查詢數(shù)據(jù)量巨大蚕苇,引起數(shù)據(jù)庫壓力過大甚至down機(jī)。和緩存擊穿不同凿叠,緩存擊穿是指某一條數(shù)據(jù)到了過期時(shí)間涩笤,大量的并發(fā)請求都來查詢這一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過期了盒件,很多數(shù)據(jù)都查不到從而查數(shù)據(jù)庫

解決方案:

1蹬碧、設(shè)置熱點(diǎn)數(shù)據(jù)永不過期
2、緩存數(shù)據(jù)的過期時(shí)間設(shè)置隨機(jī)炒刁,可以在原有的過期時(shí)間上加上一個(gè)隨機(jī)值恩沽,比如1-3min,防止同一時(shí)間大量緩存數(shù)據(jù)集體失效翔始,導(dǎo)致數(shù)據(jù)庫壓力過大罗心。
3、如果是分布式部署緩存數(shù)據(jù)庫城瞎,可將熱點(diǎn)數(shù)據(jù)分別存放到不同的緩存數(shù)據(jù)庫中渤闷,避免某一點(diǎn)由于壓力過大而down掉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脖镀,一起剝皮案震驚了整個(gè)濱河市飒箭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜒灰,老刑警劉巖弦蹂,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異强窖,居然都是意外死亡凸椿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門毕骡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來削饵,“玉大人岩瘦,你說我怎么就攤上這事×耍” “怎么了启昧?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長劈伴。 經(jīng)常有香客問我密末,道長,這世上最難降的妖魔是什么跛璧? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任严里,我火速辦了婚禮,結(jié)果婚禮上追城,老公的妹妹穿的比我還像新娘刹碾。我一直安慰自己,他們只是感情好座柱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布迷帜。 她就那樣靜靜地躺著,像睡著了一般色洞。 火紅的嫁衣襯著肌膚如雪戏锹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天火诸,我揣著相機(jī)與錄音锦针,去河邊找鬼。 笑死置蜀,一個(gè)胖子當(dāng)著我的面吹牛奈搜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盾碗,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼媚污,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了廷雅?” 一聲冷哼從身側(cè)響起耗美,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎航缀,沒想到半個(gè)月后商架,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芥玉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年忌锯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了限书。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡福扬,死狀恐怖龙助,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布蒋困,位于F島的核電站,受9級特大地震影響敬辣,放射性物質(zhì)發(fā)生泄漏雪标。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一溉跃、第九天 我趴在偏房一處隱蔽的房頂上張望村刨。 院中可真熱鬧,春花似錦撰茎、人聲如沸嵌牺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓梅。三九已至,卻和暖如春绎签,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酝锅。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工诡必, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人搔扁。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓爸舒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稿蹲。 傳聞我的和親對象是個(gè)殘疾皇子扭勉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354