Redis is an open source (BSD licensed), in-memory data structure store!
歡迎轉載,轉載請注明出處
剛剛結束一個游戲類的活動項目,由於預估的參與人數較多,產生的數據量會很大,為了達到更好的游戲效果,所以決定拋棄之前Mysql為主Redis為輔的存儲方式,而是Redis為主,Mysql為輔(負責落地一些重要的用戶數據),期間收貨了很多。
一、Redis中的數據結構以及常見的使用場景
常用的Redis數據結構:
- String # 鍵值對
- List # 列表
- Hash # 哈希
- Set # 集合
- Sorted Set # 有序集合
每種數據結構都有其合適的使用場景,但是謹記 不要手里握着錘子,看什么都是釘子 。另外當存儲的數據量較大時,要注意每個操作命令的時間復雜度
1、String
常用命令: set,get,decr,incr,mget 等。
應用場景:String類型是最簡單也是最常用的redis數據結構,key/value格式完全可以取代Memcached作為緩存服務器,單機的測試效果顯示redis的效果更好。
set、get:最簡單的數據緩存
mset、mget: 批量操作,把數據統一傳回客戶端,節省網絡io時間
decr、incr:計數器
append命令:可以作為時間序列,配合getrange、setrange,對字符串進行操作,目前redis還木有修剪操作
setbit、getbit: 省內存的好命令,可以作為簡單的布爾過濾器來判斷用戶是否執行過某些操作
2、List
常用命令:lpush,rpush,lpop,rpop,lrange等。
應用場景:List的應用場景很多,應用也相當廣泛
lpush、lpop:天然的隊列操作,輕松實現隊列任務,Celery的存儲容器我們選的就是redis
lpush、ltrim: 顯示最新的數據,很好用的!比如:游戲上方的跑馬燈,就可以用這兩個命令來存儲最新的50條記錄
還有一些其他操作:堵塞式的blpop, lrange(O(n)), lindex(O(n)),linsert(O(n)), llen(O(1)),lrem(O(n)),lset(O(n))
3、Hash
常用命令:hget,hset,hgetall 等。
應用場景:以前在memcached中如果保存一個大的數據,經常用序列化之后保存,取出來反序列化后使用,即不經濟實惠,在高並發下還存在原子性問題,在redis中, 用哈希實現,so easy啦!
hget、hset: 實現一個key對應一個數據集集合,數據集集合里包含多個單獨的key/value鍵值對,操作依然是原子性的
hmget、hmset、hgetall: 批量操作,節省網絡io時間哦
hincrby: 對哈希里域值,進行原子性的加1
其他操作: hdel(O(n))、 hkeys(O(n))、hexits(O(1))、hvals(O(n))、hscan(O(n))
4、Set
常用命令:
sadd,spop,smembers,sunion 等。
應用場景:set與list類似,只是set是經過去重的集合,需要一個不重復的數據結構,就要考慮考慮set
sadd: 存儲一個不重復數據的數據集合
sunion、sdiff、sinter: 進行集合處理,例如微博中,將一個用戶關注的所有人放入set集合中,通過並集、交集、差集操作,實現`共同關注`、`共同喜好`、`二度好友`等功能
其他操作:srem、spop、scard、sismember、smove、srandmember
5、Sorted Set
常用命令:
zadd,zrange,zrem,zcard等
應用場景:set是無序的,而Sorted set 顧名思義,它是有序的,由key、member和score組成,需要一個有序而且不重復的數據結構,就要考慮考慮sorted set
zadd:存儲一個按照score排序的數據集合,添加時自動排序,例如:優先隊列,普通消息的score為1,重要消息的score為2,然后工作線程可以選擇按score的倒序來獲取工作任務。讓重要的任務優先執行。
zrange、zrangebyscore等等:按照score順序獲取數據集,例如:微博的時間流信息,把發布時間作為score。還可以用來處理過期數據。后台任務使用ZRANGE…SCORES查詢排序集合,取出最新的10個項目。如果發現unix時間已經過期,則在數據庫中刪除條目。
zrank: 排行榜功能,score作為投票結果
其他操作:zcard、zcount、zincrby、zrem、zscore,以及set的集合操作
二、Redis使用經驗
先貼出來國內外三家重度使用redis的公司的使用經驗,猛戳這里
一、Redis部署
本司由於平時的數據量不大,一直使用的三台redis實例,一主兩從,三個哨兵sentinel分別監視三個redis實例作為高可用性的保障,這次活動的預估參與人數以及請求並發量很大,所以為了保證高可用性,在部署時做了一下變更方案:
- 更換SSD硬盤,並增加內存至128G (簡單粗暴,效果顯著)
- 雙機房兩組Redis實例,一組對外服務,另一組作為熱備份,不提供服務並定時備份,服務中的實例出現故障,立即切換備份實例為服務實例
- OS參數:vm.over_commit_memory配置 默認為0,改為1
Redis的快照、AOF文件重寫、主備同步等功 能都依賴於fork系統調用,以快照(bgsave/save)為例,Redis會fork一個子進程出來,由子進程來將當前的數據存儲為一個RDB文件。
vm.over_commit_memory會影響到內存分配,其值可以是:
-
-
- 0: 表示內核將檢查是否有足夠的可用內存供應用進程使用;如果有足夠的可用內存,內存申請允許;否則,內存申請失敗,並把錯誤返回給應用進程。
- 1: 表示內核允許分配所有的物理內存,而不管當前的內存狀態如何。
- 2: 表示內核允許分配超過所有物理內存和交換空間總和的內存。
-
vm.over_commit_memory默認值為0,在該配置下,當Redis執行fork時,服務器可用內存必須大於Redis當前使用內存2倍時,fork才能成功。
而實際上Linux在fork時,使用COW(copy-on-write)技術,子進程共享父進程的地址空間,只有共享地址空間發生改變時,才需要復制改變部分的內存;大部分情況下,fork子進程並不會導致內存使用翻倍,為fork預留一倍的內存是完全沒有必要的,Redis啟動時也會建議使用者將vm.over_commit_memory設置為1。
- OS參數:vm.swapiness設置為0
Redis是全內存的KV數據庫,當服務器內存不足時,OS的swap機制可能會把Redis的部分數據換出到磁盤,訪問Redis時,如果被訪問的數據剛好不在內存里,則會產生缺頁中斷,從磁盤讀取數據,這種行為會極大的影響Redis服務的性能。
為保證Redis服務的性能,應該盡量避免發生swap,將vm.swapiness設置為0(該參數可在0-100之間取值,默認為60,越高使用swap空間的可能性越大)。
- OS參數:transparent hugepage
Linux-2.6.38內核引入透明大內存頁的支持,這個參數對Redis有利有弊。
-
- 好處在於:大內存頁意味着更小的頁表,fork的開銷會降低不少
- 壞處在於:大內存頁,意味着頁被修改的幾率更大,COW時拷貝成本更高
僅供參考,目前未使用該特性。
我的Redis的配置文件, 內容如下:(配置文件詳細介紹請點這里)
daemonize yes pidfile "./redis.pid" port 6379 timeout 0 tcp-keepalive 60 loglevel notice logfile "./redis.log" databases 16 save 900 1 save 300 10 save 60 10000 tcp-backlog 511 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename "redis.rdb" dir "./redis" slave-serve-stale-data yes slave-read-only yes repl-disable-tcp-nodelay no slave-priority 100 maxclients 20000 maxmemory 60gb maxmemory-policy noeviction appendonly no # 可以忍受一小段時間內的數據丟失,所以關閉了aof持久化 appendfilename "appendonly.aof" appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb lua-time-limit 5000 slowlog-log-slower-than 10000 slowlog-max-len 128 hash-max-ziplist-entries 512 hash-max-ziplist-value 512 list-max-ziplist-entries 512 list-max-ziplist-value 64 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 activerehashing yes client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 hz 10 aof-rewrite-incremental-fsync yes # Generated by CONFIG REWRITE # 由sentinel自動故障轉移生成的 #slaveof 10.181.60.113 6382 slaveof 10.181.254.157 6379
二、壓測過程中遇到的問題及解決方案
保證服務可靠性,對主要單接口壓測,以及復合場景下的多接口壓測,觀察接口的響應時間以及服務的吞吐量,還有對突發情況下的Redis機器故障轉移,如機房斷電,主redis掛掉等情況。
問題:
-
- 業務邏輯復雜,頻繁訪問redis,接口響應時間有提升空間
- 線上要清空一個db的數據,執行flushdb后,數據依然存在(原因:該db內數據量大,清空操作超過3秒,sentinel發現3秒內,master無正確響應,就將slave拉起作為master,從進行同步,陷入循環。。。)
- Redis目前的主從同步機制,主從連接斷開后,如果從落后的數據超過1M(可配置,越大內存開銷越大),則需要重新全量同步一次,一次全量同步會產生極大的內存、磁盤、CPU及網絡開銷。主從全量同步過程中,如果寫入比較多,主從同步緩沖區就會不斷累積寫入的數據,當累積的數據超出限制時,主從連接就會斷開,此時從又必須重新向主請求全量同步,如此往復... 導致同步一直不能成功
- 數據量很大時,redis的bgsave操作,雖然是子進程操作,但是也會阻塞redis,導致超時
方案:
- mysql讀寫分離,並分庫,分表
- redis數據分片,把重要、不重要的數據分開到兩個redis實例分別存放
- 代碼中減少redis網絡IO請求,多用mget、hmget、pipeline等一次性讀多條數據命令
- 代碼中考慮用更合適的redis數據結構以及時間復雜度少的命令。 Instagram公司優化Redis使用內存案例
- 設計數據結構時,盡量減小單個key的value大小,如果單例redis中數據量過大,故障恢復或者同步過程會很慢。單例redis最大使用內存一定要小於機器最大內存的一半。
- 清空一個數據量很大的db前,先調大sentinel的自動故障轉移時間,清空后再設置回來。
- 適當擴大client-output-buffer-limit slave 256mb 64mb 60 的配置值
- 一定要設置限流,運維層限制一道,代碼層限制一道,保證整體服務器不宕機。
備選方案:
-
- 復雜的redis操作,可以考慮用短小的lua腳本執行,用eval和evalsha命令執行(優點:充分利用CPU,減少網絡IO時間。缺點:不方便維護,要保證腳本不阻塞)
- 傳統的一主多從A--B&C 當把B拉起為master時,C仍然會清空自身數據來同步B,psync的斷點續傳對此無作用,所以考慮改為變為A--B--C同步結構
- redis集群(時間緊,考慮但是暫時不會采用)
- 采用主不開持久化,從開持久化,不會出現數據量大,bgsave導致的連接超時情況(缺點:A--B&C模式下,A故障並自動拉起或者由sentinel把B被拉起為master,中間的心跳檢測過程中,還是會有一個間隔,導致從同步主,發現A沒有數據,會清空自身的數據,很危險!)