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


第3章 Redis數據類型詳解

3.1 Redis鍵/值介紹

Redis key值是二進制安全的,這意味着可以用任何二進制序列作為key值,從形如“foo”的簡單字符串到一個JPG文件的內容都可以。空字符串也是有效key值。

關於key的幾條規則:

  • 太長的鍵值不是個好主意,例如1024字節的鍵值就不是個好主意,不僅因為消耗內存,而且在數據中查找這類鍵值的計算成本很高。
  • 太短的鍵值通常也不是好主意,如果你要用“u:1000:pwd”來代替

user:1000:password,這沒有什么問題,但后者更易閱讀,並且由此增加的空間消耗相對於key object和value object本身來說很小。當然,沒人阻止你一定要用更短的鍵值節省一丁點兒空間。

  • 最好堅持一種模式。例如:"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還是多很多的,我們來玩兒一下字符串類型:

(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,最后將結果保存為新的字符串值,類似的命令如下列表:

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文件的最小大小


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM