主頁 > 資料庫 > 有點長的博客:Redis不是只有get set那么簡單

有點長的博客:Redis不是只有get set那么簡單

2020-09-12 19:25:27 資料庫

我以前還沒接觸Redis的時候,聽到大資料組的小伙伴在討論Redis,覺得這東西好高端,要是哪天我們組也可以使用下Redis就好了,好長一段時間后,我們專案中終于引入了Redis這個技術,我用了幾下,感覺Redis也就那么回事啊,不就是get set嗎?當我又知道Redis還有自增、自減操作,而且這些操作還是原子性的,秒殺就可以用這個技術,我就覺得我已經熟悉Redis了,相信有不少curd boy是和以前的我一個想法:Redis不就是get set increment嗎?其實不然,Redis遠遠沒有我們想象中的那么簡單,今天我就在此獻丑來談談Redis,

關于Redis是什么,如何安裝等問題就不闡述了,我們直接進入正題吧,

Redis五種資料型別及應用場景

Redis有五種資料型別,即 string,list,hash,set,zset(sort set),我想這點只要稍微對Redis有點了解的小伙伴都應該清楚,下面,我們就來討論下這五種資料型別的應用場景,

string

這個型別相信是大家最熟悉的了,但是千萬不要小瞧它,它可以做很多事情,也可以牽出一系列的問題,

我們先從最簡單的入手:

localhost:6379> set coderbear hello
OK
localhost:6379> get codebear
"hello"

這兩個命令相信大家都知道,我就不解釋了,我們再來用下strlen這個命令:

localhost:6379> strlen codebear
(integer) 5

哦,我明白了strlen這個命令可以獲得Value的長度啊,hello的長度是5,所以輸出就是5,這個解釋對不對呢?不著急,我們慢慢往下看,

我們使用append命令為codebear這個key追加點東西:

APPEND codebear 中國

如果我們再次使用strlen命令會輸出什么呢?當然是7啊,雖然我數學不好,但是10以內的數數,我還是no problem的,但是當我們再次執行strlen命令,你會發現一個奇怪的現象:

localhost:6379> strlen codebear
(integer) 11

納尼,為什么是11,是不是我們的打開方式不對,要不再試下?不用了,就算再試上三生三世,你看到的輸出還是11,這是為什么呢?這就牽扯到二進制安全問題了,

二進制安全

所謂的二進制安全就是只會嚴格的按照二進制的資料存取,不會妄圖以某種特殊格式決議資料,Redis就是二進制安全的,它存取的永遠是二進制資料,也可以說存取的永遠是位元組陣列,

我們來get下codebear康康:

get codebear
"hello\xe4\xb8\xad\xe5\x9b\xbd"

你會發現好端端的"hello中國",存盤到Redis竟然變成這樣了,因為我們的Xshell客戶端使用的是UTF-8,在UTF-8下,一個中文通常是三個位元組,兩個中文就是6個位元組,所以在Redis內部"hello中國"占了5+6=11個位元組,

如果你還不信,我們把Xshell的編碼改成GBK看看,在GBK的世界里,一個中文通常占兩個位元組,所以:

localhost:6379> set codebeargbk 中國
OK
localhost:6379> get codebeargbk
"\xd6\xd0\xb9\xfa"
localhost:6379> strlen codebeargbk
(integer) 4

所以說,醒醒吧,小伙計,在Redis里面是不可能存中文的,我們之所以在程式里面可以輕輕松松的拿到中文,只是因為API做了解碼的操作,

沒想到一個String還牽出二進制安全的問題,看來真是不能小瞧任何一個知識點啊,這也就是常說的搜索地獄,當你查找一個問題,發現這個問題的答案又出現了一個你不懂的東西,于是你又開始看那個你不懂的東西,然后又冒出另外一個你不懂的概念,于是...說多了都是淚啊,

我們經常用Redis做快取,用到的就是set get這兩個命令了,我們還可以用Redis做秒殺系統,在絕大部分情況下,用的也是String這個資料型別,讓我們繼續往下看:

localhost:6379> set codebearint 5
OK
localhost:6379> incr codebearint 
(integer) 6

也許你沒用過incr命令,但是可以從結果和命令名稱猜出incr這個命令是干嘛的把,沒錯,就是自增,既然有自增,還可以做自減:

localhost:6379> decr codebearint 
(integer) 5

剛才是6,呼叫了decr 命令后,又變成5了,

好了,又有一個問題,String是字串啊,它怎么可以做加法或減法的操作?

我們用type命令來檢查下codebearint 這個key的型別是什么:

localhost:6379> type  codebearint
string

沒錯,是如假包換的String型別啊,

我們再來看一個東西:

localhost:6379> object encoding codebear
"raw"
localhost:6379> object encoding codebearint
"int"

原來在Redis的內部還會為這個key打上一個標記,來標記它是什么型別的(這個型別可和Redis的五種資料型別不一樣哦),

bitmap

有這么一個需求:統計指定用戶一年之中登錄的天數?這還不簡單,我建個登錄表,不就可以了嗎?沒錯,確實可以,但是這代價是不是有點高,如果有100萬個用戶,那么這登錄表要有多大啊,這個時候,bitmap就橫空出世了,它簡直是解決此類問題的神器,

我們先來看看什么是bitmap,說穿了,就是二進制陣列,我們來畫一張圖說明下:

圖片.png

這就是bitmap了,由許許多多的小格子組成,格子里面只能放1或者0,說的專業點,那一個個小格子就是一個個bit,
String就很好的支持了bitmap,提供了一系列bitmap的命令,讓我們來試試:

setbit codebear 3 1
(integer) 0
localhost:6379> get codebear
"\x10"

這是什么意思呢,就是說現在有8個小格子,第四個格子里面放的是1(索引從0開始嘛),其他都是0,就像這樣的:
圖片.png

讓我們計算下,大小應該是多少,1 2 4 8 16 ,沒錯,用十進制表示是16,而我們get codebear輸出的是“\x10”,“\x”代表是十六進制,也就是16進制的10,16進制的10就是十進制的16了,

我們再用下strlen命令看下:

localhost:6379> strlen codebear
(integer) 1

看來只占據了一個位元組,讓我們繼續:

localhost:6379> setbit codebear 5 1
(integer) 0

bitmap就變成了下面這個醬紫:
圖片.png

大小用十進制表示就是20,

我們繼續看下strlen:

localhost:6379> strlen codebear
(integer) 1

還是只占據了一個位元組,我們明明已經存盤了兩個資料了,是不是非常神奇,

讓我們繼續:

localhost:6379> setbit codebear 15 1
(integer) 0
localhost:6379> strlen codebear
(integer) 2

從這里可以看出bitmap是可以擴展的,由于現在我在第16個格子里面放了1,所以bitmap擴展了,現在strlen是2,
那么我想知道現在16個格子里面有多少格子是1的,怎么辦呢?用bitcount命令:

localhost:6379> bitcount codebear
(integer) 3

到了這一步,是不是豁然開朗了,用bitmap可以輕松統計指定用戶一年之中登錄的天數,
我們假設codebear第一天登錄過,第二天登錄過,最后一天登錄過:

localhost:6379> setbit codebear 0 1
(integer) 0
localhost:6379> setbit codebear 1 1
(integer) 0
localhost:6379> setbit codebear 364 1
(integer) 0
localhost:6379> bitcount codebear 
(integer) 3

繼續用strlen來看看,記錄了全年登錄過的日子占據了多少位元組:

localhost:6379> strlen codebear
(integer) 46

僅僅46個位元組,就算每年都登錄,也只占用46個位元組,我不知道這樣的資料存在資料庫應該是多大的,但是我想遠遠不止46個位元組把,如果有100萬個用戶,也就不到50M,哪怕這100萬個用戶天天登錄占據的位元組也是這些,

我們再把上面的需求改下:統計指定用戶在任意時間視窗內登錄的天數?

bitcount命令后面還可以帶兩個引數,即 開始 和 結束:

localhost:6379> bitcount codebear 0 2
(integer) 2

我們還可以把第二個引數寫成-1,代表直到最后一位,即:

localhost:6379> bitcount codebear 0 -1
(integer) 3

bitmap的強大遠遠不止這些,我們再來康康第二個需求:統計任意日期內,所有用戶登錄情況:

  1. 統計任意一天內登錄過的用戶
  2. 統計任意幾天內登錄過的用戶
  3. 統計任意幾天內每天都登錄的用戶

腦闊疼啊,這特么的是人干的事情嗎?別急,這一切都可以用bitmap來實作,

第一個需求,很好實作,假設用戶codebear的userId是5,用戶小強的userId是10,我們可以建立一個key為日期的bitmap,其中第四個、第九個小格子是1,代表userId是5、userId是1的用戶在這一天登錄過,然后bitcount下就萬事大吉,如下所示:

localhost:6379> setbit 20200301 4 1
(integer) 0
localhost:6379> setbit 20200301 9 1
(integer) 0
localhost:6379> bitcount 20200301
(integer) 2

要實作下面兩個需求,得用新的命令了,直接看結果吧:

localhost:6379> setbit 20200229 9 1
(integer) 1
localhost:6379> bitop and andResult 20200301 20200229
(integer) 2
localhost:6379> bitcount andResult
(integer) 1
localhost:6379> bitop or orResult 20200301 20200229
(integer) 2
localhost:6379> bitcount orResult
(integer) 2

下面來解釋下,首先又創建了一個key為20200229的bitmap,其中第10個小格子為1,代表用戶Id為10的用戶在20200229這一天登錄過,接下來對key為20200301和20200229的bitmap做與運算,結果也是一個bitmap,并且把結果放入了andResult這個key中,下面就是熟悉的bitcount命令了,康康有多少個小格子為1的,結果是1,也就是這兩天,每天都登錄的用戶有一個,

既然有與運算,那么就有或運算,下面就是或運算的命令,求出了這兩天有兩位用戶登錄,

這樣后面兩個需求就輕松搞定了,

還有大名鼎鼎的布隆過濾器也是用bitmap實作的,關于布隆過濾器在以前的博客也介紹過,

看了那么多的例子,大家有沒有發現一個問題,所有的運算都在Redis內部完成,對于這種情況,有一個很高大上的名詞:計算向資料移動,與之相對的,把資料從某個地方取出來,然后在外部計算,就叫資料向計算移動,

list

Redis的list底層是一個雙向鏈表,我們先來康康幾個命令:

localhost:6379> lpush codebear a b c d e
(integer) 5
localhost:6379> lrange codebear 0 -1
1) "e"
2) "d"
3) "c"
4) "b"
5) "a"

push,我懂,推嘛,但是前面+個l是什么意思呢,前面的l代表左邊,lpush就是在左邊推,這樣第一個推進去的,就是在最右邊,lrange是從左開始拿出指定索引范圍內的資料,后面的-1就是代表拿到最后一個為止,

既然可以在左邊推,那么必須可以在右推啊,我們康康:

localhost:6379> rpush codebear z
(integer) 6
localhost:6379> lrange codebear 0 -1
1) "e"
2) "d"
3) "c"
4) "b"
5) "a"
6) "z"

還有兩個彈出命令也很常用,我們來使用下:

localhost:6379> lpop codebear
"e"
localhost:6379> lrange codebear 0 -1
1) "d"
2) "c"
3) "b"
4) "a"
5) "z"
localhost:6379> rpop codebear
"z"
localhost:6379> lrange codebear 0 -1
1) "d"
2) "c"
3) "b"
4) "a"

lpop是彈出左邊第一個元素,rpop就是彈出右邊第一個元素,

如果我們使用lpush,rpop或者rpush,lpop這樣的組合,就是先進先出,就是佇列了;如果我們使用lpush,lpop或者rpush,rpop這樣的組合,就是先進后出,就是堆疊了,所以Redis還可以作為訊息佇列來使用,用到的就是list這個資料型別了,

相信大家一定都玩過論壇,后面發帖的,帖子通常在前面,為了性能,我們可以把帖子的資料放在Redis中的list里面,但是總不能無限往list里面扔資料吧,一般前面幾頁的帖子翻看的人會多一些,再往后面的帖子就很少有人看了,所以我們可以把前面幾頁的帖子資料放在list中,然后設定一個規則,定時去list刪資料,我們就可以用到list的ltrim嗎,命令:

localhost:6379> ltrim codebear 1 -1
OK
localhost:6379> lrange codebear 0 -1
1) "c"
2) "b"
3) "a"

這個ltrim有點奇怪,它是保留索引范圍之內的資料,洗掉索引范圍之外的資料,現在給定的第一個引數是1,第二個引數是-1,就是要保留從索引為1到結束的資料,所以索引為0的資料被洗掉了,

hash

現在有一個產品詳情頁,里面有產品介紹,有價格,有溫馨提示,有瀏覽數,有購買人數等等一堆資訊,當然我們可以把整個物件都用String來存盤,但是可能有一些地方只需要產品介紹,盡管是這樣,我們還是必須得把整個物件都拿出來,是不是有點不太劃算呢?hash就可以解決這樣的問題:

localhost:6379> hset codebear name codebear
(integer) 1
localhost:6379> hset codebear age 18
(integer) 1
localhost:6379> hset codebear sex true
(integer) 1
localhost:6379> hset codebear address suzhou
(integer) 1
localhost:6379> hget codebear address
"suzhou"

如果我們是存盤整個物件,現在想修改下age,怎么辦?要把整個物件全部拿出來,然后再賦值,最后又得放回去,但是現在:

localhost:6379> hincrby codebear age 2 
(integer) 20
localhost:6379> hget codebear age
"20"

set

set是一種無序,且去重的資料結構,我們可以用它去重,比如我現在要存盤所有商品的Id,就可以用set來實作,有什么場景需要存盤所有商品的Id呢?防止快取穿透,當然防止快取穿透有很多實作方案,set方案只是其中的一種,我們來康康它的基本用法:

localhost:6379> sadd codebear 6 1 2 3 3 8 6
(integer) 5
localhost:6379> smembers codebear 
1) "1"
2) "2"
3) "3"
4) "6"
5) "8"

可以很清楚的看到我們存進去的資料被去重了,而且資料被打亂了,

我們再來看看srandmember這個命令有什么用?

localhost:6379> srandmember codebear 2
1) "6"
2) "3"
localhost:6379> srandmember codebear 2
1) "6"
2) "2"
localhost:6379> srandmember codebear 2
1) "6"
2) "3"
localhost:6379> srandmember codebear 2
1) "6"
2) "2"
localhost:6379> srandmember codebear 2
1) "8"
2) "3"

srandmember 后面可以帶引數,后面跟著2,就代表隨機取出兩個不重復的元素,如果想取出兩個可以重復的元素,怎么辦呢?

localhost:6379> srandmember codebear -2
1) "6"
2) "6"

如果后面跟著負數,就代表取出的元素可以是重復的,

如果后面跟的數字大于set元素的個數呢?

localhost:6379> srandmember codebear 100
1) "1"
2) "2"
3) "3"
4) "6"
5) "8"
localhost:6379> srandmember codebear -10
 1) "8"
 2) "1"
 3) "1"
 4) "1"
 5) "6"
 6) "1"
 7) "1"
 8) "2"
 9) "6"
10) "8"

如果是正數的話,最多把set中所有的元素都回傳出來,因為正數是不重復的,再多回傳一個出來,就重復了,如果是負數,那么不影響,后面跟著幾,就回傳多少個元素出來,

我們做抽獎系統,就可以用到這個命令了,如果可以重復中獎,后面帶著負數,如果不能重復中獎,后面帶著正數,

set還可以計算差集、并集、交集:

localhost:6379> sadd codebear1 a b c
(integer) 3
localhost:6379> sadd codebear2 a z y
(integer) 3
localhost:6379> sunion codebear1 codebear2
1) "a"
2) "c"
3) "b"
4) "y"
5) "z"
localhost:6379> sdiff codebear1 codebear2
1) "b"
2) "c"
localhost:6379> sinter codebear1 codebear2
1) "a"

上面的命令就不過多解釋了,這有什么用呢,我們可以利用它來做一個“騙取融資”的推薦系統:你們的共同好友是誰,你們都在玩的游戲是哪個,你可能認識的人,

zset

set是無序的,而zset是有序的,其中每個元素都有一個score的概念,score越小排在越前面,還是先來康康它的基本使用把:

localhost:6379> zadd codebear 1 hello 3 world 2 tree
(integer) 3
localhost:6379> zrange codebear 0 -1 withscores
1) "hello"
2) "1"
3) "tree"
4) "2"
5) "world"
6) "3"
localhost:6379> zrange codebear 0 -1
1) "hello"
2) "tree"
3) "world"

現在我們就創建了一個key為codebear 的zset,往里面添加了三個元素:hello ,world ,tree,score分別為1,3,2,后面用zrange取出結果,發現已經按照score的大小排好序了,如果后面跟著withscores,就會把score一起取出來,

如果我們想看看tree排在第幾位,我們可以用zrank命令:

localhost:6379> zrank codebear tree
(integer) 1

因為是從0開始的,所以結果是1,

如果我們想查詢tree的score是多少:

localhost:6379> zscore codebear tree
"2"

如果我們想取出從大到小的前兩個,怎么辦:

localhost:6379> zrange codebear -2 -1
1) "tree"
2) "world"

但是這樣的結果是有些錯誤的,從大到小的前兩個,第一個元素是world,又該如何呢:

localhost:6379> zrevrange codebear 0 1
1) "world"
2) "tree"

像排行榜,熱點資料,延遲任務佇列都可以用zset來實作,其中延遲任務佇列在我以前的博客有介紹過,

談到szet,可能還會引出一個問題,Redis中的zset是用什么實作的?跳表,

關于跳表,在這里就不展開了,為什么要用跳表實作呢,是因為跳表的特性:
讀寫均衡,

Redis為什么那么快

這是一個經典的面試題,幾乎面試談到Redis,80%都會問這問題,為什么Redis那么快呢?主要有以下原因:

  1. 編程語言:Redis是用C語言撰寫的,更接近底層,可以直接呼叫os函式,
  2. 基于記憶體:因為Redis的資料都是放在記憶體的,所以更快,如果放在硬碟,性能要看兩個指標:尋址(轉速),吞吐,尋址是毫秒級別的,一般來說,吞吐在500M左右,就算服務器性能再牛逼,也不會有幾個G的吞吐,而放在記憶體,是納秒級別的,
  3. 單執行緒:因為Redis是單執行緒的,所以避免了執行緒切換的消耗,也不會有競爭,所以更快,
  4. 網路模型:由于Redis的網路模型是epoll,是多路復用的網路模型,(關于epoll后面會展開討論)
  5. Redis資料結構的優化:Redis中提供了5種資料型別,其中zset用跳表做了優化,而且整個Redis其實也都用hash做了優化,使其的時間成本是O(1),查找更快,
  6. Redis6.0推出了I/O Threads,所以更快,(關于I/O Threads后面會展開討論)

Redis有什么缺點

這就是一個開放式的問題了,有很多答案,比如:

  1. 因為Redis是單執行緒的,所以無法發揮出多核CPU的優勢,
  2. 因為Redis是單執行緒的,一旦執行了一個復雜的命令,后面所有的命令都被堵在門外了,
  3. 無法做到對hash中的某一項添加過期時間,

Redis為什么可以保證原子性

因為Redis是單執行緒的,所以同時只能處理一個讀寫請求,所以可以保證原子性,

Redis是單執行緒的,到底該如何解釋

我們一直在強調Redis是單執行緒的,Redis是單執行緒的,但是Redis真的完全是單執行緒的嗎?其實不然,我們說的Redis是單執行緒的,只是Redis的讀寫是單執行緒的,也就是work thread只有一個,

什么是I/O Threads

I/O Threads是Redis 6.0推出的新特性,在以前Redis從socket拿到請求、處理、把結果寫到socket是串行化的,即:
圖片.png

而Redis6.0推出了I/O Threads后:
圖片.png
可以看到I/O Thread有多個,I/O Thread負責從socket讀資料和寫資料到socket,work thread在處理資料的同時,其他I/O Thread可以再從socke讀資料,先準備好,等work thread忙完手中的事情了,立馬可以處理下個請求,
但是work thread只有一個,這點要牢記,

什么是epoll

epoll是一種多路復用IO模型,在說epoll之前,不得不說下傳統的IO模型,傳統的IO模型是同步阻塞的,什么意思呢?就是服務端建立的socket會死死的等待客戶端的連接,等客戶端連接上去了,又會死死的等待客戶端的寫請求,一個服務端只能為一個客戶端服務,

后來,程式員們發現可以用多執行緒來解決這個問題:

  1. 當第一個客戶端連接到服務端后,服務端會啟動第一個執行緒,以后第一個客戶端和服務端的互動就在第一個執行緒中進行,
  2. 當第二個客戶端連接到服務端后,服務端又會啟動第二個執行緒,以后第二個客戶端和服務端的互動就在第二個執行緒中進行,
  3. 當第三個客戶端連接到服務端后,服務端又會啟動第三個執行緒,以后第三個客戶端和服務端的互動就在第三個執行緒中進行,

看起來,很美好,一個服務端可以為N個客戶端服務,但是總不能無限開執行緒把 ,在Java中,執行緒是有自己的獨立堆疊的,一個執行緒至少消耗1M,而且無限開執行緒,CPU也會受不鳥啊,

雖然后面還經歷了好幾個時代才慢慢來到了epoll的時代,但是我作為一個curd boy,api boy就不去研究的那么深了,現在我們跨過中間的時代,直接來到epoll的時代吧,

我們先來認識下epoll的方法,在linux中,可以用man來看看OS函式:

man epoll

在介紹中有這么一段話:

       *  epoll_create(2) creates a new epoll instance and returns a file descriptor referring to that instance.  (The more recent epoll_create1(2) extends
          the functionality of epoll_create(2).)

       *  Interest in particular file descriptors is then registered via epoll_ctl(2).  The set of  file  descriptors  currently  registered  on  an  epoll
          instance is sometimes called an epoll set.

       *  epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.

雖然我英語實在是爛,但是借助翻譯,還是可以勉強看懂一些,大概的意思是:

  1. epoll_create創建了一個epoll示例,并且會回傳一個檔案描述符,
  2. epoll_ctl用于注冊感興趣的事件,
  3. epoll_wait用于等待IO事件,如果當前沒有感興趣的IO事件,則阻塞,言外之意就是如果發生了感興趣的事件,這個方法便會回傳,

下面還給出了一個demo,我們來試著看下:

        epollfd = epoll_create1(0);//創建一個epoll實體,回傳一個 epoll檔案描述符
           if (epollfd == -1) {
               perror("epoll_create1");
               exit(EXIT_FAILURE);
           }

           ev.events = EPOLLIN;
           ev.data.fd = listen_sock;
            // 注冊感興趣的事件
            // 第一個引數為epoll檔案描述符
            // 第二個引數為動作,現在是要添加感興趣的事件
            // 第三個引數為被監聽的檔案描述符
            // 第四個引數告訴內核需要監聽什么事件
            // 現在監聽的事件是EPOLLIN,表示對應的檔案描述符上有可讀資料
           if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
               perror("epoll_ctl: listen_sock");
               exit(EXIT_FAILURE);
           }
           // 一個死回圈
           for (;;) {
               //  等待IO事件的發生
               //  第一個引數是epoll檔案描述符
               //  第二個引數是發生的事件集合
               //  第三個引數不重要
               //  第四個引數是等待時間,-1為永遠等待
               //  回傳值是發生的事件的個數
               nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
               if (nfds == -1) {
                   perror("epoll_wait");
                   exit(EXIT_FAILURE);
               }
               // 回圈 
               for (n = 0; n < nfds; ++n) {
                    // 如果發生的事件對應的檔案描述符是listen_sock
                   if (events[n].data.fd == listen_sock) {
                       // 建立連接 
                       conn_sock = accept(listen_sock,
                                          (struct sockaddr *) &addr, &addrlen);
                       if (conn_sock == -1) {
                           perror("accept");
                           exit(EXIT_FAILURE);
                       }
                       setnonblocking(conn_sock);// 設定非阻塞
                       ev.events = EPOLLIN | EPOLLET;// 設定感興趣的事件
                       ev.data.fd = conn_sock;
                       // 添加感興趣的事件,為 EPOLLIN或者EPOLLET
                       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                                   &ev) == -1) {
                           perror("epoll_ctl: conn_sock");
                           exit(EXIT_FAILURE);
                       }
                   } else {
                       do_use_fd(events[n].data.fd);
                   }
               }
           }

由于本人沒有學習過C語言,有些注釋的不對的地方請多擔待,但是自認為大概就是這么個意思,

如果學過Java中的NIO的話,看到上面的代碼應該會覺得似曾相似,

epoll到底是個什么鬼呢,說的簡單點,就是告訴內核我對哪些事件感興趣,內核就會幫你監聽,當發生了你感興趣的事件后,內核就會主動通知你,

這有什么優點呢:

  1. 減少用戶態和內核態的切換,
  2. 基于事件就緒通知方式:內核主動通知你,不牢你費心去輪詢、去判斷,
  3. 檔案描述符幾乎沒有上限:你想和幾個客戶端互動就和幾個客戶端互動,一個執行緒可以監聽N個客戶端,并且完成互動,

epoll函式是基于OS的,在windows里面,沒有epoll這東西,

好了,關于epoll的介紹就到這里了,又出現了三個新名詞:用戶態、內核態、檔案描述符,就先不解釋了,以后寫NIO的博客再說吧,那時候,會更詳細的介紹epoll,

Redis的過期策略

一般來說,常用的過期策略有三種:

  1. 定時洗掉:需要給每個key添加一個定時器,一到期就移除資料,優點是非常精確,缺點是消耗比較大,
  2. 定期洗掉:每隔一段時間,就會掃描一定數量的key,發現過期的,就移除資料,優點是消耗比較小,缺點是過期的key無法被及時移除,
  3. 懶洗掉:使用某個key的時候,先判斷下這個key是否過期,過期則洗掉,優點是消耗小,缺點是過期的key無法被及時移除,只有使用到了,才會被移除,

Redis使用的是定期洗掉+懶洗掉的策略,

管道

如果我們有好多命令要交給Redis,第一個方案是一條一條發,缺點不言而喻:每條命令都需要經過網路,性能比較低下,第二個方案就是用管道,
在介紹管道之前,先要演示一個東西:

[root@localhost ~]# nc localhost 6379
set codebear hello
+OK
get codebear
$5
hello

我們往Redis發送命令,不一定必須要用Redis的客戶端,只要連接上Redis服務器的埠就可以了,至于get codebear命令后面輸出了$5是什么意思,就不在這里討論了,

管道到底怎么使用呢,有了上面的基礎,其實也很簡單:

[root@localhost ~]# echo -e "set codebear hello1234 \n incr inttest \n set haha haha" | nc localhost 6379
+OK
:1
+OK

命令與命令之間用\n分割,然后通過nc發送給Redis,

我們再來康康是否成功了:

[root@localhost ~]# nc localhost 6379
get inttest
$1
1
get codebear
$9
hello1234
get haha
$4
haha

需要注意的,雖然多條命令是一起發送出去的,但是整體不具有原子性,
各大操作Redis的組件也提供了管道發送的方法,如果下次在專案中需要發送多個命令不妨試下,

發布訂閱

當我們有個訊息需要以廣播的形式推送給各個系統,除了采用訊息佇列的方式,還可以采用發布與訂閱的方式,在Redis中就提供了發布訂閱的功能,我們來看下如何使用,

首先,我們要創建一個訂閱者,訂閱名稱為hello的channel:

localhost:6379> subscribe hello
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "hello"
3) (integer) 1

然后,要創建一個發布者,往名稱為hello的channel發送訊息:

localhost:6379> publish hello goodmorning
(integer) 1

最后,再回到訂閱者,發現接收到了訊息:

1) "message"
2) "hello"
3) "goodmorning"

但是需要注意,如果先發布訊息,訂閱者再去訂閱,是收不到歷史訊息的,

是不是特別簡單,在我還不知道有ZooKeeper的時候,我覺得可以用Redis的發布訂閱功能來做配置中心,

記憶體淘汰

如果Redis記憶體滿了,再也容納不下新資料了,就會觸發Redis的記憶體淘汰策略,在redis.conf有一個配置,就是用來配置具體的記憶體淘汰策略的:

maxmemory-policy volatile-lru

它有好幾個配置,在講具體的配置前,要先說兩個名詞,如果這兩個名詞不了解的話,那么每個配置的含義真是只能死記硬背了,

  • LRU:最少使用淘汰演算法:如果這個key很少被使用,被淘汰
  • LFU:最近不使用淘汰演算法:如果這個key最近沒有被使用過,被淘汰

下面就是具體的配置了,我們一一來看:

  • volatile-lru:在設定了過期時間的key中,移除最近最少使用的key
  • allkeys-lru:在所有的key中,移除最近最少使用的key
  • volatile-lfu:在設定了過期時間的key中,移除最近不使用的key
  • allkeys-lfu:在所有的key中,移除最近不使用的key
  • volatile-random:在設定了過期時間的key中,隨機移除一個key
  • allkeys-random:隨機移除一個key
  • volatile-ttl:在設定了過期時間的鍵空間中,具有更早過期時間的key優先移除
  • noeviction:神馬也不干,直接拋出例外

在生產環境中,到底應該使用哪個配置呢?
可以說網上的答案千差萬別,但是可以統一的是一般不會選擇noeviction,所以這個問題還是用萬金油的答案,一個完全正確的廢話答案:看場景,

本篇博客到這里就結束了,還有很多東西沒有提到,先拋開主從、集群,光單機版的Redis就還有持久化、事務、協議、modules、GEO、hyperLogLog等等,還有Redis的延伸問題——快取擊穿、快取雪崩、快取穿透等等問題,都沒有提到,等以后再和大家嘮嘮嗑把,

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/16413.html

標籤:NoSQL

上一篇:MySQL實驗 子查詢優化雙引數limit

下一篇:快取資料庫

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more