目前對(duì)于互聯(lián)網(wǎng)公司不使用 Redis 的很少,Redis 不僅僅可以作為 key-value 緩存,而且提供了豐富的數(shù)據(jù)結(jié)果如 set、list、map 等,可以實(shí)現(xiàn)很多復(fù)雜的功能;但是 Redis 本身主要用作內(nèi)存緩存,不適合做持久化存儲(chǔ),因此目前有如 SSDB、ARDB 等,還有如京東的 JIMDB,它們都支持 Redis 協(xié)議,可以支持 Redis 客戶端直接訪問(wèn);而這些持久化存儲(chǔ)大多數(shù)使用了如LevelDB、RocksDB、LMDB 持久化引擎來(lái)實(shí)現(xiàn)數(shù)據(jù)的持久化存儲(chǔ);京東的 JIMDB 主要分為兩個(gè)版本:LevelDB 和 LMDB,而我們看到的京東商品詳情頁(yè)就是使用 LMDB 引擎作為存儲(chǔ)的,可以實(shí)現(xiàn)海量KV存儲(chǔ);當(dāng)然 SSDB 在京東內(nèi)部也有些部門在使用;另外調(diào)研過(guò)得如豆瓣的 beansDB 也是很不錯(cuò)的。具體這些持久化引擎之間的區(qū)別可以自行查找資料學(xué)習(xí)。
Twemproxy 是一個(gè) Redis/Memcached 代理中間件,可以實(shí)現(xiàn)諸如分片邏輯、HashTag、減少連接數(shù)等功能;尤其在有大量應(yīng)用服務(wù)器的場(chǎng)景下 Twemproxy 的角色就凸顯了,能有效減少連接數(shù)。
Java 代碼
cd /usr/servers/
wget https://github.com/antirez/redis/archive/2.8.19.tar.gz
tar -xvf 2.8.19.tar.gz
cd redis-2.8.19/
make
通過(guò)如上步驟構(gòu)建完畢。
Java 代碼
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/servers/redis-2.8.19/redis.conf &
Java 代碼
ps -aux | grep redis
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 6379
Java 代碼
127.0.0.1:6379> set i 1
OK
127.0.0.1:6379> get i
"1"
通過(guò)如上命令可以看到我們的 Redis 安裝成功。更多細(xì)節(jié)請(qǐng)參考 http://redis.io/。
Java 代碼
\#首先確保安裝了g++,如果沒(méi)有安裝,如ubuntu可以使用如下命令安裝
apt-get install g++
cd /usr/servers
wget https://github.com/ideawu/ssdb/archive/1.8.0.tar.gz
tar -xvf 1.8.0.tar.gz
make
Java 代碼
nohup /usr/servers/ssdb-1.8.0/ssdb-server /usr/servers/ssdb-1.8.0/ssdb.conf &
Java 代碼
ps -aux | grep ssdb
Java 代碼
/usr/servers/ssdb-1.8.0/tools/ssdb-cli -p 8888
/usr/servers/redis-2.8.19/src/redis-cli -p 888
因?yàn)?SSDB 支持 Redis 協(xié)議,所以用 Redis 客戶端也可以訪問(wèn)
Java 代碼
127.0.0.1:8888> set i 1
OK
127.0.0.1:8888> get i
"1"
安裝過(guò)程中遇到錯(cuò)誤請(qǐng)參考 http://ssdb.io/docs/zh_cn/install.html;對(duì)于 SSDB 的配置請(qǐng)參考官方文檔 https://github.com/ideawu/ssdb[http://ssdb.io/docs/zh_cn/install.html](http://ssdb.io/docs/zh_cn/install.html)。
首先需要安裝 autoconf、automake、libtool 工具,比如 ubuntu 可以使用如下命令安裝
Java 代碼
apt-get install autoconf automake
apt-get install libtool
Java 代碼
cd /usr/servers
wget https://github.com/twitter/twemproxy/archive/v0.4.0.tar.gz
tar -xvf v0.4.0.tar.gz
cd twemproxy-0.4.0/
autoreconf -fvi
./configure && make
此處根據(jù)要注意,如上安裝方式在有些服務(wù)器上可能在大量如mset時(shí)可能導(dǎo)致 Twemproxy 崩潰,需要使用如 CFLAGS="-O1" ./configure && make 或 CFLAGS="-O3 -fno-strict-aliasing" ./configure && make 安裝。
Java 代碼
vim /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml
Java 代碼
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
servers:
- 127.0.0.1:6379:1
Java 代碼
/usr/servers/twemproxy-0.4.0/src/nutcracker -d -c /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml
-d 指定后臺(tái)啟動(dòng) -c 指定配置文件;此處我們指定了代理端口為 1111,其他配置的含義后續(xù)介紹。
Java 代碼
ps -aux | grep nutcracker
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 1111
Java 代碼
127.0.0.1:1111> set i 1
OK
127.0.0.1:1111> get i
"1"
Twemproxy 文檔請(qǐng)參考 https://github.com/twitter/twemproxy。
到此基本的安裝就完成了。接下來(lái)做一些介紹。
Java 代碼
\#端口設(shè)置,默認(rèn)6379
port 6379
\#日志文件,默認(rèn)/dev/null
logfile ""
Java 代碼
內(nèi)存大小對(duì)應(yīng)關(guān)系
\# 1k => 1000 bytes
\# 1kb => 1024 bytes
\# 1m => 1000000 bytes
\# 1mb => 1024*1024 bytes
\# 1g => 1000000000 bytes
\# 1gb => 1024*1024*1024 bytes
\#設(shè)置Redis占用100mb的大小
maxmemory 100mb
\#如果內(nèi)存滿了就需要按照如相應(yīng)算法進(jìn)行刪除過(guò)期的/最老的
\#volatile-lru 根據(jù)LRU算法移除設(shè)置了過(guò)期的key
\#allkeys-lru 根據(jù)LRU算法移除任何key(包含那些未設(shè)置過(guò)期時(shí)間的key)
\#volatile-random/allkeys->random 使用隨機(jī)算法而不是LRU進(jìn)行刪除
\#volatile-ttl 根據(jù)Time-To-Live移除即將過(guò)期的key
\#noeviction 永不過(guò)期,而是報(bào)錯(cuò)
maxmemory-policy volatile-lru
\#Redis并不是真正的LRU/TTL,而是基于采樣進(jìn)行移除的,即如采樣10個(gè)數(shù)據(jù)移除其中最老的/即將過(guò)期的
maxmemory-samples 10
而如 Memcached 是真正的 LRU,此處要根據(jù)實(shí)際情況設(shè)置緩存策略,如緩存用戶數(shù)據(jù)時(shí)可能帶上了過(guò)期時(shí)間,此時(shí)采用 volatile-lru 即可;而假設(shè)我們的數(shù)據(jù)未設(shè)置過(guò)期時(shí)間,此時(shí)可以考慮使用 allkeys-lru/allkeys->random;假設(shè)我們的數(shù)據(jù)不允許從內(nèi)存刪除那就使用noeviction。
內(nèi)存大小盡量在系統(tǒng)內(nèi)存的 60%~80% 之間,因?yàn)槿缈蛻舳恕⒅鲝臅r(shí)復(fù)制時(shí)都需要緩存區(qū)的,這些也是耗費(fèi)系統(tǒng)內(nèi)存的。
Redis 本身是單線程的,因此我們可以設(shè)置每個(gè)實(shí)例在 6-8GB 之間,通過(guò)啟動(dòng)更多的實(shí)例提高吞吐量。如 128GB 的我們可以開(kāi)啟 8GB * 10
個(gè)實(shí)例,充分利用多核 CPU。
實(shí)際項(xiàng)目時(shí),為了提高吞吐量,我們使用主從策略,即數(shù)據(jù)寫到主 Redis,讀的時(shí)候從從 Redis上讀,這樣可以通過(guò)掛載更多的從來(lái)提高吞吐量。而且可以通過(guò)主從機(jī)制,在葉子節(jié)點(diǎn)開(kāi)啟持久化方式防止數(shù)據(jù)丟失。
Java 代碼
\#在配置文件中掛載主從,不推薦這種方式,我們實(shí)際應(yīng)用時(shí)Redis可能是會(huì)宕機(jī)的
slaveof masterIP masterPort
\#從是否只讀,默認(rèn)yes
slave-read-only yes
\#當(dāng)從失去與主的連接或者復(fù)制正在進(jìn)行時(shí),從是響應(yīng)客戶端(可能返回過(guò)期的數(shù)據(jù))還是返回“SYNC with master in progress”錯(cuò)誤,默認(rèn)yes響應(yīng)客戶端
slave-serve-stale-data yes
\#從庫(kù)按照默認(rèn)10s的周期向主庫(kù)發(fā)送PING測(cè)試連通性
repl-ping-slave-period 10
\#設(shè)置復(fù)制超時(shí)時(shí)間(SYNC期間批量I/O傳輸、PING的超時(shí)時(shí)間),確保此值大于repl-ping-slave-period
\#repl-timeout 60
\#當(dāng)從斷開(kāi)與主的連接時(shí)的復(fù)制緩存區(qū),僅當(dāng)?shù)谝粋€(gè)從斷開(kāi)時(shí)創(chuàng)建一個(gè),緩存區(qū)越大從斷開(kāi)的時(shí)間可以持續(xù)越長(zhǎng)
\# repl-backlog-size 1mb
\#當(dāng)從與主斷開(kāi)持續(xù)多久時(shí)清空復(fù)制緩存區(qū),此時(shí)從就需要全量復(fù)制了,如果設(shè)置為0將永不清空
\# repl-backlog-ttl 3600
\#slave客戶端緩存區(qū),如果緩存區(qū)超過(guò)256mb將直接斷開(kāi)與從的連接,如果持續(xù)60秒超過(guò)64mb也會(huì)斷開(kāi)與從的連接
client-output-buffer-limit slave 256mb 64mb 60
此處需要根據(jù)實(shí)際情況設(shè)置client-output-buffer-limit slave和 repl-backlog-size;比如如果網(wǎng)絡(luò)環(huán)境不好,從與主經(jīng)常斷開(kāi),而每次設(shè)置的數(shù)據(jù)都特別大而且速度特別快(大量設(shè)置html片段)那么就需要加大repl-backlog-size。
主從示例
Java 代碼
cd /usr/servers/redis-2.8.19
cp redis.conf redis_6660.conf
cp redis.conf redis_6661.conf
vim redis_6660.conf
vim redis_6661.conf
將端口分別改為 port 6660 和 port 6661,然后啟動(dòng)
Java 代碼
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/servers/redis-2.8.19/redis_6660.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/servers/redis-2.8.19/redis_6661.conf &
查看是否啟動(dòng)
Java 代碼
ps -aux | grep redis
進(jìn)入從客戶端,掛主
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 6661
Java 代碼
127.0.0.1:6661> slaveof 127.0.0.1 6660
OK
127.0.0.1:6661> info replication
\# Replication
role:slave
master_host:127.0.0.1
master_port:6660
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:57
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
進(jìn)入主
Java 代碼
/usr/servers/redis-2.8.19# /usr/servers/redis-2.8.19/src/redis-cli -p 6660
Java 代碼
127.0.0.1:6660> info replication
\# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6661,state=online,offset=85,lag=1
master_repl_offset:85
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:84
127.0.0.1:6660> set i 1
OK
進(jìn)入從
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 6661
Java 代碼
127.0.0.1:6661> get i
"1"
此時(shí)可以看到主從掛載成功,可以進(jìn)行主從復(fù)制了。使用 slaveof no one 斷開(kāi)主從。
Redis 雖然不適合做持久化存儲(chǔ),但是為了防止數(shù)據(jù)丟失有時(shí)需要進(jìn)行持久化存儲(chǔ),此時(shí)可以掛載一個(gè)從(葉子節(jié)點(diǎn))只進(jìn)行持久化存儲(chǔ)工作,這樣假設(shè)其他服務(wù)器掛了,我們可以通過(guò)這個(gè)節(jié)點(diǎn)進(jìn)行數(shù)據(jù)恢復(fù)。
Redis 持久化有 RDB 快照模式和 AOF 追加模式,根據(jù)自己需求進(jìn)行選擇。
Java 代碼
\#格式save seconds changes 即N秒變更N次則保存,從如下默認(rèn)配置可以看到丟失數(shù)據(jù)的周期很長(zhǎng),通過(guò)save “” 配置可以完全禁用此持久化
save 900 1
save 300 10
save 60 10000
\#RDB是否進(jìn)行壓縮,壓縮耗CPU但是可以減少存儲(chǔ)大小
rdbcompression yes
\#RDB保存的位置,默認(rèn)當(dāng)前位置
dir ./
\#RDB保存的數(shù)據(jù)庫(kù)名稱
dbfilename dump.rdb
\#不使用AOF模式,即RDB模式
appendonly no
可以通過(guò) set 一個(gè)數(shù)據(jù),然后很快的 kill 掉 redis 進(jìn)程然后再啟動(dòng)會(huì)發(fā)現(xiàn)數(shù)據(jù)丟失了。
AOF(append only file)即文件追加模式,即把每一個(gè)用戶操作的命令保存下來(lái),這樣就會(huì)存在好多重復(fù)的命令導(dǎo)致恢復(fù)時(shí)間過(guò)長(zhǎng),那么可以通過(guò)相應(yīng)的配置定期進(jìn)行 AOF 重寫來(lái)減少重復(fù)。
Java 代碼
\#開(kāi)啟AOF
appendonly yes
\#AOF保存的位置,默認(rèn)當(dāng)前位置
dir ./
\#AOF保存的數(shù)據(jù)庫(kù)名稱
appendfilename appendonly.aof
\#持久化策略,默認(rèn)每秒fsync一次,也可以選擇always即每次操作都進(jìn)行持久化,或者no表示不進(jìn)行持久化而是借助操作系統(tǒng)的同步將緩存區(qū)數(shù)據(jù)寫到磁盤
appendfsync everysec
\#AOF重寫策略(同時(shí)滿足如下兩個(gè)策略進(jìn)行重寫)
\#當(dāng)AOF文件大小占到初始文件大小的多少百分比時(shí)進(jìn)行重寫
auto-aof-rewrite-percentage 100
\#觸發(fā)重寫的最小文件大小
auto-aof-rewrite-min-size 64mb
\#為減少磁盤操作,暫緩重寫階段的磁盤同步
no-appendfsync-on-rewrite no
此處的 appendfsync everysec 可以認(rèn)為是 RDB 和 AOF 的一個(gè)折中方案。
#當(dāng) bgsave 出錯(cuò)時(shí)停止寫(MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk.),遇到該錯(cuò)誤可以暫時(shí)改為 no,當(dāng)寫成功后再改回 yes stop-writes-on-bgsave-error yes
更多 Redis 持久化請(qǐng)參考 http://redis.readthedocs.org/en/latest/topic/persistence.html。
獲取 maxmemory(10mb)
Java 代碼
127.0.0.1:6660> config get maxmemory
1) "maxmemory"
2) "10485760"
設(shè)置新的 maxmemory(20mb)
Java 代碼
127.0.0.1:6660> config set maxmemory 20971520
OK
但是此時(shí)重啟 redis 后該配置會(huì)丟失,可以執(zhí)行如下命令重寫配置文件
Java 代碼
127.0.0.1:6660> config rewrite
OK
注意:此時(shí)所以配置包括主從配置都會(huì)重寫。
Redis 客戶端支持解析和處理 lua 腳本,因?yàn)?Redis 的單線程機(jī)制,我們可以借助 Lua 腳本實(shí)現(xiàn)一些原子操作,如扣減庫(kù)存/紅包之類的。此處不建議使用 EVAL 直接發(fā)送 lua 腳本到客戶端,因?yàn)槠涿看味紩?huì)進(jìn)行 Lua 腳本的解析,而是使用 SCRIPT LOAD+ EVALSHA 進(jìn)行操作。未來(lái)不知道是否會(huì)用 luajit 來(lái)代替 lua,讓redis lua 腳本性能更強(qiáng)。
到此基本的 Redis 知識(shí)就講完了。
一旦涉及到一臺(tái)物理機(jī)無(wú)法存儲(chǔ)的情況就需要考慮使用分片機(jī)制將數(shù)據(jù)存儲(chǔ)到多臺(tái)服務(wù)器,可以說(shuō)是 Redis 集群;如果客戶端都是如 Java 沒(méi)什么問(wèn)題,但是如果有多種類型客戶端(如 PHP、C)等也要使用那么需要保證它們的分片邏輯是一樣的;另外隨著客戶端的增加,連接數(shù)也會(huì)隨之增多,發(fā)展到一定地步肯定會(huì)出現(xiàn)連接數(shù)不夠用的;此時(shí) Twemproxy 就可以上場(chǎng)了。主要作用:分片、減少連接數(shù)。另外還提供了 Hash Tag 機(jī)制來(lái)幫助我們將相似的數(shù)據(jù)存儲(chǔ)到同一個(gè)分片。另外也可以參考豌豆莢的 https://github.com/wandoulabs/codis。
其使用 YML 語(yǔ)法,如
Java 代碼
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
timeout:1000
redis: true
servers:
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
hash 算法:
one_at_a_time
md5
crc16
crc32 (crc32 implementation compatible with libmemcached)
crc32a (correct crc32 implementation as per the spec)
fnv1_64
fnv1a_64
fnv1_32
fnv1a_32
hsieh
murmur
jenkins
分片算法:
ketama(一致性 Hash 算法)
modula(取模)
random(隨機(jī)算法)
servers:
推薦使用后一種方式,默認(rèn)情況下使用 ip:port:weight 進(jìn)行散列并分片,這樣假設(shè)服務(wù)器宕機(jī)換上新的服務(wù)器,那么此時(shí)得到的散列值就不一樣了,因此建議給每個(gè)配置起一個(gè)別名來(lái)保證映射到自己想要的服務(wù)器。即如果不使用一致性 Hash 算法來(lái)作緩存服務(wù)器,而是作持久化存儲(chǔ)服務(wù)器時(shí)就更有必要了(即不存在服務(wù)器下線的情況,即使服務(wù)器 ip:port 不一樣但仍然要得到一樣的分片結(jié)果)。
比如一個(gè)商品有:商品基本信息(p:id:)、商品介紹(d:id:)、顏色尺碼(c:id:)等,假設(shè)我們存儲(chǔ)時(shí)不采用 HashTag 將會(huì)導(dǎo)致這些數(shù)據(jù)不會(huì)存儲(chǔ)到一個(gè)分片,而是分散到多個(gè)分片,這樣獲取時(shí)將需要從多個(gè)分片獲取數(shù)據(jù)進(jìn)行合并,無(wú)法進(jìn)行 mget;那么如果有了 HashTag,那么可以使用“::”中間的數(shù)據(jù)做分片邏輯,這樣 id 一樣的將會(huì)分到一個(gè)分片。
Java 代碼
server1:
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
hash_tag: "::"
servers:
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 1111
Java 代碼
127.0.0.1:1111> set p:12: 1
OK
127.0.0.1:1111> set d:12: 1
OK
127.0.0.1:1111> set c:12: 1
OK
Java 代碼
/usr/servers/redis-2.8.19/src/redis-cli -p 6660
127.0.0.1:6660> get p:12:
"1"
127.0.0.1:6660> get d:12:
"1"
127.0.0.1:6660> get c:12:
"1"
如果我們把 Redis 服務(wù)器作為緩存服務(wù)器并使用一致性 Hash 進(jìn)行分片,當(dāng)有服務(wù)器宕機(jī)時(shí)需要自動(dòng)從一致性 Hash 環(huán)上摘掉,或者其上線后自動(dòng)加上,此時(shí)就需要如下配置:
#是否在節(jié)點(diǎn)故障無(wú)法響應(yīng)時(shí)自動(dòng)摘除該節(jié)點(diǎn),如果作為存儲(chǔ)需要設(shè)置為為false auto_eject_hosts: true #重試時(shí)間(毫秒),重新連接一個(gè)臨時(shí)摘掉的故障節(jié)點(diǎn)的間隔,如果判斷節(jié)點(diǎn)正常會(huì)自動(dòng)加到一致性Hash環(huán)上 server_retry_timeout: 30000 #節(jié)點(diǎn)故障無(wú)法響應(yīng)多少次從一致性Hash環(huán)臨時(shí)摘掉它,默認(rèn)是2 server_failure_limit: 2
不是所有 Redis 命令都支持,請(qǐng)參考 https://github.com/twitter/twemproxy/blob/master/notes/redis.md。
因?yàn)槲覀兯械?Twemproxy 配置文件規(guī)則都是一樣的,因此我們應(yīng)該將其移到我們項(xiàng)目中。
Java 代碼
cp /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml /usr/example/
另外 Twemproxy 提供了啟動(dòng)/重啟/停止腳本方便操作,但是需要修改配置文件位置為 /usr/example/nutcracker.yml。
Java 代碼
chmod +x /usr/servers/twemproxy-0.4.0/scripts/nutcracker.init
vim /usr/servers/twemproxy-0.4.0/scripts/nutcracker.init
將 OPTIONS 改為
OPTIONS="-d -c /usr/example/nutcracker.yml"
另外注釋掉. /etc/rc.d/init.d/functions;將 daemon --user ${USER} ${prog} $OPTIONS 改為 ${prog} $OPTIONS;將 killproc 改為 killall。
這樣就可以使用如下腳本進(jìn)行啟動(dòng)、重啟、停止了。
/usr/servers/twemproxy-0.4.0/scripts/nutcracker.init {start|stop|status|restart|reload|condrestart}
對(duì)于擴(kuò)容最簡(jiǎn)單的辦法是: