Redis開創了一種新的數據存儲思路,使用Redis,我們不用在面對功能單調的數據庫時,把精力放在如何把大象放進冰箱這樣的問題上,而是利用Redis靈活多變的數據結構和數據操作,為不同的大象構建不同的冰箱。
Redis常用數據類型
Redis最為常用的數據類型:
1.String 2.Hash 3.List 4.Set 5.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 的 5 個常見使用場景
1、會話緩存(Session Cache)
最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在於:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?
幸運的是,隨着 Redis 這些年的改進,很容易找到怎么恰當的使用Redis來緩存會話的文檔。甚至廣為人知的商業平台Magento也提供Redis的插件。
2、全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC平台。回到一致性問題,即使重啟了Redis實例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。
再次以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。
此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
3、隊列
Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列平台來使用。Redis作為隊列使用的操作,就類似於本地程序語言(如Python)對 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的后端工具,以滿足各種隊列需求。例如,Celery有一個后台就是使用Redis作為broker,你可以從這里去查看。
排行榜/計數器
Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之為“user_scores”,我們只需要像下面一樣執行即可:
當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的,你可以在這里看到。
Redis實際應用場景
Redis在很多方面與其他數據庫解決方案不同:它使用內存提供主存儲支持,而僅使用硬盤做持久性的存儲;它的數據模型非常獨特,用的是單線程。另一個大區別在於,你可以在開發環境中使用Redis的功能,但卻不需要轉到Redis。
轉向Redis當然也是可取的,許多開發者從一開始就把Redis作為首選數據庫;但設想如果你的開發環境已經搭建好,應用已經在上面運行了,那么更換數據庫框架顯然不那么容易。另外在一些需要大容量數據集的應用,Redis也並不適合,因為它的數據集不會超過系統可用的內存。所以如果你有大數據應用,而且主要是讀取訪問模式,那么Redis並不是正確的選擇。
然而我喜歡Redis的一點就是你可以把它融入到你的系統中來,這就能夠解決很多問題,比如那些你現有的數據庫處理起來感到緩慢的任務。這些你就可以通過Redis來進行優化,或者為應用創建些新的功能。在本文中,我就想探討一些怎樣將Redis加入到現有的環境中,並利用它的原語命令等功能來解決 傳統環境中碰到的一些常見問題。在這些例子中,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機器故障轉移,如機房斷電,主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沒有數據,會清空自身的數據,很危險!)