目錄
· 特點
· 安裝
· 數據庫
· 服務器命令
· 數據結構
· string
· list
· set
· hash
· zset
· 發布與訂閱
· 排序
· 事務
· pipeline
· 基准測試
· 鍵的過期
· 持久化
· 概況
· AOF
· 主從復制
· HA
· Lua
· 示例:分布式日志
特點
1. Redis是一個開源的、C語言編寫的、面向鍵值對類型數據的分布式NoSQL數據庫系統。
2. 特點:高性能(內存數據庫,隨機讀寫非常快)、持久存儲,適應高並發應用場景。
3. 對比:一些數據庫和緩存服務器的特性與功能。
名稱 |
類型 |
數據存儲 |
查詢類型 |
附加功能 |
Redis |
使用內存存儲(in-memory)的非關系數據庫 |
字符串、列表、集合、散列、有序集合 |
每種數據類型都有自己的專屬命令,另外還有批量操作(buld operation)和不完全(partial)事務支持 |
發布與訂閱,主從復制(master/slave replication),持久化,腳本 |
memcached |
使用內存存儲的鍵值緩存 |
鍵值之間的映射 |
創建命令、讀取命令、更新命令、刪除命令以及其他幾個命令 |
為提升性能而設的多線程服務器 |
MySQL |
關系數據庫 |
每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖;支持空間和第三方擴展 |
SELECT、INSERT、UPDATE、DELETE、函數、存儲過程 |
支持ACID(InnoDB),主從復制和主主復制 |
PostgreSQL |
關系數據庫 |
每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖;支持空間(spatial)和第三方擴展;支持定制類型 |
SELECT、INSERT、UPDATE、DELETE、函數、存儲過程 |
支持ACID(InnoDB),主從復制,由第三方支持的多主復制 |
MongoDB |
使用硬盤存儲(on-disk)的非關系數據庫 |
每個數據庫可以包含多個表,每個表可以包含多個無schema(schema-less)的BSON文檔 |
創建命令、讀取命令、更新命令、刪除命令、條件查詢命令等 |
支持map-reduce操作,主從復制,分片,空間索引(spatial index) |
4. 性能測試結果:set操作每秒可達110000次,get操作每秒81000次(與服務器配置有關)。
安裝
1. 安裝。
tar zxvf redis-3.2.0.tar.gz cd redis-3.2.0.tar.gz yum install gcc # 安裝依賴 cd deps make hiredis lua jemalloc linenoise geohash-int cd .. make # 編譯
2. 配置。
vi redis.conf
# bind 127.0.0.1 # 不綁定表示監聽所有IP protected-mode no # 無密碼 daemonize yes # 后台運行 logfile "/opt/app/redis-3.2.0/logs/redis.log" # 日志文件路徑 dir "/opt/app/redis-3.2.0/data/" # 快照文件路徑 appendonly yes # 開啟AOF
3. 啟動、關閉。
src/redis-server redis.conf # 啟動 src/redis-cli # 客戶端 src/redis-cli shutdown # 關閉
數據庫
1. Redis默認有16個數據庫。
2. 數據庫個數配置項:databases。
3. 切換數據庫命令:
127.0.0.1:6379> select 0 OK 127.0.0.1:6379> select 15 OK 127.0.0.1:6379[15]>
服務器命令
命令 |
說明 |
dbsize |
獲取當前數據庫中鍵的個數 |
info |
獲取服務器信息 |
select |
切換數據庫 |
config get |
config get config-key,獲取配置項config-key的值 |
數據類型及其操作命令
數據結構
1. 存儲鍵與5種不同數據結構類型之間的映射。
2. 鍵是string類型。
3. 5種數據類型:string、list、set、hash、zset。
4. 命令:部分命令(如del、type、rename)對於5種類型通用;部分命令只能對特定的一種或者兩種類型使用。另注:有很多命令尾部帶“nx”表示不存在鍵時才執行。
5. 常用通用命令。
命令 |
說明 |
keys |
keys pattern,獲取滿足pattern的所有鍵,支持通配符星號“*” |
exists |
exists key,判斷鍵key是否存在 |
del |
del key,刪除鍵key |
expire |
設置鍵的過期時間(后面詳細介紹) |
move |
move key database,將鍵key移動到數據庫database |
rename |
rename old-key new-key,將鍵old-key重命名為new-key |
type |
type key,獲取鍵的數據結構 |
string
1. 可以是字符串、整數或浮點數。
2. Redis的字符串是由字節組成的序列。
3. 對於整數、浮點數的字符串可執行自增和自減;對無法解釋成整數或浮點數的字符串執行自增或自減會返回錯誤。
4. 常用命令。
命令 |
說明 |
get |
獲取給定鍵的值 |
set |
設置給定鍵的值 |
incr |
incr key-name,將鍵存儲的值加上1 |
decr |
decr key-name,將鍵存儲的值減去1 |
incrby |
incrby key-name amount,將鍵存儲的值加上整數amont |
decrby |
decrby key-name amount,將鍵存儲的值減去整數amont |
incrbyfloat |
incrbyfloat key-name amount,將鍵存儲的值加上浮點數amont |
append |
append key-name value,將值value追加到給定鍵key-name當前存儲的值的末尾 |
getrange |
getrange key-name start end,獲取一個偏移量start至偏移量end范圍內所有字符組成的子串,包括start和end在內 |
setrange |
setrange key-name offset value,將從start偏移量開始的子串設置為給定值 |
getbit |
getbit key-name offset value,將字節串看作是二進制位串(bit string),並返回位串中偏移量為offset的二進制位的值 |
setbit |
setbit key-name offset value,將字節串看作是二進制位串,並將位串中偏移量為offset的二進制位的值設置為value |
bitcount |
bitcount key-name [start end],統計二進制位串里面值為1的二進制位的數量,如果給定了可選的start偏移量和end偏移量,那么只對偏移量指定范圍內二進制位進行統計 |
bitop |
bitop operation dest-key key-name [key-name ...],對一個或多個二進制位串執行包括並and、或or、異或xor、非not在內的任意一種按位運算,並將計算結果保存在dest-key鍵里面 |
5. 舉例。
127.0.0.1:6379> set hello world OK 127.0.0.1:6379> get hello "world" 127.0.0.1:6379> del hello (integer) 1 127.0.0.1:6379> get hello (nil) 127.0.0.1:6379> set num 100 OK 127.0.0.1:6379> incrby num 10 (integer) 110 127.0.0.1:6379> append num abc (integer) 6 127.0.0.1:6379> get num "110abc" 127.0.0.1:6379> getrange num 2 4 "0ab"
list
1. Redis的list是鏈表(linked-list)。
2. 應用:列表、棧、隊列、消息隊列MQ等。
3. 命令。
命令 |
說明 |
rpush |
rpush key-name value [value ...],將一個或多個值推入列表的右端 |
lpush |
lpush key-name value [value ...],將一個或多個值推入列表的左端 |
rpop |
rpop key-name,移除並返回列表最右端的元素 |
lpop |
lpop key-name,移除並返回列表最左端的元素 |
lindex |
lindex key-name offset,返回列表中偏移量為offset的元素 |
lrange |
lrange key-name start end,返回列表從start偏移量到end偏移量范圍內的所有元素,其中偏移量為start和偏移量為end的元素也會包含在內 |
ltrim |
ltrim key-name start end,對列表進行修剪,只保留從start偏移量到end偏移量范圍內的元素,其中偏移量為start和偏移量為end的元素也會被保留 |
blpop |
blpop key-name [key-name…] timeout,從第一個非空列表中彈出位於最左端的元素,或者在timeout秒之內阻塞並等待可彈出的元素出現 |
brpop |
brpop key-name [key-name…] timeout,從第一個非空列表中彈出位於最右端的元素,或者在timeout秒之內阻塞並等待可彈出的元素出現 |
rpoplpush |
rpoplpush source-key dest-key,從source-key列表中彈出位於最右端的元素,然后將這個元素推入dest-key列表的最左端,並向用戶返回這個元素 |
brpoplpush |
brpoplpush source-key dest-key timeout,從source-key列表中彈出位於最右端的元素,然后將這個元素推入dest-key列表的最左端,並向用戶返回這個元素;如果source-key為空,那么在timeout秒之內阻塞並等待可彈出的元素出現 |
4. 舉例。
127.0.0.1:6379> rpush list-key item1 (integer) 1 127.0.0.1:6379> rpush list-key item2 item1 (integer) 3 127.0.0.1:6379> lpush list-key item0 (integer) 4 127.0.0.1:6379> lrange list-key 0 -1 1) "item0" 2) "item1" 3) "item2" 4) "item1" 127.0.0.1:6379> lindex list-key 3 "item1" 127.0.0.1:6379> lpop list-key "item0" 127.0.0.1:6379> ltrim list-key 0 1 OK 127.0.0.1:6379> lrange list-key 0 -1 1) "item1" 2) "item2"
set
1. list允許有重復值,set不允許有重復值。
2. list是有序的,set是無序的。
3. set通過hash保證值不重復(這些hash表只有鍵,沒有與鍵對應的值)。
4. 應用:去重列表、集合運算(交、並、差集)。
5. 命令。
命令 |
說明 |
sadd |
sadd key-name item [item...],將一個或多個元素添加到集合里面,並返回被添加元素當中原本並不存在於集合里面的元素數量 |
srem |
srem key-name item [item...],從集合里面移除一個或多個元素,並返回被移除元素的數量 |
sismember |
sismember key-name item,檢查元素item是否存在於集合key-name里 |
scard |
scard key-anem,返回集合包含的元素數量 |
smembers |
smembers key-name,返回集合包含的所有元素 |
srandmember |
srandmember key-name [count],從集合里面隨機地返回一個或多個元素。當count為正數時,命令返回的隨機元素不會重復;當count為負數時,命令返回的隨機元素可能會出現重復 |
spop |
spop key-name,隨機地移除集合中一個元素,並返回移除的元素 |
smove |
smove source-key dest-key item,如果集合source-key包含元素item,那么從集合source-key里面移除元素item,並將元素item添加到集合dest-key中;如果item被成功移除,那么命令返回1,否則返回0 |
sdiff |
sdiff key-name [key-name…],返回那些存在於第一個集合但不存在於其他集合中的元素(數學上的差集運算) |
sdiffstore |
sdiffstore dest-key key-name [key-name…],將那些存在於第一個集合但不存在於其他集合中的元素(數學上的差集運算)存儲到dest-key鍵里面 |
sinter |
sinter key-name [key-name…],返回那些同時存在於所有集合的元素(數學上的交集運算) |
sinterstore |
sinterstore dest-key key-name [key-name…],將那些同時存在於所有集合的元素(數學上的交集運算)存儲到dest-key鍵里面 |
sunion |
sunion key-name [key-name…],返回那些至少存在於一個集合中的元素(數學上的並集運算) |
sunionstore |
sunionstore dest-key key-name [key-name…],將那些至少存在於一個集合中的元素(數學上的並集運算)存儲到dest-key鍵里面 |
6. 舉例。
127.0.0.1:6379> sadd set-key item0 (integer) 1 127.0.0.1:6379> sadd set-key item1 item2 (integer) 2 127.0.0.1:6379> sadd set-key item0 (integer) 0 127.0.0.1:6379> smembers set-key 1) "item2" 2) "item1" 3) "item0" 127.0.0.1:6379> sismember set-key item3 (integer) 0 127.0.0.1:6379> sismember set-key item0 (integer) 1 127.0.0.1:6379> srem set-key item2 (integer) 1 127.0.0.1:6379> srem set-key item2 (integer) 0 127.0.0.1:6379> smembers set-key 1) "item1" 2) "item0"
hash
1. Redis的散列可以存儲多個鍵值對之間的映射,在很多方面就像是一個微縮版的Redis。
2. 命令。
命令 |
說明 |
hset |
在散列里面關聯起給定的鍵值對 |
hget |
獲取指定散列鍵的值 |
hmget |
hmget key-name key [key...],從散列里面獲取一個或多個鍵的值 |
hmset |
hmget key-name key value [key value...],為散列里面的一個或多個鍵設置值 |
hgetall |
獲取散列包含的所有鍵值對 |
hdel |
如果給定鍵存在於散列里面,那么移除這個鍵 |
hlen |
hlen key-name,返回散列包含的鍵值對數量 |
hexists |
hexists key-name key,檢查給定鍵是否存在於散列中 |
hkeys |
hkeys key-name,獲取散列包含的所有鍵 |
hvals |
hvals key-name,獲取散列包含的所有值 |
hincrby |
hincrby key-name key increment,將鍵key保存的值加上整數increment |
hincrbyfloat |
hincrbyfloat key-name key increment,將鍵key保存的值加上浮點數increment |
3. 舉例。
127.0.0.1:6379> hset hash-key sub-key0 value0 (integer) 1 127.0.0.1:6379> hset hash-key sub-key1 value1 (integer) 1 127.0.0.1:6379> hmset hash-key sub-key2 value2 sub-key3 value3 OK 127.0.0.1:6379> hset hash-key sub-key0 value0 (integer) 0 127.0.0.1:6379> hgetall hash-key 1) "sub-key0" 2) "value0" 3) "sub-key1" 4) "value1" 5) "sub-key2" 6) "value2" 7) "sub-key3" 8) "value3" 127.0.0.1:6379> hdel hash-key sub-key3 (integer) 1 127.0.0.1:6379> hmget hash-key sub-key0 sub-key1 1) "value0" 2) "value1" 127.0.0.1:6379> hget hash-key sub-key2 "value2" 127.0.0.1:6379> hkeys hash-key 1) "sub-key0" 2) "sub-key1" 3) "sub-key2" 127.0.0.1:6379> hvals hash-key 1) "value0" 2) "value1" 3) "value2"
4. 應用:可以把hash看作關系數據庫的行,hash中的key為字段名,hash中的value為字段值。以用戶為例,新增/查詢ID為1和2的兩個用戶:
127.0.0.1:6379> hmset user:1 name zhangsan age 18 OK 127.0.0.1:6379> hmset user:2 name lisi age 19 OK 127.0.0.1:6379> hgetall user:1 1) "name" 2) "zhangsan" 3) "age" 4) "18" 127.0.0.1:6379> hgetall user:2 1) "name" 2) "lisi" 3) "age" 4) "19"
zset
1. zset和hash一樣,都用於存儲鍵值對。
2. zset的鍵稱為成員(member),不允許重復。
3. zset的值稱為分值(score),必須是浮點數。
4. zset既可以根據member訪問元素(與hash相同),也可以根據分值及分值的排序順序來訪問元素。
5. 應用:排序、去重。
6. 命令。
命令 |
說明 |
zadd |
zadd key-name score member [score member...],將帶有給定分值的成員添加到有序集合里面 |
zrem |
zrem key-name member [member...],從有序集合里面移除給定的成員,並返回被移除成員的數量 |
zcard |
zcard key-name,返回有序集合包含的成員數量 |
zincrby |
zincrby key-name increment member,將member成員的分值加上increment |
zcount |
zcount key-name min max,返回分值介於min和max之間的成員數量 |
zrank |
zrank key-name member,返回成員member在key-name中的排名 |
zscore |
zscore key-name member,返回成員member的分值 |
zrange |
zrange key-name start stop [withscores],返回有序集合中排名介於start和stop之間的成員,如果給定了可選的withscores選項,那么命令會將成員的分值也一並返回 |
zrevrank |
zrevrank key-name member,返回有序集合里成員member所處的位置,成員按照分值從大到小排列 |
zrevrange |
zrevrange key-name start stop [withscores],返回有序集合給定排名范圍內的成員,成員按照分值從大到小排列 |
zrangebyscore |
zrangebyscore key min max [withscores] [limit offset count],返回有序集合中,分值介於min和max之間的所有成員 |
zrevrangebyscore |
zrevrangebyscore key max min [withscores] [limit offset count],獲取有序集合中分值介於min和max之間的所有成員,並按照分值從大到小的順序來返回它們 |
zremrangebyrank |
zremrangebyrank key-name start stop,移除有序集合中排名介於start和stop之間的所有成員 |
zremrangebyscore |
zremrangebyscore key-name min max,移除有序集合中分值介於min和max之間的所有成員 |
zinterstore |
zinterstore dest-key key-count key [key...] [weights weight [weight...]] [aggregate sum|min|max],對給定的有序集合執行類似於集合的交集運算 |
zunionstore |
zunionstore dest-key key-count key [key...] [weights weight [weight...]] [aggregate sum|min|max],對給定的有序集合執行類似於集合的並集運算 |
7. 舉例。
127.0.0.1:6379> zadd zset-key 200 member1 (integer) 1 127.0.0.1:6379> zadd zset-key 300 member0 400 member2 (integer) 2 127.0.0.1:6379> zadd zset-key 100 member1 (integer) 0 127.0.0.1:6379> zcard zset-key (integer) 3 127.0.0.1:6379> zcount zset-key 100 350 (integer) 2 127.0.0.1:6379> zrank zset-key member1 (integer) 0 127.0.0.1:6379> zrank zset-key member1 (integer) 0 127.0.0.1:6379> zscore zset-key member1 "100" 127.0.0.1:6379> zrange zset-key 1 2 1) "member0" 2) "member2" 127.0.0.1:6379> zrevrank zset-key member1 (integer) 2 127.0.0.1:6379> zrevrange zset-key 1 2 1) "member0" 2) "member1" 127.0.0.1:6379> zrangebyscore zset-key 100 350 1) "member1" 2) "member0" 127.0.0.1:6379> zrevrangebyscore zset-key 100 350 (empty list or set) 127.0.0.1:6379> zrevrangebyscore zset-key 350 100 1) "member0" 2) "member1"
8. zinterstore交集舉例。
9. zunionstore並集舉例。
發布與訂閱
1. 發布與訂閱(pub/sub)的特點是訂閱者(listener)負責訂閱頻道(channel),發送者(publisher)負責向頻道發送二進制字符串消息(binary string message)。
2. 當有消息被發送至給定頻道時,頻道的所有訂閱者都會收到消息。
3. 備注:將list作為隊列,同時使用阻塞命令同樣可以實現發布/訂閱,具體代碼參見“示例:分布式日志”。
4. 命令。
命令 |
說明 |
subscribe |
subscribe channel [channel...],訂閱給定的一個或多個頻道 |
unsubscribe |
unsubscribe [channel [channel...]],退訂給定的一個或多個頻道,如果執行時沒有給定任何頻道,那么退訂所有頻道 |
psubscribe |
psubscribe pattern [pattern...],訂閱與給定模式相匹配的所有頻道 |
punsubscribe |
punsubscribe [pattern [pattern...]],退訂給定的模式,如果執行時沒有給定任何模式,那么退訂所有模式 |
publish |
publish channel message,向給定頻道發送消息 |
5. 舉例。
127.0.0.1:6379> subscribe channel0 channel1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channel0" 3) (integer) 1 1) "subscribe" 2) "channel1" 3) (integer) 2 1) "message" 2) "channel0" 3) "hello" 1) "message" 2) "channel1" 3) "world"
127.0.0.1:6379> publish channel0 hello (integer) 1 127.0.0.1:6379> publish channel1 world (integer) 1
6. 訂閱者讀取速度。
a) 問題:如果訂閱者讀取消息速度不夠快,那么不斷積壓的消息會使Redis輸出緩沖區的體積越來越大,可能會導致Redis速度變慢,甚至崩潰。
b) 解決:自動斷開不符合client-output-buffer-limit pubsub配置選項的訂閱客戶端。
7. 數據傳輸可靠性。
a) 問題:網絡連接錯誤會使網絡連接兩端中的其中一端重新連接,導致客戶端丟失在短線期間的所有消息。
b) 解決:TODO 第六章兩個不同方法。
排序
1. 對list、set、zset排序。
2. 命令。
命令 |
說明 |
sort |
sort source-key [by pattern] [limit offset count] [get pattern [get pattern...]] [asc|desc] [alpha] [store dest-key],根據給定的選項,對輸入列表、集合或者有序集合進行排序,然后返回或者存儲排序的結果 |
3. 舉例。
127.0.0.1:6379> rpush sort-key v1 v0 v3 v4 v2 (integer) 5 127.0.0.1:6379> sort sort-key alpha 1) "v0" 2) "v1" 3) "v2" 4) "v3" 5) "v4" 127.0.0.1:6379> sort sort-key alpha desc 1) "v4" 2) "v3" 3) "v2" 4) "v1" 5) "v0"
事務
1. Redis的基本事務(basic transaction)可以讓一個客戶端在不被其他客戶端打斷的情況下執行多個命令。
2. 與關系數據庫不同,Redis的基本事務在執行完事務內所有命令后,才會處理其他客戶端的命令。
3. 命令。
命令 |
說明 |
multi |
標記一個事務開始。 |
exec |
執行所有multi之后的命令 |
discard |
丟棄所有multi之后的命令 |
watch |
對指定鍵監視,直到執行exec命令結束。如果期間其他客戶端對被監視的鍵執行寫入命令,那么當前客戶端執行exec命令時將報錯。相當於樂觀鎖。 |
unwatch |
取消監視。如果執行exec或discard命令,則無需再執行unwatch命令。 |
4. 舉例。
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k0 v0 QUEUED 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> exec 1) OK 2) OK
127.0.0.1:6379> watch k0 k1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set k0 v0x QUEUED 127.0.0.1:6379> set k1 v1x QUEUED 127.0.0.1:6379> exec
5. Redis事務內有部分命令失敗時,整個事務不會自動discard,導致事務內可能部分命令成功,部分失敗。舉例:
127.0.0.1:6379> set str-key halo OK 127.0.0.1:6379> set num-key 100 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr str-key QUEUED 127.0.0.1:6379> incr num-key QUEUED 127.0.0.1:6379> exec 1) (error) ERR value is not an integer or out of range 2) (integer) 101 127.0.0.1:6379> get str-key "halo" 127.0.0.1:6379> get num-key "101"
pipeline
1. 應用程序連接Redis執行事務及事務中所有命令(5個)時,一定要使用pipeline。
2. 由於pipeline會一次發送所有命令,可減少通信次數並降低延遲,在非事務時也推薦使用。
基准測試
1. Redis附帶基准測試程序redis-benchmark。
2. 舉例:模擬單個客戶端。
src/redis-benchmark -c 1 -q PING_INLINE: 77399.38 requests per second PING_BULK: 81566.07 requests per second SET: 58513.75 requests per second GET: 80840.74 requests per second INCR: 57208.24 requests per second LPUSH: 54229.93 requests per second RPUSH: 55555.56 requests per second LPOP: 55401.66 requests per second RPOP: 57937.43 requests per second SADD: 77459.34 requests per second SPOP: 79113.92 requests per second LPUSH (needed to benchmark LRANGE): 54495.91 requests per second LRANGE_100 (first 100 elements): 37271.71 requests per second LRANGE_300 (first 300 elements): 16537.13 requests per second LRANGE_500 (first 450 elements): 11799.41 requests per second LRANGE_600 (first 600 elements): 9273.00 requests per second MSET (10 keys): 31735.96 requests per second
3. 應用程序在使用pipeline和連接池的情況下,基本與上面模擬的測試性能一致。
鍵的過期
1. 設置鍵的過期時間,讓鍵在在給定的時限后自動被刪除(相當於執行del命令)。
2. 只能設置整個鍵的過期時間(支持5中數據結構),無法設置list、set、hash和zset中單個元素的過期時間。
3. 命令。
命令 |
說明 |
persist |
persist key-name,移除鍵的過期時間 |
ttl |
ttl key-name,返回給定鍵距離過期還有多少秒 |
expire |
expire key-name seconds,讓鍵key-name在給定的seconds秒之后過期 |
expireat |
expireat key-name timestamp,將給定鍵的過期時間設置為給定的UNIX時間戳 |
pttl |
pttl key-name,返回給定鍵距離過期時間還有多少毫秒,這個命令在Redis 2.6或以上版本可用 |
pexpire |
pexpire key-name milliseconds,讓鍵key-name在milliseconds毫秒之后過期 |
pexpireat |
pexpireat key-name timestamp-milliseconds,將一個毫秒級精度的UNIX時間戳設置為給定鍵的過期時間 |
4. 舉例。
127.0.0.1:6379> set expire-key v OK 127.0.0.1:6379> ttl expire-key (integer) -1 127.0.0.1:6379> expire expire-key 10 (integer) 1 127.0.0.1:6379> ttl expire-key (integer) 7 127.0.0.1:6379> get expire-key (nil)
持久化
概況
1. 兩種持久化方式:
a) snapshoting(快照):將某一時刻內存中所有數據寫入硬盤;
b) AOF(append-only file):執行寫入命令時,將命令追加到硬盤文件。
2. 兩種持久化方式可同時使用,也可單獨使用,某些情況下也可以兩種都不使用。
3. 配置。
save 60 1000 # snapshoting配置 stop-writes-on-bgsave-error no rdbcompression yes rdbchecksum yes dbfilename dump.rdb
appendonly no # AOF配置 appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb aof-load-truncated yes
dir ./ # 共用配置
snapshoting
1. 創建snapshoting的方法/時機。
a) 執行bgsave命令。Redis會調用fork創建一個子進程,子進程負責將快照寫入硬盤,父進程繼續處理命令請求。
b) 執行save命令。Redis在創建快照完成之前不再響應任何其他命令。不常用,通常只會在內存不足時使用。
c) 設置save配置。“save 60 100000”表示當滿足“60秒之內有10000次寫入”條件時,自動觸發bgsave命令。如果有多個save配置,那么任意一個條件滿足時都會觸發。
d) 執行shutdown命令或收到標准term信號時,會先觸發save命令(不再響應任何客戶端請求)。
e) 一個Redis服務器連接另一個Redis服務器,並向對方發送sync命令開始一次復制時,如果主服務器目前沒有執行bgsave命令,或主服務器並非剛剛執行完bgsave命令,那么主服務器會執行gbsave命令。
2. snapshoting注意:如果系統真的發生崩潰,將丟失最近一次生成快照后更新的所有數據。
3. snapshoting與大數據:如果Redis內存占用高達幾十GB,並且空閑內存不多,或者硬件性能較差時,執行bgsave命令可能會導致長時間停頓(幾秒,甚至幾十分鍾),也可能引發系統大量使用虛擬內存,從而導致Redis性能降低至無法使用。
AOF
1. appendfsync同步頻率。
a) always。每個寫命令都同步寫入硬盤。嚴重降低Redis性能。降低固態硬盤SSD壽命。
b) everysec。每秒同步一次,將多個寫命令同步到硬盤。兼顧數據安全和寫入性能。
c) no。讓操作系統決定何時同步。一般不影響性能,但崩潰將導致不定數量的數據丟失。不推薦。
2. 重寫AOF文件:移除AOF文件中的冗余命令,壓縮AOF文件體積。
3. 重寫AOF文件解決的問題。
a) 隨着Redis不斷運行,AOF文件不斷增大,極端時甚至用完硬盤空間。
b) Redis重啟后需要重新執行AOF文件中的寫命令還原數據,如果AOF文件非常大,那么還原時間可能會非常長。
4. 重寫AOF文件的方法/時機。
a) 執行bgrewriteaof命令。與bgsave命令相似,Redis會創建一個子進程負責AOF文件重寫,也存在影響性能的問題。
b) 設置auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置。“auto-aof-rewrite-percentage 100”和“auto-aof-rewrite-min-size 64mb”表示當AOF文件大於64MB且AOF文件比上次重寫后至少大一倍(100%)時,觸發bgrewriteaof命令。
主從復制
1. 解決:雖然Redis性能優秀,但也會有無法快速處理請求的情況。伸縮(scalability)。
2. 客戶端效果:客戶端每次向主服務器執行寫入命令時,從服務器都會實時更新,客戶端就可以向任意一個服務器執行讀取命令。
3. 配置:
a) 主服務器設置dir和dbfilename配置。保證從服務器連接主服務器時,主服務器能執行bgsave操作。
b) 從服務器設置slaveof host port配置,或執行slaveof host port命令。讓從服務器復制主服務器。slaveof no one命令可終止復制。
4. 從服務器連接主服務器的過程。
步驟 |
主服務器 |
從服務器 |
1 |
(等待命令進入) |
連接(或重連)主服務器,發送sync命令 |
2 |
開始執行bgsave命令,並使用緩沖區記錄bgsave之后執行的所有寫命令 |
根據配置決定繼續使用現有數據(如果有)來處理客戶端請求,還是向發送請求的客戶端返回錯誤 |
3 |
bgsave執行完畢,向從服務器發送快照文件,並在發送期間繼續使用緩沖區記錄被執行的寫命令 |
丟棄所有舊數據(如果有),開始載入主服務器發來的快照文件 |
4 |
快照文件發送完畢,開始向從服務器發送緩沖區中的寫命令 |
完成對快照文件的解釋操作,像往常一樣開始接收請求 |
5 |
緩沖區的寫命令發送完畢,從此,每執行一個寫命令,就向從服務器發送相同的寫命令 |
執行主服務器發送來的緩沖區中的寫命令,從此,接收並執行主服務器傳來的每個寫命令 |
5. 優化:實際中最好讓主服務器只使用50%~65%的內存,剩余30%~45%內存用於執行bgsave命令和創建記錄寫命令的緩沖區。
6. 主從鏈:從服務器也可以擁有自己的從服務器,由此形成主從鏈(master/slave chaning)。
7. 主從鏈解決問題。
a) 讀請求遠多於寫請求。
b) 負荷上升,主服務器無法快速更新所有從服務器。
8. 主從鏈結構:不一定是樹狀結構。
9. 更換故障主服務器步驟:
a) 從服務器執行save命令,生成最新快照文件;
b) 將快照文件復制到新主服務器;
c) 配置並啟動新主服務器;
d) 從服務器連接新主服務器。
HA
1. Redis-Sentinel是Redis的Master-Slave高可用方案。
2. Master宕機時,自動完成主備切換。
3. 資料:http://redis.cn/topics/sentinel.html。
Lua
1. Redis中的Lua類似關系數據庫中的存儲過程,可封裝邏輯。
2. Lua腳本跟單個Redis命令以及“multi/exec”事務一樣,都是原子操作,因此可替代事務。
3. 命令。
命令 |
說明 |
eval |
eval script numkeys key [key...] arg [arg...],執行腳本script,numkeys表示要使用的鍵個數,key表示鍵,arg表示參數。腳本內部通過KEYS數組獲取鍵,如KEYS[1]獲取第1個鍵;通過ARGV數組獲取參數,如ARGV[1]獲取第1個參數 |
evalsha |
evalsha sha1 numkeys key [key...] arg [arg...],根據SHA1校驗碼執行腳本 |
script load |
script load script,加載腳本script,返回SHA1校驗碼 |
script exists |
script exists sha1,根據SHA1校驗碼判斷腳本是否已加載 |
script flush |
清除全部腳本 |
script kill |
停止當前正在執行的腳本 |
4. 舉例(第3個證明腳本中Redis命令執行失敗時不會discard已執行過的命令,即“事務”提到的第5點)。
$ redis-cli eval "return 'Hello World'" 0 "Hello World"
$ redis-cli eval "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 arg1 arg2 1) "key1" 2) "key2" 3) "arg1" 4) "arg2"
$ vi test.lua local ret = redis.call("set", KEYS[1], ARGV[1]) if redis.call("exists", KEYS[2]) == 1 then redis.call("incr", KEYS[2]) else redis.call("set", KEYS[2], ARGV[2]) end return ret $ redis-cli script load "$(cat test.lua)" "07aa590946287d9ae0c3df41dd9ba06a64280d85" $ redis-cli evalsha 07aa590946287d9ae0c3df41dd9ba06a64280d85 2 mykey1 mykey2 myarg1 myarg2 OK $ redis-cli evalsha 07aa590946287d9ae0c3df41dd9ba06a64280d85 2 mykey1 mykey2 myarg1111111 myarg2 (error) ERR Error running script (call to f_07aa590946287d9ae0c3df41dd9ba06a64280d85): @user_script:3: ERR value is not an integer or out of range $ redis-cli get mykey1 "myarg1111111" $ redis-cli get mykey2 "myarg2"
示例:分布式日志
1. 生產者-消費者模式。
2. 多台機器將日志保存到Redis隊列,一個線程從該隊列取出日志並保存到日志文件。個數比:生產者:消費者=N:1。
3. 代碼(使用Jedis API):
1 import static gz.redis.DistributedLog.HOST; 2 import static gz.redis.DistributedLog.LOG_QUEUE_KEY; 3 import static gz.redis.DistributedLog.PORT; 4 import static gz.redis.DistributedLog.connection; 5 6 import java.io.BufferedWriter; 7 import java.io.FileWriter; 8 import java.io.IOException; 9 import java.util.Date; 10 import java.util.List; 11 import java.util.UUID; 12 import java.util.concurrent.ExecutorService; 13 import java.util.concurrent.Executors; 14 15 import redis.clients.jedis.Jedis; 16 import redis.clients.jedis.JedisPool; 17 import redis.clients.jedis.JedisPoolConfig; 18 19 public class DistributedLog { 20 21 static final String HOST = "centos1"; 22 23 static final int PORT = 6379; 24 25 private static JedisPool jedisPool; 26 27 static final String LOG_QUEUE_KEY = "log-queue"; 28 29 public static void main(String[] args) { 30 initConnectionPoll(); 31 32 ExecutorService threadPool = Executors.newFixedThreadPool(50); 33 for (int index = 0; index < 500000; index++) { 34 threadPool.execute(new Writer()); 35 } 36 new Thread(new Processor()).run(); 37 threadPool.shutdown(); 38 } 39 40 private static void initConnectionPoll() { 41 if (jedisPool == null) { 42 JedisPoolConfig config = new JedisPoolConfig(); 43 config.setMaxTotal(51); 44 config.setMinIdle(51); 45 config.setMaxIdle(51); 46 config.setMaxWaitMillis(60 * 1000); 47 config.setTestOnCreate(true); 48 config.setTestOnReturn(true); 49 config.setTestOnBorrow(true); 50 config.setTestWhileIdle(true); 51 jedisPool = new JedisPool(config, HOST, PORT); 52 } 53 } 54 55 static Jedis connection() { 56 return jedisPool.getResource(); 57 } 58 59 } 60 61 class Writer implements Runnable { 62 63 @Override 64 public void run() { 65 String log = new Date() + " - " + UUID.randomUUID() + "\n"; 66 Jedis jedis = null; 67 try { 68 jedis = connection(); 69 // 隊尾追加 70 jedis.rpush(LOG_QUEUE_KEY, log); 71 } finally { 72 if (jedis != null) { 73 jedis.close(); 74 } 75 } 76 } 77 78 } 79 80 class Processor implements Runnable { 81 82 @Override 83 public void run() { 84 BufferedWriter writer = null; 85 Jedis jedis = null; 86 try { 87 writer = new BufferedWriter(new FileWriter("D:/movie/MyTest.log")); 88 jedis = new Jedis(HOST, PORT); 89 int count = 0; 90 while (true) { 91 // 隊頭取出,無限時間阻塞,直至取出 92 List<String> logs = jedis.blpop(0, LOG_QUEUE_KEY); 93 if (logs != null && logs.size() >= 2) { 94 writer.write(logs.get(1)); 95 if (++count > 100) { 96 writer.flush(); 97 } 98 } 99 } 100 } catch (IOException e) { 101 e.printStackTrace(); 102 } finally { 103 if (jedis != null) { 104 jedis.close(); 105 } 106 if (writer != null) { 107 try { 108 writer.close(); 109 } catch (IOException e) { 110 e.printStackTrace(); 111 } 112 } 113 } 114 } 115 116 }
作者:netoxi
出處:http://www.cnblogs.com/netoxi
本文版權歸作者和博客園共有,歡迎轉載,未經同意須保留此段聲明,且在文章頁面明顯位置給出原文連接。歡迎指正與交流。