1.方案選型
緩存方案的基本要求:避免單點故障;較好的性能和穩定性;便於運維管理。
常見方案如下:A. 客戶端直接訪問多個memcache 實例
優點:簡單,未引入新的節點;
缺點:維護不方便,未實現集中管理;性能不滿足,實例宕機後不能自動踢出(hash到該實例的請求都要等到超時才能轉到其他正常實例)。
B. magent代理
優點:簡單,滿足緩存對代理的大部分要求;
缺點:無成熟案例;性能不滿足,實例宕機後不能自動踢出。
C. moxi代理
優點:功能豐富,提供本地緩存、Memcache實例數據複製等。
缺點:無成熟案例;代碼很久沒更新。
D. twemproxy代理
優點:twitter的成熟案例。
缺點:主要配合是twemproxy+redis,與memcache配合使用的案例較少;不支持緩存的複製功能。
E. mcrouter
優點:facebook的成熟案例;功能強大,支持Memcache實例分組、實例複製功能,實例宕機後可自動踢出。
缺點:一般用於ubuntu平臺,在centos上安裝較複雜。
2.mcrouter介紹
Mcrouter是一個支持memcached實例的代理,被facebook用於遍佈全球的數十個集羣幾千個服務器之間控制流量。它適用於大規模的緩存處理中,在峯值的時候,mcrouter處理接近50億的請求/秒。
2.1.mcrouter主要特點
1.支持標準開源的memcached ASCII編碼協議
支持memcahed協議的所有客戶端無需做任何修改即可被mcrouter支持。
2.多種hash算法
支持consistent hashing算法,集羣擴容只有少量數據需要遷移。也可根據需要自定義其他hash算法:weighted ch3,crc32,HostIdRoute 等。
3.前綴路由
mcrouter可以根據key前綴把客戶端分配到不同的memcahed池,從而實現集羣中的memcache實例分組。
4.memcached池備份
通過將add\set\delete分發到兩個池,而get只從其中一個池中獲取數據,實現兩個池的備份。當其中一個池有實例宕機時,可以從備份池中獲取數據。
5.熱加載
mcrouter監控它所有的配置文件,一旦檢測到任何配置文件被修改,mcrouter的一個後臺線程將自動的重新加載並分析這些文件。該過程對客戶端完全透明。
6.自動故障轉移
一旦某實例無響應,mcrouter將直接將相關請求轉移到另一個可用的實例。同時,後臺繼續向無響應實例發送心跳請求,只要該實例恢復正常,mcrouter將會重新啓用這個實例。該過程對客戶端完全透明。
2.2.自動故障轉移策略
Memcache實例異常將被mcrouter標記爲"TKO" ("technical knockout")狀態。根據不同情況分爲Soft TKO和Hard TKP兩種狀態:
Soft TKO:連續3次請求超時(failures_until_tko控制);
Hard TKO:發生1次connect error或1次connect timeout。
Memcache實例被標記爲TKO狀態後,原本應該Hash到該實例的請求將轉發到”FailoverRouter”指定的備份實例;同時,mcrouter通過探針以一定頻率(初始probe_delay_initial_ms 默認10000,以50%左右的幅度增長,最大probe_delay_max_ms 默認60000)檢測TKO狀態的實例,只要該實例恢復正常,mcrouter將會重新啓用這個實例。該過程對客戶端完全透明。
3.功能測試
3.1.數據切分
3.1.1.數據分佈
測試數據在Memcache實例上的分佈。配置文件:
{
"pools": {
"A": {
"servers": [
"192.168.188.150:11211",
"192.168.188.150:11212",
"192.168.188.150:11213",
"192.168.188.150:11214",
"192.168.188.150:11215",
"192.168.188.150:11216",
"192.168.188.150:11217",
"192.168.188.150:11218",
"192.168.188.150:11219",
]
}
},
"route": "PoolRoute|A"
}
測試結果:
10000個數據,9個Memcache實例,每個實例的數據量分別是:
1096, 1116, 1110, 1075, 1180, 1058, 1129, 1138, 1098
3.1.2.一致性HASH
測試新增兩個實例後,數據在Memcache實例上的分佈變化情況。配置文件:
{
"pools": {
"A": {
"servers": [
"192.168.188.150:11211",
"192.168.188.150:11212",
"192.168.188.150:11213",
"192.168.188.150:11214",
"192.168.188.150:11215",
"192.168.188.150:11216",
"192.168.188.150:11217",
"192.168.188.150:11218",
"192.168.188.150:11219",
"192.168.188.150:11220",
"192.168.188.150:11221",
]
}
},
"route": "PoolRoute|A"
}
測試結果:
新增實例後數據量移動比例:新增機器數 / 調整後服務器總數 = 2 / 11
新增2個Memcache實例後,get miss 數:1805 (10000* 2/11 = 1818)
注:新增實例在配置文件中往後順延,不能插入現存的的實例配置之前。
3.2.實例分組
測試不同前綴的KV能夠分發到不同實例分組中。前綴p1,p2,p3的key分別分發到pool1,pool2,pool3中,其他默認到common_cache中,每個 pool由2個memcache構成。
{
"pools": {
"pool1": { "servers": [ "192.168.188.150:11211","192.168.188.150:11212" ] },
"pool2": { "servers": [ "192.168.188.150:11213","192.168.188.150:11214" ] },
"pool3": { "servers": [ "192.168.188.150:11215","192.168.188.150:11216" ] },
"common_cache": { "servers": [ "192.168.188.150:11217","192.168.188.150:11218" ] }
},
"route": {
"type": "PrefixSelectorRoute",
"policies": {
"p1": "PoolRoute|pool1",
"p2": "PoolRoute|pool2",
"p3": "PoolRoute|pool3"
},
"wildcard": "PoolRoute|common_cache"
}
}
測試結果:
p1,p2,p3爲前綴的key自動分發到相應的pool中,其他key分發到默認pool中。
3.3.實例故障檢測
測試TKO狀態的運行機制。配置文件
{
"pools": {
"p1": {
"servers": [
"192.168.188.150:11211"
]
},
"p2": {
"servers": [
"192.168.188.150:11212"
]
}
},
"route": "PoolRoute|p1",
"route": "PoolRoute|p2",
"route": {
"type": "FailoverRoute",
"children": [ "PoolRoute|p1","PoolRoute|p2" ],
"failover_errors": [ "tko" ]
}
}
3.3.1.soft TKO
測試結果:模擬超時:iptables -I INPUT -p tcp --dport 11211 --tcp-flags PSH PSH -j DROP
取消超時:iptables -D INPUT 1
3.3.2.hard TKO
測試結果:3.4.不同代理分發的一致性
兩個代理都用默認的一致性hash算法,分發到同1個由8個Memcache實例組成的集羣(配置文件相同)。以一個代理set 1000個不同的key,再用另一個代理get,可以正常get到所有結果。3.5.Memcache命令支持
客戶端:java_memcached-release_2.6.6服務器:mcrouter-0.6.0
客戶端對代理的以下操作正常:
//add
public boolean add(String key, Object val)
public boolean add(String key, Object val, Date expireDate)
public boolean add(String key, Object val, Integer hashCode)
public boolean add(String key, Object val, Date expireDate, Integer hashCode)
//set
public boolean set(String key, Object val)
publicboolean set(String key, Object val, Date expireDate)
publicboolean set(String key, Object val, Integer hashCode)
publicboolean set(String key, Object val, Date expireDate, Integer hashCode)
//get
public Object get(String key)
publicObject get(String key, Integer hashCode)
publicObject get(String key, Integer hashCode, Boolean asString)
//delete
public boolean delete(String key)
//gets and cas
public MemcachedItem gets(String key)
publicMemcachedItem gets(String key, Integer hashCode)
publicboolean cas(String key, Object val, long casUnique)
publicboolean cas(String key, Object val, Date expireDate, long casUnique)
publicboolean cas(String key, Object val, Integer hashCode, long casUnique)
publicboolean cas(String key, Object val, Date expireDate, Integer hashCode, long casUnique)
//incr and decr
public long incr(String key)
publiclong incr(String key, long inc)
publiclong incr(String key, long inc, Integer hashCode)
publiclong decr(String key)
publiclong decr(String key, long inc)
publiclong decr(String key, long inc, Integer hashCode)
//thread safe way to incr and decr
public long addOrIncr(String key)
publiclong addOrIncr(String key, long inc)
publiclong addOrIncr(String key, long inc, Integer hashCode)
publiclong addOrDecr(String key)
publiclong addOrDecr(String key, long inc)
publiclong addOrDecr(String key, long inc, Integer hashCode)
4.性能測試
測試環境
- 客戶端
軟件:shell調用java執行jar文件:java -jar mc.jar;
每個shell中最多起100個java線程,10個shell併發可進行1000併發測試;
每個線程死循環不停set或get;
SET與GET操作比1:10,KV大小30與KV大小500之比1:10。
- 代理
軟件:mcrouter-0.6,啓動4線程(線程數量=CPU核數量)。
- Memcache
軟件:memcache-1.4,根據需要創建N個實例。
4.1.代理對性能的影響
測試方法:每個線程連續set 10000個kv,key\value大小分別爲4字節、60字節。- 性能比較
客戶端併發數 |
memcache |
mcrouter |
請求數比較 memcache/mcrouter |
||
每秒請求數 |
CPU負載 |
每秒請求數 |
CPU負載 |
||
1 |
3500 |
4% |
1500 |
5% |
2 |
10 |
20000 |
13% |
9600 |
22% |
2 |
50 |
43000 |
30% |
23000 |
32% |
2 |
100 |
104000 |
60% |
40300 |
48% |
3 |
500 |
320000 |
89% |
64600 |
55% |
5 |
1000 |
323000 |
90% |
87000 |
82% |
4 |
注:“客戶端併發數”不是指緩存只能支持這麼多併發,而是指在這麼多併發下,壓力測試出相應的每秒請求數。
測試結果:
壓力不同的情況下,memcache處理能力是mcrouter的2-5倍;
假設生產環境下服務器CPU的正常負載規劃不得超過30%,可認定memcache的性能是mcrouter的2倍。
- 性能下降原因
訪問方式 |
每秒請求數 |
每個請求耗時(微秒) |
遠程訪問memcache |
43000 |
23 |
本地訪問mcrouter |
31300 |
32 |
遠程訪問mcrouter |
23000 |
43 |
測試結果:
性能下降由代理自身和增加網絡節點導致,兩者性能消耗各50%左右。
4.2.不同實例數對性能的影響
測試方法:以50個併發壓力測試,逐步增加集羣中的Memcache實例數。
Memcache實例數 |
每秒請求數 |
性能下降 |
代理服務器CPU負載 |
1 |
25564 |
0% |
23% |
2 |
24667 |
4% |
30% |
4 |
24093 |
6% |
23% |
8 |
24473 |
4% |
32% |
16 |
23290 |
9% |
33% |
32 |
21810 |
15% |
23% |
測試結果:
集羣中實例數增加後,CPU負載在一定範圍波動,性能逐步小範圍下降。
4.3.連接數
4.3.1.代理最大併發連接數
測試最佳吞吐量負載下的不同連接數的性能情況。通過客戶端併發線程和每個線程set後的sleep時間,實現單個代理最佳吞吐量:每秒20000個請求。
客戶端併發線程 |
sleep時間(秒) |
每秒請求數 |
Socket連接 |
平均延遲(毫秒) |
CPU負載 |
5000 |
0.25 |
20000 |
4900 |
1 |
23% |
10000 |
0.5 |
20000 |
5600 |
1.6 |
24% |
20000 |
1 |
20000 |
7500 |
1.1 |
23% |
30000 |
1.5 |
20000 |
14800 |
7.4 |
30% |
測試結果:
單個代理在每秒20000請求的負載下,10000個客戶端併發連接可正常工作。
4.3.2.客戶端連接數設置
java memcached client中關於連接數的設置:setInitConn:無效
setMinConn:無效
setMaxConn:有效
測試客戶端setMaxConn爲5時的每秒請求數:
單個客戶端併發線程 |
每秒請求 |
1 |
1512 |
5 |
5785 |
10 |
5928 |
50 |
5524 |
100 |
5979 |
測試結果:
最大連接設置爲5時,可爲客戶端提供每秒5000個請求,滿足要求。
4.4.穩定性
持續測試滿4天,mcrouter性能與負載穩定,具體數據如下:GET平均79000次/秒,SET平均8700次/秒,合計平均87000次/秒。
5.高可用
兩臺Mcrouter服務器分別爲應用A、應用B提供服務,通過keepalived組成集羣,形成互備。兩臺服務器各安裝1個Mcrouter實例,端口相同。Keepalived監測Mcrouter端口11200,判斷是否需要漂移VIP。
檢測腳本:
vrrp_script chk_11200 {
script "</dev/tcp/127.0.0.1/11200" --檢測端口
interval 2 --監測頻率2秒
weight 2 --監測成功權重+2
fall 3 --端口連續3次不通返回失敗
rise 3 --失敗後,端口連續3次正常返回成功
}
測試:每秒set一次,關閉mcrouter後,6秒內請求失敗,之後恢復正常。
16:08:02 set k9 true
16:08:03 set k10 true
16:08:04 set k11 true
11081 [main] ERROR com.danga.MemCached.MemCachedClient - ++++ exception thrown while writing bytes to server on set
11081 [main] ERROR com.danga.MemCached.MemCachedClient - 您的主機中的軟件中止了一個已建立的連接。
java.io.IOException: 您的主機中的軟件中止了一個已建立的連接。
……
16:08:05 set k12 false
16:08:06 set k13 false
16:08:08 set k14 false
16:08:10 set k15 true
16:08:17 set k16 true
16:08:18 set k17 true
6.實施
6.1.環境部署
- 物理機:5臺
- 虛擬機:11臺
- mcrouter:2個實例
- memcache:9個實例(8個組成pool1,1個作爲poolbak)
6.2.安裝配置
6.2.1.mcrouter
- 操作系統
硬件配置:4C4G
軟件配置:關閉iptables,selinux
參數調整:
/etc/security/limits.conf
* soft nofile 102400
* hard nofile 102400
* soft nproc 102400
* hard nproc 102400
/etc/pam.d/login
session required /lib64/security/pam_limits.so
/etc/sysctl.conf
net.ipv4.ip_local_port_range = 9000 65500
net.ipv4.tcp_keepalive_time = 600
- mcrouter
啓動命令:
cd /root/mcrouter
nohup mcrouter myflavor &
啓動選項:
cat myflavor-standalone
{
"libmcrouter_options": {
"config_file": "/root/mcrouter/mcrouter.conf",
"async_spool": "/root/mcrouter/spool",
"num_proxies": "4",
"reset_inactive_connection_interval": "0"
},
"standalone_options": {
"log_file": "/root/mcrouter/mcrouter.log",
"ports": "11200"
}
}
配置文件:
{
"pools": {
"pool1": {
"servers": [
"192.168.188.150:11211",
"192.168.188.150:11212",
"192.168.188.150:11213",
"192.168.188.150:11214",
"192.168.188.150:11215",
"192.168.188.150:11216",
"192.168.188.150:11217",
"192.168.188.150:11218"
]
},
"pool1_bak": {
"servers": [
"192.168.188.150:11219",
]
}
},
"route": {
"type": "FailoverRoute",
"default_policy": "PoolRoute|pool1",
"children": [ "PoolRoute|pool1", "PoolRoute|pool1_bak" ],
"failover_errors": [ "tko" ]
}
}
6.2.2.memcache
- 操作系統
硬件配置:4C4G
軟件配置:關閉iptables,selinux
- memcache
啓動命令:
memcached -d -l 192.168.188.150 -p 11211 -m 3200 -c 4096 -u nobody -v >> /root/memcache_log/11211.log 2>&1
6.3.key規劃
Key包含5個字節的前綴,如“p1aaa”:前兩位指定前綴路由,如p1作爲前綴的轉發到pool1中(目前未使用該特性,設計key便於以後使用);
後三位作爲功能模塊的標識,確保不同功能的緩存不會衝突,同時便於運維管理,有問題的key可立即定位來源,維護清單:
前綴 |
項目 |
功能模塊 |
分配日期 |
p1aaa |
項目A |
主站session共享 |
20150929 |
p1aab |
項目B |
主站session共享 |
20150929 |
7.監控
通過Zabbix實時監控操作系統的CPU、內存、網絡負載,以及軟件本身的關鍵指標,分別用於實時報警和負載分析。7.1.mcrouter
可用性 </dev/tcp/127.0.0.1/11200;echo $?
異常Memcache實例 echo "stats suspect_servers" | nc 0 11200 | grep -v END
每秒SET次數 echo "stats" | nc 0 11200 | grep cmd_set_count | awk '{print $3}'
每秒GET次數 echo "stats" | nc 0 11200 | grep cmd_get_count | awk '{print $3}'
連接數 echo "stats" | nc 0 11200 | grep num_client | awk '{print $3}'
7.2.memcache
每秒SET次數 echo "stats" | nc ip 11211 | grep cmd_set | awk '{print $3}'
每秒GET次數 echo "stats" | nc ip 11211 | grep cmd_get | awk '{print $3}'
每秒GET命中次數 echo "stats" | nc ip 11211 | grep get_hits | awk '{print $3}'
每秒被踢出的未過期KV數量 echo "stats" | nc ip 11211 | grep evictions | awk '{print $3}'
每秒回收的已到期KV數量 echo "stats" | nc ip 11211 | grep “ reclaimed “ | awk '{print $3}'
各SLAB中“最老KV的存活時間“的最小值 echo "stats items" | nc ip 11211 | grep age | awk '{print $3}' | sort | head -1
8.問題處理
8.1.對象序列化
mcrouter只支持assci協議,不支持binary協議,因此對象要序列化之後再存儲(未測試驗證);8.2.超時配置
tomcat session共享,延遲至少設置爲3秒,默認100毫秒會超時<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.n.n:11200"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "3000" --至少設置爲3000毫秒
transcoderFactoryClass="de.javakaffee.web.msm.JavaSerializationTranscoderFactory"/>
否則會報錯:
警告: Could not store session 12B8D86FC987FBAA902B559E735451B5-n1 in memcached.
net.spy.memcached.internal.CheckedOperationTimeoutException: Timed out waiting for operation - failing node: /192.168.n.n:11200