突然發現我們的redis 已經用了30G了,好吧這是個很尷尬的數字因為我們的緩存機器的內存目前是32G的,內存已經告竭。幸好上上周公司采購了90G的機器,現在已經零時遷移到其中的一台機器上了。(跑題下,90G的內存太爽了是我除了koding.com 之外第二次用到90G的機器,koding 是個好網站,在線編程IDE。) 但是隨着數據量越來越大單機始終無法承受的,改造勢在必行。經過初步思考我們得出了很簡單的方案 概括起來就是 "內外兼修"
1.內功修煉
先從我們的應用層說起 看看redis 使用情況 ,有沒有辦法回收一些key ,先進入redis 服務器執行 info ,有刪減
1: redis 127.0.0.1:6391> info
2: used_memory_human:35.58G
3: keyspace_hits:2580207188
4: db0:keys=2706740,expires=1440700
目前我們只使用了1個DB 但是key 太多了 有270W個key,已經過期的有144W。第一個想到的就是我勒個去,怎么會有這么多key ,第二個想法就是可能存在過大的key
看看能不能針對過大的key 做優化?可是遺憾的是官方並沒有命令顯示db 的key 大小,我們只能自己想辦法了
Google 一番,發現國外友人已經寫好了shell
傳送門: https://gist.github.com/epicserve/5699837
可以列出每個key 大小了。可是這並不適用我們,因為我們key 太大了 執行了9個小時都沒跑完,無力吐槽了。 其實還有一個選擇就是用另外一個工具
傳送門:https://github.com/sripathikrishnan/redis-rdb-tools
可惜這個太重了 ,不想麻煩ops ,我們就只能撩起袖子,造輪子。
把shell 代碼簡單看了下發件DEBUG OBJECT 是個好東西啊 ,google 下發現官網 http://redis.io/commands/object
已經有簡單的調試信息了,剩下的就好處理了
1: #coding=utf-8
2: import redis
3:
4: COLOR_RED = "\033[31;49;1m %s \033[31;49;0m"
5:
6: COLOR_GREED = "\033[32;49;1m %s \033[39;49;0m"
7:
8: COLOR_YELLOW = "\033[33;49;1m %s \033[33;49;0m"
9:
10: COLOR_BLUE = "\033[34;49;1m %s \033[34;49;0m"
11:
12: COLOR_PINK = "\033[35;49;1m %s \033[35;49;0m"
13:
14: COLOR_GREENBLUE = "\033[36;49;1m %s \033[36;49;0m"
15:
16:
17: def getHumanSize(value):
18: gb = 1024 * 1024 * 1024.0
19: mb = 1024 * 1024.0
20: kb = 1024.0
21: if value >= gb:
22: return COLOR_RED % (str(round(value / gb, 2)) + " gb")
23: elif value >= mb:
24: return COLOR_YELLOW % (str(round(value / mb, 2)) + " mb")
25: elif value >= kb:
26: return COLOR_BLUE % (str(round(value / kb, 2)) + " kb")
27: else:
28: return COLOR_GREED % (str(value) + "b")
29:
30:
31: month = 3600 * 24 * 30
32: result = []
33: client = redis.Redis(host="XXXXX", port=XXXX)
36: client.info()
37:
38: count = 0
39: for key in client.keys('*'):
40: try:
41: count += 1
42: idleTime = client.object('idletime', key)
43: refcount = client.object('refcount', key)
44: length = client.debug_object(key)['serializedlength']
45: value = idleTime * refcount
46: print "%s key :%s , idletime : %s,refcount :%s, length : %s , humSize :%s" % (count, key, idleTime, refcount, length, getHumanSize(length))
47: except Exception:
48: pass
寫了個簡單的python 腳本輸出每個key 的大小和idle time,和refer count 。有了這么多數據結合awk 就可以很好的統計每個key 的使用情況。有一點要注意的是這個size 是key 在redis 中的大小,並非實際的大小,這個是經過redis 壓縮的。經過分析之后發現不存在過大的key ,但是存在有些key 半年都沒有被訪問過 Orz 。
接下來就很好處理了,我們為每個key 設置的過期時間,若key 被hit 上則更新這個expire time 。這樣可以逐步淘汰冷數據,達到冷熱分離
2. 外功修煉
我們對內清理了無效的key,對外我們要做到水平擴展,單機的承載始終有限,於是我們開始了傳說中的分布式改造
分布式這東西看起來很唬人做起來更唬人,幸好我們是緩存服務 CAP約束有限。 緩存服務做分布式最好的當然是一致性hash 咯。其實當我們改造完成之后,才發現官方已經准備做這個分布式的緩存體系了(流口水啊) 只是現在還在開發中 給了個備用的響當當的 Twemproxy 奈何我們已經做好了,就先用着,坐等官方測試之后再說
傳送門: http://redis.io/topics/cluster-spec
我們實現了數據的平滑遷移,而且對server 的修改實現了最小影響。 因為原來是用的是phpredis 所以就擴展了下,代碼可以平滑過渡。
我們自己的實現:https://github.com/trigged/redis_con_hash
其實扯了這么多就是要把redis 的數據分散開,單機的承載始終是個瓶頸,但是redis 在這方面沒有Memcached 完善,不過以后會越來越好