Linux實戰教學筆記46:NoSQL數據庫之redis持久化存儲 (二)

第3章 Redis數據類型詳解

3.1 Redis鍵/值介紹

Redis key值是二進制安全的,這意味着能夠用任何二進制序列做爲key值,從形如「foo」的簡單字符串到一個JPG文件的內容均可以。空字符串也是有效key值。node

關於key的幾條規則:git

  • 太長的鍵值不是個好主意,例如1024字節的鍵值就不是個好主意,不只由於消耗內存,並且在數據中查找這類鍵值的計算成本很高。
  • 過短的鍵值一般也不是好主意,若是你要用「u:1000:pwd」來代替

user:1000:password,這沒有什麼問題,但後者更易閱讀,而且由此增長的空間消耗相對於key object和value object自己來講很小。固然,沒人阻止你必定要用更短的鍵值節省一丁點兒空間。web

  • 最好堅持一種模式。例如:"object-type🆔field"就是個不錯的注意,像這樣「user:1000:password」。或者一個鍵值對的字段名中加上一個點,就想這樣「comment🔢reply.to」。
127.0.0.1:6379> set user:01:passwd 99999
OK
127.0.0.1:6379> get user:01:passwd
"99999"

QQ20171005-140352@2x.png-494.1kB

3.2 數據類型

3.2.1 String字符串類型

這是Redis最簡單的數據類型之一。若是隻使用這種數據類型,那麼redis就是一個持久化的memcached服務器(注:memcache的數據僅保存在內存中,服務器重啓後,數據將丟失)。固然redis對string類型的功能比memcached仍是多不少的,咱們來玩兒一下字符串類型:redis

(1)常規的String字符串類型算法

[root@redis01 scripts]# redis-cli -a yunjisuan set work ">9000"
OK
[root@redis01 scripts]# redis-cli -a yunjisuan get work
">9000"
  • 在redis中,咱們一般用set設置一對key/value鍵值,而後用get來獲取字符串的值。
  • value值能夠是任何類型的字符串(包括二進制數據),例如你能夠在一個鍵下保存一個jpg圖片。但值的長度不能超過1GB
  • 雖然字符串是Redis的基本值類型,redis還支持更多對字符串的操做功能。

(2)String類型也能夠用來存儲數字,並支持對數字的加減操做。數據庫

[root@redis01 scripts]# redis-cli -a yunjisuan set counter 1
OK
[root@redis01 scripts]# redis-cli -a yunjisuan incr counter #自增1
(integer) 2
[root@redis01 scripts]# redis-cli -a yunjisuan incr counter
(integer) 3
[root@redis01 scripts]# redis-cli -a yunjisuan get counter
"3"
[root@redis01 scripts]# redis-cli -a yunjisuan incrby counter 2 #自增指定數值
(integer) 5
[root@redis01 scripts]# redis-cli -a yunjisuan incrby counter 2
(integer) 7
[root@redis01 scripts]# redis-cli -a yunjisuan get counter
"7"
[root@redis01 scripts]# redis-cli -a yunjisuan decr counter #自減1
(integer) 6
[root@redis01 scripts]# redis-cli -a yunjisuan decr counter
(integer) 5
[root@redis01 scripts]# redis-cli -a yunjisuan get counter
"5"
[root@redis01 scripts]# redis-cli -a yunjisuan decrby counter 2 #自減指定數值
(integer) 3
[root@redis01 scripts]# redis-cli -a yunjisuan decrby counter 2
(integer) 1
[root@redis01 scripts]# redis-cli -a yunjisuan get counter
"1"

INCR命令將字符串值解析成整型,將其加1,最後將結果保存爲新的字符串值,相似的命令以下列表:api

incr 自動自增長1 INCR key
incrby 自動自增指定數值 INCRBY key increment
decr 自動自減1 DECR key
decrby 自動自減指定數值 DECRBY key decrement

(3)爲key設置新值而且返回原值數組

對字符串,另外一個操做是GETSET命令,行如其名:他爲key設置新值而且返回原值。這有什麼用處呢?例如:你的系統每當有新用戶訪問時就用INCR命令操做一個Redis key。
你但願每小時對這個信息收集一次。你就能夠GETSET這個key並給其賦值0並讀取原值。緩存

[root@redis01 scripts]# redis-cli -a yunjisuan
127.0.0.1:6379> set user01 zhangsan #設置新key-value
OK
127.0.0.1:6379> get user01      
"zhangsan"
127.0.0.1:6379> getset user01 wangwu    #設置新數據並返回舊數據
"zhangsan"
127.0.0.1:6379> getset user01 liliu     #設置新數據並返回舊數據
"wangwu"
127.0.0.1:6379> getset user01 gongli    #設置新數據並返回舊數據
"liliu"
127.0.0.1:6379> get user01      
"gongli"

(4)String類型還支持批量讀寫操做安全

127.0.0.1:6379> mset name zhangsan age 44
OK
127.0.0.1:6379> mget name age
1) "zhangsan"
2) "44"

(5)string類型還支持對其部分的修改和獲取操做

127.0.0.1:6379> set images flower
OK
127.0.0.1:6379> get images
"flower"
127.0.0.1:6379> append images .jpg  #追加字符串
(integer) 10
127.0.0.1:6379> get images
"flower.jpg"
127.0.0.1:6379> strlen images
(integer) 10
127.0.0.1:6379> substr images 0 6
"flower."
127.0.0.1:6379> substr images 0 5
"flower"

命令使用幫助:

#查看單個命令help+命令名
127.0.0.1:6379> help set

  SET key value [EX seconds] [PX milliseconds] [NX|XX]
  summary: Set the string value of a key
  since: 1.0.0
  group: string

127.0.0.1:6379> help mset

  MSET key value [key value ...]
  summary: Set multiple keys to multiple values
  since: 1.0.1
  group: string

3.2.2 List類型

  • 要說清楚列表數據類型,最好先講一點理論背景,在信息技術界List這個詞經常被使用不當。例如「Python Lists」就名存實亡(名爲Linked Lists),但它們其實是數組(一樣的數據類型在Ruby中叫數組)
  • 通常意義上講,列表就是有序元素的序列:10,20,1,2,3就是一個列表。但用數組實現的List和用Linked List實現的List,在屬性方面大不相同。
  • redis lists基於Linked Lists實現。這意味着即便在一個list中有數百萬個元素,在頭部或尾部添加一個元素的操做,其時間複雜度也是常數級別的。用LPUSH命令在十個元素的list頭部添加新元素,和在千萬元素list頭部添加新元素的速度相同。
  • 那麼,壞消息是什麼?在數組實現的list中利用索引訪問元素的速度極快,而一樣的操做在linked list實現的list上沒有那麼快。
  • Redis Lists用linked list實現的緣由是:對於數據庫系統來講,相當重要的特性是:能很是快的在很大的列表上添加元素。另外一個重要因素是,正如你將要看到的:Redis lists能在常數時間取得常數長度。

Redis lists入門:

LPUSH命令可向list的左邊(頭部)添加一個新元素,而RPUSH命令可向list的右邊(尾部)添加一個新元素。最後LRANGE命令可從list中取出必定範圍的元素。

Redis可以將數據存儲成一個列表,並能對這個列表進行豐富的操做:

127.0.0.1:6379> lpush students "zhangsan"   #將元素「zhangsan」放在students列表的最左邊
(integer) 1
127.0.0.1:6379> lpush students "wangwu" #將元素「wangwu」插入列表的最左邊
(integer) 2
127.0.0.1:6379> lpush students "liliu"  #將元素「liliu」插入列表的最左邊
(integer) 3
127.0.0.1:6379> lrange students 0 2 #查看序列是0到2的元素
1) "liliu"
2) "wangwu"
3) "zhangsan"
127.0.0.1:6379> rpush students "wangyue"    #將元素wangyue插入列表的最右邊
(integer) 4
127.0.0.1:6379> lrange students 0 3 #查看序列是0到3的元素
1) "liliu"
2) "wangwu"
3) "zhangsan"
4) "wangyue"
127.0.0.1:6379> llen students   #查看列表元素的個數
(integer) 4
127.0.0.1:6379> lpop students   #移除最左邊的元素值
"liliu"
127.0.0.1:6379> rpop students   #移除最右邊的元素值
"wangyue"
127.0.0.1:6379> lrange students 0 3 #列表裏只剩下兩個元素了
1) "wangwu"
2) "zhangsan"
127.0.0.1:6379> rpush students zhangsan
(integer) 3
127.0.0.1:6379> rpush students zhangsan
(integer) 4
127.0.0.1:6379> lrange students 0 3
1) "wangwu"
2) "zhangsan"
3) "zhangsan"
4) "zhangsan"
127.0.0.1:6379> lrem students 2 "zhangsan"  #刪除列表裏是「zhangsan」的元素,刪除兩次(從左向右刪)
127.0.0.1:6379> lrange students 0 3
1) "wangwu"
2) "zhangsan"
127.0.0.1:6379> rpush students zhangsan
(integer) 2
127.0.0.1:6379> rpush students zhangsan
(integer) 3
127.0.0.1:6379> lrem students 1 "zhangsan"  #刪除列表裏的元素zhangsan一次
127.0.0.1:6379> lrange students 0 3
1) "wangwu"
2) "zhangsan"
3) "zhangsan"
127.0.0.1:6379> lrem students 0 "zhangsan"  #清空列表全部的zhangsan元素
127.0.0.1:6379> lrange students 0 3
1) "wangwu"

Redis也支持不少修改操做

#linsert 在列表裏的某個值的先後插入元素
127.0.0.1:6379> lrange students 0 5
1) "wangwu"
127.0.0.1:6379> lpush students a b c d  #左插入元素abcd
(integer) 5
127.0.0.1:6379> lrange students 0 5
1) "d"
2) "c"
3) "b"
4) "a"
5) "wangwu"
127.0.0.1:6379> linsert students before b xxxx  #在元素b的前邊插入元素xxxx
(integer) 6
127.0.0.1:6379> lrange students 0 9
1) "d"
2) "c"
3) "xxxx"
4) "b"
5) "a"
6) "wangwu"
127.0.0.1:6379> linsert students after b xxxx   #在元素b的後邊插入元素xxxx
(integer) 7
127.0.0.1:6379> lrange students 0 9
1) "d"
2) "c"
3) "xxxx"
4) "b"
5) "xxxx"
6) "a"
7) "wangwu"

更多操做,請查詢help @list

列表list的用途:

  • 正如你能夠從上面的例子中猜到的,list可被用來實現聊天系統。還能夠做爲不一樣進程間傳遞消息的隊列。關鍵是,你能夠每次都以原先添加的順序訪問數據。這不須要任何SQLORDER操做,將會很是快,也會很容易擴展到百萬級別的規模。
  • 例如在評級系統中,好比社會化新聞網站reddit.com,你能夠把每一個新提交的連接添加到一個list,用LRANGE可簡單的對結果分頁。
  • 在博客引擎實現中,你可爲每篇日誌設置一個list,在該list中推入進博客評論,等等。
  • 向Redis list壓入ID而不是實際的數據。
  • 在上面的例子裏,咱們將「對象」(此例中是簡單消息)直接壓入Redis list,但一般不該這麼作,因爲對象可能被屢次引用:例如在一個list中維護其時間順序,在一個集合中保存它的類別,只要有必要,它還會出如今其餘list中,等等。

3.2.3 集合(Sets)類型

  • Redis集合是未排序的集合,其元素是二進制安全的字符串。SADD命令能夠向集合添加一個新元素。和sets相關的操做也有許多,好比檢測某個元素是否存在,以及實現交集,並集,差集等等。一例勝千言:
  • Redis可以將一系列不重複的值存儲成一個集合
127.0.0.1:6379> sadd users laoda    #向集合users裏添加一個元素「laoda」
(integer) 1
127.0.0.1:6379> sadd users laoer laosan #向結合users裏添加兩個元素laoer,laosan
(integer) 2
127.0.0.1:6379> smembers users  #查看集合裏的全部元素
1) "laosan"         #能夠看到集合裏的元素是無序的
2) "laoda"
3) "laoer"

#咱們向集合中添加了三個元素,並讓Redis返回全部元素。如今讓咱們看一下某個元素是否存在於集合中
127.0.0.1:6379> sismember users laoda   #查看元素laoda是否存在於集合users中
(integer) 1 #存在
127.0.0.1:6379> sismember users laoer   #查看元素laoer是否存在於集合users中
(integer) 1 #存在
127.0.0.1:6379> sismember users laosan  #查看元素laosan是否存在於集合users中
(integer) 1 #存在
127.0.0.1:6379> sismember users laosi   ##查看元素laosi是否存在於集合users中
(integer) 0 #不存在
  • 「laoda」是這個集合的成員,而「laosi」不是。集合特別適合表現對象之間的關係。例如用Redis集合能夠很容易實現標籤功能。
  • 下面是一個簡單的方案:對每一個想加標籤的對象,用一個標籤ID集合與之關聯,而且對每一個已有的標籤,一組對象ID與之關聯。
  • 例如,假設咱們的新聞ID1000被加了三個標籤tag1,2,5和77,就能夠設置下面兩個集合:
root@redis-master ~]# redis-cli -a yunjisuan sadd news:1000:tags 1
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd news:1000:tags 2
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd news:1000:tags 5
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd news:1000:tags 77
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd tag:1:objects 1000
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd tag:2:objects 1000
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd tag:5:objects 1000
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan sadd tag:27:objects 1000
(integer) 1

#要獲取一個對象的全部標籤,咱們只須要:
#獲取ID號爲1000的全部新聞的題目
[root@redis-master ~]# redis-cli -a yunjisuan smembers news:1000:tags      #獲取集合爲news:1000:tags的全部元素
1) "1"      #新聞標題
2) "2"      #新聞標題
3) "5"      #新聞標題
4) "77"     #新聞標題

#查詢某個標籤的具體內容,咱們只須要:
#獲取某個新聞標題的具體內容
[root@redis-master ~]# redis-cli -a yunjisuan smembers tag:5:objects       #獲取集合爲tag:5:objects的全部元素
1) "1000"       #新聞內容

而有些看上去並不簡單的操做仍然能使用相應的Redis命令輕鬆實現。例如咱們也許想得到一份同時擁有標籤1,2,10和27的對象列表。則能夠用SINTER命令來作,他能夠在不一樣集合之間取出交集。所以爲達目的咱們只需:

[root@redis-master ~]# redis-cli -a yunjisuan sadd tag:1:objects 500       #向集合tag:1:objects裏添加元素「500」
(integer) 1
[root@redis-master ~]# redis-cli -a yunjisuan smembers tag:1:objects   #查看集合tag:1:objects裏的全部元素
1) "500"
2) "1000"
[root@redis-master ~]# redis-cli -a yunjisuan smembers tag:2:objects   #查看集合tag:2:objects裏的全部元素
1) "1000"
[root@redis-master ~]# redis-cli -a yunjisuan sinter tag:1:objects tag:2:objects tag:5:objects tag:27:objects    #求集合tag:1:objects ...tag:27:objects裏的全部元素的交集
1) "1000"

如何爲字符串獲取惟一標識:

  • 在標籤的例子裏,咱們用到了標籤ID,卻沒有提到ID從何而來。基本上你得爲每一個加入系統的標籤分配一個惟一標識。你也但願在多個客戶端同時試着添加一樣的標籤時不要出現競爭的狀況。此外,若是標籤已經存在,你但願返回他的ID,不然建立一個新的惟一標識並將其與此標籤關聯。
  • Redis 1.4增長了Hash類型。有了它,字符串和惟一ID關聯的事兒將不值一提,但現在咱們如何用現有Redis命令可靠的解決它呢?
  • 咱們首先的嘗試(以失敗了結)可能以下:

假設咱們想爲標籤「redis」獲取一個惟一ID:

  • 爲了讓算法是二進制安全的(只是標籤而不考慮utf8,空格等等)咱們對用戶的註冊名作SHA1簽名。SHA1(redis)=b840fc02d524045429941cc15f59e41cb7be6c52
  • 檢查這個標籤是否已與一個惟一ID關聯(驗證用戶名是否重複)
    用命令GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id
  • 若是上面的GET操做返回一個ID,則將其返回給用戶。表示用戶名已經被註冊了。
  • 不然...用INCR next.tag.id(自增長1)命令生成一個新的惟一ID(假定它返回123456)
  • 最後關聯標籤和新的ID
    SET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id 123456
    並將新ID返回給調用者(表示註冊成功)。

可是這裏會出現一個問題,假如兩個客戶端同時使用這組指令嘗試爲標籤「redis」獲取惟一ID時會發生什麼呢?若是時間湊巧,他們倆都會從GET操做得到nil,都將對next.tag.id key作自增操做,這個key會被自增兩次。其中一個客戶端會將錯誤的ID返回給調用者。

幸運的是修復這個算法並不難,這是明智的版本:

  • 爲了讓算法是二進制安全的(只是標籤而不考慮utf8,空格等等)咱們對用戶的註冊名作SHA1簽名。SHA1(redis)=b840fc02d524045429941cc15f59e41cb7be6c52
  • 檢查這個標籤是否已與一個惟一ID關聯(驗證用戶名是否重複)
    用命令GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id
  • 若是上面的GET操做返回一個ID,則將其返回給用戶。表示用戶名已經被註冊了。
  • 不然...用INCR next.tag.id(自增長1)命令生成一個新的惟一ID(假定它返回123456)
  • 最後關聯標籤和新的ID
    SETNX tag:b840fc02d524045429941cc15f59e41cb7be6c52:id 123456 (若是另外一個客戶端比當前客戶端更快,SETNX將不會設置key。並且,當key被成功設置時SETNX返回1,不然返回0.那麼...讓咱們再作最後一步運算)
  • 若是SETNX返回1(key設置成功)則將123456返回給調用者,這就是咱們的用戶名ID,不然執行GET tag:b840fc02d524045429941cc15f59e41cb7be6c52:id 並將結果返回給調用者(表示註冊成功)。

3.2.4 有序集合(Sorted Sets)類型

Sorted Sets和Sets結構類似,不一樣的是存在Sorted Sets中的數據會有一個score屬性,並會在寫入時就按這個score拍好序。

#向一個有序集合裏添加元素
127.0.0.1:6379> ZADD days 0 mon #days是有序集合名,0是序號,mon是值
(integer) 1
127.0.0.1:6379> ZADD days 1 tue
(integer) 1
127.0.0.1:6379> ZADD days 2 web
(integer) 1
127.0.0.1:6379> ZADD days 3 thu
(integer) 1
127.0.0.1:6379> ZADD days 4 fri
(integer) 1
127.0.0.1:6379> ZADD days 5 sat
(integer) 1
127.0.0.1:6379> ZADD days 6 sun
(integer) 1
127.0.0.1:6379> zrange days 0 6 #查看集合索引0到6的元素
1) "mon"
2) "tue"
3) "web"
4) "thu"
5) "fri"
6) "sat"
7) "sun"
#從上面咱們能夠看出,ZADD建立的集合是有序集合。

#查看有序集合days的具體值的排序
127.0.0.1:6379> zscore days mon
"0"
127.0.0.1:6379> zscore days web 
"2"
127.0.0.1:6379> zscore days fri
"4"
root@redis-master ~]# redis-cli -a yunjisuan
127.0.0.1:6379> zscore days mon
"0"
127.0.0.1:6379> zscore days web
"2"
127.0.0.1:6379> zscore days fri
"4"
127.0.0.1:6379> zcount days 3 6
(integer) 4
127.0.0.1:6379> ZRANGEBYSCORE days 3 6
1) "thu"
2) "fri"
3) "sat"
4) "sun"
  • 集合是使用頻率很高的數據類型,可是...對許多問題來講他們也有點太不講順序了;所以Redis1.2引入了有序集合。它和集合很是類似,也是二進制安全的字符串集合,可是此次帶有關聯的score,以及一個相似LRANGE的操做能夠返回有序元素,此操做只能做用於有序集合,它就是,ZRANGE命令。
  • 基本上有序集合從某種程度上說是SQL世界的索引在Redis中的等價物。例如在上面提到的reddit.com例子中,並無提到如何根據用戶投票和時間因素將新聞組合生成首頁。咱們將看到有序集合如何解決這個問題,但最好先從更簡單的事情開始,闡明這個高級數據類型是如何工做的。讓咱們添加幾個黑客,並將他們的生日做爲「score」。
127.0.0.1:6379> zadd hackers 1940 "1940-Alan Kay"
(integer) 1
127.0.0.1:6379> zadd hackers 1953 "1953-Richard Stallman"
(integer) 1
127.0.0.1:6379> zadd hackers 1965 "1965-Yukihiro Matsumoto"
(integer) 1
127.0.0.1:6379> zadd hackers 1916 "1916-Claude Shannon"
(integer) 1
127.0.0.1:6379> zadd hackers 1969 "1969-Linus Torvalds"
(integer) 1
127.0.0.1:6379> zadd hackers 1912 "1912-Alan Turing"
(integer) 1

對有序集合來講,按生日排序返回這些黑客易如反掌,由於他們已是有序的。有序集合是經過一個dual-ported數據結構實現的,它包含一個精簡的有序列表和一個hash table,所以添加一個元素的時間複雜度是O(log(N))。這還行,但當咱們須要訪問有序的元素時,Redis沒必要再作任何事情,它已是有序的了:

127.0.0.1:6379> zadd hackers 1940 "1940-Alan Kay"
(integer) 1
127.0.0.1:6379> zadd hackers 1953 "1953-Richard Stallman"
(integer) 1
127.0.0.1:6379> zadd hackers 1965 "1965-Yukihiro Matsumoto"
(integer) 1
127.0.0.1:6379> zadd hackers 1916 "1916-Claude Shannon"
(integer) 1
127.0.0.1:6379> zadd hackers 1969 "1969-Linus Torvalds"
(integer) 1
127.0.0.1:6379> zadd hackers 1912 "1912-Alan Turing"
(integer) 1

#利用zrange進行排序查詢
127.0.0.1:6379> zrange hackers 0 6
1) "1912-Alan Turing"
2) "1916-Claude Shannon"
3) "1940-Alan Kay"
4) "1953-Richard Stallman"
5) "1965-Yukihiro Matsumoto"
6) "1969-Linus Torvalds"

#利用zrevrange進行反向查詢
127.0.0.1:6379> zrevrange hackers 0 -1
1) "1969-Linus Torvalds"
2) "1965-Yukihiro Matsumoto"
3) "1953-Richard Stallman"
4) "1940-Alan Kay"
5) "1916-Claude Shannon"
6) "1912-Alan Turing"

3.2.5 Hash類型

Redis可以存儲key對多個屬性的數據(好比user1,uname user1.passwd)

#存儲一個hash類型test,他的屬性是name,屬性數據是yunjisuan
127.0.0.1:6379> hset test name yunjisuan
(integer) 1
#存儲一個hash類型test,他的屬性是age,屬性數據是35
127.0.0.1:6379> hset test age 35
(integer) 1
#存儲一個hash類型test,他的屬性是sex,屬性數據是non
127.0.0.1:6379> hset test sex nan
(integer) 1
#查看hash類型test的全部屬性的值
127.0.0.1:6379> hvals test
1) "yunjisuan"
2) "35"
3) "nan"
#查看hash類型test的全部屬性及屬性所對應的值
127.0.0.1:6379> hgetall test
1) "name"
2) "yunjisuan"
3) "age"
4) "35"
5) "sex"
6) "nan"

第4章 redis多實例實戰

4.1 建立redis的存儲目錄

#建立redis存儲目錄
[root@redis-master redis]# cat -n /usr/local/redis/conf/redis.conf | sed -n '187p'
   187  dir ./                      #修改本行的存儲路徑配置路徑
[root@redis-master redis]# cat -n /usr/local/redis/conf/redis.conf | sed -n '187p'
   187  dir /usr/local/redis/data/              #改爲這個
[root@redis-master redis]# redis-cli -a yunjisuan shutdown  #關閉redis服務
[root@redis-master redis]# mkdir /usr/local/redis/data  #建立redis存儲目錄
[root@redis-master redis]# redis-server /usr/local/redis/conf/redis.conf &  #後臺啓動redis進程


#向redis裏寫入數據,並保存
[root@redis-master redis]# redis-cli -a yunjisuan
127.0.0.1:6379> set name2 yunjisuan
OK
127.0.0.1:6379> save            #保存數據
[3456] 08 Oct 04:39:05.169 * DB saved on disk
OK
127.0.0.1:6379> quit
[root@redis-master redis]# ll /usr/local/redis/
total 12
drwxr-xr-x. 2 root root 4096 Oct  7 16:53 bin
drwxr-xr-x. 2 root root 4096 Oct  8 04:33 conf
drwxr-xr-x. 2 root root 4096 Oct  8 04:39 data
[root@redis-master redis]# ll /usr/local/redis/data/
total 4
-rw-r--r--. 1 root root 49 Oct  8 04:39 dump.rdb    #保存的rdb文件

4.2 建立redis多實例的存儲目錄及文件

#建立redis多實例存儲目錄
[root@redis-master redis]# mkdir -p /data/6380/data
[root@redis-master redis]# mkdir -p /data/6381/data

#建立redis多實例配置文件
[root@redis-master redis]# cp /usr/local/redis/conf/redis.conf /data/6380/
[root@redis-master redis]# cp /usr/local/redis/conf/redis.conf /data/6381/

#修改多實例配置文件的數據存儲路徑
[root@redis-master redis]# sed -n '187p' /data/6380/redis.conf 
dir /data/6380/data                 #照此修改存儲路徑
[root@redis-master redis]# sed -n '187p' /data/6381/redis.conf 
dir /data/6381/data                 #照此修改存儲路徑

#修改多實例配置文件的佔用端口
[root@redis-master redis]# sed -n '45p' /data/6380/redis.conf 
port 6380                       #照此修改啓動端口
[root@redis-master redis]# sed -n '45p' /data/6381/redis.conf 
port 6381                       #照此修改啓動端口

#修改多實例配置文件的pid文件位置
[root@redis-master redis]# sed -n '41p' /data/6380/redis.conf
pidfile /data/6380/redis.pid            #照此修改
[root@redis-master redis]# sed -n '41p' /data/6381/redis.conf
pidfile /data/6381/redis.pid            #照此修改

#開啓多實例配置文件的持久化日誌
[root@redis-master redis]# sed -n '449p' /data/6380/redis.conf 
appendonly yes                      #照此修改
[root@redis-master redis]# sed -n '449p' /data/6381/redis.conf 
appendonly yes                      #照此修改

4.3 啓動redis多實例進程

[root@redis-master redis]# redis-server /data/6380/redis.conf &
[root@redis-master redis]# redis-server /data/6381/redis.conf &

4.4 查看redis多實例的進程啓動狀況

[root@redis-master redis]# netstat -antup | grep redis
tcp        0      0 0.0.0.0:6379                0.0.0.0:*                   LISTEN      3456/redis-server * 
tcp        0      0 0.0.0.0:6380                0.0.0.0:*                   LISTEN      3493/redis-server * 
tcp        0      0 0.0.0.0:6381                0.0.0.0:*                   LISTEN      3496/redis-server * 
tcp        0      0 :::6379                     :::*                        LISTEN      3456/redis-server * 
tcp        0      0 :::6380                     :::*                        LISTEN      3493/redis-server * 
tcp        0      0 :::6381                     :::*                        LISTEN      3496/redis-server *

4.5 查看多實例文件夾目錄樹一覽

[root@redis-master data]# tree /data
/data
├── 6380    #redis實例6380啓動目錄
│   ├── data    #redis實例6380數據目錄
│   │   ├── appendonly.aof      #redis實例6380的數據持久化日誌(記錄了數據庫的修改,相似binlog)
│   │   └── dump.rdb    #redis實例6380數據存儲文件
│   └── redis.conf      #redis實例6380配置文件
└── 6381    #redis實例6381啓動目錄
    ├── data    #redis實例6381數據目錄
    │   ├── appendonly.aof      #redis實例6381的數據持久化日誌(記錄了數據庫的修改,相似binlog)
    │   └── dump.rdb    #redis實例6381數據存儲文件
    └── redis.conf      #redis實例6381配置文件

4 directories, 6 files

或許有些同窗會迷糊,appendonly.aof是作什麼用的呢?
咱們打開文件的內容查看以下:

[root@redis-master data]# cat /data/6380/data/appendonly.aof 
*2
$6
SELECT
$1
0
*3
$3
set
$4
name
$9
yunjisuan
*1
$4
save

咱們發現appendonly.aof實際上裏面記錄的是咱們對redis數據庫的修改記錄,這點相似於MySQL的binlog日誌。

第5章 Redis主從同步

5.1 Redis主從同步特色

  1. 一個master能夠擁有多個slave
  2. 多個slave能夠鏈接同一個master,還能夠鏈接到其餘slave
  3. 主從複製不會阻塞master,在同步數據時,master能夠繼續處理client請求。
  4. 提升系統的伸縮性

5.2 Redis主從同步的過程

  1. 配置好slave服務器鏈接master後,slave會創建和master的鏈接,而後發送sync命令。
  2. 不管是第一次同步創建的鏈接仍是鏈接斷開後的從新鏈接,master都會啓動一個後臺進程,將數據庫快照保存到磁盤文件中,同時master主進程會開始收集新的寫命令並緩存起來。
  3. 當後臺進程完成寫磁盤文件後,master就將快照文件發送給slave,slave將文件保存到磁盤上,而後加載到內存將數據庫快照恢復到slave上。
  4. slave完成快照文件的恢復後,master就會把緩存的命令都轉發給slave,slave更新內存數據庫。
  5. 後續master收到的寫命令都會經過開始創建的鏈接發送給slave。從master到slave的同步數據的命令和從client到master發送的命令使用相同的協議格式。當master和slave的鏈接斷開時,slave能夠自動從新創建鏈接。若是master同時收到多個slave發來的同步鏈接命令,只會使用啓動一個進程來寫數據庫鏡像,而後發送給全部slave。

QQ截圖20171028232508.png-21.8kB

(1)Slave服務器鏈接到Master
服務器
(2)Slave服務器發送SYNC命令
(3)Master服務器備份數據庫到.rdb文件
(4)Master服務器把.rdb文件傳輸給Slave服務器
(5)Slave服務器把.rdb文件數據導入到數據庫中。

上面的這5步是同步的第一階段,接下來在Master服務器上調用每個命令都使用replicationFeedSlaves()來同步到Slave服務器。

Redis的主從同步具備明顯的分佈式緩存特色:

(1)一個master能夠有多個slave,一個slave下面還能夠有多個slave
(2)slave不只能夠鏈接到master,slave也能夠鏈接其餘slave造成樹狀。
(3)主從同步不會阻塞master,可是會阻塞slave。也就是說當一個或多個slave與master進行初次同步數據時,master能夠繼續處理client發來的請求。相反slave在初次同步數據時則會阻塞不能處理client的請求。
(4)主從同步能夠同來提升系統的可伸縮性,咱們能夠用多個slave專門處理client端的讀請求,也能夠用來作簡單的數據冗餘或者只在slave上進行持久化從而提高集羣的總體性能。
(5)對於老版本的redis,每次重連都會從新發送全部數據。

5.3 Redis主動同步設置方法

有兩種方式能夠用來完成進行主從Redis服務器的同步設置。都須要在slave服務器上進行,指定slave須要鏈接的Redis服務器(多是master,也多是slave)。

5.3.1 在redis.conf配置文件中設置

經過簡單的配置slave(master端無需配置),用戶就能使用redis的主從複製
咱們讓端口6379的redis作master;端口6380的redis作slave

#咱們修改/data/6380/redis.conf的配置文件
[root@redis-master ~]# cat -n /data/6380/redis.conf | sed -n '189,215p'
   189  ################################# REPLICATION #################################
   190  
   191  # Master-Slave replication. Use slaveof to make a Redis instance a copy of
   192  # another Redis server. Note that the configuration is local to the slave
   193  # so for example it is possible to configure the slave to save the DB with a
   194  # different interval, or to listen to another port, and so on.
   195  #
   196  # slaveof <masterip> <masterport>
   197  slaveof 192.168.0.135 6379              在此處添加本行內容,指定主master的IP和端口
   198  # If the master is password protected (using the "requirepass" configuration
   199  # directive below) it is possible to tell the slave to authenticate before
   200  # starting the replication synchronization process, otherwise the master will
   201  # refuse the slave request.
   202  #
   203  # masterauth <master-password>
   204  masterauth yunjisuan                    在此處添加本行內容,指定驗證的密碼
   205  # When a slave loses its connection with the master, or when the replication
   206  # is still in progress, the slave can act in two different ways:
   207  #
   208  # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
   209  #    still reply to client requests, possibly with out of date data, or the
   210  #    data set may just be empty if this is the first synchronization.
   211  #
   212  # 2) if slave-serve-stale-data is set to 'no' the slave will reply with
   213  #    an error "SYNC with master in progress" to all the kind of commands
   214  #    but to INFO and SLAVEOF.
   215  #

接下來咱們重啓redis的服務進程

[root@redis-master ~]# redis-cli -p 6380 -a yunjisuan shutdown      #關閉6380redis進程
[3558] 08 Oct 09:03:10.218 # User requested shutdown...
[3558] 08 Oct 09:03:10.218 * Calling fsync() on the AOF file.
[3558] 08 Oct 09:03:10.218 * Saving the final RDB snapshot before exiting.
[3558] 08 Oct 09:03:10.220 * DB saved on disk
[3558] 08 Oct 09:03:10.220 # Redis is now ready to exit, bye bye...
[3]+  Done                    redis-server /data/6380/redis.conf  (wd: /data)
(wd now: ~)
[root@redis-master ~]# redis-server /data/6380/redis.conf &     #後臺啓動

當再次啓動從庫時出現以下信息:

[3616] 08 Oct 09:07:50.955 # Server started, Redis version 2.8.9
[3616] 08 Oct 09:07:50.965 * DB saved on disk
[3616] 08 Oct 09:07:50.965 * DB loaded from append only file: 0.010 seconds
[3616] 08 Oct 09:07:50.965 * The server is now ready to accept connections on port 6380
[3616] 08 Oct 09:07:51.958 * Connecting to MASTER 192.168.0.135:6379        #鏈接master
[3616] 08 Oct 09:07:51.958 * MASTER <-> SLAVE sync started              #開始發送sync
[3616] 08 Oct 09:07:51.958 * Non blocking connect for SYNC fired the event. #這是一個不阻塞事件
[3616] 08 Oct 09:07:51.958 * Master replied to PING, replication can continue...    #master應答了ping,同步開始
[3616] 08 Oct 09:07:51.959 * Partial resynchronization not possible (no cached master)  #從新進行同步不可能(master沒有緩存內容)
[3616] 08 Oct 09:07:51.961 * Full resync from master:   #從master同步所有數據 933d3b0123f2d72cf106d901434898aab24d2a6e:1
[3616] 08 Oct 09:07:52.052 * MASTER <-> SLAVE sync: receiving 49 bytes from master  #從master接收到49字節數據
[3616] 08 Oct 09:07:52.052 * MASTER <-> SLAVE sync: Flushing old data       #刷新舊數據
[3616] 08 Oct 09:07:52.053 * MASTER <-> SLAVE sync: Loading DB in memory    #數據放到內存
[3616] 08 Oct 09:07:52.053 * MASTER <-> SLAVE sync: Finished with success   #同步完成
[3616] 08 Oct 09:07:52.054 * Background append only file rewriting started by pid 3620  #AOF重寫
[3620] 08 Oct 09:07:52.060 * SYNC append only file rewrite performed    
[3620] 08 Oct 09:07:52.060 * AOF rewrite: 6 MB of memory used by copy-on-write
[3616] 08 Oct 09:07:52.159 * Background AOF rewrite terminated with success #AOF重寫成功
[3616] 08 Oct 09:07:52.159 * Parent diff successfully flushed to the rewritten AOF (0 bytes)
[3616] 08 Oct 09:07:52.159 * Background AOF rewrite finished successfully   #AOF重寫完畢

5.3.2 進行redis主從同步測試

[root@redis-master ~]# redis-cli -a yunjisuan -p 6380 get name      #獲取redis6380的鍵name的值
"benet"
[root@redis-master ~]# redis-cli -a yunjisuan -p 6379 set name xxxxx    #向redis6379裏存一個key=name,value=xxxxx的數據
OK
[root@redis-master ~]# redis-cli -a yunjisuan -p 6380 get name  #獲取redis6380的鍵name的值
"xxxxx"

綜上所示:redis主從同步成功

5.3.3 redis主從同步相關配置參數解釋

[root@redis-master ~]# cat -n /data/6380/redis.conf | sed -n "189,324p"
   189  ################################# REPLICATION #################################
   190  
   191  # Master-Slave replication. Use slaveof to make a Redis instance a copy of
   192  # another Redis server. Note that the configuration is local to the slave
   193  # so for example it is possible to configure the slave to save the DB with a
   194  # different interval, or to listen to another port, and so on.
   195  #
   196  # slaveof <masterip> <masterport>
   197  slaveof 192.168.0.135 6379              用於標識master的鏈接IP及端口號
   198  # If the master is password protected (using the "requirepass" configuration
   199  # directive below) it is possible to tell the slave to authenticate before
   200  # starting the replication synchronization process, otherwise the master will
   201  # refuse the slave request.
   202  #
   203  # masterauth <master-password>
   204  masterauth yunjisuan                    若是master設置了鏈接密碼,這裏要寫上
   205  # When a slave loses its connection with the master, or when the replication
   206  # is still in progress, the slave can act in two different ways:
   207  #
   208  # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
   209  #    still reply to client requests, possibly with out of date data, or the
   210  #    data set may just be empty if this is the first synchronization.
   211  #
   212  # 2) if slave-serve-stale-data is set to 'no' the slave will reply with
   213  #    an error "SYNC with master in progress" to all the kind of commands
   214  #    but to INFO and SLAVEOF.
   215  #
   216  slave-serve-stale-data yes              若是設置yes,那麼一旦從庫鏈接不上主庫,從庫繼續響應客戶端發來的請求並回復,可是回覆的內容有多是過時的。若是no,那麼slave會應答一個錯誤提示,就不提供訪問了。
   217  
   218  # You can configure a slave instance to accept writes or not. Writing against
   219  # a slave instance may be useful to store some ephemeral data (because data
   220  # written on a slave will be easily deleted after resync with the master) but
   221  # may also cause problems if clients are writing to it because of a
   222  # misconfiguration.
   223  #
   224  # Since Redis 2.6 by default slaves are read-only.
   225  #
   226  # Note: read only slaves are not designed to be exposed to untrusted clients
   227  # on the internet. It's just a protection layer against misuse of the instance.
   228  # Still a read only slave exports by default all the administrative commands
   229  # such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
   230  # security of read only slaves using 'rename-command' to shadow all the
   231  # administrative / dangerous commands.
   232  slave-read-only yes                 yes:從庫被設置爲只能讀
   233  
   234  # Slaves send PINGs to server in a predefined interval. It's possible to change
   235  # this interval with the repl_ping_slave_period option. The default value is 10
   236  # seconds.
   237  #
   238  # repl-ping-slave-period 10         
   239  
   240  # The following option sets the replication timeout for:
   241  #
   242  # 1) Bulk transfer I/O during SYNC, from the point of view of slave.
   243  # 2) Master timeout from the point of view of slaves (data, pings).
   244  # 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
   245  #
   246  # It is important to make sure that this value is greater than the value
   247  # specified for repl-ping-slave-period otherwise a timeout will be detected
   248  # every time there is low traffic between the master and the slave.
   249  #
   250  # repl-timeout 60
   251  
   252  # Disable TCP_NODELAY on the slave socket after SYNC?
   253  #
   254  # If you select "yes" Redis will use a smaller number of TCP packets and
   255  # less bandwidth to send data to slaves. But this can add a delay for
   256  # the data to appear on the slave side, up to 40 milliseconds with
   257  # Linux kernels using a default configuration.
   258  #
   259  # If you select "no" the delay for data to appear on the slave side will
   260  # be reduced but more bandwidth will be used for replication.
   261  #
   262  # By default we optimize for low latency, but in very high traffic conditions
   263  # or when the master and slaves are many hops away, turning this to "yes" may
   264  # be a good idea.
   265  repl-disable-tcp-nodelay no
   266  
   267  # Set the replication backlog size. The backlog is a buffer that accumulates
   268  # slave data when slaves are disconnected for some time, so that when a slave
   269  # wants to reconnect again, often a full resync is not needed, but a partial
   270  # resync is enough, just passing the portion of data the slave missed while
   271  # disconnected.
   272  #
   273  # The biggest the replication backlog, the longer the time the slave can be
   274  # disconnected and later be able to perform a partial resynchronization.
   275  #
   276  # The backlog is only allocated once there is at least a slave connected.
   277  #
   278  \# repl-backlog-size 1mb                 用於同步的backlog大小,用於從庫增量同步
   279  
   280  # After a master has no longer connected slaves for some time, the backlog
   281  # will be freed. The following option configures the amount of seconds that
   282  # need to elapse, starting from the time the last slave disconnected, for
   283  # the backlog buffer to be freed.
   284  #
   285  # A value of 0 means to never release the backlog.
   286  #
   287  \# repl-backlog-ttl 3600                 當主從鏈接斷開,backlog的生存週期                    
   288  
   289  # The slave priority is an integer number published by Redis in the INFO output.
   290  # It is used by Redis Sentinel in order to select a slave to promote into a
   291  # master if the master is no longer working correctly.
   292  #
   293  # A slave with a low priority number is considered better for promotion, so
   294  # for instance if there are three slaves with priority 10, 100, 25 Sentinel will
   295  # pick the one with priority 10, that is the lowest.
   296  #
   297  # However a special priority of 0 marks the slave as not able to perform the
   298  # role of master, so a slave with priority of 0 will never be selected by
   299  # Redis Sentinel for promotion.
   300  #
   301  # By default the priority is 100.
   302  slave-priority 100                      slave的優先級
   303  
   304  # It is possible for a master to stop accepting writes if there are less than
   305  # N slaves connected, having a lag less or equal than M seconds.
   306  #
   307  # The N slaves need to be in "online" state.
   308  #
   309  # The lag in seconds, that must be <= the specified value, is calculated from
   310  # the last ping received from the slave, that is usually sent every second.
   311  #
   312  # This option does not GUARANTEES that N replicas will accept the write, but
   313  # will limit the window of exposure for lost writes in case not enough slaves
   314  # are available, to the specified number of seconds.
   315  #
   316  # For example to require at least 3 slaves with a lag <= 10 seconds use:
   317  #
   318  # min-slaves-to-write 3
   319  # min-slaves-max-lag 10
   320  #
   321  # Setting one or the other to 0 disables the feature.
   322  #
   323  # By default min-slaves-to-write is set to 0 (feature disabled) and
   324  # min-slaves-max-lag is set to 10.

5.4 查看redis各項參數的方法

#咱們登錄redis-master
[root@redis-master ~]# redis-cli -a yunjisuan -p 6379   #登錄
127.0.0.1:6379> info    #查看各項信息
# Server
redis_version:2.8.9
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:125c8b01feaf5fd0
redis_mode:standalone
os:Linux 2.6.32-431.el6.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.7
process_id:3456
run_id:933d3b0123f2d72cf106d901434898aab24d2a6e
tcp_port:6379
uptime_in_seconds:23790
uptime_in_days:0
hz:10
lru_clock:14303250
config_file:/usr/local/redis/conf/redis.conf

# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:1879144
used_memory_human:1.79M
used_memory_rss:10010624
used_memory_peak:1915072
used_memory_peak_human:1.83M
used_memory_lua:33792
mem_fragmentation_ratio:5.33
mem_allocator:jemalloc-3.2.0

# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1507468973
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:0
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok

# Stats
total_connections_received:5
total_commands_processed:7362
instantaneous_ops_per_sec:1
rejected_connections:0
sync_full:1
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:242

# Replication
role:master
connected_slaves:1
slave0:ip=192.168.0.135,port=6380,state=online,offset=10348,lag=1
master_repl_offset:10348
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:10347

# CPU
used_cpu_sys:9.79
used_cpu_user:7.03
used_cpu_sys_children:0.01
used_cpu_user_children:0.00

# Keyspace
db0:keys=2,expires=0,avg_ttl=0

若是咱們只想單獨查看某些信息,那麼操做以下:

127.0.0.1:6379> info cpu            #查看CPU信息
# CPU
used_cpu_sys:10.11
used_cpu_user:7.46
used_cpu_sys_children:0.01
used_cpu_user_children:0.00
127.0.0.1:6379> info memory         #查看內存信息
# Memory
used_memory:1878304
used_memory_human:1.79M
used_memory_rss:10027008
used_memory_peak:1915072
used_memory_peak_human:1.83M
used_memory_lua:33792
mem_fragmentation_ratio:5.34
mem_allocator:jemalloc-3.2.0
127.0.0.1:6379> info clients        #查看客戶端信息
# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
127.0.0.1:6379> info replication    #查看同步信息
# Replication
role:master     #本redis是主
connected_slaves:1
slave0:ip=192.168.0.135,port=6380,state=online,offset=11972,lag=1   #主庫ip,端口,狀態,偏移量等
master_repl_offset:11972
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:11971

第六章 redis的高級特性

6.1 redis數據過時設置及過時機制

Redis支持按key設置過時時間,過時後值將被刪除(在客戶端看來是被刪除了的)
用TTL命令能夠獲取某個key值的過時時間(-1表示永久不過時)

127.0.0.1:6379> keys *
1) "name"
2) "name2"
127.0.0.1:6379> TTL name    查看key過時時間(-1爲永久不過時,-2爲已通過期)
(integer) -1
127.0.0.1:6379> TTL name2
(integer) -1

下面經過命令先用EXISTS查看key值是否存在,而後設置5秒過時時間

127.0.0.1:6379> expire name 5       #給key  name設置5秒過時時間
(integer) 1
127.0.0.1:6379> TTL name    #查看key過時時間
(integer) 3     #3秒後過時
127.0.0.1:6379> TTL name
(integer) 2     #2秒後過時
127.0.0.1:6379> TTL name
(integer) 1     #1秒後過時
127.0.0.1:6379> TTL name
(integer) -2    #key已通過期
127.0.0.1:6379> TTL name
(integer) -2
127.0.0.1:6379> get name    #過時了的key是沒法經過key獲取value的
(nil)

6.2 redis持久化

  • Redis的全部數據都存儲在內存中,可是他也提供對這些數據的持久化。
  • redis是一個支持持久化的內存數據庫,也就是說redis須要常常將內存中的數據同步到磁盤來保證持久化。redis支持兩種持久化方式,一種是Snapshotting(快照)也是默認方式,另外一種是Append-only file(縮寫aof)的方式。

6.2.1 數據快照

快照是redis默認的持久化方式。這種方式就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名爲dump.rdb。能夠經過配置設置自動作快照持久化。例如,能夠配置redis在n秒內若是超過m個key被修改就自動作快照,下面是redis默認的快照保存設置參數:

save 900 1      #900 秒內若是超過1個key被修改,則發起快照保存
save 300 10     #300 秒內若是超過10個key被修改,則發起快照保存
save 60  10000

下面介紹詳細的快照保存過程:

1)redis調用fork,如今有了子進程和父進程.
2)父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。因爲Linux的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時Linux會爲父進程要修改的頁面建立副本,而不是寫共享的頁面。因此子進程地址空間內的數據是fork時的整個數據庫的一個快照。
3)當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,而後子進程退出。client也可使用save或者bgsave命令通知redis作一次快照持久化。save操做是在主線程中保存快照的,因爲redis是用一個主線程來處理全部client的請求,這種方式會阻塞全部client請求。因此不推薦使用。另外一點須要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並非增量的只同步變動數據。若是數據量大的話,並且寫操做比較多,必然會引發大量的磁盤io操做,可能會嚴重影響性能。

數據快照的原理是將整個Redis中存的全部數據遍歷一遍存到一個擴展名爲rdb的數據文件中。經過SAVE命令能夠調用這個過程。

進行有rdb文件的數據還原測試

#進行有rdb文件的數據還原測試
[root@redis-master redis]# redis-cli -a yunjisuan set names john      #向redis裏寫入一個鍵值對
OK
[root@redis-master redis]# redis-cli -a yunjisuan get names               #查看鍵的值
"john"
[root@redis-master redis]# ll /usr/local/redis/data/    #此時/usr/local/redis/data目錄下沒有任何東西
total 0
[root@redis-master redis]# redis-cli -a yunjisuan shutdown          #關閉redis進程
[3940] 08 Oct 19:09:08.932 # User requested shutdown...             
[3940] 08 Oct 19:09:08.932 * Saving the final RDB snapshot before exiting.  #關閉時,redis自動進行RDB文件的保存
[3940] 08 Oct 19:09:08.943 * DB saved on disk       #RDB文件已經保存到了磁盤上
[3940] 08 Oct 19:09:08.943 # Redis is now ready to exit, bye bye...
[1]+  Done                    redis-server /usr/local/redis/conf/redis.conf #進程Done
[root@redis-master redis]# ll /usr/local/redis/data/        #此時目錄下已經生成了RDB快照
total 4
-rw-r--r--. 1 root root 32 Oct  8 19:09 dump.rdb
[root@redis-master redis]# redis-server /usr/local/redis/conf/redis.conf &  #後臺啓動redis
[root@redis-master redis]# redis-cli -a yunjisuan get names     #查詢redis中的鍵值對
"john"                  #數據恢復

進行無rdb文件的數據丟失測試

#登錄redis
[root@redis-master redis]# redis-cli -a yunjisuan
127.0.0.1:6379> keys *  #有數據
1) "names"
127.0.0.1:6379> quit    #退出
[root@redis-master redis]# redis-cli -a yunjisuan shutdown  #關閉服務
[root@redis-master redis]# netstat -antup | grep redis  #默認端口6379已經消失
tcp        0      0 0.0.0.0:6381                0.0.0.0:*                   LISTEN      3519/redis-server * 
tcp        0      0 :::6381                     :::*                        LISTEN      3519/redis-server * 
[root@redis-master redis]# pwd  #當前路徑位置
/usr/local/redis
[root@redis-master redis]# ll data/ #redis數據目錄下存在.rdb文件
total 4
-rw-r--r--. 1 root root 32 Oct  8 21:20 dump.rdb
[root@redis-master redis]# rm -rf data/dump.rdb #刪除.rdb文件
[root@redis-master redis]# ll data/     ##查看目錄爲空
total 0
[root@redis-master redis]# redis-server /usr/local/redis/conf/redis.conf &  #後臺啓動redis
[root@redis-master redis]# netstat -antup | grep redis
tcp        0      0 0.0.0.0:6379                0.0.0.0:*                   LISTEN      4022/redis-server * 
tcp        0      0 0.0.0.0:6381                0.0.0.0:*                   LISTEN      3519/redis-server * 
tcp        0      0 :::6379                     :::*                        LISTEN      4022/redis-server * 
tcp        0      0 :::6381                     :::*                        LISTEN      3519/redis-server * 
[root@redis-master redis]# redis-cli -a yunjisuan   #登錄redis
127.0.0.1:6379> keys *  #數據已經丟失
(empty list or set)

6.2.2 Append-Only File(追加式的操做日誌)

  • 另外因爲快照方式是在必定間隔時間作一次的,因此若是redis意外down掉的話,就會丟失最後一次快照後的全部修改。若是應用要求不能丟失任何修改的話,能夠採用aof持久化方式。下面介紹Append-only file。
  • aof比快照方式有更好的持久化性,是因爲在使用aof持久化方式時,redis會將每個收到的寫命令都經過write函數追加到文件中(默認是appendonly.aof)。當redis重啓時會經過從新執行文件中保存的寫命令來在內存中重建整個數據庫的內容.固然因爲os會在內核中緩存write作的修改,因此可能不是當即寫到磁盤上。這樣aof方式的持久化也仍是有可能會丟失部分修改。不過咱們能夠經過配置文件告訴redis咱們想要經過fsync函數強制os寫入到磁盤的時機。有三種方式以下(默認是:每秒fsync一次)
  • appendonly yes #啓用aof持久化方式
  • appendfsync always #收到寫命令就當即寫入磁盤,最慢,可是保證徹底的持久化
  • appendfsync everysec #美秒鐘寫入磁盤一次,在性能和持久化方面作了很好的折中
  • appendfsync no #徹底依賴os,性能最好,持久化沒保證
  • redis還支持一種追加式的操做日誌記錄,叫append only file,其日誌文件以aof結尾,咱們通常各爲aof文件。要開啓aof日誌的記錄,你須要在配置文件中進行以下設置:
appendonly yes

aof引起的問題:

aof的方式也同時帶來了另外一個問題。持久化文件會變得愈來愈大.例如咱們調用incr test命令100次,文件中必須保存所有的100條命令,其實有99條都是多餘的。由於要恢復數據庫的狀態其實文件中保存一條set test 100 就夠了。爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照相似的方式將內存中的數據以命令的方式保存到臨時文件中,最後替換原來的文件。具體過程以下:

  1. redis調用fork,如今有父子兩個進程
  2. 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令。
  3. 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來.這樣就能保證若是子進程重寫失敗的話並不會出問題。
  4. 當子進程把快照內容寫入已命令方式寫到臨時文件中後,子進程發信號通知父進程。而後父進程把緩存的寫命令也寫入到臨時文件。
  5. 如今父進程可使用臨時文件替換老的aof文件,並重命令名,後面收到的寫命令也開始往新的aof文件中追加。

須要注意到是重寫aof文件的操做,並無讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點相似。接下來咱們看一下實際的例子。

開啓bgrewriteaof重寫的方式

##開啓AOF
[root@redis-master redis]# cat -n /usr/local/redis/conf/redis.conf | grep 449
   449  appendonly yes                  #修改本行內容開啓AOF
   
#重啓redis服務
[root@redis-master redis]# redis-cli -a yunjisuan shutdown
[4022] 08 Oct 23:27:22.183 # User requested shutdown...
[4022] 08 Oct 23:27:22.183 * Saving the final RDB snapshot before exiting.
[4022] 08 Oct 23:27:22.195 * DB saved on disk
[4022] 08 Oct 23:27:22.195 # Redis is now ready to exit, bye bye...
[1]+  Done                    redis-server /usr/local/redis/conf/redis.conf
[root@redis-master redis]# redis-server /usr/local/redis/conf/redis.conf &

#關於bgrewriteaof重寫的配置文件代碼以下:
[root@redis-master ~]# cat -n /usr/local/redis/conf/redis.conf | sed -n '503,521p'
   503  # Automatic rewrite of the append only file.
   504  # Redis is able to automatically rewrite the log file implicitly calling
   505  # BGREWRITEAOF when the AOF log size grows by the specified percentage.
   506  # 
   507  # This is how it works: Redis remembers the size of the AOF file after the #它是如何工做的呢?redis會記住AOF文件的大小
   508  # latest rewrite (if no rewrite has happened since the restart, the size of #當最後一次重寫的時候,若是在重啓時沒有重寫發生。
   509  # the AOF at startup is used).  #那麼AOF文件會在開始時被使用
   510  #
   511  # This base size is compared to the current size. If the current size is
   512  # bigger than the specified percentage, the rewrite is triggered. Also
   513  # you need to specify a minimal size for the AOF file to be rewritten, this
   514  # is useful to avoid rewriting the AOF file even if the percentage increase
   515  # is reached but it is still pretty small.
   516  #
   517  # Specify a percentage of zero in order to disable the automatic AOF
   518  # rewrite feature.
   519  
   520  auto-aof-rewrite-percentage 100 #當100%達到最小大小的時候纔會執行重寫
   521  auto-aof-rewrite-min-size 64mb  #自動重寫aof文件的最小大小
相關文章
相關標籤/搜索