redis緩存技術學習


1 什么是redis

redis是一個key-value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、 list(鏈表)、set(集合)和zset(有序集合)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操 作,而且這些操作都是原子性的。在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。區別的 是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,並且在此基礎上實現了master-slave(主從)同步。

2 性能怎么樣

Redis是一個高性能的key-value內存數據庫。官方性能測試結果:
set操作每秒110000次,get操作每秒81000次。

3 可不可以存對象

和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)和zset(有序集合)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操作。

4 Redis與memcache的最大區別

Replication(樹形)

data types(String、Lists、Sorted Sets、Hashes)

persistence (snapshot、aof)

很多開發者都認為Redis不可能比Memcached快,Memcached完全基於內存,而Redis具有持久化保存特性,即使是異步 的,Redis也不可能比Memcached快。但是測試結果基本是Redis占絕對優勢。一直在思考這個原因,目前想到的原因有這幾方面。

Libevent。和Memcached不同,Redis並沒有選擇libevent。Libevent為了迎合通用性造成代碼龐大(目前 Redis代碼還不到libevent的1/3)及犧牲了在特定平台的不少性能。Redis用libevent中兩個文件修改實現了自己的epoll event loop(4)。業界不少開發者也建議Redis使用另外一個libevent高性能替代libev,但是作者還是堅持Redis應該小巧並去依賴的思 路。一個印象深刻的細節是編譯Redis之前並不需要執行./configure。 

CAS問題。CAS是Memcached中比較方便的一種防止競爭修改資源的方法。CAS實現需要為每個cache key設置一個隱藏的cas token,cas相當value版本號,每次set會token需要遞增,因此帶來CPU和內存的雙重開銷,雖然這些開銷很小,但是到單機10G+ cache以及QPS上萬之后這些開銷就會給雙方相對帶來一些細微性能差別(5)。

5單台Redis的存放數據必須比物理內存小

Redis的數據全部放在內存帶來了高速的性能,但是也帶來一些不合理之處。比如一個中型網站有100萬注冊用戶,如果這些資料要用Redis來存 儲,內存的容量必須能夠容納這100萬用戶。但是業務實際情況是100萬用戶只有5萬活躍用戶,1周來訪問過1次的也只有15萬用戶,因此全部100萬用 戶的數據都放在內存有不合理之處,RAM需要為冷數據買單。

這跟操作系統非常相似,操作系統所有應用訪問的數據都在內存,但是如果物理內存容納不下新的數據,操作系統會智能將部分長期沒有訪問的數據交換到磁盤,為新的應用留出空間。現代操作系統給應用提供的並不是物理內存,而是虛擬內存(Virtual Memory)的概念。

基於相同的考慮,Redis 2.0也增加了VM特性。讓Redis數據容量突破了物理內存的限制。並實現了數據冷熱分離。

6 Redis的VM實現是重復造輪子

Redis的VM依照之前的epoll實現思路依舊是自己實現。但是在前面操作系統的介紹提到OS也可以自動幫程序實現冷熱數據分離,Redis只 需要OS申請一塊大內存,OS會自動將熱數據放入物理內存,冷數據交換到硬盤,另外一個知名的“理解了現代操作系統(3)”的Varnish就是這樣實 現,也取得了非常成功的效果。

作者antirez在解釋為什么要自己實現VM中提到幾個原因(6)。主要OS的VM換入換出是基於Page概念,比如OS VM1個Page是4K, 4K中只要還有一個元素即使只有1個字節被訪問,這個頁也不會被SWAP, 換入也同樣道理,讀到一個字節可能會換入4K無用的內存。而Redis自己實現則可以達到控制換入的粒度。另外訪問操作系統SWAP內存區域時block 進程,也是導致Redis要自己實現VM原因之一。

7 用get/set方式使用Redis

作為一個key value存在,很多開發者自然的使用set/get方式來使用Redis,實際上這並不是最優化的使用方法。尤其在未啟用VM情況下,Redis全部數據需要放入內存,節約內存尤其重要。

假如一個key-value單元需要最小占用512字節,即使只存一個字節也占了512字節。這時候就有一個設計模式,可以把key復用,幾個key-value放入一個key中,value再作為一個set存入,這樣同樣512字節就會存放10-100倍的容量。

這就是為了節約內存,建議使用hashset而不是set/get的方式來使用Redis

8使用aof代替snapshot

Redis有兩種存儲方式,默認是snapshot方式,實現方法是定時將內存的快照(snapshot)持久化到硬盤,這種方法缺點是持久化之后 如果出現crash則會丟失一段數據。因此在完美主義者的推動下作者增加了aof方式。aof即append only mode,在寫入內存數據的同時將操作命令保存到日志文件,在一個並發更改上萬的系統中,命令日志是一個非常龐大的數據,管理維護成本非常高,恢復重建時 間會非常長,這樣導致失去aof高可用性本意。另外更重要的是Redis是一個內存數據結構模型,所有的優勢都是建立在對內存復雜數據結構高效的原子操作 上,這樣就看出aof是一個非常不協調的部分。

其實aof目的主要是數據可靠性及高可用性,在Redis中有另外一種方法來達到目的:Replication。由於Redis的高性能,復制基本沒有延遲。這樣達到了防止單點故障及實現了高可用。

9 Redis是否支持集群

支持

  redis主從復制配置和使用都非常簡單。通過主從復制可以允許多個slave server擁有和master server相同的數據庫副本。下面是關於redis主從復制的一些特點
1.master可以有多個slave
2.除了多個slave連到相同的master外,slave也可以連接其他slave形成圖狀結構
3.主從復制不會阻塞master。也就是說當一個或多個slave與master進行初次同步數據時,master可以繼續處理client發來的請求。相反slave在初次同步數據時則會阻塞不能處理client的請求。
4.主從復制可以用來提高系統的可伸縮性,我們可以用多個slave 專門用於client的讀請求,比如sort操作可以使用slave來處理。也可以用來做簡單的數據冗余
5.可以在master禁用數據持久化,只需要注釋掉master 配置文件中的所有save配置,然后只在slave上配置數據持久化。
下面介紹下主從復制的過程
當 設置好slave服務器后,slave會建立和master的連接,然后發送sync命令。無論是第一次同步建立的連接還是連接斷開后的重新連 接,master都會啟動一個后台進程,將數據庫快照保存到文件中,同時master主進程會開始收集新的寫命令並緩存起來。后台進程完成寫文件 后,master就發送文件給slave,slave將文件保存到磁盤上,然后加載到內存恢復數據庫快照到slave上。接着master就會把緩存的命 令轉發給slave。而且后續master收到的寫命令都會通過開始建立的連接發送給slave。從master到slave的同步數據的命令和從 client發送的命令使用相同的協議格式。當master和slave的連接斷開時slave可以自動重新建立連接。如果master同時收到多個 slave發來的同步連接命令,只會使用啟動一個進程來寫數據庫鏡像,然后發送給所有slave。

在多台服務器上簡單實現Redis的數據主從復制

      Redis的主從復制功能非常強大,一個master可以擁有多個slave,而一個slave又可以擁有多個slave,如此下去,形成了強大的多級服 務器集群架構。下面我演示下怎樣在多台服務器上進行Redis數據主從復制。這里我假設有兩台服務器,一台是Windows操作系統(局域網 IP:192.168.3.82),一台是Linux操作系統(局域網IP:192.168.3.90),在兩個操作系統都安裝 redis,Windows操作系統使用cygwin工具進行安裝,命令為:

view sourceprint?

1

$ tar xzf redis-2.2.2.tar.gz 

2

$ cd redis-2.2.2 

3

$ make

可以通過"make test”命令判斷是否安裝成功。

這里我使用1個master以及2個slave(master在Windows下,一個slave在Windows下,一個slave在Linux下),基本流程是:

redis緩存技術學習

1. 在Windows服務器上創建兩個目錄,Demo1,Demo2,其中Demo1用來存放Master服務,Demo2用來存放Slave服務,

在Master服務中的配置文件修改:

view sourceprint?

1

bind 192.168.3.82

在Slave服務中的配置文件修改:

view sourceprint?

1

port 6381(服務端口號要分開) 

2

bind 192.168.3.82 

3

slaveof 192.168.3.82 6379 (設置master的Host以及Port)

2. 在Linux服務器上創建一個目錄,Demo,Demo存放Slave服務,在服務中的配置文件修改:

view sourceprint?

1

bind 192.168.3.90 

2

slaveof 192.168.3.82 6379(設置master的Host以及Port)

這樣就完成了所有的配置。

3. 現在運行這3個服務,通過命令:

view sourceprint?

1

./redis-server redis.conf

來啟動redis服務。

注意到,當我啟動master,然后啟動一個slave的時候,可以發現slave上:

redis緩存技術學習

會發送一個SYNC請求,從Master上面進行相應,而且它支持自動重連,即當master掉線的情況下,它會處於等待請求的狀態。

而Master上:

redis緩存技術學習

能夠接受Slave的應答,並且開始持久化操作,說明在Slave每次去連接Master的時候,都會去持久化磁盤。

4. 現在開始寫一個客戶端程序,使用到ServiceStack.Redis.dll的.NET組件:

view sourceprint?

01

using ServiceStack.Redis; 

02

03

static void Main(string[] args) 

04

05

    IRedisClientFactory factory = new RedisCacheClientFactory(); 

06

    IRedisClient client = factory.CreateRedisClient("192.168.3.82", 6379); 

07

08

    client.Set<STRING>("username", "leepy"); 

09

10

    string username = client.Get<STRING>("username"); 

11

12

    client.Save(); 

13

14

    Console.WriteLine("username: {0}", username); 

15

16

    Console.ReadLine(); 

17

}

運行結果:

redis緩存技術學習

數據Set的時候,數據保存在內存中,當調用Save方法時候,將數據保存在磁盤中。

其中你會發現在3個服務目錄中,都出現了dump.rdb,說明Master的文件都同步到Slave中去了。

redis緩存技術學習

redis緩存技術學習

用UE編輯器打開文件查看:

redis緩存技術學習
從Redis源碼中,可以發現rdb文件采用的是lzf壓縮算法進行實現,默認lzf壓縮算法是開啟的。

10安裝與使用

一、 下載安裝
Wget http://redis.googlecode.com/files/redis-2.2.7.tar.gz

二、.安裝部署

Redis代碼 redis緩存技術學習 redis緩存技術學習redis緩存技術學習

1. tar zxvf    

2. redis-2.2.7.tar.gz   

3. cd redis-2.2.7.tar.gz   

4. make  

tar zxvf 

redis-2.2.7.tar.gz

cd redis-2.2.7.tar.gz

make

可以將redis.conf 復制到 /etc/下 

Redis代碼 redis緩存技術學習 redis緩存技術學習redis緩存技術學習

1. cp redis.conf /etc/   

2. cp src/redis-server  src/redis-cli src/redis-benchmark /usr/local/redis  

cp redis.conf /etc/

cp src/redis-server  src/redis-cli src/redis-benchmark /usr/local/redis

啟動redis 

Redis代碼 redis緩存技術學習 redis緩存技術學習redis緩存技術學習

1. /usr/local/redis/redis-server redis.conf 

Port默認端口是 6379

簡單的測試:

存值:

./redis-cli set hx aaa

取值:

./redis-cli get hx

要配置參數的意義: 

· daemonize:是否以后台daemon方式運行 

· pidfile:pid文件位置 

· port:監聽的端口號 

· timeout:請求超時時間 

· loglevel:log信息級別 

· logfile:log文件位置 

· databases:開啟數據庫的數量 

· save * *:保存快照的頻率,第一個*表示多長時間,第三個*表示執行多少次寫操作。在一定時間內執行一定數量的寫操作時,自動保存快照。可設置多個條件。 

· rdbcompression:是否使用壓縮 

· dbfilename:數據快照文件名(只是文件名,不包括目錄) 

· dir:數據快照的保存目錄(這個是目錄) 

· appendonly:是否開啟appendonlylog,開啟的話每次寫操作會記一條log,這會提高數據抗風險能力,但影響效率。 

· appendfsync:appendonlylog如何同步到磁盤(三個選項,分別是每次寫都強制調用fsync、每秒啟用一次fsync、不調用fsync等待系統自己同步) 

Redis命令總結

Redis提供了豐富的命令(command)對數據庫和各種數據類型進行操作,這些command可以在Linux終端使用。在編程時,比如使用Redis 的java語言包,這些命令都有對應的方法,比如上面例子中使用的sadd方法,就是對集合操作中的SADD命令。下面將Redis提供的命令做一總結。

連接操作相關的命令

Ÿ quit:關閉連接(connection)

Ÿ auth:簡單密碼認證

對value操作的命令

Ÿ exists(key):確認一個key是否存在

Ÿ del(key):刪除一個key

Ÿ type(key):返回值的類型

Ÿ keys(pattern):返回滿足給定pattern的所有key

Ÿ randomkey:隨機返回key空間的一個key

Ÿ rename(oldname, newname):將key由oldname重命名為newname,若newname存在則刪除newname表示的key

Ÿ dbsize:返回當前數據庫中key的數目

Ÿ expire:設定一個key的活動時間(s)

Ÿ ttl:獲得一個key的活動時間

Ÿ select(index):按索引查詢

Ÿ move(key, dbindex):將當前數據庫中的key轉移到有dbindex索引的數據庫

Ÿ flushdb:刪除當前選擇數據庫中的所有key

Ÿ flushall:刪除所有數據庫中的所有key

對String操作的命令

Ÿ set(key, value):給數據庫中名稱為key的string賦予值value

Ÿ get(key):返回數據庫中名稱為key的string的value

Ÿ getset(key, value):給名稱為key的string賦予上一次的value

Ÿ mget(key1, key2,…, key N):返回庫中多個string(它們的名稱為key1,key2…)的value

Ÿ setnx(key, value):如果不存在名稱為key的string,則向庫中添加string,名稱為key,值為value

Ÿ setex(key, time, value):向庫中添加string(名稱為key,值為value)同時,設定過期時間time

Ÿ mset(key1, value1, key2, value2,…key N, value N):同時給多個string賦值,名稱為key i的string賦值value i

Ÿ msetnx(key1, value1, key2, value2,…key N, value N):如果所有名稱為key i的string都不存在,則向庫中添加string,名稱key i賦值為value i

Ÿ incr(key):名稱為key的string增1操作

Ÿ incrby(key, integer):名稱為key的string增加integer

Ÿ decr(key):名稱為key的string減1操作

Ÿ decrby(key, integer):名稱為key的string減少integer

Ÿ append(key, value):名稱為key的string的值附加value

Ÿ substr(key, start, end):返回名稱為key的string的value的子串

對List操作的命令

Ÿ rpush(key, value):在名稱為key的list尾添加一個值為value的元素

Ÿ lpush(key, value):在名稱為key的list頭添加一個值為value的 元素

Ÿ llen(key):返回名稱為key的list的長度

Ÿ lrange(key, start, end):返回名稱為key的list中start至end之間的元素(下標從0開始,下同)

Ÿ ltrim(key, start, end):截取名稱為key的list,保留start至end之間的元素

Ÿ lindex(key, index):返回名稱為key的list中index位置的元素

Ÿ lset(key, index, value):給名稱為key的list中index位置的元素賦值為value

Ÿ lrem(key, count, value):刪除count個名稱為key的list中值為value的元素。count為0,刪除所有值為value的元素,count>0從 頭至尾刪除count個值為value的元素,count<0從尾到頭刪除|count|個值為value的元素。

Ÿ lpop(key):返回並刪除名稱為key的list中的首元素

Ÿ rpop(key):返回並刪除名稱為key的list中的尾元素

Ÿ blpop(key1, key2,… key N, timeout):lpop命令的block版本。即當timeout為0時,若遇到名稱為key i的list不存在或該list為空,則命令結束。如果timeout>0,則遇到上述情況時,等待timeout秒,如果問題沒有解決,則對 key i+1開始的list執行pop操作。

Ÿ brpop(key1, key2,… key N, timeout):rpop的block版本。參考上一命令。

Ÿ rpoplpush(srckey, dstkey):返回並刪除名稱為srckey的list的尾元素,並將該元素添加到名稱為dstkey的list的頭部

對Set操作的命令

Ÿ sadd(key, member):向名稱為key的set中添加元素member

Ÿ srem(key, member) :刪除名稱為key的set中的元素member

Ÿ spop(key) :隨機返回並刪除名稱為key的set中一個元素

Ÿ smove(srckey, dstkey, member) :將member元素從名稱為srckey的集合移到名稱為dstkey的集合

Ÿ scard(key) :返回名稱為key的set的基數

Ÿ sismember(key, member) :測試member是否是名稱為key的set的元素

Ÿ sinter(key1, key2,…key N) :求交集

Ÿ sinterstore(dstkey, key1, key2,…key N) :求交集並將交集保存到dstkey的集合

Ÿ sunion(key1, key2,…key N) :求並集

Ÿ sunionstore(dstkey, key1, key2,…key N) :求並集並將並集保存到dstkey的集合

Ÿ sdiff(key1, key2,…key N) :求差集

Ÿ sdiffstore(dstkey, key1, key2,…key N) :求差集並將差集保存到dstkey的集合

Ÿ smembers(key) :返回名稱為key的set的所有元素

Ÿ srandmember(key) :隨機返回名稱為key的set的一個元素

對zset(sorted set)操作的命令

Ÿ zadd(key, score, member):向名稱為key的zset中添加元素member,score用於排序。如果該元素已經存在,則根據score更新該元素的順序。

Ÿ zrem(key, member) :刪除名稱為key的zset中的元素member

Ÿ zincrby(key, increment, member) :如果在名稱為key的zset中已經存在元素member,則該元素的score增加increment;否則向集合中添加該元素,其score的值為increment

Ÿ zrank(key, member) :返回名稱為key的zset(元素已按score從小到大排序)中member元素的rank(即index,從0開始),若沒有member元素,返回“nil”

Ÿ zrevrank(key, member) :返回名稱為key的zset(元素已按score從大到小排序)中member元素的rank(即index,從0開始),若沒有member元素,返回“nil”

Ÿ zrange(key, start, end):返回名稱為key的zset(元素已按score從小到大排序)中的index從start到end的所有元素

Ÿ zrevrange(key, start, end):返回名稱為key的zset(元素已按score從大到小排序)中的index從start到end的所有元素

Ÿ zrangebyscore(key, min, max):返回名稱為key的zset中score >= min且score <= max的所有元素

Ÿ zcard(key):返回名稱為key的zset的基數

Ÿ zscore(key, element):返回名稱為key的zset中元素element的score

Ÿ zremrangebyrank(key, min, max):刪除名稱為key的zset中rank >= min且rank <= max的所有元素

Ÿ zremrangebyscore(key, min, max) :刪除名稱為key的zset中score >= min且score <= max的所有元素

Ÿ zunionstore / zinterstore(dstkeyN, key1,…,keyN, WEIGHTS w1,…wN, AGGREGATE SUM|MIN|MAX):對N個zset求並集和交集,並將最后的集合保存在dstkeyN中。對於集合中每一個元素的score,在進行 AGGREGATE運算前,都要乘以對於的WEIGHT參數。如果沒有提供WEIGHT,默認為1。默認的AGGREGATE是SUM,即結果集合中元素 的score是所有集合對應元素進行SUM運算的值,而MIN和MAX是指,結果集合中元素的score是所有集合對應元素中最小值和最大值。

對Hash操作的命令

Ÿ hset(key, field, value):向名稱為key的hash中添加元素field<—>value

Ÿ hget(key, field):返回名稱為key的hash中field對應的value

Ÿ hmget(key, field1, …,field N):返回名稱為key的hash中field i對應的value

Ÿ hmset(key, field1, value1,…,field N, value N):向名稱為key的hash中添加元素field i<—>value i

Ÿ hincrby(key, field, integer):將名稱為key的hash中field的value增加integer

Ÿ hexists(key, field):名稱為key的hash中是否存在鍵為field的域

Ÿ hdel(key, field):刪除名稱為key的hash中鍵為field的域

Ÿ hlen(key):返回名稱為key的hash中元素個數

Ÿ hkeys(key):返回名稱為key的hash中所有鍵

Ÿ hvals(key):返回名稱為key的hash中所有鍵對應的value

Ÿ hgetall(key):返回名稱為key的hash中所有的鍵(field)及其對應的value

持久化

Ÿ save:將數據同步保存到磁盤

Ÿ bgsave:將數據異步保存到磁盤

Ÿ lastsave:返回上次成功將數據保存到磁盤的Unix時戳

Ÿ shundown:將數據同步保存到磁盤,然后關閉服務

遠程服務控制

Ÿ info:提供服務器的信息和統計

Ÿ monitor:實時轉儲收到的請求

Ÿ slaveof:改變復制策略設置

Ÿ config:在運行時配置Redis服務器

在192.168.134.96 上安裝了redis的master   

在192.168.134.97  上安裝了slave 綁定了192.168.134.96 

11 redis事務

redis對事務的支持目前還比較簡單。redis只能保證一個client發起的事務中的命令可以連續的執行,而中間不會插入其他client的 命令。 由於redis是單線程來處理所有client的請求的所以做到這點是很容易的。一般情況下redis在接受到一個client發來的命令后會立即處理並 返回處理結果,但是當一個client在一個連接中發出multi命令有,這個連接會進入一個事務上下文,該連接后續的命令並不是立即執行,而是先放到一 個隊列中。當從此連接受到exec命令后,redis會順序的執行隊列中的所有命令。並將所有命令的運行結果打包到一起返回給client.然后此連接就 結束事務上下文。下面可以看一個例子 

redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> exec
1. (integer) 1
2. (integer) 1

從這個例子我們可以看到incr a ,incr b命令發出后並沒執行而是被放到了隊列中。調用exec后倆個命令被連續的執行,最后返回的是兩條命令執行后的結果
我們可以調用discard命令來取消一個事務。接着上面例子 

redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> discard
OK
redis> get a
"1"
redis> get b
"1"

可以發現這次incr a incr b都沒被執行。discard命令其實就是清空事務的命令隊列並退出事務上下文。
雖說redis事務在本質上也相當於序列化隔離級別的了。但是由於事務上下文的命令只排隊並不立即執行,所以事務中的寫操作不能依賴事務中的讀操作結果。看下面例子

redis> multi
OK
redis> get a
QUEUED
redis> get b
QUEUED
redis> exec
1. "1"
2. "1"

發現問題了吧。假如我們想用事務實現incr操作怎么辦?可以這樣做嗎?

redis> get a
"1"
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
"2"

結論很明顯這樣是不行的。這樣和 get a 然后直接set a是沒區別的。很明顯由於get a 和set a並不能保證兩個命令是連續執行的(get操作不在事務上下文中)。很可能有兩個client同時做這個操作。結果我們期望是加兩次a從原來的1變成3. 但是很有可能兩個client的get a,取到都是1,造成最終加兩次結果卻是2。主要問題我們沒有對共享資源a的訪問進行任何的同步
也就是說redis沒提供任何的加鎖機制來同步對a的訪問。
還好redis 2.1后添加了watch命令,可以用來實現樂觀鎖。看個正確實現incr命令的例子,只是在前面加了watch a

redis> watch a
OK
redis> get a
"1"
redis> multi
OK
redis> set a 2
QUEUED
redis> exec
1. OK
redis> get a,
"2"

watch 命令會監視給定的key,當exec時候如果監視的key從調用watch后發生過變化,則整個事務會失敗。也可以調用watch多次監視多個key.這 樣就可以對指定的key加樂觀鎖了。注意watch的key是對整個連接有效的,事務也一樣。如果連接斷開,監視和事務都會被自動清除。當然了 exec,discard,unwatch命令都會清除連接中的所有監視.
redis的事務實現是如此簡單,當然會存在一些問題。第一個問題是redis只能保證事務的每個命令連續執行,但是如果事務中的一個命令失敗了,並不回滾其他命令,比如使用的命令類型不匹配。

redis> set a 5
OK
redis> lpush b 5
(integer) 1
redis> set c 5
OK
redis> multi
OK
redis> incr a
QUEUED
redis> incr b
QUEUED
redis> incr c
QUEUED
redis> exec
1. (integer) 6
2. (error) ERR Operation against a key holding the wrong kind of value
3. (integer) 6

可以看到雖然incr b失敗了,但是其他兩個命令還是執行了。
最 后一個十分罕見的問題是 當事務的執行過程中,如果redis意外的掛了。很遺憾只有部分命令執行了,后面的也就被丟棄了。當然如果我們使用的append-only file方式持久化,redis會用單個write操作寫入整個事務內容。即是是這種方式還是有可能只部分寫入了事務到磁盤。發生部分寫入事務的情況 下,redis重啟時會檢測到這種情況,然后失敗退出。可以使用redis-check-aof工具進行修復,修復會刪除部分寫入的事務內容。修復完后就 能夠重新啟動了。

12 redis的持久化

redis是一個支持持久化的內存數據庫,也就是說redis需要經常將內存中的數據同步到磁盤來保證持久化。redis支持兩種持久化方式,一種 是 Snapshotting(快照)也是默認方式,另一種是Append-only file(縮寫aof)的方式。下面分別介紹
Snapshotting
快照是默認的持久化方式。這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。可以通過配置設置自動做快照持久 化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置
save 900 1  #900秒內如果超過1個key被修改,則發起快照保存
save 300 10 #300秒內容如超過10個key被修改,則發起快照保存
save 60 10000
下面介紹詳細的快照保存過程
1.redis調用fork,現在有了子進程和父進程。
2. 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由於os的寫時復制機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會為父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數 據是fork時刻整個數據庫的一個快照。
3.當子進程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由於redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求。所以不推薦使用。另一點需要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,並不 是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。
另外由於快照方式是在一定間隔時間做一次的,所以如果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,性能最好,持久化沒保證
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文件,這點和快照有點類似。


免責聲明!

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



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