2015年2月16日

如何使用 redis

以下根據 redis date types 來查看 redis 基本的資料操作指令,並提供幾個可以利用 redis 實做的使用情境。

redis 指令

redis 指令共有 12 類,從指令的分類,我們可以再區分為

  1. key/value data
    Keys, Strings, Hashes, Lists, Sets, Sorted Sets, HyperLogLog
  2. 連線相關
    Pub/Sub, Transactions
  3. server 系統相關
    Scripting, Connection, Server

redis data types

redis 支援的資料型別

  1. Binary-safe strings
    中文也沒問題的 string
  2. Lists
    string collection,依照insert的順序排序,換句話說就是 linked lists
  3. Sets
    唯一的、沒有順序的 string 集合
  4. Sorted sets
    類似 Sets,但每一個string element關聯到一個稱為 score 的 floating number。所有 elements 都是由 score 排序,可以取得某一個範圍的 elements(例如前10個或最後10個)
  5. Hashes
    key 跟 value 都是 strings 的 map
  6. Bit arrays (or simply bitmaps):
    可透過指令操作 string value 的內容,將 string 視為 an array of bits,例如設定或清除某個 bit,計算設定為 1 的 bits 數量
  7. HyperLogLogs
    這是 probabilistic data structure,用來計算一個 set 的基數 (cardinality of a set)

    在集合論裡面的基數就是用來計算數量並比較數量多寡的方法,有兩個集合 X 與 Y,如果 X 裡面所有元素都可以一對一映射到 Y,那麼就表示 X 與 Y 裡面的元素數量是一樣的。基數並不一定就是一個整數的數字,因為在無限集合(例如所有雙數的集合)中,我們無法直接說出這個集合的基數是某一個數字。

keys

redis keys 是 binary safe,我們可以用一般的字串,或是用 JPEG file 作為 key,空字串也是一個正確的 key。key 的規則如下

  1. 避免使用太長的 key
    例如使用 1024 bytes 的 key 是很糟糕的,除了浪費記憶體與頻寬的問題之外,key lookup 也需要消耗掉一些資源。如果真的有這種情境要使用,建議先將 key 進行一次 SHA1 hash。
  2. 避免使用太短的 key
    在 key 上面增加一些描述用途的字串,可增加可讀性,例如 "user:1000:followers"
  3. try to stick with a schema
    可以在 key 使用"object-type:id"(例如 "user:1000")這樣固定的模式,模式固定有助於 key 的管理,也可以使用 dot 或 dash(例如 "comment:1234:reply.to", "comment:1234:reply-to")

  4. key 最大允許的長度為 512MB

strings

這是最基本的最常使用的資料型別

存取 string key, value,set 將會把既有的 value 取代掉

127.0.0.1:6379> set mykey somevalue
OK
127.0.0.1:6379> get mykey
"somevalue"

set 可加上參數 nx (如果 key 不存在,才能設定成功) 或 xx (如果 key 已經存在,才能設定成功),因為剛剛已經設定了 mykey,所以第一行 nx 的指令是失敗的。

127.0.0.1:6379> set mykey newval nx
(nil)
127.0.0.1:6379> set mykey newval xx
OK

set 可設定 expire time,ex seconds 或是 px miliseconds

127.0.0.1:6379> set mykey test ex 5
OK
127.0.0.1:6379> get mykey
"test"
127.0.0.1:6379> get mykey
"test"
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379> get mykey
(nil)

如果 string value 是存放整數,則可以直接進行運算,類似的指令有 incr, incrby, decr, decrby

127.0.0.1:6379> set counter 100
OK
127.0.0.1:6379> incr counter
(integer) 101
127.0.0.1:6379> incr counter
(integer) 102
127.0.0.1:6379> incrby counter 50
(integer) 152
127.0.0.1:6379> incrby counter 50xx
(error) ERR value is not an integer or out of range

如果有兩個 client 同時進行 incr,也不會造成 race condition,因為 redis 可保證 read-increment-set 這個 atomic 操作不會被中斷。

getset 可取得舊值,同時設定為新值

127.0.0.1:6379> getset mykey val2
"newval"
127.0.0.1:6379> getset mykey val3
"val2"

mset, mget 可一次處理多個 key

127.0.0.1:6379> mset a 10 b 20 c 30
OK
127.0.0.1:6379> mget a b c
1) "10"
2) "20"
3) "30"

del 回傳 1 代表既存的 key 已經刪除了,回傳 0 代表不存在該 key
exists 可判斷是否存在某個 key

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> exists mykey
(integer) 1
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> del mykey
(integer) 0
127.0.0.1:6379> exists mykey
(integer) 0

用 type 查詢該 key 儲存的 value 的資料型別,none 表示不存在該 key

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> type mykey
string
127.0.0.1:6379> del mykey
(integer) 1
127.0.0.1:6379> type mykey
none

用 expire 設定某個 key 的 expire time,ttl 可查詢該 key 存活的剩餘時間,回傳 -2 表示該 key 不存在,回傳 -1 表示該 key 沒有設定 expire time

127.0.0.1:6379> set mykey hello
OK
127.0.0.1:6379> expire mykey 5
(integer) 1
127.0.0.1:6379> get mykey
"hello"
127.0.0.1:6379> ttl mykey
(integer) 2
127.0.0.1:6379> get mykey
(nil)
127.0.0.1:6379> ttl mykey
(integer) -2

lists

redis 是以 linked list 實做 list,用 lpush 指令加入 1個 elements 跟 加入 10 million 個 elements 所需要的時間是一樣的。缺點是計算 list 裡面的 index 會比較慢,如果程式需要快速取得 value 中某個範圍的 elements,建議改使用 sorted sets。

rpush 將新元素放在 list 最右邊
lpush 將新元素放在 list 最左邊
lrange 由 list 取出一部份的資料

lrange key 第一個index 最後的index
index 為 0 是 list 第一個元素,index 為 -1 是 list 最後一個元素,-2 是 list 倒數第二個元素。

127.0.0.1:6379> rpush mylist A
(integer) 1
127.0.0.1:6379> rpush mylist B
(integer) 2
127.0.0.1:6379> lpush mylist first
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
127.0.0.1:6379> lrange mylist 0 -2
1) "first"
2) "A"

可以一次放入多個 elements

127.0.0.1:6379> rpush mylist 1 2 3 4 5 "foo bar"
(integer) 9
127.0.0.1:6379> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
4) "1"
5) "2"
6) "3"
7) "4"
8) "5"
9) "foo bar"

以 rpop 或 lpop 取出 list 裡面的元素

127.0.0.1:6379> rpush newlist a b c
(integer) 3
127.0.0.1:6379> rpop newlist
"c"
127.0.0.1:6379> lpop newlist
"a"
127.0.0.1:6379> lpop newlist
"b"
127.0.0.1:6379> lpop newlist
(nil)

lrem key count value,移除某個 value
count > 0: 從頭往尾尋找,移除幾個 value
count < 0: 從尾往頭尋找,移除幾個 value
count = 0: 移除所有等於 value 的 elements

# 從尾往頭移除 2 個等於 a 的 value
127.0.0.1:6379> lrem new list -2 a

lists 常用的使用情境

  1. 在 social network 中,記錄使用者的最新貼文
    例如 twitter 的 lastest tweets
  2. processes 之間的資料傳輸,類似 consumer-producer pattern,producer 產生 item 到 list 中,consumer(worker) 取出 item 並執行動作

在某些使用情境中,我們只需要最新的 items,這在 redis 稱為 capped collection,就是只記錄最新的 N items,並用 ltrim 把舊的 item 刪掉。

127.0.0.1:6379> rpush mylist 1 2 3 4 5
(integer) 5
127.0.0.1:6379> ltrim mylist 0 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

blocking operations on lists

當我們在 list 使用 producer consumer 模式時,會在 producer 端呼叫 lpush,在 consumer 端呼叫 rpop,但 rpop 可能會因為 list 空了,而回傳 nil。

redis 實做了 brpop, blpop 指令,可在 list 為空的時候,卡住 client 端,直到可取得新的 element 或是 timeout 時間到了。

127.0.0.1:6379> brpop tasks 5
... (client 端等待 5 秒)
(nil)
(5.04s)

127.0.0.1:6379> brpop tasks 5
... (在另一個 client 執行 lpush tasks mytask)
1) "tasks"
2) "mytask"
(3.74s)

rpoplpush, brpoplpush 是更安全的 list 操作指令

127.0.0.1:6379> rpush mylist "one"
(integer) 1
127.0.0.1:6379> rpush mylist "two"
(integer) 2
127.0.0.1:6379> rpush mylist "three"
(integer) 3
127.0.0.1:6379> rpoplpush mylist otherlist
"three"
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
127.0.0.1:6379> lrange otherlist 0 -1
1) "three"

lpush 時,會判斷資料型別,如果不是 list,會回傳 error

127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> lpush foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value

當 list 被 lpop 變成空字串時,就等於把該 key 刪除

127.0.0.1:6379> lpush mylist 1 2
(integer) 2
127.0.0.1:6379> exists mylist
(integer) 1
127.0.0.1:6379> lpop mylist
"2"
127.0.0.1:6379> lpop mylist
"1"
127.0.0.1:6379> lpop mylist
(nil)
127.0.0.1:6379> exists mylist
(integer) 0

hashes

hmset 設定 hash 的多個 fields
hget 取得某個特定的 field
hmget 類似 hget,同時取得多個 fields
hincrby 可直接增加某個 field 的數值

127.0.0.1:6379> hmset user:1000 username antirez birthyear 1977 verified 1
OK
127.0.0.1:6379> hget user:1000
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget user:1000 username
"antirez"
127.0.0.1:6379> hget user:1000 birthyear
"1977"
127.0.0.1:6379> hget user:1000 birthday
(nil)
127.0.0.1:6379> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
127.0.0.1:6379> hmget user:1000 username birthyear no-such-field
1) "antirez"
2) "1977"
3) (nil)
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 1987
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 1997

sets

sets 是 unordered collection of strings,這些 strings 不會重複,可以檢測某個 string 是否有在 set 裡面

sadd 增加 set elements
smembers 取得此集合的所有 strings
sismember 檢測是否有存在某個 string

127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> sismember myset 3
(integer) 1
127.0.0.1:6379> sismember myset 30
(integer) 0

假設新聞 news:1000:tags 有四個 tags
另外有記錄每個 tag 分別都是由 1000 這個 user 標記的
使用 sinter 可取得這些 sets 的交集,結果為 1000

127.0.0.1:6379> sadd news:1000:tags 1 2 5 77
(integer) 4
127.0.0.1:6379> sadd tag:1:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:2:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:5:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:77:news 1000
(integer) 1
127.0.0.1:6379> smembers news:1000:tags
1) "1"
2) "2"
3) "5"
4) "77"
127.0.0.1:6379> sinter tag:1:news tag:2:news tag:10:news tag:27:news
(empty list or set)
127.0.0.1:6379> sinter tag:1:news tag:2:news tag:5:news tag:77:news
1) "1000"

有一副撲克牌 deck,利用 sunionstore 複製一副撲克牌,再用 spop 以亂數抽牌,scard 可計算剩餘的 elements 數量,srandmember則是抽牌後,又將牌放回牌堆。

127.0.0.1:6379> sadd deck C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 CJ CQ CK D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 DJ DQ DK H1 H2 H3  H4 H5 H6 H7 H8 H9 H10 HJ HQ HK S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 SJ SQ SK
(integer) 52
127.0.0.1:6379> sunionstore game:1:deck deck
(integer) 52
127.0.0.1:6379> spop game:1:deck
"S8"
127.0.0.1:6379> spop game:1:deck
"C10"
127.0.0.1:6379> spop game:1:deck
"D9"
127.0.0.1:6379> scard game:1:deck
(integer) 49
127.0.0.1:6379> srandmember game:1:deck
"D5"
127.0.0.1:6379> scard game:1:deck
(integer) 49

sorted sets

每個 sorted set 裡面的 element 都關聯到一個 floating number,稱為 score,此 set 利用以下的規則排序

  1. 如果 A.score > B.score 則 A > B
  2. 如果 A.score = B.score ,且 A string 的字串比 B 大(用字母及長度比較),

zadd 可增加 element,前面的數字是 score
zrange 可取得某個 index 範圍的 elements,最後面加上 withscores,可一併取得 scores
zrevrange 可取得某個 index 範圍的 elements,用反向的順序

127.0.0.1:6379> zadd hackers 1940 "Alan Kay"
(integer) 1
127.0.0.1:6379> zadd hackers 1957 "Sophie Wilson"
(integer) 1
127.0.0.1:6379> zadd hackers 1953 "Richard Stallman"
(integer) 1
127.0.0.1:6379> zadd hackers 1949 "Anita Borg"
(integer) 1
127.0.0.1:6379> zrange hackers 0 -1
1) "Alan Kay"
2) "Anita Borg"
3) "Richard Stallman"
4) "Sophie Wilson"
127.0.0.1:6379> zrevrange hackers 0 -1
1) "Sophie Wilson"
2) "Richard Stallman"
3) "Anita Borg"
4) "Alan Kay"
127.0.0.1:6379> zrange hackers 0 -1 withscores
1) "Alan Kay"
2) "1940"
3) "Anita Borg"
4) "1949"
5) "Richard Stallman"
6) "1953"
7) "Sophie Wilson"
8) "1957"

zrangebyscore 是以 score 的條件範圍取得 elements
zremrangebyscore 將某個score 範圍的元素刪除
zrank 可取得某個元素的 index
zrevrank 可取得某個元素反向的 index

127.0.0.1:6379> zrangebyscore hackers -inf 1950
1) "Alan Kay"
2) "Anita Borg"
127.0.0.1:6379> zremrangebyscore hackers 1940 1950
(integer) 2
127.0.0.1:6379> zrank hackers "Sophie Wilson"
(integer) 1
127.0.0.1:6379> zrevrange hackers 0 -1
1) "Sophie Wilson"
2) "Richard Stallman"

bitmaps

setbit 將第幾個 bit 設定為 1 或 0
getbit 取得某個 bit 的數值
bitop 有四種: AND, OR, XOR, NOT
bitcount 計算 bit 被設定為 1 的數量
bitpos 找到第一個設定為 0 或 1 的 bit 位置

127.0.0.1:6379> setbit key 0 1
(integer) 0
127.0.0.1:6379> setbit key 100 1
(integer) 0
127.0.0.1:6379> bitcount key
(integer) 2
127.0.0.1:6379> getbit key 100
(integer) 1

hyperloglogs (HLL)

這是一種 probabilistic data structure,用來計算某些特殊的東西,例如要計算一個集合的基數,某些精確的計算會耗費較多記憶體,但有時候,我們只需要一個大約的數字,在特別的演算法幫助下,我們就不需要 item 數量 x 單一 item 記憶體量這麼多記憶體。

使用情境

顯示最新的項目列表

在網頁應用中常遇到要顯示最新的 reply,如果是由 DB 取資料會寫成:

SELECT * FROM comments WHERE ... ORDER BY time DESC LIMIT 10

改用 redis,可在發表新 comment 時,就改用以下的方式

# 將新 comment 加入到 list
lpush latest.comments <userid>
# 只保留 5000 個 comments
ltrim latest.comments 0 5000

只有在超過第 5000 筆的情況下,才需要直接存取資料庫。

排行榜

常有線上遊戲,需要依照得分排序,並要能即時更新排序的結果,通常會需要列出前 100 名的選手,列出目前這個 user 的全球排名。

假設有百萬個 user,每分鐘都有百萬個新的分數,每次獲得新分數就

zadd leaderboard <score> <username>

也可以用 userid 換掉 username

前 100 名的選手列表

zrevrange leaderboard 0 99

全球排名

zrank leaderboard <username>

按照用戶投票和時間排序

新聞會根據時間以及使用者投票的得分來排序

score = points / time^alpha

因此時間越久,新聞所得到的分數越低。

每次有新的新聞,就用 LPUSH + LTRIM,保留前 1000筆新聞,並持續以一個 scheduler,計算這 1000個新聞的 score,計算結果由 zadd 排序。

過期項目處理

依照時間排序,每次有新的資料,就搭配時間的屬性 current_time 與 time_to_live,將資料放入 sorted set,用 zrange 查詢 sorted set,可取得最新的10個 item,過期的資料則刪除。

計算數量

搭配 incrby 指令,可計算 user 在網頁上,60s 內的頁面瀏覽量。

incr user:<id>
expire user:<id> 60

特定時間內的特定項目

統計在某段特點時間裡有多少特定用戶訪問了某個特定資源。例如我們想要知道某些特定的註冊用戶或IP地址,他們到底有多少人閱讀了某篇文章。

只要在每一次有人閱讀該文章,就

sadd page:day1:<page_id> <user_id>

想知道特定用戶的數量嗎?只需要使用

SCARD page:day1:<page_id>

需要測試某個特定用戶是否訪問了這個頁面?就用

SISMEMBER page:day1:<page_id> <user_id>

Pub/Sub

redis 實做了 list,因此就可以用來實做 publish/subscribe pattern 的 queue 工作

如果有個任務,需要依序執行,就可以用這功能實做。

利用 bitmap 進行 active user 統計

將 user 1 對應到第一個 bit,user 2 對應到第二個 bit,user 10 對應到第十個 bit,當某個 user 登入系統時,就將該 bit 設定為 1,每天更換一個 key,進行 active user 的記錄。

redis.setbit(play:yyyy-mm-dd, user_id, 1)

因為有每天的紀錄,我們可以利用 AND 運算,得到連續 N 天每天都有使用的 active user。

References

Redis: Zero to Master in 30 minutes - Part 1
[翻譯]Redis: 三十分鐘從入門到精通 - 第一部分

Redis: Zero to Master in 30 minutes - Part 2

使用Redis bitmap進行活躍用戶統計
用Redis bitmap統計活躍用戶、留存

幾點建議,讓Redis在你的系統中發揮更大作用
How to take advantage of Redis just adding it to your stack

各種不同程式語言的 redis clients

jedis

2015年2月9日

安裝 redis

以下簡述如何在 Centos 6 安裝 redis

編譯 redis 2.8.19

目前 redis stable 版本為 2.8.19,如果是直接用 yum 安裝,版本是 2.4,但我們需要測試內建的 Sentinel 功能,所以就自行編譯。

tar zxvf redis-2.8.19.tar.gz
cd redis-2.8.19
make
make install

src 目錄中會出現 5 個 執行檔,分別是

  1. redis-server
    主要的 redis server

  2. redis-sentinel
    監控 redis server 的 sentinel 執行檔

  3. redis-cli
    redis CLI 界面

  4. redis-benchmark
    用來檢查 redis 效能

  5. redis-check-aof and redis-check-dump
    檢查壞掉的 data

在 make install 之後,所有執行檔都會被複製到 /usr/local/bin/ 目錄中。

設定 redis service

mkdir /etc/redis
mkdir /var/redis
mkdir /var/redis/6379

編輯 redis service,redis 提供的 script 沒辦法直接在 centos 中使用,必須自己編輯一個

vi /etc/init.d/redis

#!/bin/sh
#
# redis - this script starts and stops the redis-server daemon
#
# chkconfig:   - 85 15 
# description:  Redis is a persistent key-value database
# processname: redis-server
# config:      /etc/redis/6379.conf
# pidfile:     /var/run/redis_6379.pid

REDISPORT=6379
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli

PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="/etc/redis/${REDISPORT}.conf"

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

prog=$(basename $EXEC)

lockfile=/var/lock/subsys/redis_${REDISPORT}

start() {
    [ -x $redis ] || exit 5
    [ -f $REDIS_CONF_FILE ] || exit 6
    echo -n $"Starting $prog: "
    daemon $EXEC $CONF
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    stop
    start
}

reload() {
    echo -n $"Reloading $prog: "
    killproc $EXEC -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}

case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
        exit 2
esac

編輯 redis 設定檔

cp redis.conf /etc/redis/6379.conf

vi /etc/redis/6379.conf

daemonize yes
    因為是 redis damone service,要改成 yes
pidfile /var/run/redis_6379.pid
    設定 process id 檔案
port 6379
    redis 預設使用 TCP Port 6379,如果要使用多個 redis daemon,必須修改 port number
loglevel notice
    設定 loglevel
logfile "/var/log/redis_6379.log"
    設定 log file
dir /var/redis/6379
    這個設定很重要,這是存放redis資料的目錄

啟動redis

chkconfig --level 345 redis on
service redis start

修正啟動發生的錯誤

overcommit_memory

如果在 log file 裡面看到

WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

修正方法為

echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
service redis-server start

overcommit_memory 的說明

  1. 記憶體配置策略
    /proc/sys/vm/overcommit_memory 裡面的設定值有三種
    0: 表示kernel將檢查是否有足夠的可用記憶體供process使用;如果有足夠的可用記憶體,記憶體申請允許;否則,記憶體申請失敗,並把錯誤返回給process。
    1: 表示kernel允許分配所有的記憶體,而不管當前的記憶體狀態如何。
    2: 表示kernel允許分配超過所有物理記憶體和交換空間總和的記憶體

  2. Overcommit
    在Unix中,當一個user process使用malloc()函數申請記憶體時,假如返回值是NULL,則這個process知道目前沒有可用記憶體空間,就會做相應的處理工作。許多process會列印錯誤訊息並退出。

  3. OOM (out-of-memory)
    Linux使用另外一種處理方式叫做Overcommit,它對大部分申請記憶體的請求都回覆"yes",以便能跑更多更大的程序。因為申請記憶體後,並不會馬上使用記憶體。

    當記憶體真的不足的時候,就會發生OOM killer(OOM=out-of-memory)。它會選擇殺死一些 user process,以便釋放記憶體。

  4. Overcommit的三種方式
    Linux下overcommit有三種策略(Documentation/vm/overcommit-accounting):
    0: 啟發式策略。合理的overcommit會被接受,不合理的overcommit會被拒絕。
    1: 任何overcommit都會被接受。
    2: 當系統分配的記憶體超過swap+N%*物理RAM(N%由vm.overcommit_ratio決定)時,會拒絕commit。

    overcommit的策略通過vm.overcommit_memory設置。
    overcommit的百分比由vm.overcommit_ratio設置。

    範例

     # echo 2 > /proc/sys/vm/overcommit_memory
     # echo 80 > /proc/sys/vm/overcommit_ratio
    

    當oom-killer發生時,linux會選擇殺死哪些process,選擇process的函數是oom_badness函數(在mm/oom_kill.c中),該函數會計算每個進程的點數(0~1000)。點數越高,這個process越有可能被殺死。每個進程的點數跟oom_score_adj有關,而且oom_score_adj可以被設置(-1000最低,1000最高)。

TCP backlog

如果在 log file 裡面看到

WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

解決方式如下

echo 511 > /proc/sys/net/core/somaxconn
service redis restart

如果這個設定要能夠在 reboot 之後還能持續生效,必須修改 /etc/sysctl.conf,在裡面增加一行設定,然後 reboot 機器。

vi /etc/sysctl.conf
net.core.somaxconn = 511

/proc/sys/net/core/somaxconn 是個全域參數,定義了每一個Tcp Port最大的監聽 queue 的長度

Transparent Huge Pages (THP)

如果在 log file 裡面看到

# WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

解決方式如下

echo never > /sys/kernel/mm/transparent_hugepage/enabled
service redis restart

如果這個設定要能夠在 reboot 之後還能持續生效,必須修改 /etc/grub.conf,在kernel 參數的最後面增加一段設定 transparent_hugepage=never ,然後 reboot 機器。

kernel /vmlinuz-2.6.32-431.el6.x86_64 ...... transparent_hugepage=never

測試

利用 redis-clie 進行測試

redis-cli
127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> get hello
"world"

遠端存取的測試

redis-cli -h 192.168.1.16 -p 6379
192.168.1.16:6379> get hello
"world"

redis.conf(6379.conf) 設定檔

設定檔可以用這樣的語法,引入其他的設定檔,如果要設定為單台機器,多個 redis daemon 的環境,可以利用這個方式,將通用設定與獨立的 process 設定分離。

# include /path/to/local.conf
# include /path/to/other.conf
# 預設值 no 是不以 daemon 的方式啟動,正式環境建議改為yes 
daemonize no

# 紀錄 process id 的檔案
pidfile /var/run/redis.pid

# redis 監聽埠號
port 6379

# 預設為不設定,代表監聽此機器所有 IP
# bind 192.168.1.16 127.0.0.1

# 指定用來接受 tcp 連線的 unix socket
# unixsocket /tmp/redis.sock
# unixsocketperm 700

# client 連線 idle timeout 的時間 N seconds (預設值 0 代表 disable)
timeout 0

# 非 0 時,就是使用 TCP ACKs 的 SO_KEEPALIVE 來偵測 client 連線的狀態 (1) 可偵測 dead peer (2) 讓此 TCP 連線不被中間的 router 中斷
# 需要兩倍的時間,才能確定此連線已經中斷了
# 合理的設定值為 60 seconds
tcp-keepalive 0

# debug, verbose, notice, warning
# 越後面,log的資料越精簡
loglevel notice

# log file 位置,空字串代表將 log 輸出到 stdout
logfile "/var/log/redis_6379.log"

# 將 log 寫入 system logger,通常保持為 no
# syslog-enabled no

# 指定 syslog id
# syslog-ident redis

# 指定 syslog facility,必須為 USER 或 LOCAL0-LOCAL7
# syslog-facility local0

# 預設為 dbid 為 0
databases 16

##################

# RDB snapshot 將資料儲存到 disk 的策略
# save <seconds> <changes>
# 例如 save 900 1 代表 900 sec(15min) 之後,至少有一個 key 更動了 value,就會啟動 save to disk
# 可以移除這些策略,就不會啟動 save to disk
save 900 1
save 300 10
save 60 10000

# 當 RDB snapshot 啟動時,redis 預設會停止接受寫入資料,所以 bgsave 會 failed。當 redis 允許寫入時,background save 會再度啟動。
# 如果有設定 redis monitor 機制,就可以將這個設定值 disabled,這樣即使遇到 disk erro,也可以正常繼續工作
stop-writes-on-bgsave-error yes

# dump .rdb database 時,以 LZF 壓縮 string objects,需要消耗一些 CPU resource
rdbcompression yes

# version 5 of RDB 在檔案最後面加上了 CRC64 checksum
rdbchecksum yes

# dump DB 的檔名
dbfilename dump.rdb

# dump DB 的目錄
dir /var/redis/6379


########### replication
# Master-Slave replication, redis replication 是非同步的
# slaveof <masterip> <masterport>

# 如果 master 有設定 requirepass,就需要填寫密碼
# masterauth <master-password>

# 當 master 無法工作,slave 接手處理 client 的 requests
slave-serve-stale-data yes

# redis 2.6 之後,slaves 預設為 read-only
slave-read-only yes


############ LIMITS

# 最大 clients 連線數量上限
# maxclients 10000

# 最多使用多少記憶體,預設為使用所有的記憶體
# 建議應該設定此值,保留一些記憶體給系統及其他 process 使用
# maxmemory <bytes>

# memory policy: 達到 maxmemory 時,要進行什麼處置
# (1) volatile-lru: 以 LRU algorithm 移除有標記為 expire 的 key
# (2) allkeys-lru: 以 LRU algorithm,移除任意的 key
# (3) volatile-random: 移除有標記為 expire 的 random key
# (4) allkeys-random: 移除 randon key
# (5) volatile-ttl: 移除有著最接近的 expire time(較小的 TTL)的 key
# (6) noeviction: 不移除, 直接回應寫入錯誤
# maxmemory-policy volatile-lru

# LRU and minimal TTL algorithms 並不是最精確的找出 key 的方式,為節省 memory 消耗,這是 approximated algorithms
# maxmemory-samples 3

參考資料

Redis Quick Start

Redis 啟動警告錯誤解決

各種不同程式語言的 redis clients

Linux之TCPIP內核參數優化

2015年2月2日

memcached or redis ?

在選擇 cache 系統時,傳統會使用 memcached,但現在有另一個選擇就是 redis,這兩個 cache server 主要的功能就是在記憶體中暫存資料,用以加速網站資料處理的效能,兩個都是儲存 key-value 的資料結構,且都是存放在記憶體中。

Memcached or Redis ?

Why Redis beats Memcached for caching 這篇文章提出了討論,什麼時候該用 memcached,而什麼時候該用 redis。

memcached 相對於 redis 是比較年長的 server,因此 redis 的功能比較多,在大多數的時候都是比較好的選擇。但還是有兩種用途,建議使用 memcached (1) caching small and static data,例如 html code fragment,因為memcached 內部比較簡單,處理單純的字串效率更好一些 (2) horizontal scaling,但未來這個部份將會隨著 redis 3.0 內建了 cluster 以及 auto sharding 的功能而有不同的結果。

memcached 使用 LRU(Least Recently Used) algorithm 作為刪除舊資料的基準,redis 允許使用者選擇 6 種 data eviction policies。memcached 限制 key 上限為 250 bytes,value上限為 1MB,只能處理 string,redis 允許 key 與 value 上限各達到 512MB,可以儲存 binary data,共支援 6 種data types。

redis 支援資料的 replication,搭配 sentinel 服務,可自動偵測所有 master-slave server 的狀態,並自動切換 master node。

因為 redis 是單執行緒執行的 daemon,disk/memory/netowrk IO 序列化的條件下,如果資料量太大,就需要等待 IO 處理的時間,在一個 CPU 且資料不大的情況下,效能比 memcached 好,memcached 本身就是多執行緒的,單一個 daemon 就可直接使用多個 CPU 與多個 TCP Ports。

High Availability

考量到 HA 的議題,要思考兩點 (1) data availability (2) service availability,以目前 2.8 版的狀況,redis 支援 data replication,針對 service availabilty 也提供了sentinel做 redis daemon 的監控。

站在備援的角度來看,因為 redis 內建了 replication 機制,可存放的資料型別比較多,而老牌的 memcached 並沒有在 replication 上多做著墨,要不然就得選擇使用支援非同步 data replication 的 repcached

cache 需不需要備援

cache 本身並不是最終儲存資料的地方,它的存在單純是為了效能考量,簡單地說,通常網頁都直接去資料庫取得資料並顯示在畫面上,假設是顯示公告的頁面,如果目前網頁只有 1~5 個人去瀏覽,同一時間內 5 個人瀏覽就等於要透過網路向資料庫取得公告 5 次。

但如果同一時間內增加到 100人 甚至是 1000 人去瀏覽公告,資料庫在瞬間就必須承受 100 ~ 1000 個連線的流量,而且這 1000個取得的資料都是一樣的,這明顯是個資源與效能的浪費。

cache 的作用在於暫存常用的資料,如果在記憶體中沒有這個資料,就會去向資料庫取得,並存放到 cache 中。以這個角度來看,cache 究竟需不需要 replication 備援,似乎不是那麼重要的事情。應該這樣說,如果 cache daemon 的資料備援,並不因此影響到過多本身提供服務的效率,分配些許資源去進行 replication。

但這樣的假設,奠基於 Web Application Server 前面的 Load Balancer 採用了 sticky session 的流量分享策略,如果是不需要登入,沒有 server side session 的情況,cache 的 replication 重要性就會拉高,因為 Load Balancer 平均分攤網路 request 到多個 web application server 之後,取得了相同的資料,不同的 application server 使用到相同資料的機會大增。

因此 cache server 有沒有支援 data replication 的重要性,取決於應用程式系統中,到底是不是跨使用者之間使用到了大量相同的資料,如果是這樣的話,那麼 cache replication 就會比較重要。

cache 需不需要 sharding

單一 cache daemon 不管是記憶體或是網路頻寬的限制,都有對外提供服務能力的上限,資料 sharding 對一個服務超級大量資料的系統來說是重要的,但以一般的網站服務來說,因為需要進入 cache 的資料量沒那麼多,似乎重要性還就沒那麼高了。

memcached 的 sharding 必須由 application 自行處理,這種情況就像是使用 MySQL 一樣,Application 必須依照自己的資料的特性,自行決定要以什麼方式進行 data sharding。

redis 3.0 版支援 cluster,但目前還在 alpha 階段,redis cluster 會做資料的 auto sharding,這意味著資料並不會儲存在所有 redis 節點上,而是以多數決的方式進行。

如果願意以一些系統效能,換來簡易的 data sharding 機制,用這樣的想法,就可以使用 redis。

結論

Memcached vs. Redis不同key大小性能測試報告 的結果似乎也證實了這個想法,當 redis 啟用了 disk persistence 之後,會造成效能下降,另外當 redis 達到 memory 上限之後,效能會急速下降,而且這份測試報告測試的資料是比較小的,所以大部分的結果都是 memcached 效能比較好。

memcached 比較像是個短小精幹的螺絲釘,它將自己的目標定位在高效率的 cache,redis 在追求高效率的同時,為了增加自己面對 memcached 的優勢,所以支援了 binary data,支援了多種資料型別,做了 replicaiton 還有 cluster 的功能,換句話說,也等於是以一些系統效能換來了增強的功能。

如果有多種資料型別要儲存,那就選擇 redis,至於一般字串的情況,還是選擇 memcached 就好了。不過未來當 redis 的 sharding 穩定後,如果能靠這個功能,簡化使用 memcached 的 client 實做 sharding 功能的負擔,或許會有不同的情況。