Redis學習之數據結構與持久化(一)

API的使用

通用命令

1)keysios

遍歷全部key。redis

可使用keys he*遍歷全部以he開頭的鍵。數據庫

使用方式:熱備從節點,scan。編程

2)dbsize緩存

計算key的總數。安全

3)existsbash

檢查key是否存在。服務器

4)del key網絡

刪除指定的key-value。數據結構

5)expire key seconds

設置key的過時時間。

6)ttl key

查看key剩餘的過時時間。

7)persist key

去掉key的過時時間。

8)type key

查看key的類型。

速度快的緣由

1)純內存。

2)非阻塞IO。

3)避免線程切換和靜態消耗。

數據結構和內部編碼

String

Redis String類型是能夠與Redis鍵關聯的最簡單的值類型。它是Memcached中惟一的數據類型,所以新手在Redis中使用它也很天然。

因爲Redis鍵是字符串,當咱們使用字符串類型做爲值時,咱們將字符串映射到另外一個字符串。字符串數據類型對於許多用例頗有用,例如緩存HTML片斷或頁面。

set命令

set:無論key是否存在,都設置。

setnx:key不存在,才設置。

set key value xx:key存在,才設置。

mget、mset命令

mget key1 key2 key3,....:批量獲取key,原子操做。

mset key1 value1 key2 value2 key3 value3:批量設置key-value。

HASH

其實是一個map->map的結構,能夠很方便的存儲Java類。Redis哈希看起來正是人們可能指望看到一個「哈希」,使用字段值對:

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
複製代碼

相關命令:

hget、hset、hdel、hesists、hlen、hmget、hmset。

List

所述LPUSH命令將一個新元素到一個列表,在左側(在頭部),而RPUSH命令將一個新元素到一個列表,在右側(在尾部)。最後, LRANGE命令從列表中提取元素範圍:

> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
複製代碼

請注意,LRANGE須要兩個索引,即要返回的範圍的第一個和最後一個元素。兩個索引均可以是負數,告訴Redis從結尾開始計數:因此-1是最後一個元素,-2是列表的倒數第二個元素,依此類推。

正如您所見,RPUSH附加了列表右側的元素,而最後的LPUSH附加了左側的元素。

Redis列表中定義的一個重要操做是彈出元素的能力。彈出元素是從列表中檢索元素並同時從列表中刪除元素的操做。您能夠從左側和右側彈出元素,相似於如何在列表的兩側推送元素:

> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
複製代碼

Capped lists(上限列表)

在許多用例中,咱們只想使用列表來存儲最新的項目,不管它們是什麼:社交網絡更新,日誌或其餘任何內容。

Redis容許咱們使用列表做爲上限集合,只記住最新的N項並使用LTRIM命令丟棄全部最舊的項。

LTRIM命令相似於LRANGE,可是**,而不是顯示元件的指定範圍**它設置在該範圍做爲新的列表值。超出給定範圍以外的全部元素。

一個例子將使它更清楚:

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
複製代碼

上面的LTRIM命令告訴Redis只從索引0到2中獲取列表元素,其餘全部內容都將被丟棄。這容許一個很是簡單但有用的模式:一塊兒執行List推操做+ List修剪操做以添加新元素並丟棄超出限制的元素:

LPUSH mylist <some element>
LTRIM mylist 0 999
複製代碼

上面的組合添加了一個新元素,而且只將1000個最新元素放入列表中。使用LRANGE,您能夠訪問頂級項目,而無需記住很是舊的數據。

注意:雖然LRANGE在技術上是一個O(N)命令,可是訪問列表的頭部或尾部的小範圍是一個恆定時間操做。

Set

Redis集是字符串的無序集合。該 SADD命令添加新的元素到set。對於集合執行許多其餘操做也是可能的,例如測試給定元素是否已存在,執行多個集合之間的交集,並集或差別等等。

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
複製代碼

在這裏,我已經爲個人集合添加了三個元素,並告訴Redis返回全部元素。正如您所看到的那樣,它們沒有排序 - Redis能夠在每次調用時以任意順序返回元素,由於與用戶沒有關於元素排序的合同。

Redis有命令來測試會員資格。例如,檢查元素是否存在:

> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
複製代碼

Sorted sets

排序集是一種數據類型,相似於Set和Hash之間的混合。與集合同樣,有序集合由惟一的,非重複的字符串元素組成,所以在某種意義上,有序集合也是一個集合。

可是,雖然內部集合中的元素沒有排序,可是有序集合中的每一個元素都與浮點值相關聯,稱爲分數 (這就是爲何類型也相似於散列,由於每一個元素都映射到一個值)。

此外,排序集合中的元素按順序排列(所以它們不是根據請求排序的,順序是用於表示排序集合的數據結構的特性)。它們按照如下規則訂購:

  • 若是A和B是兩個具備不一樣分數的元素,若是A.score> B.score,那麼A> B。
  • 若是A和B具備徹底相同的分數,若是A字符串按字典順序大於B字符串,則A> B。A和B字符串不能相等,由於有序集只有惟一元素。

讓咱們從一個簡單的例子開始,添加一些選定的黑客名稱做爲有序集合元素,其出生年份爲「得分」。

> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer) 1
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
複製代碼

正如您所看到的,ZADD與SADD相似,可是須要一個額外的參數(放在要添加的元素以前),即分數。 ZADD也是可變參數,所以您能夠自由指定多個得分 - 值對,即便在上面的示例中未使用它。

對於排序集,返回按出生年份排序的黑客列表是微不足道的,由於實際上它們已經排序了。

實現說明:排序集是經過包含跳過列表和散列表的雙端口數據結構實現的,所以每次添加元素時,Redis都會執行O(log(N))操做。這很好,可是當咱們要求排序的元素時,Redis根本不須要作任何工做,它已經所有排序了:

> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"
複製代碼

若是我想以相反的方式訂購它們,最小到最老的怎麼辦?使用ZREVRANGE而不是ZRANGE:

> zrevrange hackers 0 -1
1) "Linus Torvalds"
2) "Yukihiro Matsumoto"
3) "Sophie Wilson"
4) "Richard Stallman"
5) "Anita Borg"
6) "Alan Kay"
7) "Claude Shannon"
8) "Hedy Lamarr"
9) "Alan Turing"
複製代碼

使用WITHSCORES參數也能夠返回分數:

> zrange hackers 0 -1 withscores
1) "Alan Turing"
2) "1912"
3) "Hedy Lamarr"
4) "1914"
5) "Claude Shannon"
6) "1916"
7) "Alan Kay"
8) "1940"
9) "Anita Borg"
10) "1949"
11) "Richard Stallman"
12) "1953"
13) "Sophie Wilson"
14) "1957"
15) "Yukihiro Matsumoto"
16) "1965"
17) "Linus Torvalds"
18) "1969"
複製代碼

在切換到下一個主題以前,只須要關於排序集的最後一點。排序集的分數能夠隨時更新。僅針對已經包含在已排序集合中的元素調用ZADD將使用O(log(N))時間複雜度更新其得分(和位置)。所以,當有大量更新時,排序集合是合適的。

因爲這個特性,常見的用例是排行榜。典型的應用程序是一個Facebook遊戲,在這個遊戲中,您能夠結合使用高分進行排序的用戶以及獲取排名操做,以顯示前N個用戶以及排行榜中的用戶排名(例如,「你是這裏#4932的最佳成績「)。

配置建議

maxToTal

理論值=命令平均執行時間*業務總QPS數。

maxIdle&&minIdle(最大空閒數與最少空閒數)

1)maxIdle=maxTotal,減小建立新鏈接的開銷。

2)預熱minIdle,減小第一次啓動後的新鏈接的開銷。

redis經常使用功能

慢查詢

生命週期

1)慢查詢發生在第三階段。

2)客戶端超時不必定慢查詢,但慢查詢是客戶端超時的一個可能因素。

兩個配置

1)slowlog-max-len

1:先進先出隊列

2:固定長度

3:保存在內存內

2)slowlog-log-slower-than

慢查詢閾值(單位:微秒)

命令

1)slowlog get [n]:獲取慢查詢隊列。

2)slowlog len:獲取慢查詢隊列長度。

3)slowlog reset:清空慢查詢隊列。

運維經驗

1)slowlog-max-len不要設置太小,一般設置1000左右。

2)slowlog-log-slower-than不要設置過大,默認10ms,一般設置1ms。

3)理解生命週期。

4)按期持久化慢查詢。

Pipeline

Redis是使用客戶端 - 服務器模型和所謂的請求/響應協議的TCP服務器。

這意味着一般經過如下步驟完成請求:

  • 客戶端向服務器發送查詢,並一般以阻塞方式從套接字讀取服務器響應。
  • 服務器處理該命令並將響應發送回客戶端。

例如,四個命令序列是這樣的:

  • 客戶: INCR X.
  • 服務器: 1
  • 客戶: INCR X.
  • 服務器: 2
  • 客戶: INCR X.
  • 服務器: 3
  • 客戶: INCR X.
  • 服務器: 4

客戶端和服務器經過網絡連接鏈接。這樣的連接能夠很是快(環回接口)或很是慢(在因特網上創建的鏈接,在兩個主機之間有許多跳)。不管網絡延遲是什麼,數據包都有時間從客戶端傳輸到服務器,而後從服務器返回到客戶端以進行回覆。

此時間稱爲RTT(Round Trip Time)。當客戶端須要連續執行許多請求時(例如,將多個元素添加到同一列表或使用多個鍵填充數據庫),很容易看出這會如何影響性能。例如,若是RTT時間是250毫秒(在因特網上的鏈路很是慢的狀況下),即便服務器可以每秒處理100k個請求,咱們也可以以每秒最多四個請求進行處理。

若是使用的接口是環回接口,則RTT要短得多(例如個人主機報告0,044毫秒,ping 127.0.0.1),但若是你須要連續執行屢次寫入,它仍然不少。

幸運的是,有一種方法能夠改進這個用例。

Redis Pipelining

能夠實現請求/響應服務器,以便即便客戶端還沒有讀取舊響應,它也可以處理新請求。這樣就能夠將多個命令發送到服務器而無需等待回覆,最後只需一步便可讀取回復。

這被稱爲流水線技術,而且是幾十年來普遍使用的技術。例如,許多POP3協議實現已經支持此功能,大大加快了從服務器下載新電子郵件的過程。

Redis從很早就開始支持流水線操做,所以不管您運行什麼版本,均可以使用Redis進行流水線操做。這是使用原始netcat實用程序的示例:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
複製代碼

重要說明:當客戶端使用流水線發送命令時,服務器將被強制使用內存對回覆進行排隊。所以,若是您須要使用流水線發送大量命令,最好將它們做爲具備合理數量的批次發送,例如10k命令,讀取回復,而後再次發送另外10k命令,依此類推。速度將幾乎相同,但使用的額外內存將最大爲此10k命令的回覆排隊所需的數量。

mset與pipeline對比

mset:原子操做。

pipeline:非原子操做。

使用建議

1)注意每次pipeline攜帶數據量。

2)pipeline每次只能做用在一個Redis節點上。

發佈訂閱

SUBSCRIBE,UNSUBSCRIBE和PUBLISH 實現了發佈/訂閱消息傳遞範例,其中(引用維基百科)發件人(發佈者)沒有被編程爲將其消息發送給特定接收者(訂閱者)。相反,發佈的消息被表徵爲信道,而不知道可能存在什麼(若是有的話)訂戶。訂閱者表達對一個或多個頻道的興趣,而且僅接收感興趣的消息,而不知道有哪些(若是有的話)發佈者。發佈者和訂閱者的這種分離能夠容許更大的可擴展性和更動態的網絡拓撲。

例如,爲了訂閱頻道foo,bar客戶端發出提供頻道名稱的SUBSCRIBE:

SUBSCRIBE foo bar
複製代碼

其餘客戶端發送到這些頻道的消息將由Redis推送到全部訂閱的客戶端。

訂閱一個或多個頻道的客戶端不該發出命令,儘管它能夠訂閱和取消訂閱其餘頻道。對訂閱和取消訂閱操做的回覆以消息的形式發送,以便客戶端能夠只讀取連貫的消息流,其中第一個元素指示消息的類型。訂閱客戶端上下文中容許的命令是SUBSCRIBE,PSUBSCRIBE,UNSUBSCRIBE,PUNSUBSCRIBE, PING和QUIT。

請注意,redis-cli在訂閱模式下不會接受任何命令,而且只能退出模式Ctrl-C。

消息是具備三個元素的Array回覆

第一個元素是消息的類型:

  • subscribe:表示咱們成功訂閱了做爲回覆中第二個元素的通道。第三個參數表示咱們當前訂閱的頻道數。
  • unsubscribe:表示咱們成功取消訂閱做爲回覆中第二個元素的頻道。第三個參數表示咱們當前訂閱的頻道數。當最後一個參數爲零時,咱們再也不訂閱任何通道,而且客戶端能夠發出任何類型的Redis命令,由於咱們在Pub / Sub狀態以外。
  • message:它是由另外一個客戶端發出的PUBLISH命令收到的消息。第二個元素是原始通道的名稱,第三個參數是實際的消息有效負載。

Redis的持久化

Redis提供了不一樣的持久性選項:

  • RDB持久性以指定的時間間隔執行數據集的時間點快照。
  • AOF持久性記錄服務器接收的每一個寫入操做,將在服務器啓動時再次播放,重建原始數據集。使用與Redis協議自己相同的格式以僅追加方式記錄命令。當Redis太大時,Redis可以重寫日誌。
  • 若是您願意,只要服務器正在運行,您就能夠根據須要禁用持久性。
  • 能夠在同一實例中組合AOF和RDB。請注意,在這種狀況下,當Redis從新啓動時,AOF文件將用於重建原始數據集,由於它保證是最完整的。

最重要的是要理解RDB和AOF持久性之間的不一樣權衡。讓咱們從RDB開始:

RDB的優點

  • RDB是Redis數據的一個很是緊湊的單文件時間點表示。RDB文件很是適合備份。例如,您可能但願在最近24小時內每小時歸檔您的RDB文件,而且天天保存RDB快照30天。這使您能夠在發生災難時輕鬆恢復數據集的不一樣版本。
  • RDB很是適合災難恢復,能夠將單個壓縮文件傳輸到遠端數據中心,也能夠傳輸到Amazon S3(多是加密的)。
  • RDB最大限度地提升了Redis的性能,由於Redis父進程爲了堅持不懈而須要作的惟一工做就是分配一個將完成全部其他工做的孩子。父實例永遠不會執行磁盤I/O或相似操做。
  • 與AOF相比,RDB容許使用大數據集更快地重啓。

RDB的缺點

  • 若是您須要在Redis中止工做時(例如斷電後)將數據丟失的可能性降至最低,則RDB並很差。您能夠配置生成RDB的不一樣保存點(例如,在對數據集進行至少五分鐘和100次寫入以後,但您能夠有多個保存點)。可是,您一般每五分鐘或更長時間建立一個RDB快照,所以若是Redis因任何緣由中止工做而沒有正確關閉,您應該準備丟失最新的數據分鐘。
  • RDB常常須要fork()才能使用子進程持久存儲在磁盤上。若是數據集很大,Fork()可能會很是耗時,而且若是數據集很是大且CPU性能不佳,可能會致使Redis中止服務客戶端幾毫秒甚至一秒鐘。AOF也須要fork(),但你能夠調整你想要重寫日誌的頻率而不須要對耐久性進行任何權衡。

AOF優點

  • 使用AOF Redis更持久:您可使用不一樣的fsync策略:no fsync at all, fsync every second, fsync at every query。使用fsync的默認策略,每秒寫入性能仍然很好(使用後臺線程執行fsync,而且當沒有fsync正在進行時,主線程將努力執行寫入。)可是您只能丟失一秒的寫入。
  • AOF日誌是僅附加日誌,所以若是停電,則沒有搜索,也沒有損壞問題。即便因爲某種緣由(磁盤已滿或其餘緣由)日誌以半寫命令結束,redis-check-aof工具也可以輕鬆修復它。
  • 當Redis太大時,Redis可以在後臺自動重寫AOF。重寫是徹底安全的,由於當Redis繼續附加到舊文件時,使用建立當前數據集所需的最小操做集生成一個全新的文件,而且一旦第二個文件準備就緒,Redis會切換兩個並開始附加到新的那一個。
  • AOF以易於理解和解析的格式一個接一個地包含全部操做的日誌。您甚至能夠輕鬆導出AOF文件。例如,即便您使用FLUSHALL命令刷新了全部錯誤,若是在此期間未執行重寫日誌,您仍然能夠保存數據集,只需中止服務器,刪除最新命令,而後從新啓動Redis。

AOF的缺點

  • AOF文件一般比同一數據集的等效RDB文件大。
  • 根據確切的fsync策略,AOF可能比RDB慢。通常來講,fsync設置爲every second性能仍然很是高,而且在fsync禁用的狀況下,即便在高負載下也應該與RDB同樣快。即便在寫入負載很大的狀況下,RDB仍可以提供有關最大延遲的更多保證。
  • 在過去,咱們遇到了特定命令中的罕見錯誤(例如,有一個涉及阻塞命令,如BRPOPLPUSH)致使生成的AOF在從新加載時不會重現徹底相同的數據集。這個錯誤不多見,咱們在測試套件中進行測試,自動建立隨機複雜數據集並從新加載它們以檢查一切正常,但RDB持久性幾乎不可能出現這種錯誤。爲了更清楚地說明這一點:Redis AOF逐步更新現有狀態,如MySQL或MongoDB,而RDB快照一次又一次地建立全部內容,這在概念上更加健壯。可是 —— 1)應該注意的是,每次經過Redis重寫AOF時,都會從數據集中包含的實際數據開始從新建立,與老是附加的AOF文件(或者重寫舊的AOF而不是讀取內存中的數據)相比,對bug的抵抗力更強。2)咱們從未向用戶提供過關於在現實世界中檢測到的AOF損壞的單一報告。

好的,那我該怎麼用?

通常的跡象是,若是您但願必定程度的數據安全性與PostgreSQL爲您提供的數據安全性至關,則應使用兩種持久性方法。

若是你很是關心你的數據,可是在發生災難的狀況下仍然會有幾分鐘的數據丟失,你能夠單獨使用RDB。

有許多用戶單獨使用AOF,但咱們不鼓勵它,由於不時有RDB快照是進行數據庫備份,更快重啓以及AOF引擎中出現錯誤的好主意。

注意:因爲全部這些緣由,咱們可能最終將AOF和RDB統一爲將來的單一持久性模型(長期計劃)。

有關詳細的服務器配置請查閱我以前的博客。

常見的持久化開發運維問題

改善fork

1)優先使用物理機或者高效支持fork操做的虛擬化技術。

2)控制Redis實例最大可用內存:maxmemory。

3)合理配置Linux內存分配策略:vm.overcommit_memory=1。

4)下降fork頻率:例如放寬AOF重寫自動觸發時機,沒必要要的全量複製。

子進程開銷和優化

CPU

開銷:RDB和AOF文件生成,屬於CPU密集型。

優化:不作CPU綁定,不和CPU密集型部署。

內存

開銷:fork內存開銷,copy-on-write。

優化:echo never > /sys/kernel/mm/transparent_hugepage/enabled。

硬盤

開銷:AOF和RDB文件寫入,能夠結合iostat,iotop分析。

優化:no-appendfsync-on-rewrite = yes

AOF阻塞

定位

1)Redis日誌

2)info Persistence

3)硬盤使用狀況

相關文章
相關標籤/搜索