Redis筆記——技術點匯總


目錄

· 特點

· 安裝

· 數據庫

· 服務器命令

· 數據類型及其操作命令

    · 數據結構

    · string

    · list

    · set

    · hash

    · zset

· 發布與訂閱

· 排序

· 事務

· pipeline

· 基准測試

· 鍵的過期

· 持久化

    · 概況

    · snapshoting

    · AOF

· 主從復制

· HA

· Lua

· 示例:分布式日志


 

特點

1. Redis是一個開源的、C語言編寫的、面向鍵值對類型數據的分布式NoSQL數據庫系統。

2. 特點:高性能(內存數據庫,隨機讀寫非常快)、持久存儲,適應高並發應用場景。

3. 對比:一些數據庫和緩存服務器的特性與功能。

名稱

類型

數據存儲

查詢類型

附加功能

Redis

使用內存存儲(in-memory)的非關系數據庫

字符串、列表、集合、散列、有序集合

每種數據類型都有自己的專屬命令,另外還有批量操作(buld operation)和不完全(partial)事務支持

發布與訂閱,主從復制(master/slave replication),持久化,腳本

memcached

使用內存存儲的鍵值緩存

鍵值之間的映射

創建命令、讀取命令、更新命令、刪除命令以及其他幾個命令

為提升性能而設的多線程服務器

MySQL

關系數據庫

每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖;支持空間和第三方擴展

SELECTINSERTUPDATEDELETE、函數、存儲過程

支持ACID(InnoDB),主從復制和主主復制

PostgreSQL

關系數據庫

每個數據庫可以包含多個表,每個表可以包含多個行;可以處理多個表的視圖;支持空間(spatial和第三方擴展;支持定制類型

SELECTINSERTUPDATEDELETE、函數、存儲過程

支持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范圍內所有字符組成的子串,包括startend在內

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,返回分值介於minmax之間的成員數量

zrank

zrank key-name member,返回成員memberkey-name中的排名

zscore

zscore key-name member,返回成員member的分值

zrange

zrange key-name start stop [withscores],返回有序集合中排名介於startstop之間的成員,如果給定了可選的withscores選項,那么命令會將成員的分值也一並返回

zrevrank

zrevrank key-name member,返回有序集合里成員member所處的位置,成員按照分值從大到小排列

zrevrange

zrevrange key-name start stop [withscores],返回有序集合給定排名范圍內的成員,成員按照分值從大到小排列

zrangebyscore

zrangebyscore key min max [withscores] [limit offset count],返回有序集合中,分值介於minmax之間的所有成員

zrevrangebyscore

zrevrangebyscore key max min [withscores] [limit offset count],獲取有序集合中分值介於minmax之間的所有成員,並按照分值從大到小的順序來返回它們

zremrangebyrank

zremrangebyrank key-name start stop,移除有序集合中排名介於startstop之間的所有成員

zremrangebyscore

zremrangebyscore key-name min max,移除有序集合中分值介於minmax之間的所有成員

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

取消監視。如果執行execdiscard命令,則無需再執行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-namemilliseconds毫秒之后過期

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...],執行腳本scriptnumkeys表示要使用的鍵個數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
本文版權歸作者和博客園共有,歡迎轉載,未經同意須保留此段聲明,且在文章頁面明顯位置給出原文連接。歡迎指正與交流。

 


免責聲明!

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



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