redis學習總結

redis學習總結css

 

 參考:html

https://blog.csdn.net/richard_jason/article/details/53130369java

https://www.cnblogs.com/mrhgw/p/6278619.htmlpython

https://www.cnblogs.com/xuliangxing/p/7151812.html linux

https://blog.csdn.net/i10630226/article/details/79058513nginx

https://blog.csdn.net/happy_wu/article/details/78736641 web

https://blog.csdn.net/black_ox/article/details/48972085redis

 

 

Redis各個數據類型的使用場景

Redis支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。算法

Redis列表命令

參考:http://www.redis.net.cn/tutorial/3501.htmlsql


使用場景

String

String數據結構是簡單的key-value類型,value其實不只能夠是String,也能夠是數字。 
常規key-value緩存應用; 
常規計數:微博數,粉絲數等。

hash

Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。 
存儲部分變動的數據,如用戶信息等。

list

list就是鏈表,略有數據結構知識的人都應該能理解其結構。使用Lists結構,咱們能夠輕鬆地實現最新消息排行等功能。List的另外一個應用就是消息隊列,能夠利用List的PUSH操做,將任務存在List中,而後工做線程再用POP操做將任務取出進行執行。Redis還提供了操做List中某一段的api,你能夠直接查詢,刪除List中某一段的元素。 
Redis的list是每一個子元素都是String類型的雙向鏈表,能夠經過push和pop操做從列表的頭部或者尾部添加或者刪除元素,這樣List便可以做爲棧,也能夠做爲隊列。

消息隊列系統 
使用list能夠構建隊列系統,使用sorted set甚至能夠構建有優先級的隊列系統。 
好比:將Redis用做日誌收集器 
實際上仍是一個隊列,多個端點將日誌信息寫入Redis,而後一個worker統一將全部日誌寫到磁盤。

取最新N個數據的操做

//把當前登陸人添加到鏈表裏
ret = r.lpush("login:last_login_times", uid) //保持鏈表只有N位 ret = redis.ltrim("login:last_login_times", 0, N-1) //得到前N個最新登錄的用戶Id列表 last_login_list = r.lrange("login:last_login_times", 0, N-1)

好比sina微博: 
在Redis中咱們的最新微博ID使用了常駐緩存,這是一直更新的。可是作了限制不能超過5000個ID,所以獲取ID的函數會一直詢問Redis。只有在start/count參數超出了這個範圍的時候,才須要去訪問數據庫。 
系統不會像傳統方式那樣「刷新」緩存,Redis實例中的信息永遠是一致的。SQL數據庫(或是硬盤上的其餘類型數據庫)只是在用戶須要獲取「很遠」的數據時纔會被觸發,而主頁或第一個評論頁是不會麻煩到硬盤上的數據庫了。

set

set就是一個集合,集合的概念就是一堆不重複值的組合。利用Redis提供的set數據結構,能夠存儲一些集合性的數據。set中的元素是沒有順序的。 
案例: 
在微博應用中,能夠將一個用戶全部的關注人存在一個集合中,將其全部粉絲存在一個集合。Redis還爲集合提供了求交集、並集、差集等操做,能夠很是方便的實現如共同關注、共同喜愛、二度好友等功能,對上面的全部集合操做,你還能夠使用不一樣的命令選擇將結果返回給客戶端仍是存集到一個新的集合中。

交集,並集,差集

//book表存儲book名稱 set book:1:name "The Ruby Programming Language" set book:2:name "Ruby on rail" set book:3:name "Programming Erlang" //tag表使用集合來存儲數據,由於集合擅長求交集、並集 sadd tag:ruby 1 sadd tag:ruby 2 sadd tag:web 2 sadd tag:erlang 3 //即屬於ruby又屬於web的書? inter_list = redis.sinter("tag:web", "tag:ruby") //即屬於ruby,但不屬於web的書? diff_list = redis.sdiff("tag:ruby", "tag:web") //屬於ruby和屬於web的書的合集? union_list = redis.sunion("tag:ruby", "tag:web")

獲取某段時間全部數據去重值 
這個使用Redis的set數據結構最合適了,只須要不斷地將數據往set中扔就好了,set意爲集合,因此會自動排重。

sorted set

和set相比,sorted set增長了一個權重參數score,使得集合中的元素可以按score進行有序排列,好比一個存儲全班同窗成績的sorted set,其集合value能夠是同窗的學號,而score就能夠是其考試得分,這樣在數據插入集合的時候,就已經進行了自然的排序。能夠用sorted set來作帶權重的隊列,好比普通消息的score爲1,重要消息的score爲2,而後工做線程能夠選擇按score的倒序來獲取工做任務。讓重要的任務優先執行。

排行榜應用,取TOP N操做 
這個需求與上面需求的不一樣之處在於,前面操做以時間爲權重,這個是以某個條件爲權重,好比按頂的次數排序,這時候就須要咱們的sorted set出馬了,將你要排序的值設置成sorted set的score,將具體的數據設置成相應的value,每次只須要執行一條ZADD命令便可。

//將登陸次數和用戶統一存儲在一個sorted set裏 zadd login:login_times 5 1 zadd login:login_times 1 2 zadd login:login_times 2 3 //當用戶登陸時,對該用戶的登陸次數自增1 ret = r.zincrby("login:login_times", 1, uid) //那麼如何得到登陸次數最多的用戶呢,逆序排列取得排名前N的用戶 ret = r.zrevrange("login:login_times", 0, N-1)

好比在線遊戲的排行榜,根據得分你一般想要:

- 列出前100名高分選手

     - 列出某用戶當前的全球排名

這些操做對於Redis來講小菜一碟,即便你有幾百萬個用戶,每分鐘都會有幾百萬個新的得分。 
模式是這樣的,每次得到新得分時,咱們用這樣的代碼:

ZADD leaderboard <score> <username>

你可能用userID來取代username,這取決於你是怎麼設計的。 
獲得前100名高分用戶很簡單:

ZREVRANGE leaderboard 0 99

用戶的全球排名也類似,只須要:

ZRANK leaderboard <username>

須要精準設定過時時間的應用 
好比你能夠把上面說到的sorted set的score值設置成過時時間的時間戳,那麼就能夠簡單地經過過時時間排序,定時清除過時數據了,不只是清除Redis中的過時數據,你徹底能夠把Redis裏這個過時時間當成是對數據庫中數據的索引,用Redis來找出哪些數據須要過時刪除,而後再精準地從數據庫中刪除相應的記錄。

範圍查找 
來自Redis在Google Group上的一個問題,有一位同窗發貼求助,說要解決以下的一個問題:他有一個IP範圍對應地址的列表,如今須要給出一個IP的狀況下,迅速的查找到這個IP在哪一個範圍,也就是要判斷此IP的全部地。這個問題引來了Redis做者Salvatore Sanfilippo(@antirez)的回答。解答以下: 
例若有下面兩個範圍,10-20和30-40 
- A_start 10, A_end 20 
- B_start 30, B_end 40 
咱們將這兩個範圍的起始位置存在Redis的sorted set數據結構中,基本範圍起始值做爲score,範圍名加start和end爲其value值:

redis 127.0.0.1:6379> zadd ranges 10 A_start 1 redis 127.0.0.1:6379> zadd ranges 20 A_end 1 redis 127.0.0.1:6379> zadd ranges 30 B_start 1 redis 127.0.0.1:6379> zadd ranges 40 B_end 1

 

這樣數據在插入sorted set後,至關因而將這些起始位置按順序排列好了。 
如今我須要查找15這個值在哪個範圍中,只須要進行以下的zrangbyscore查找:

redis 127.0.0.1:6379> zrangebyscore ranges (15 +inf LIMIT 0 1 1) "A_end"

這個命令的意思是在Sorted Sets中查找大於15的第一個值。(+inf在Redis中表示正無窮大,15前面的括號表示>15而非>=15) 
查找的結果是A_end,因爲全部值是按順序排列的,因此能夠斷定15是在A_start到A_end區間上,也就是說15是在A這個範圍裏。至此大功告成。 
固然,若是你查找到的是一個start,好比我們用25,執行下面的命令:

redis 127.0.0.1:6379> zrangebyscore ranges (25 +inf LIMIT 0 1 1) "B_start"

返回結果代表其下一個節點是一個start節點,也就是說25這個值不處在任何start和end之間,不屬於任何範圍。 
固然,這個例子僅適用於相似上面的IP範圍查找的案例,由於這些值範圍之間沒有重合。若是是有重合的狀況,這個問題自己也就變成了一個一對多的問題。

Pub/Sub

Pub/Sub 從字面上理解就是發佈(Publish)與訂閱(Subscribe),在Redis中,你能夠設定對某一個key值進行消息發佈及消息訂閱,當一個key值上進行了消息發佈後,全部訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用做實時消息系統,好比普通的即時聊天,羣聊等功能。

使用場景

Pub/Sub構建實時消息系統

Redis的Pub/Sub系統能夠構建實時的消息系統 
好比不少用Pub/Sub構建的實時聊天系統的例子。

參考: 
http://www.redis.net.cn/tutorial/3501.html 
http://www.cnblogs.com/markhe/p/5689356.html 
http://www.cnblogs.com/ggjucheng/p/3349102.html

 

 

 

 

redis 數據類型詳解 以及 redis適用場景場合

Redis經常使用數據類型

Redis最爲經常使用的數據類型主要有如下:

  • String
  • Hash
  • List
  • Set
  • Sorted set
  • pub/sub
  • Transactions

在具體描述這幾種數據類型以前,咱們先經過一張圖瞭解下Redis內部內存管理中是如何描述這些不一樣數據類型的:

         首先Redis內部使用一個redisObject對象來表示全部的key和value,redisObject最主要的信息如上圖所示:

         type表明一個value對象具體是何種數據類型,

         encoding是不一樣數據類型在redis內部的存儲方式,

         好比:type=string表明value存儲的是一個普通字符串,那麼對應的encoding能夠是raw或者是int,若是是int則表明實際redis內部是按數值型類存儲和表示這個字符串的,固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。

       這裏須要特殊說明一下vm字段,只有打開了Redis的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的,該功能會在後面具體描述。經過上圖咱們能夠發現Redis使用redisObject來表示全部的key/value數據是比較浪費內存的,固然這些內存管理成本的付出主要也是爲了給Redis不一樣數據類型提供一個統一的管理接口,實際做者也提供了多種方法幫助咱們儘可能節省內存使用,咱們隨後會具體討論。

 

3.  各類數據類型應用和實現方式

下面咱們先來逐一的分析下這7種數據類型的使用和內部實現方式:

  • String:
Strings 數據結構是簡單的key-value類型,value其實不只是String,也能夠是數字.

經常使用命令:  set,get,decr,incr,mget 等。

 

應用場景:String是最經常使用的一種數據類型,普通的key/ value 存儲均可以歸爲此類.便可以徹底實現目前 Memcached 的功能,而且效率更高。還能夠享受Redis的定時持久化,操做日誌及 Replication等功能。除了提供與 Memcached 同樣的get、set、incr、decr 等操做外,Redis還提供了下面一些操做:

 

 

    • 獲取字符串長度
    • 往字符串append內容
    • 設置和獲取字符串的某一段內容
    • 設置及獲取字符串的某一位(bit)
    • 批量設置一系列字符串的內容

 

實現方式:String在redis內部存儲默認就是一個字符串,被redisObject所引用,當遇到incr,decr等操做時會轉成數值型進行計算,此時redisObject的encoding字段爲int。

  • Hash

經常使用命令:hget,hset,hgetall 等。

應用場景:在Memcached中,咱們常常將一些結構化的信息打包成HashMap,在客戶端序列化後存儲爲一個字符串的值,好比用戶的暱稱、年齡、性別、積分等,這時候在須要修改其中某一項時,一般須要將全部值取出反序列化後,修改某一項的值,再序列化存儲回去。這樣不只增大了開銷,也不適用於一些可能併發操做的場合(好比兩個併發的操做都須要修改積分)。而Redis的Hash結構能夠使你像在數據庫中Update一個屬性同樣只修改某一項屬性值。

        咱們簡單舉個實例來描述下Hash的應用場景,好比咱們要存儲一個用戶信息對象數據,包含如下信息:

用戶ID爲查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,若是用普通的key/value結構來存儲,主要有如下2種存儲方式:

 

第一種方式將用戶ID做爲查找key,把其餘信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增長了序列化/反序列化的開銷,而且在須要修改其中一項信息時,須要把整個對象取回,而且修改操做須要對併發進行保護,引入CAS等複雜問題。

第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應屬性的名稱做爲惟一標識來取得對應屬性的值,雖然省去了序列化開銷和併發問題,可是用戶ID爲重複存儲,若是存在大量這樣的數據,內存浪費仍是很是可觀的。

那麼Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部存儲的Value爲一個HashMap,並提供了直接存取這個Map成員的接口,以下圖:

也就是說,Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取均可以直接經過其內部Map的Key(Redis裏稱內部Map的key爲field), 也就是經過 key(用戶ID) + field(屬性標籤) 就能夠操做對應屬性數據了,既不須要重複存儲數據,也不會帶來序列化和併發修改控制的問題。很好的解決了問題。

這裏同時須要注意,Redis提供了接口(hgetall)能夠直接取到所有的屬性數據,可是若是內部Map的成員不少,那麼涉及到遍歷整個內部Map的操做,因爲Redis單線程模型的緣故,這個遍歷操做可能會比較耗時,而另其它客戶端的請求徹底不響應,這點須要格外注意。

實現方式:

上面已經說到Redis Hash對應Value內部實際就是一個HashMap,實際這裏會有2種不一樣實現,這個Hash的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。

  • List

經常使用命令:lpush,rpush,lpop,rpop,lrange等。

應用場景:

Redis list的應用場景很是多,也是Redis最重要的數據結構之一,好比twitter的關注列表,粉絲列表等均可以用Redis的list結構來實現。

Lists 就是鏈表,相信略有數據結構知識的人都應該能理解其結構。使用Lists結構,咱們能夠輕鬆地實現最新消息排行等功能。Lists的另外一個應用就是消息隊列,
能夠利用Lists的PUSH操做,將任務存在Lists中,而後工做線程再用POP操做將任務取出進行執行。Redis還提供了操做Lists中某一段的api,你能夠直接查詢,刪除Lists中某一段的元素。

實現方式:

Redis list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。

  • Set

經常使用命令:

sadd,spop,smembers,sunion 等。

應用場景:

Redis set對外提供的功能與list相似是一個列表的功能,特殊之處在於set是能夠自動排重的,當你須要存儲一個列表數據,又不但願出現重複數據時,set是一個很好的選擇,而且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。

Sets 集合的概念就是一堆不重複值的組合。利用Redis提供的Sets數據結構,能夠存儲一些集合性的數據,好比在微博應用中,能夠將一個用戶全部的關注人存在一個集合中,將其全部粉絲存在一個集合。Redis還爲集合提供了求交集、並集、差集等操做,能夠很是方便的實現如共同關注、共同喜愛、二度好友等功能,對上面的全部集合操做,你還能夠使用不一樣的命令選擇將結果返回給客戶端仍是存集到一個新的集合中。

實現方式:

set 的內部實現是一個 value永遠爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的緣由。

  • Sorted Set

經常使用命令:

zadd,zrange,zrem,zcard等

使用場景:

Redis sorted set的使用場景與set相似,區別是set不是自動有序的,而sorted set能夠經過用戶額外提供一個優先級(score)的參數來爲成員排序,而且是插入有序的,即自動排序。當你須要一個有序的而且不重複的集合列表,那麼能夠選擇sorted set數據結構,好比twitter 的public timeline能夠以發表時間做爲score來存儲,這樣獲取時就是自動按時間排好序的。

另外還能夠用Sorted Sets來作帶權重的隊列,好比普通消息的score爲1,重要消息的score爲2,而後工做線程能夠選擇按score的倒序來獲取工做任務。讓重要的任務優先執行。

實現方式:

Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是全部的成員,排序依據是HashMap裏存的score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。

 

  • Pub/Sub

 

Pub/Sub 從字面上理解就是發佈(Publish)與訂閱(Subscribe),在Redis中,你能夠設定對某一個key值進行消息發佈及消息訂閱,當一個key值上進行了消息發佈後,全部訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用做實時消息系統,好比普通的即時聊天,羣聊等功能。

 

  • Transactions

 

誰說NoSQL都不支持事務,雖然Redis的Transactions提供的並非嚴格的ACID的事務(好比一串用EXEC提交執行的命令,在執行中服務器宕機,那麼會有一部分命令執行了,剩下的沒執行),可是這個Transactions仍是提供了基本的命令打包執行的功能(在服務器不出問題的狀況下,能夠保證一連串的命令是順序在一塊兒執行的,中間有會有其它客戶端命令插進來執行)。Redis還提供了一個Watch功能,你能夠對一個key進行Watch,而後再執行Transactions,在這過程當中,若是這個Watched的值進行了修改,那麼這個Transactions會發現並拒絕執行。

 

 

4.  Redis實際應用場景

 

        Redis在不少方面與其餘數據庫解決方案不一樣:它使用內存提供主存儲支持,而僅使用硬盤作持久性的存儲;它的數據模型很是獨特,用的是單線程。另外一個大區別在於,你能夠在開發環境中使用Redis的功能,但卻不須要轉到Redis。

轉向Redis固然也是可取的,許多開發者從一開始就把Redis做爲首選數據庫;但設想若是你的開發環境已經搭建好,應用已經在上面運行了,那麼更換數據庫框架顯然不那麼容易。另外在一些須要大容量數據集的應用,Redis也並不適合,由於它的數據集不會超過系統可用的內存。因此若是你有大數據應用,並且主要是讀取訪問模式,那麼Redis並非正確的選擇。

        然而我喜歡Redis的一點就是你能夠把它融入到你的系統中來,這就可以解決不少問題,好比那些你現有的數據庫處理起來感到緩慢的任務。這些你就能夠經過Redis來進行優化,或者爲應用建立些新的功能。在本文中,我就想探討一些怎樣將Redis加入到現有的環境中,並利用它的原語命令等功能來解決 傳統環境中碰到的一些常見問題。在這些例子中,Redis都不是做爲首選數據庫。

一、顯示最新的項目列表

下面這個語句經常使用來顯示最新項目,隨着數據多了,查詢毫無疑問會愈來愈慢。

 

  1. SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10   

 

        在Web應用中,「列出最新的回覆」之類的查詢很是廣泛,這一般會帶來可擴展性問題。這使人沮喪,由於項目原本就是按這個順序被建立的,但要輸出這個順序卻不得不進行排序操做。

        相似的問題就能夠用Redis來解決。好比說,咱們的一個Web應用想要列出用戶貼出的最新20條評論。在最新的評論邊上咱們有一個「顯示所有」的連接,點擊後就能夠得到更多的評論。

        咱們假設數據庫中的每條評論都有一個惟一的遞增的ID字段。

        咱們能夠使用分頁來製做主頁和評論頁,使用Redis的模板,每次新評論發表時,咱們會將它的ID添加到一個Redis列表:

 

  1. LPUSH latest.comments <ID>   

 

       咱們將列表裁剪爲指定長度,所以Redis只須要保存最新的5000條評論:

       LTRIM latest.comments 0 5000 

      每次咱們須要獲取最新評論的項目範圍時,咱們調用一個函數來完成(使用僞代碼):

 

  1. FUNCTION get_latest_comments(start, num_items):  
  2.     id_list = redis.lrange("latest.comments",start,start+num_items - 1)  
  3.     IF id_list.length < num_items  
  4.         id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")  
  5.     END  
  6.     RETURN id_list  
  7. END  

 

 

      這裏咱們作的很簡單。在Redis中咱們的最新ID使用了常駐緩存,這是一直更新的。可是咱們作了限制不能超過5000個ID,所以咱們的獲取ID函數會一直詢問Redis。只有在start/count參數超出了這個範圍的時候,才須要去訪問數據庫。

        咱們的系統不會像傳統方式那樣「刷新」緩存,Redis實例中的信息永遠是一致的。SQL數據庫(或是硬盤上的其餘類型數據庫)只是在用戶須要獲取「很遠」的數據時纔會被觸發,而主頁或第一個評論頁是不會麻煩到硬盤上的數據庫了。

二、刪除與過濾

      咱們能夠使用LREM來刪除評論。若是刪除操做很是少,另外一個選擇是直接跳過評論條目的入口,報告說該評論已經不存在。

       有些時候你想要給不一樣的列表附加上不一樣的過濾器。若是過濾器的數量受到限制,你能夠簡單的爲每一個不一樣的過濾器使用不一樣的Redis列表。畢竟每一個列表只有5000條項目,但Redis卻可以使用很是少的內存來處理幾百萬條項目。

三、排行榜相關

      另外一個很廣泛的需求是各類數據庫的數據並不是存儲在內存中,所以在按得分排序以及實時更新這些幾乎每秒鐘都須要更新的功能上數據庫的性能不夠理想。

      典型的好比那些在線遊戲的排行榜,好比一個Facebook的遊戲,根據得分你一般想要:

         - 列出前100名高分選手

         - 列出某用戶當前的全球排名

      這些操做對於Redis來講小菜一碟,即便你有幾百萬個用戶,每分鐘都會有幾百萬個新的得分。

      模式是這樣的,每次得到新得分時,咱們用這樣的代碼:

      ZADD leaderboard  <score>  <username> 

     你可能用userID來取代username,這取決於你是怎麼設計的。

      獲得前100名高分用戶很簡單:ZREVRANGE leaderboard 0 99。

      用戶的全球排名也類似,只須要:ZRANK leaderboard <username>。

 

四、按照用戶投票和時間排序

      排行榜的一種常見變體模式就像Reddit或Hacker News用的那樣,新聞按照相似下面的公式根據得分來排序:

       score = points / time^alpha 

      所以用戶的投票會相應的把新聞挖出來,但時間會按照必定的指數將新聞埋下去。下面是咱們的模式,固然算法由你決定。

      模式是這樣的,開始時先觀察那些多是最新的項目,例如首頁上的1000條新聞都是候選者,所以咱們先忽視掉其餘的,這實現起來很簡單。

      每次新的新聞貼上來後,咱們將ID添加到列表中,使用LPUSH + LTRIM,確保只取出最新的1000條項目。

      有一項後臺任務獲取這個列表,而且持續的計算這1000條新聞中每條新聞的最終得分。計算結果由ZADD命令按照新的順序填充生成列表,老新聞則被清除。這裏的關鍵思路是排序工做是由後臺任務來完成的。

 

五、處理過時項目

      另外一種經常使用的項目排序是按照時間排序。咱們使用unix時間做爲得分便可。

      模式以下:

       - 每次有新項目添加到咱們的非Redis數據庫時,咱們把它加入到排序集合中。這時咱們用的是時間屬性,current_time和time_to_live。

       - 另外一項後臺任務使用ZRANGE…SCORES查詢排序集合,取出最新的10個項目。若是發現unix時間已通過期,則在數據庫中刪除條目。

 

六、計數

       Redis是一個很好的計數器,這要感謝INCRBY和其餘類似命令。

       我相信你曾許屢次想要給數據庫加上新的計數器,用來獲取統計或顯示新信息,可是最後卻因爲寫入敏感而不得不放棄它們。

       好了,如今使用Redis就不須要再擔憂了。有了原子遞增(atomic increment),你能夠放心的加上各類計數,用GETSET重置,或者是讓它們過時。

       例如這樣操做:

         INCR user:<id> EXPIRE 

         user:<id> 60 

       你能夠計算出最近用戶在頁面間停頓不超過60秒的頁面瀏覽量,當計數達到好比20時,就能夠顯示出某些條幅提示,或是其它你想顯示的東西。

七、特定時間內的特定項目

        另外一項對於其餘數據庫很難,但Redis作起來卻垂手可得的事就是統計在某段特色時間裏有多少特定用戶訪問了某個特定資源。好比我想要知道某些特定的註冊用戶或IP地址,他們到底有多少訪問了某篇文章。

      每次我得到一次新的頁面瀏覽時我只須要這樣作:

       SADD page:day1:<page_id> <user_id> 

      固然你可能想用unix時間替換day1,好比time()-(time()%3600*24)等等。

      想知道特定用戶的數量嗎?只須要使用SCARD page:day1:<page_id>。

       須要測試某個特定用戶是否訪問了這個頁面?SISMEMBER page:day1:<page_id>。

 

八、實時分析正在發生的狀況,用於數據統計與防止垃圾郵件等

        咱們只作了幾個例子,但若是你研究Redis的命令集,而且組合一下,就能得到大量的實時分析方法,有效並且很是省力。使用Redis原語命令,更容易實施垃圾郵件過濾系統或其餘實時跟蹤系統。

 

九、Pub/Sub

       Redis的Pub/Sub很是很是簡單,運行穩定而且快速。支持模式匹配,可以實時訂閱與取消頻道。

十、隊列

        你應該已經注意到像list push和list pop這樣的Redis命令可以很方便的執行隊列操做了,但能作的可不止這些:好比Redis還有list pop的變體命令,可以在列表爲空時阻塞隊列。

       現代的互聯網應用大量地使用了消息隊列(Messaging)。消息隊列不只被用於系統內部組件之間的通訊,同時也被用於系統跟其它服務之間的交互。消息隊列的使用能夠增長系統的可擴展性、靈活性和用戶體驗。非基於消息隊列的系統,其運行速度取決於系統中最慢的組件的速度(注:短板效應)。而基於消息隊列能夠將系統中各組件解除耦合,這樣系統就再也不受最慢組件的束縛,各組件能夠異步運行從而得以更快的速度完成各自的工做。

    此外,當服務器處在高併發操做的時候,好比頻繁地寫入日誌文件。能夠利用消息隊列實現異步處理。從而實現高性能的併發操做。

 

十一、緩存

        Redis的緩存部分值得寫一篇新文章,我這裏只是簡單的說一下。Redis可以替代memcached,讓你的緩存從只能存儲數據變得可以更新數據,所以你再也不須要每次都從新生成數據了。

  

 

 

Redis高級特性及應用場景

 

redis中鍵的生存時間(expire)

redis中能夠使用expire命令設置一個鍵的生存時間,到時間後redis會自動刪除它。

 

  1. 過時時間能夠設置爲秒或者毫秒精度。
  2. 過時時間分辨率老是 1 毫秒。
  3. 過時信息被複制和持久化到磁盤,當 Redis 中止時時間仍然在計算 (也就是說 Redis 保存了過時時間)。

 

 

expire  設置生存時間(單位/秒)

 

[python]  view plain  copy
 
  1. expire key seconds(秒)  

 

ttl 查看鍵的剩餘生存時間

 

[python]  view plain  copy
 
  1. ttl key  

 

persist 取消生存時間

 

[python]  view plain  copy
 
  1. persist key  

 

expireat [key] unix時間戳1351858600

示例:

 

[python]  view plain  copy
 
  1. EXPIREAT cache 1355292000     # 這個 key 將在 2012.12.12 過時  

 

操做圖示:

 

應用場景:

 

  1. 限時的優惠活動信息
  2. 網站數據緩存(對於一些須要定時更新的數據,例如:積分排行榜)
  3. 手機驗證碼
  4. 限制網站訪客訪問頻率(例如:1分鐘最多訪問10次)

 

 

 

 

redis的事務(transaction)

 

redis中的事務是一組命令的集合。事務同命令同樣都是redis的最小執行單元。一組事務中的命令要麼都執行,要麼都不執行。(例如:轉帳)

 

原理:

先將屬於一個事務的命令發送給redis進行緩存,最後再讓redis依次執行這些命令。

 

應用場景:

 

  1. 一組命令必須同時都執行,或者都不執行。
  2. 咱們想要保證一組命令在執行的過程之中不被其它命令插入。

 

 

命令:

 

[python]  view plain  copy
 
  1. multi    //事務開始  
  2. .....  
  3. exec     //事務結束,開始執行事務中的命令  
  4. discard     //放棄事務  

 

錯誤處理

1:語法錯誤:致命的錯誤,事務中的全部命令都不會執行

2:運行錯誤:不會影響事務中其餘命令的執行

 

Redis 不支持回滾(roll back)

正由於redis不支持回滾功能,才使得redis在事務上能夠保持簡潔和快速。

 

watch命令

做用:監控一個或者多個鍵,當被監控的鍵值被修改後阻止以後的一個事務的執行。

可是不能保證其它客戶端不修改這一鍵值,因此咱們須要在事務執行失敗後從新執行事務中的命令。

注意:執行完事務的exec命令以後,watch就會取消對全部鍵值的監控

unwatch:取消監控

 

 操做圖示:

 

 

 

 

redis中數據的排序(sort)

sort命令能夠對列表類型,集合類型和有序集合類型進行排序。

 

[python]  view plain  copy
 
  1. sort key [desc] [limit offset count]  

 

by 參考鍵(參考鍵能夠是字符串類型或者是hash類型的某個字段,hash類型的格式爲:鍵名->字段名)

 

  1. 若是參考鍵中不帶*號則不排序
  2. 若是某個元素的參考鍵不存在,則默認參考鍵的值爲0

 

 

擴展 get參數

 

  1. get參數的規則和by參數的規則同樣
  2. get # (返回元素自己的值)

 

 

擴展 store參數

使用store 參數能夠把sort的排序結果保存到指定的列表中

 

性能優化

1:儘量減小待排序鍵中元素的數量

2:使用limit參數只獲取須要的數據

3:若是要排序的數據數量很大,儘量使用store參數將結果緩存。

 

 操做圖示:

 

  

 

 

 

「發佈/訂閱」模式

 

發佈:publish

 

[python]  view plain  copy
 
  1. publish channel message  

 

訂閱:subscribe

 

[python]  view plain  copy
 
  1. subscribe channel [.....]  

 

取消訂閱:unsubscribe

 

[python]  view plain  copy
 
  1. unsubscribe [channel]  

 

按照規則訂閱:psubscribe

 

[python]  view plain  copy
 
  1. psubscribe channel ?  

 

按照規則取消訂閱:punsubscribe

注意:使用punsubscribe命令只能退訂經過psubscribe 訂閱的頻道。

 

操做圖示:(訂閱頻道後,頻道每發佈一條消息,都能動態顯示出來)

訂閱:

 

 發佈:

 

 

 

 

redis任務隊列

任務隊列:使用lpush和rpop(brpop)能夠實現普通的任務隊列。

 brpop是列表的阻塞式(blocking)彈出原語。

它是 RPOP命令的阻塞版本,當給定列表內沒有任何元素可供彈出的時候,鏈接將被 BRPOP命令阻塞,直到等待超時或發現可彈出元素爲止。

當給定多個 key 參數時,按參數 key 的前後順序依次檢查各個列表,彈出第一個非空列表的尾部元素。

 

優先級隊列:

 

[python]  view plain  copy
 
  1. brpop key1 key2 key3 timeout  


操做圖示:

 

 

 

 

redis管道(pipeline)

redis的pipeline(管道)功能在命令行中沒有,可是redis是支持管道的,在java的客戶端(jedis)中是能夠使用的。

測試發現:

1:不使用管道方式,插入1000條數據耗時328毫秒

 

[java]  view plain  copy
 
  1. // 測試不使用管道  
  2. public static void testInsert() {  
  3.     long currentTimeMillis = System.currentTimeMillis();  
  4.     Jedis jedis = new Jedis("192.168.33.130", 6379);  
  5.     for (int i = 0; i < 1000; i++) {  
  6.         jedis.set("test" + i, "test" + i);  
  7.     }  
  8.     long endTimeMillis = System.currentTimeMillis();  
  9.     System.out.println(endTimeMillis - currentTimeMillis);  
  10. }  

 

2:使用管道方式,插入1000條數據耗時37毫秒

 

[java]  view plain  copy
 
  1. // 測試管道  
  2. public static void testPip() {  
  3.     long currentTimeMillis = System.currentTimeMillis();  
  4.     Jedis jedis = new Jedis("192.168.33.130", 6379);  
  5.     Pipeline pipelined = jedis.pipelined();  
  6.     for (int i = 0; i < 1000; i++) {  
  7.         pipelined.set("bb" + i, i + "bb");  
  8.     }  
  9.     pipelined.sync();  
  10.     long endTimeMillis = System.currentTimeMillis();  
  11.     System.out.println(endTimeMillis - currentTimeMillis);  
  12. }  

 

在插入更多數據的時候,管道的優點更加明顯:測試10萬條數據的時候,不使用管道要40秒,實用管道378毫秒。

 

 

 

redis持久化(persistence)

redis支持兩種方式的持久化,能夠單獨使用或者結合起來使用。

第一種:RDB方式(redis默認的持久化方式)

第二種:AOF方式

 

 

 

redis持久化之RDB

rdb方式的持久化是經過快照完成的,當符合必定條件時redis會自動將內存中的全部數據執行快照操做並存儲到硬盤上。默認存儲在dump.rdb文件中。(文件名在配置文件中dbfilename)

 

redis進行快照的時機(在配置文件redis.conf中)

 

[java]  view plain  copy
 
  1. save 900 1  //表示900秒內至少一個鍵被更改則進行快照。  
  2. save 300 10  //表示300秒內10條被更改則快照  
  3. save 60 10000  //60秒內10000條  

 

Redis自動實現快照的過程

一、redis使用fork函數複製一份當前進程的副本(子進程)

二、父進程繼續接收並處理客戶端發來的命令,而子進程開始將內存中的數據寫入硬盤中的臨時文件

三、當子進程寫入完全部數據後會用該臨時文件替換舊的RDB文件,至此,一次快照操做完成。

 

注意:redis在進行快照的過程當中不會修改RDB文件,只有快照結束後纔會將舊的文件替換成新的,也就是說任什麼時候候RDB文件都是完整的。

這就使得咱們能夠經過定時備份RDB文件來實現redis數據庫的備份

RDB文件是通過壓縮的二進制文件,佔用的空間會小於內存中的數據,更加利於傳輸。

 

手動執行save或者bgsave命令讓redis執行快照。

兩個命令的區別在於,save是由主進程進行快照操做,會阻塞其它請求。bgsave是由redis執行fork函數複製出一個子進程來進行快照操做。

 

文件修復:

 

[python]  view plain  copy
 
  1. redis-check-dump  

 

rdb的優缺點

優勢:因爲存儲的有數據快照文件,恢復數據很方便。

缺點:會丟失最後一次快照之後更改的全部數據。

 

 

 

redis持久化之AOF

 

aof方式的持久化是經過日誌文件的方式。默認狀況下redis沒有開啓aof,能夠經過參數appendonly參數開啓。

 

[python]  view plain  copy
 
  1. appendonly yes  

 

aof文件的保存位置和rdb文件的位置相同,都是dir參數設置的,默認的文件名是appendonly.aof,能夠經過appendfilename參數修改

 

[python]  view plain  copy
 
  1. appendfilename appendonly.aof  

 

redis寫命令同步的時機

 

[python]  view plain  copy
 
  1. appendfsync always 每次都會執行  
  2. appendfsync everysec 默認 每秒執行一次同步操做(推薦,默認)  
  3. appendfsync no不主動進行同步,由操做系統來作,30秒一次  

 

aof日誌文件重寫

 

[python]  view plain  copy
 
  1. auto-aof-rewrite-percentage 100(當目前aof文件大小超過上一次重寫時的aof文件大小的百分之多少時會再次進行重寫,若是以前沒有重寫,則以啓動時的aof文件大小爲依據)  
  2. auto-aof-rewrite-min-size 64mb  

 

手動執行bgrewriteaof進行重寫

重寫的過程只和內存中的數據有關,和以前的aof文件無關。

所謂的「重寫」實際上是一個有歧義的詞語, 實際上, AOF 重寫並不須要對原有的 AOF 文件進行任何寫入和讀取, 它針對的是數據庫中鍵的當前值。

 

文件修復:

 

[python]  view plain  copy
 
  1. redis-check-aof  

 

動態切換redis持久方式,從 RDB 切換到 AOF(支持Redis 2.2及以上)

 

[python]  view plain  copy
 
  1. CONFIG SET appendonly yes  
  2. CONFIG SET save ""(可選)  

 

注意:

一、當redis啓動時,若是rdb持久化和aof持久化都打開了,那麼程序會優先使用aof方式來恢復數據集,由於aof方式所保存的數據一般是最完整的。若是aof文件丟失了,則啓動以後數據庫內容爲空。

二、若是想把正在運行的redis數據庫,從RDB切換到AOF,建議先使用動態切換方式,再修改配置文件,重啓數據庫。(不能本身修改配置文件,重啓數據庫,不然數據庫中數據就爲空了。)

 

 

 

redis中的config命令

 

使用config set能夠動態設置參數信息,服務器重啓以後就失效了。

 

[python]  view plain  copy
 
  1. config set appendonly yes  
  2. config set save "90 1 30 10 60 100"  

 

使用config get能夠查看全部能夠使用config set命令設置的參數

 

[python]  view plain  copy
 
  1. config get *  

 

使用config rewrite命令對啓動 Redis 服務器時所指定的 redis.conf 文件進行改寫(Redis 2.8 及以上版本才能夠使用),主要是把使用config set動態指定的命令保存到配置文件中。

 

[python]  view plain  copy
 
  1. config rewrite  

 

注意:config rewrite命令對 redis.conf 文件的重寫是原子性的, 而且是一致的: 若是重寫出錯或重寫期間服務器崩潰, 那麼重寫失敗, 原有 redis.conf 文件不會被修改。 若是重寫成功, 那麼 redis.conf 文件爲重寫後的新文件。

 

 

 

redis的安全策略

 

設置數據庫密碼

修改配置

 

[python]  view plain  copy
 
  1. requirepass password  

 

驗證密碼

 

[python]  view plain  copy
 
  1. auth password  

 

bind參數(可讓數據庫只能在指定IP下訪問)

 

[python]  view plain  copy
 
  1. bind 127.0.0.1  

 

命令重命名

修改命令的名稱

 

[python]  view plain  copy
 
  1. rename-command flushall cleanall  

 

禁用命令 

 

[python]  view plain  copy
 
  1. rename-command flushall ""  

 

 

 

 

redis工具

redis-cli  命令行

 

[python]  view plain  copy
 
  1. info/monitor(調試命令)  

 

Redisclient(redis數據庫可視化工具,不怎麼實用)

http://www.oschina.net/news/53391/redisclient-1-0

http://www.oschina.net/news/55634/redisclient-2-0

 

 

 

 

redis info命令

以一種易於解釋(parse)且易於閱讀的格式,返回關於 Redis 服務器的各類信息和統計數值。

經過給定可選的參數 section ,可讓命令只返回某一部分的信息:

 

內容過多,詳細參考

http://redisdoc.com/server/info.html

 

 

 

redis內存佔用狀況

測試狀況:

100萬個鍵值對(鍵是0到999999值是字符串「hello world」)在32位操做系統的筆記本上 用了100MB

使用64位的操做系統的話,相對來講佔用的內存會多一點,這是由於64位的系統裏指針佔用了8個字節,可是64位系統也能支持更大的內存,因此運行大型的redis服務仍是建議使用64位服務器

 

 

 

Redis實例最多存keys數

 

理論上Redis能夠處理多達2的32次方的keys,而且在實際中進行了測試,每一個實例至少存放了2億5千萬的keys

也能夠說Redis的存儲極限是系統中的可用內存值。

 

 

 

 

redis優化1

 

精簡鍵名和鍵值

鍵名:儘可能精簡,可是也不能單純爲了節約空間而使用不易理解的鍵名。

鍵值:對於鍵值的數量固定的話能夠使用0和1這樣的數字來表示,(例如:male/female、right/wrong)

當業務場景不須要數據持久化時,關閉全部的持久化方式能夠得到最佳的性能

 

內部編碼優化(你們能夠本身瞭解)

redis爲每種數據類型都提供了兩種內部編碼方式,在不一樣的狀況下redis會自動調整合適的編碼方式。(如圖所示)

 

 

SLOWLOG [get/reset/len]

 

[python]  view plain  copy
 
  1. slowlog-log-slower-than  //它決定要對執行時間大於多少微秒(microsecond,1秒 = 1,000,000 微秒)的命令進行記錄  
  2. slowlog-max-len   //它決定 slowlog 最多能保存多少條日誌  

當發現redis性能降低的時候能夠查看下是哪些命令致使的

 

 

 

redis優化2

 

修改linux內核內存分配策略

緣由:

 

redis在運行過程當中可能會出現下面問題

錯誤日誌:

 

[java]  view plain  copy
 
  1. WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1'  

 

redis在備份數據的時候,會fork出一個子進程,理論上child進程所佔用的內存和parent是同樣的,好比parent佔用的內存爲8G,這個時候也要一樣分配8G的內存給child,若是內存沒法負擔,每每會形成redis服務器的down機或者IO負載太高,效率降低。因此內存分配策略應該設置爲 1(表示內核容許分配全部的物理內存,而無論當前的內存狀態如何)。

內存分配策略有三種

可選值:0、一、2。

0, 表示內核將檢查是否有足夠的可用內存供應用進程使用;若是有足夠的可用內存,內存申請容許;不然,內存申請失敗,並把錯誤返回給應用進程。

1, 無論須要多少內存,都容許申請。

2, 只容許分配物理內存和交換內存的大小。(交換內存通常是物理內存的一半)

 

向/etc/sysctl.conf添加

 

[python]  view plain  copy
 
  1. vm.overcommit_memory = 1    //而後重啓服務器  

或者執行

 

[python]  view plain  copy
 
  1. sysctl vm.overcommit_memory=1   //當即生效  

 

問題圖示:

 

 

 

redis優化3

關閉Transparent Huge Pages(THP)

THP會形成內存鎖影響redis性能,建議關閉

 

[python]  view plain  copy
 
  1. Transparent HugePages :用來提升內存管理的性能  
  2. Transparent Huge Pages在32位的RHEL 6中是不支持的  

 

使用root用戶執行下面命令

 

[python]  view plain  copy
 
  1. echo never > /sys/kernel/mm/transparent_hugepage/enabled  

 

把這條命令添加到這個文件中/etc/rc.local

 

 

 

 

 

redis優化4

修改linux中TCP 監聽的最大容納數量

在高併發環境下你須要一個高backlog值來避免慢客戶端鏈接問題。注意Linux內核默默地將這個值減少到/proc/sys/net/core/somaxconn的值,因此須要確認增大somaxconn和tcp_max_syn_backlog兩個值來達到想要的效果。

 

[python]  view plain  copy
 
  1. echo 511 > /proc/sys/net/core/somaxconn  

注意:這個參數並非限制redis的最大連接數。若是想限制redis的最大鏈接數須要修改maxclients,默認最大鏈接數爲10000。

 

 

 

 

 

redis優化5

 

限制redis的內存大小

經過redis的info命令查看內存使用狀況

若是不設置maxmemory或者設置爲0,64位系統不限制內存,32位系統最多使用3GB內存。

修改配置文件中的maxmemory和maxmemory-policy

 

[python]  view plain  copy
 
  1. maxmemory:最大內存  
  2. maxmemory-policy:內存不足時,數據清除策略  

 

若是能夠肯定數據總量不大,而且內存足夠的狀況下不須要限制redis使用的內存大小。若是數據量不可預估,而且內存也有限的話,儘可能限制下redis使用的內存大小,這樣能夠避免redis使用swap分區或者出現OOM錯誤。

注意:若是不限制內存,當物理內存使用完以後,會使用swap分區,這樣性能較低,若是限制了內存,當到達指定內存以後就不能添加數據了,不然會報OOM錯誤。能夠設置maxmemory-policy,內存不足時刪除數據。

 

拓展

used_memory是Redis使用的內存總量,它包含了實際緩存佔用的內存和Redis自身運行所佔用的內存(以字節(byte)爲單位,其中used_memory_human上的數據和used_memory是同樣的值,它以M爲單位顯示,僅爲了方便閱讀)。

 

若是一個Redis實例的內存使用率超過可用最大內存(used_memory >可用最大內存),那麼操做系統開始進行內存與swap空間交換,把內存中舊的或再也不使用的內容寫入硬盤上(硬盤上的這塊空間叫Swap分區),以便騰出新的物理內存給新頁或活動頁(page)使用。

 

在硬盤上進行讀寫操做要比在內存上進行讀寫操做,時間上慢了近5個數量級,內存是0.1us(微秒)、而硬盤是10ms(毫秒)。若是Redis進程上發生內存交換,那麼Redis和依賴Redis上數據的應用會受到嚴重的性能影響。 經過查看used_memory指標可知道Redis正在使用的內存狀況,若是used_memory>可用最大內存,那就說明Redis實例正在進行內存交換或者已經內存交換完畢。管理員根據這個狀況,執行相對應的應急措施。

 

排查方案:

如果在使用Redis期間沒有開啓rdb快照或aof持久化策略,那麼緩存數據在Redis崩潰時就有丟失的危險。由於當Redis內存使用率超過可用內存的95%時,部分數據開始在內存與swap空間來回交換,這時就可能有丟失數據的危險。

當開啓並觸發快照功能時,Redis會fork一個子進程把當前內存中的數據徹底複製一份寫入到硬盤上。所以如果當前使用內存超過可用內存的45%時觸發快照功能,那麼此時進行的內存交換會變的很是危險(可能會丟失數據)。 假若在這個時候實例上有大量頻繁的更新操做,問題會變得更加嚴重。

 

經過減小Redis的內存佔用率,來避免這樣的問題,或者使用下面的技巧來避免內存交換髮生:

一、儘量的使用Hash數據結構。由於Redis在儲存小於100個字段的Hash結構上,其存儲效率是很是高的。因此在不須要集合(set)操做或list的push/pop操做的時候,儘量的使用Hash結構。好比,在一個web應用程序中,須要存儲一個對象表示用戶信息,使用單個key表示一個用戶,其每一個屬性存儲在Hash的字段裏,這樣要比給每一個屬性單獨設置一個key-value要高效的多。 一般狀況下假若有數據使用string結構,用多個key存儲時,那麼應該轉換成單key多字段的Hash結構。 如上述例子中介紹的Hash結構應包含,單個對象的屬性或者單個用戶各類各樣的資料。Hash結構的操做命令是HSET(key, fields, value)和HGET(key, field),使用它能夠存儲或從Hash中取出指定的字段。

 

二、設置key的過時時間。一個減小內存使用率的簡單方法就是,每當存儲對象時確保設置key的過時時間。假若key在明確的時間週期內使用或者舊key不大可能被使用時,就能夠用Redis過時時間命令(expire,expireat, pexpire, pexpireat)去設置過時時間,這樣Redis會在key過時時自動刪除key。 假如你知道每秒鐘有多少個新key-value被建立,那能夠調整key的存活時間,並指定閥值去限制Redis使用的最大內存。

 

三、回收key。在Redis配置文件中(通常叫Redis.conf),經過設置「maxmemory」屬性的值能夠限制Redis最大使用的內存,修改後重啓實例生效。也能夠使用客戶端命令config set maxmemory 去修改值,這個命令是當即生效的,但會在重啓後會失效,須要使用config rewrite命令去刷新配置文件。 如果啓用了Redis快照功能,應該設置「maxmemory」值爲系統可以使用內存的45%,由於快照時須要一倍的內存來複制整個數據集,也就是說若是當前已使用45%,在快照期間會變成95%(45%+45%+5%),其中5%是預留給其餘的開銷。 若是沒開啓快照功能,maxmemory最高能設置爲系統可用內存的95%。

 

 

當內存使用達到設置的最大閥值時,須要選擇一種key的回收策略,可在Redis.conf配置文件中修改「maxmemory-policy」屬性值。 如果Redis數據集中的key都設置了過時時間,那麼「volatile-ttl」策略是比較好的選擇。但若是key在達到最大內存限制時沒可以迅速過時,或者根本沒有設置過時時間。那麼設置爲「allkeys-lru」值比較合適,它容許Redis從整個數據集中挑選最近最少使用的key進行刪除(LRU淘汰算法)。Redis還提供了一些其餘淘汰策略,以下:

 

[python]  view plain  copy
 
  1. volatile-lru: 使用LRU算法從已設置過時時間的數據集合中淘汰數據。  
  2. volatile-ttl:從已設置過時時間的數據集合中挑選即將過時的數據淘汰。  
  3. volatile-random:從已設置過時時間的數據集合中隨機挑選數據淘汰。  
  4. allkeys-lru:使用LRU算法從全部數據集合中淘汰數據。  
  5. allkeys-random:從數據集合中任意選擇數據淘汰  
  6. no-enviction:禁止淘汰數據。  

 

經過設置maxmemory爲系統可用內存的45%或95%(取決於持久化策略)和設置「maxmemory-policy」爲「volatile-ttl」或「allkeys-lru」(取決於過時設置),能夠比較準確的限制Redis最大內存使用率,在絕大多數場景下使用這2種方式可確保Redis不會進行內存交換。假若你擔憂因爲限制了內存使用率致使丟失數據的話,能夠設置noneviction值禁止淘汰數據。

 

 

 

redis優化6

 

Redis是個單線程模型,客戶端過來的命令是按照順序執行的,因此想要一次添加多條數據的時候能夠使用管道,或者使用一次能夠添加多條數據的命令,例如:

 

 

 

 

 

Redis應用場景

 

發佈與訂閱

在更新中保持用戶對數據的映射是系統中的一個廣泛任務。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令,讓這個變得更加容易。

 

代碼示例:

 

[java]  view plain  copy
 
  1. // 訂閱頻道數據  
  2. public static void testSubscribe() {  
  3.     //鏈接Redis數據庫  
  4.     Jedis jedis = new Jedis("192.168.33.130", 6379);  
  5.     JedisPubSub jedisPubSub = new JedisPubSub() {  
  6.   
  7.         // 當向監聽的頻道發送數據時,這個方法會被觸發  
  8.         @Override  
  9.         public void onMessage(String channel, String message) {  
  10.             System.out.println("收到消息" + message);  
  11.             //當收到 "unsubscribe" 消息時,調用取消訂閱方法  
  12.             if ("unsubscribe".equals(message)) {  
  13.                 this.unsubscribe();  
  14.             }  
  15.         }  
  16.   
  17.         // 當取消訂閱指定頻道的時候,這個方法會被觸發  
  18.         @Override  
  19.         public void onUnsubscribe(String channel, int subscribedChannels) {  
  20.             System.out.println("取消訂閱頻道" + channel);  
  21.         }  
  22.   
  23.     };  
  24.     // 訂閱以後,當前進程一致處於監聽狀態,當被取消訂閱以後,當前進程會結束  
  25.     jedis.subscribe(jedisPubSub, "ch1");  
  26. }  
  27.   
  28.   
  29. // 發佈頻道數據  
  30. public static void testPubSub() throws Exception {  
  31.     //連接Redis數據庫  
  32.     Jedis jedis = new Jedis("192.168.33.130", 6379);  
  33.     //發佈頻道 "ch1" 和消息 "hello redis"  
  34.     jedis.publish("ch1", "hello redis");  
  35.     //關閉鏈接  
  36.     jedis.close();  
  37. }  

打印結果:

 

 

限制網站訪客訪問頻率

進行各類數據統計的用途是很是普遍的,好比想知道何時封鎖一個IP地址。INCRBY命令讓這些變得很容易,經過原子遞增保持計數;GETSET用來重置計數器;過時屬性expire用來確認一個關鍵字何時應該刪除。

 

代碼示例:

[java]  view plain  copy
 
  1. //指定Redis數據庫鏈接的IP和端口  
  2. String host = "192.168.33.130";  
  3. int port = 6379;  
  4. Jedis jedis = new Jedis(host, port);  
  5.   
  6. /** 
  7.  * 限制網站訪客訪問頻率 一分鐘以內最多訪問10次 
  8.  *  
  9.  * @throws Exception 
  10.  */  
  11. @Test  
  12. public void test3() throws Exception {  
  13.     // 模擬用戶的頻繁請求  
  14.     for (int i = 0; i < 20; i++) {  
  15.         boolean result = testLogin("192.168.1.100");  
  16.         if (result) {  
  17.             System.out.println("正常訪問");  
  18.         } else {  
  19.             System.err.println("訪問受限");  
  20.         }  
  21.     }  
  22.   
  23. }  
  24.   
  25. /** 
  26.  * 判斷用戶是否能夠訪問網站 
  27.  *  
  28.  * @param ip 
  29.  * @return 
  30.  */  
  31. public boolean testLogin(String ip) {  
  32.     String value = jedis.get(ip);  
  33.     if (value == null) {  
  34.         //初始化時設置IP訪問次數爲1  
  35.         jedis.set(ip, "1");  
  36.         //設置IP的生存時間爲60秒,60秒內IP的訪問次數由程序控制  
  37.         jedis.expire(ip, 60);  
  38.     } else {  
  39.         int parseInt = Integer.parseInt(value);  
  40.         //若是60秒內IP的訪問次數超過10,返回false,實現了超過10次禁止分的功能  
  41.         if (parseInt > 10) {  
  42.             return false;  
  43.         } else {  
  44.             //若是沒有10次,能夠自增  
  45.             jedis.incr(ip);  
  46.         }  
  47.     }  
  48.     return true;  
  49. }  

打印結果:

 

 

 

監控變量在事務執行時是否被修改

 

代碼示例:

 

[java]  view plain  copy
 
  1. // 指定Redis數據庫鏈接的IP和端口  
  2. String host = "192.168.33.130";  
  3. int port = 6379;  
  4. Jedis jedis = new Jedis(host, port);  
  5.   
  6. /** 
  7.  * 監控變量a在一段時間內是否被修改,若沒有,則執行事務,若被修改,則事務不執行 
  8.  *  
  9.  * @throws Exception 
  10.  */  
  11. @Test  
  12. public void test4() throws Exception {  
  13.     //監控變量a,在事務執行後watch功能也結束  
  14.     jedis.watch("a");  
  15.     //須要數據庫中先有a,而且a的值爲字符串數字  
  16.     String value = jedis.get("a");  
  17.     int parseInt = Integer.parseInt(value);  
  18.     parseInt++;  
  19.     System.out.println("線程開始休息。。。");  
  20.     Thread.sleep(5000);  
  21.   
  22.     //開啓事務  
  23.     Transaction transaction = jedis.multi();  
  24.     transaction.set("a", parseInt + "");  
  25.     //執行事務  
  26.     List<Object> exec = transaction.exec();  
  27.     if (exec == null) {  
  28.         System.out.println("事務沒有執行.....");  
  29.     } else {  
  30.         System.out.println("正常執行......");  
  31.     }  
  32. }  

打印結果:

變量a沒有被修改時:

 

 

變量a被修改時:

 

 

 

各類計數

商品維度計數(喜歡數,評論數,鑑定數,瀏覽數,etc)

採用Redis 的類型: Hash. 若是你對redis數據類型不太熟悉,能夠參考 http://redis.io/topics/data-types-intro

爲product定義個key product:,爲每種數值定義hashkey, 譬如喜歡數xihuan

 

 

用戶維度計數(動態數、關注數、粉絲數、喜歡商品數、發帖數 等)

用戶維度計數同商品維度計數都採用 Hash. 爲User定義個key user:,爲每種數值定義hashkey, 譬如關注數follow

  

 

 

存儲社交關係

譬如將用戶的好友/粉絲/關注,能夠存在一個sorted set中,score能夠是timestamp,這樣求兩我的的共同好友的操做,可能就只須要用求交集命令便可。

 

 

 

用做緩存代替memcached

緩存內容示例:(商品列表,評論列表,@提示列表,etc)

相對memcached 簡單的key-value存儲來講,redis衆多的數據結構(list,set,sorted set,hash, etc)能夠更方便cache各類業務數據,性能也不亞於memcached。

例如:

 

[python]  view plain  copy
 
  1. RPUSH pagewviews.user: EXPIRE pagewviews.user: 60  //注意要update timeout  

 

 

反spam系統

例如:(評論,發佈商品,論壇發貼,etc)

做爲一個電商網站被各類spam攻擊是少難免(垃圾評論、發佈垃圾商品、廣告、刷自家商品排名等),針對這些spam制定一系列anti-spam規則,其中有些規則能夠利用redis作實時分析,譬如:1分鐘評論不得超過2次、5分鐘評論少於5次等(更多機制/規則須要結合drools )。 採用sorted set將最近一天用戶操做記錄起來(爲何不所有記錄?節省memory,所有操做會記錄到log,後續利用hadoop進行更全面分析統計),經過

 

[python]  view plain  copy
 
  1. redis> RANGEBYSCORE user:200000:operation:comment 61307510405600 +inf    //得到1分鐘內的操做記錄  
  2. redis> ZADD user:200000:operation:comment 61307510402300 "這是一條評論"  //score 爲timestamp (integer) 1  
  3. redis> ZRANGEBYSCORE user:200000:operation:comment 61307510405600 +inf   //得到1分鐘內的操做記錄  


打印結果:

 

[python]  view plain  copy
 
  1. 1) "這是一條評論"  

 

 

用戶Timeline/Feeds

在逛有個相似微博的欄目我關注,裏面包括關注的人、主題、品牌的動態。redis在這邊主要看成cache使用。

 

 

 

最新列表&排行榜

這裏採用Redis的List數據結構或sorted set 結構, 方便實現最新列表or排行榜 等業務場景。

 

 

消息通知

其實這業務場景也能夠算在計數上,也是採用Hash。以下:

 

 

 

消息隊列

當在集羣環境時候,java ConcurrentLinkedQueue 就沒法知足咱們需求,此時能夠採用Redis的List數據結構實現分佈式的消息隊列。

 

 

顯示最新的項目列表

Redis使用的是常駐內存的緩存,速度很是快。LPUSH用來插入一個內容ID,做爲關鍵字存儲在列表頭部。LTRIM用來限制列表中的項目數最多爲5000。若是用戶須要的檢索的數據量超越這個緩存容量,這時才須要把請求發送到數據庫。

 

 

刪除和過濾。

若是一篇文章被刪除,能夠使用LREM從緩存中完全清除掉。

 

 

排行榜及相關問題

排行榜(leader board)按照得分進行排序。ZADD命令能夠直接實現這個功能,而ZREVRANGE命令能夠用來按照得分來獲取前100名的用戶,ZRANK能夠用來獲取用戶排名,很是直接並且操做容易。

 

 

按照用戶投票和時間排序

這就像Reddit的排行榜,得分會隨着時間變化。LPUSH和LTRIM命令結合運用,把文章添加到一個列表中。一項後臺任務用來獲取列表,並從新計算列表的排序,ZADD命令用來按照新的順序填充生成列表。列表能夠實現很是快速的檢索,即便是負載很重的站點。

 

 

過時項目處理

使用unix時間做爲關鍵字,用來保持列表可以按時間排序。對current_time和time_to_live進行檢索,完成查找過時項目的艱鉅任務。另外一項後臺任務使用ZRANGE...WITHSCORES進行查詢,刪除過時的條目。

 

 

特定時間內的特定項目

這是特定訪問者的問題,能夠經過給每次頁面瀏覽使用SADD命令來解決。SADD不會將已經存在的成員添加到一個集合。

 

 

實時分析

使用Redis原語命令,更容易實施垃圾郵件過濾系統或其餘實時跟蹤系統。

 

 

隊列

在當前的編程中隊列隨處可見。除了push和pop類型的命令以外,Redis還有阻塞隊列的命令,可以讓一個程序在執行時被另外一個程序添加到隊列。你也能夠作些更有趣的事情,好比一個旋轉更新的RSS feed隊列。

 

 

緩存

Redis緩存使用的方式與memcache相同。

網絡應用不能無休止地進行模型的戰爭,看看這些Redis的原語命令,儘管簡單但功能強大,把它們加以組合,所能完成的就更沒法想象。固然,你能夠專門編寫代碼來完成全部這些操做,但Redis實現起來顯然更爲輕鬆。

 

 

手機驗證碼

使用expire設置驗證碼失效時間

redis既能夠做爲數據庫來用,也能夠做爲緩存系統來用

 

 

 

Redis過時策略

本文對Redis的過時機制簡單的講解一下
  講解以前咱們先拋出一個問題,咱們知道不少時候服務器常常會用到redis做爲緩存,有不少數據都是臨時緩存一下,可能用過以後好久都不會再用到了(好比暫存session,又或者只存放日行情股票數據)那麼就會出現一下幾個問題了

  1. Redis會本身回收清理不用的數據嗎?
  2. 若是能,那如何配置?
  3. 若是不能,如何防止數據累加後大量佔用存儲空間的問題?

  以前一直接觸Redis不是很深刻,最近項目當中遇到一個需求場景,須要清空一些存放在Redis的數據,主要是經過一些時間進行過濾,刪除那些不知足的數據,可是這樣的工做天天都須要進行,那工做量就比較大了,並且天天都須要按時去手動清理,這樣作也不切實際,後面發現Redis中有個設置時間過時的功能,即對存儲在Redis數據庫中的值能夠設置一個過時時間。做爲一個緩存數據庫,這是很是實用的。這就是咱們本文要講到的Redis過時機制。其實這個機制運用的場景十分普遍,好比咱們通常項目中的token或者一些登陸信息,尤爲是短信驗證碼都是有時間限制的,或者是限制請求次數,若是按照傳統的數據庫處理方式,通常都是本身判斷過時,這樣無疑會嚴重影響項目性能。

1、設置過時時間

  Redis對存儲值的過時處理其實是針對該值的鍵(key)處理的,即時間的設置也是設置key的有效時間。Expires字典保存了全部鍵的過時時間,Expires也被稱爲過時字段。

  • expire key time(以秒爲單位)--這是最經常使用的方式
  • setex(String key, int seconds, String value)--字符串獨有的方式

注:
  一、除了字符串本身獨有設置過時時間的方法外,其餘方法都須要依靠expire方法來設置時間
  二、若是沒有設置時間,那緩存就是永不過時
  三、若是設置了過時時間,以後又想讓緩存永不過時,使用persist key

一、經常使用方式

通常主要包括4種處理過時方,其中expire都是以秒爲單位,pexpire都是以毫秒爲單位的。

1 EXPIRE key seconds  //將key的生存時間設置爲ttl秒
2 PEXPIRE key milliseconds  //將key的生成時間設置爲ttl毫秒
3 EXPIREAT key timestamp  //將key的過時時間設置爲timestamp所表明的的秒數的時間戳
4 PEXPIREAT key milliseconds-timestamp  //將key的過時時間設置爲timestamp所表明的的毫秒數的時間戳

備註:timestamp爲unix時間戳(例如:timestamp=1499788800 表示將在2017.07.12過時)
一、2兩種方式是設置一個過時的時間段,就是我們處理驗證碼最經常使用的策略,設置三分鐘或五分鐘後失效,把分鐘數轉換成秒或毫秒存儲到Redis中。 
三、4兩種方式是指定一個過時的時間 ,好比優惠券的過時時間是某年某月某日,只是單位不同。

下面咱們就以EXPIREAT爲例子簡單講解下用法。

返回值

一個整數值1或0,以下:

  • 若是成功地爲該鍵設置了超時時間,返回 1
  • 若是鍵不存在或沒法設置超時時間,返回 0

語法
如下是以Redis的EXPIREAT命令的基本語法。

1 redis 127.0.0.1:6379> Expireat KEY_NAME TIME_IN_UNIX_TIMESTAMP

示例

首先,在Redis中建立一個鍵:akey,並在akey中設置一些值。

1 redis 127.0.0.1:6379> SET akey redis 
2 OK

如今,爲設置建立的鍵設置超時時間爲60 秒。

複製代碼
 1 127.0.0.1:6379> SET akey redis
 2 OK
 3 127.0.0.1:6379> EXPIREAT akey 1393840000
 4 (integer) 1
 5 127.0.0.1:6379> EXISTS akey
 6 (integer) 0
 7 127.0.0.1:6379> SET akey redis
 8 OK
 9 127.0.0.1:6379> EXPIREAT akey 1493840000
10 (integer) 1
11 127.0.0.1:6379> EXISTS akey
12 (integer) 1
複製代碼

其餘三個用法相似,這裏不逐一闡述

二、字符串獨有方式

對字符串特殊處理的方式爲SETEX命令,SETEX命令爲指定的 key 設置值及其過時時間。若是 key 已經存在, SETEX 命令將會替換舊的值。

返回值

設置成功時返回 OK 。

語法

Redis Setex 命令基本語法以下:

redis 127.0.0.1:6379> SETEX KEY_NAME TIMEOUT VALUE

示例

1 redis 127.0.0.1:6379> SETEX mykey 60 redis
2 OK
3 redis 127.0.0.1:6379> TTL mykey
4 60
5 redis 127.0.0.1:6379> GET mykey
6 "redis

2、3種過時策略

  • 定時刪除
    • 含義:在設置key的過時時間的同時,爲該key建立一個定時器,讓定時器在key的過時時間來臨時,對key進行刪除
    • 優勢:保證內存被儘快釋放
    • 缺點:
      • 若過時key不少,刪除這些key會佔用不少的CPU時間,在CPU時間緊張的狀況下,CPU不能把全部的時間用來作要緊的事兒,還須要去花時間刪除這些key
      • 定時器的建立耗時,若爲每個設置過時時間的key建立一個定時器(將會有大量的定時器產生),性能影響嚴重
      • 沒人用
  • 惰性刪除
    • 含義:key過時的時候不刪除,每次從數據庫獲取key的時候去檢查是否過時,若過時,則刪除,返回null。
    • 優勢:刪除操做只發生在從數據庫取出key的時候發生,並且只刪除當前key,因此對CPU時間的佔用是比較少的,並且此時的刪除是已經到了非作不可的地步(若是此時還不刪除的話,咱們就會獲取到了已通過期的key了)
    • 缺點:若大量的key在超出超時時間後,好久一段時間內,都沒有被獲取過,那麼可能發生內存泄露(無用的垃圾佔用了大量的內存)
  • 按期刪除
    • 含義:每隔一段時間執行一次刪除(在redis.conf配置文件設置hz,1s刷新的頻率)過時key操做
    • 優勢:
      • 經過限制刪除操做的時長和頻率,來減小刪除操做對CPU時間的佔用--處理"定時刪除"的缺點
      • 按期刪除過時key--處理"惰性刪除"的缺點
    • 缺點
      • 在內存友好方面,不如"定時刪除"
      • 在CPU時間友好方面,不如"惰性刪除"
    • 難點
      • 合理設置刪除操做的執行時長(每次刪除執行多長時間)和執行頻率(每隔多長時間作一次刪除)(這個要根據服務器運行狀況來定了)

看完上面三種策略後能夠得出如下結論: 
定時刪除和按期刪除爲主動刪除:Redis會按期主動淘汰一批已過去的key

惰性刪除爲被動刪除:用到的時候纔會去檢驗key是否是已過時,過時就刪除

惰性刪除爲redis服務器內置策略

按期刪除能夠經過:

  • 第1、配置redis.conf 的hz選項,默認爲10 (即1秒執行10次,100ms一次,值越大說明刷新頻率越快,最Redis性能損耗也越大) 
  • 第2、配置redis.conf的maxmemory最大值,當已用內存超過maxmemory限定時,就會觸發主動清理策略

 注意:

  • 上邊所說的數據庫指的是內存數據庫,默認狀況下每一臺redis服務器有16個數據庫(關於數據庫的設置,看下邊代碼),默認使用0號數據庫,全部的操做都是對0號數據庫的操做,關於redis數據庫的存儲結構,查看 第八章 Redis數據庫結構與讀寫原理
# 設置數據庫數量。默認爲16個庫,默認使用DB 0,能夠使用"select 1"來選擇一號數據庫
# 注意:因爲默認使用0號數據庫,那麼咱們所作的全部的緩存操做都存在0號數據庫上,
# 當你在1號數據庫上去查找的時候,就查不到以前set過得緩存
# 若想將0號數據庫上的緩存移動到1號數據庫,能夠使用"move key 1"
databases 16
  • memcached只是用了惰性刪除,而Redis同時使用了惰性刪除與按期刪除,這也是兩者的一個不一樣點(能夠看作是redis優於memcached的一點)
  • 對於惰性刪除而言,並非只有獲取key的時候纔會檢查key是否過時,在某些設置key的方法上也會檢查(eg.setnx key2 value2:該方法相似於memcached的add方法,若是設置的key2已經存在,那麼該方法返回false,什麼都不作;若是設置的key2不存在,那麼該方法設置緩存key2-value2。假設調用此方法的時候,發現redis中已經存在了key2,可是該key2已通過期了,若是此時不執行刪除操做的話,setnx方法將會直接返回false,也就是說此時並無從新設置key2-value2成功,因此對於必定要在setnx執行以前,對key2進行過時檢查)

3、Redis採用的過時策略

惰性刪除+按期刪除

  • 惰性刪除流程
    • 在進行get或setnx等操做時,先檢查key是否過時,
    • 若過時,刪除key,而後執行相應操做;
    • 若沒過時,直接執行相應操做
  • 按期刪除流程(簡單而言,對指定個數個庫的每個庫隨機刪除小於等於指定個數個過時key)
    • 遍歷每一個數據庫(就是redis.conf中配置的"database"數量,默認爲16)
      • 檢查當前庫中的指定個數個key(默認是每一個庫檢查20個key,注意至關於該循環執行20次,循環體時下邊的描述)
        • 若是當前庫中沒有一個key設置了過時時間,直接執行下一個庫的遍歷
        • 隨機獲取一個設置了過時時間的key,檢查該key是否過時,若是過時,刪除key
        • 判判定期刪除操做是否已經達到指定時長,若已經達到,直接退出按期刪除。

4、RDB對過時key的處理

過時key對RDB沒有任何影響

  • 從內存數據庫持久化數據到RDB文件
    • 持久化key以前,會檢查是否過時,過時的key不進入RDB文件
  • 從RDB文件恢復數據到內存數據庫
    • 數據載入數據庫以前,會對key先進行過時檢查,若是過時,不導入數據庫(主庫狀況)

5、AOF對過時key的處理

過時key對AOF沒有任何影響

  • 從內存數據庫持久化數據到AOF文件:
    • 當key過時後,尚未被刪除,此時進行執行持久化操做(該key是不會進入aof文件的,由於沒有發生修改命令)
    • 當key過時後,在發生刪除操做時,程序會向aof文件追加一條del命令(在未來的以aof文件恢復數據的時候該過時的鍵就會被刪掉)
  • AOF重寫

              重寫時,會先判斷key是否過時,已過時的key不會重寫到aof文件                       




 

Redis事件與事務

1、Redis特色

  • 特性:速度快、基於鍵值對的數據結構服務器、功能豐富、簡單穩定、客戶端語言多、持久化、主從複製、支持高可用和分佈式
  • 單線程架構:redis使用單線程架構和I/O多路複用模型來實現高性能的內存數據庫服務。一條命令從客戶端到服務端不會馬上被執行,全部命令都會進入一個隊列中,而後逐個被執行。(執行順序不肯定的)。

   單線程如何知足線上運行速度? 

  • 純內存訪問:全部數據放在內存中,響應時長大約100納秒。 
  • 非阻塞I/O,使用epoll做爲I/O多路複用技術的實現,再加上Redis自身的事件處理模型將poll中的鏈接、讀寫、關閉都轉換爲事件,不在網絡I/O上浪費過多的時間。
  • 單線程避免了線程切換和競態產生的消耗。

2、事件

Redis服務器是一個事件驅動程序。處理如下兩類事件:

  • 文件事件:Redis服務器經過套接字與客戶端進行鏈接,文件事件就是服務器對套接字操做的抽象。
  • 時間事件:服務器對定時操做的抽象。

1.文件事件

Redis 基於Reactor模式開發了本身的網絡事件處理器即文件事件處理器

文件事件處理器使用I/O多路複用程序來同時監聽多個套接字,並根據套接字目前執行的任務來爲套接字關聯不一樣的事務處理器。

當被監聽的套接字準備好執行鏈接應答、讀取、寫入、關閉等操做時,與操做相對應的文件事件就會產生,這時文件事件處理器就會調用套接字以前關聯好的事件處理器來處理這些事件。

  • 文件事件處理器 由四個部分組成:套接字、I/O多路複用程序、文件事件分派器以及事件處理器 I/O多路複用程序老是會將全部產生事件的套接字放到一個隊列裏面,而後經過這個隊列以有序、同步、每次一個套接字的方式向文件事件分派器傳送套接字。當上一個套接字產生的事件被處理完畢以後,I/O多路複用程序纔會繼續向文件事件分派器傳送下一個套接字。

     

  • I/O多路複用程序 I/O多路複用程序的功能是經過包裝常見的select、epoll、evport和kqueue這些I/O多路複用函數庫來實現的。

    2.時間事件

    • 分類
      • 定時事件:指定時間執行一次。
      • 週期性事件:每隔一段時間執行一次。

    目前Redis只使用週期性事件,而沒有使用定時事件。 一個事件時間主要由三個屬性組成:

    1)id:服務器爲時間事件建立的全局惟一ID

    2) when:毫秒精度的UNIX時間戳,記錄了時間事件的到達時間

    3) timeProc:時間事件處理器,一個函數

    • 實現 服務器將全部時間事件都放在一個無序鏈表中,每當時間事件執行器運行時,遍歷整個鏈表,查找全部已到達的時間事件,並調用相應的事件處理器。(該鏈表爲無序鏈表,不按when屬性的大小排序)
 

Redis事務

Redis經過 MULTI 、 EXEC 、 WATCH 等命令來實現事務功能。事務提供了一種將多個命令請求打包,而後一次性、按順序地執行多個命令的機制,而且在事務執行期間,服務器不會中斷事務而改去執行其餘客戶端的命令請求,它會將事務中的全部命令都執行完畢,而後纔去處理其餘客戶端的命令請求。

1.事務的實現

下面給了一個事務的簡單例子:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set "name" "wangjia06"
QUEUED
127.0.0.1:6379> get "name"
QUEUED
127.0.0.1:6379> set "company" "dianping"
QUEUED
127.0.0.1:6379> get "company"
QUEUED
127.0.0.1:6379> set age 28
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) OK
2) "wangjia06"
3) OK
4) "dianping"
5) OK
6) "28"

退出一個事務能夠用 MULTI 命令, 此時再執行事務會報錯:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> get "company"
QUEUED
127.0.0.1:6379> get "age"
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

一個事務包括三個步驟:

  • 事務開始:事務以 MULTI 開始,返回OK命令。
  • 命令入隊:每一個事務命令成功進入隊列後,返回 QUEUED 。
  • 事務執行: EXEC 執行事務。

Redis事務內會遇到兩種錯誤:

  • 隊列入隊不成功:語法錯誤,錯誤的命令名字或者一些重要的條件如OOM條件。這種客戶端會進行校驗,若是入隊成功則返回QUEUED ,不然返回錯誤。若是一個命令入隊失敗,大多數客戶端會終止該事務。
127.0.0.1:6379> get name
"wangjia08"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name "wangjia09"
QUEUED
127.0.0.1:6379> get
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379>
  • 命令執行時報錯:如對一個String值進行列表操做。全部其餘的命令會被正常執行即便在事務執行中一些命令失敗。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set sex man
QUEUED
127.0.0.1:6379> lpop sex
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get sex
"man"
127.0.0.1:6379>

Redis不支持事務回滾功能,緣由大概有兩點:

  • 一個redis命令執行失敗,通常是語法錯誤或者應該在開發階段被檢測出來,而不是在生產環境。
  • redis追求簡單、速度快,因此不提供事務回滾功能.

2.WATCH命令的實現

watch 命令給redis事務提供了一個 CAS 功能。它是一個樂觀鎖,檢查被監視的健是否至少有一個已經被修改過了,若是是的話拒絕執行,並返回nil表明執行失敗。

以下面例子,在一個客戶端開啓事務前對鍵「name」進行監視,若在這個事務執行前,另外一個客戶端修改了鍵「name」的值,以後事務執行後就會報nil。

127.0.0.1:6379> watch "name"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 10
QUEUED
127.0.0.1:6379> get "name"
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> set "name" "wangjia08"
OK

watch 命令監視多個鍵:

127.0.0.1:6379> watch "name" "age"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> get "name"
QUEUED
127.0.0.1:6379> get "age"
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379>
127.0.0.1:6379> set "age" 11
OK

3.事務的ACID性質

  • 原子性:redis事務多個操做當作一個總體來執行,要麼執行事務中的全部操做,要麼一個也不執行。
  • 一致性:「一致」指的是數據符合數據庫自己的定義和要求,沒有包含非法的或無效的錯誤數據
    • 入隊錯誤:服務器拒絕執行該事務。
    • 執行錯誤:出錯的命令會被服務器識別出來,並進行相應的錯誤處理,不會對數據庫作任何修改,也不會對數據一致性形成影響。
    • 服務器停機: RDB 、 AOF 還原數據庫狀態
  • 隔離性:單線程執行事務,且服務器保證事務期間不會對事務進行中斷
  • 持久性:當一個事務執行完畢時,執行這個事務所得的結果已經被保存到永久性存儲介質(好比硬盤)裏面了。Redis事務的持久性由Redis所使用的持久化模式決定(無持久化模式、 RDB 、 AOF )。Redis工做在無持久化模式下時,事務無持久性。在 AOF 模式下 appenfsync 爲 no 時,事務也無持久性

 

 

 

Redis數據淘汰策略

在 redis 中,容許用戶設置最大使用內存大小 server.maxmemory,在內存限定的狀況下是頗有用的。譬如,在一臺 8G 機子上部署了 4 個 redis 服務點,每個服務點分配 1.5G 的內存大小,減小內存緊張的狀況,由此獲取更爲穩健的服務。 
  redis中當內存超過限制時,按照配置的策略,淘汰掉相應的kv,使得內存能夠繼續留有足夠的空間保存新的數據。redis 肯定驅逐某個鍵值對後,會刪除這個數據,並將這個數據變動消息發佈到本地(AOF 持久化)和從機(主從鏈接)。

redis 提供 6種數據淘汰策略:

  1. volatile-lru:從設置了過時時間的數據集中,選擇最近最久未使用的數據釋放;
  2. allkeys-lru:從數據集中(包括設置過時時間以及未設置過時時間的數據集中),選擇最近最久未使用的數據釋放;
  3. volatile-random:從設置了過時時間的數據集中,隨機選擇一個數據進行釋放;
  4. allkeys-random:從數據集中(包括了設置過時時間以及未設置過時時間)隨機選擇一個數據進行入釋放;
  5. volatile-ttl:從設置了過時時間的數據集中,選擇立刻就要過時的數據進行釋放操做;
  6. noeviction:不刪除任意數據(但redis還會根據引用計數器進行釋放),這時若是內存不夠時,會直接返回錯誤。

      默認的內存策略是noeviction,在Redis中LRU算法是一個近似算法,默認狀況下,Redis隨機挑選5個鍵,而且從中選取一個最近最久未使用的key進行淘汰,在配置文件中能夠經過maxmemory-samples的值來設置redis須要檢查key的個數,可是栓查的越多,耗費的時間也就越久,可是結構越精確(也就是Redis從內存中淘汰的對象未使用的時間也就越久~),設置多少,綜合權衡。

LRU 數據淘汰機制是這樣的:  

在數據集中隨機挑選幾個鍵值對,取出其中 lru 最小的鍵值對淘汰。因此,你會發現,redis 
並非保證取得全部數據集中最近最少使用(LRU)的鍵值對,而只是隨機挑選的幾個鍵值對中的。

 

TTL 數據淘汰機制是這樣的:  

從過時時間的表中隨機挑選幾個鍵值對,取出其中 ttl 最大的鍵值對淘汰。一樣你會發現,redis 
並非保證取得全部過時時間的表中最快過時的鍵值對,而只是隨機挑選的幾個鍵值對中的。

   

 

 

Redis的併發競爭問題如何解決

Redis的併發競爭問題,主要是發生在併發寫競爭。

考慮到redis沒有像db中的sql語句,update val = val + 10 where ...,沒法使用這種方式進行對數據的更新。

假若有某個key = "price",  value值爲10,如今想把value值進行+10操做。正常邏輯下,就是先把數據key爲price的值讀回來,加上10,再把值給設置回去。若是隻有一個鏈接的狀況下,這種方式沒有問題,能夠工做得很好,但若是有兩個鏈接時,兩個鏈接同時想對還price進行+10操做,就可能會出現問題了。

例如:兩個鏈接同時對price進行寫操做,同時加10,最終結果咱們知道,應該爲30纔是正確。

考慮到一種狀況:

T1時刻,鏈接1將price讀出,目標設置的數據爲10+10 = 20。

T2時刻,鏈接2也將數據讀出,也是爲10,目標設置爲20。

T3時刻,鏈接1將price設置爲20。

T4時刻,鏈接2也將price設置爲20,則最終結果是一個錯誤值20。

如何解決?

方案一:能夠使用獨佔鎖的方式,相似操做系統的mutex機制。(網上有例子,http://blog.csdn.net/black_ox/article/details/48972085 不過實現相對複雜,成本較高)

方案二:使用樂觀鎖的方式進行解決(成本較低,非阻塞,性能較高)

如何用樂觀鎖方式進行解決?

本質上是假設不會進行衝突,使用redis的命令watch進行構造條件。僞代碼以下:

watch price

get price $price

$price = $price + 10

multi

set price $price

exec

解釋一下:

watch這裏表示監控該key值,後面的事務是有條件的執行,若是從watch的exec語句執行時,watch的key對應的value值被修改了,則事務不會執行。

一樣考慮剛剛的場景,

T1時刻,鏈接1對price進行watch,讀出price值爲10,目標計算爲20;

T2時刻,鏈接2對price進行watch,讀出price值爲10,目標計算爲20;

T3時刻,鏈接2將目標值爲20寫到redis中,執行事務,事務返回成功。

T4時刻,鏈接1也對price進行寫操做,執行事務時,因爲以前已經watch了price,price在T1至T4之間已經被修改過了,因此事務執行失敗。

綜上,該樂觀鎖機制能夠簡單明瞭的解決了寫衝突的問題。

又問:若是多個寫操做同時過來,100個寫操做同時watch,則最終只會有一個成功,99個執行失敗,何解?

 

若是同時進行有多個請求進行寫操做,例如同一時刻有100個請求過來,那麼只會有一個最終成功,其他99個所有會失敗,效率不高。

並且從業務層面,有些是不可接受的場景。例如:你們同時去搶一個紅包,若是背後也是用樂觀鎖的機制去處理,那每一個請求後都只有一我的成功打開紅包,這對業務是不可忍受的。

在這種狀況下,若是想讓整體效率最大化,能夠採用排隊的機制進行。

將全部須要對同一個key的請求進行入隊操做,而後用一個消費者線程從隊頭依次讀出請求,並對相應的key進行操做。

這樣對於同一個key的全部請求就都是順序訪問,正常邏輯下則不會有寫失敗的狀況下產生 。從而最大化寫邏輯的整體效率。

 

Redis 併發, 鎖, 競爭鎖問題

Redis併發問題

Redis爲單進程單線程模式,採用隊列模式將併發訪問變爲串行訪問。Redis自己沒有鎖的概念,Redis對於多個客戶端鏈接並不存在競爭,可是在Jedis客戶端對Redis進行併發訪問時會發生鏈接超時、數據轉換錯誤、阻塞、客戶端關閉鏈接等問題,這些問題均是因爲客戶端鏈接混亂形成。對此有2種解決方法:

1.客戶端角度,爲保證每一個客戶端間正常有序與Redis進行通訊,對鏈接進行池化,同時對客戶端讀寫Redis操做採用內部鎖synchronized。

2.服務器角度,利用setnx實現鎖。

對於第一種,須要應用程序本身處理資源的同步,能夠使用的方法比較通俗,能夠使用synchronized也能夠使用lock;第二種須要用到Redis的setnx命令,可是須要注意一些問題。

SETNX命令(SET if Not eXists)

語法:
SETNX key value

功能:
將 key 的值設爲 value ,當且僅當 key 不存在;若給定的 key 已經存在,則 SETNX 不作任何動做。

時間複雜度:
O(1)
返回值:
設置成功,返回 1 。
設置失敗,返回 0 。

模式:將 SETNX 用於加鎖(locking)

SETNX 能夠用做加鎖原語(locking primitive)。好比說,要對關鍵字(key) foo 加鎖,客戶端能夠嘗試如下方式:

SETNX lock.foo <current Unix time + lock timeout + 1>

若是 SETNX 返回 1 ,說明客戶端已經得到了鎖, key 設置的unix時間則指定了鎖失效的時間。以後客戶端能夠經過 DEL lock.foo 來釋放鎖。

若是 SETNX 返回 0 ,說明 key 已經被其餘客戶端上鎖了。若是鎖是非阻塞(non blocking lock)的,咱們能夠選擇返回調用,或者進入一個重試循環,直到成功得到鎖或重試超時(timeout)。

可是已經證明僅僅使用SETNX加鎖帶有競爭條件,在特定的狀況下會形成錯誤。

處理死鎖(deadlock)

上面的鎖算法有一個問題:若是由於客戶端失敗、崩潰或其餘緣由致使沒有辦法釋放鎖的話,怎麼辦?

這種情況能夠經過檢測發現——由於上鎖的 key 保存的是 unix 時間戳,假如 key 值的時間戳小於當前的時間戳,表示鎖已經再也不有效。

可是,當有多個客戶端同時檢測一個鎖是否過時並嘗試釋放它的時候,咱們不能簡單粗暴地刪除死鎖的 key ,再用 SETNX 上鎖,由於這時競爭條件(race condition)已經造成了:

C1 和 C2 讀取 lock.foo 並檢查時間戳, SETNX 都返回 0 ,由於它已經被 C3 鎖上了,但 C3 在上鎖以後就崩潰(crashed)了。
C1 向 lock.foo 發送 DEL 命令。
C1 向 lock.foo 發送 SETNX 併成功。
C2 向 lock.foo 發送 DEL 命令。
C2 向 lock.foo 發送 SETNX 併成功。
出錯:由於競爭條件的關係,C1 和 C2 兩個都得到了鎖。



幸虧,如下算法能夠避免以上問題。來看看咱們聰明的 C4 客戶端怎麼辦:

C4 向 lock.foo 發送 SETNX 命令。
由於崩潰掉的 C3 還鎖着 lock.foo ,因此 Redis 向 C4 返回 0 。
C4 向 lock.foo 發送 GET 命令,查看 lock.foo 的鎖是否過時。若是不,則休眠(sleep)一段時間,並在以後重試。
另外一方面,若是 lock.foo 內的 unix 時間戳比當前時間戳老,C4 執行如下命令:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>


由於 GETSET 的做用,C4 能夠檢查看 GETSET 的返回值,肯定 lock.foo 以前儲存的舊值還是那個過時時間戳,若是是的話,那麼 C4 得到鎖。
若是其餘客戶端,好比 C5,比 C4 更快地執行了 GETSET 操做並得到鎖,那麼 C4 的 GETSET 操做返回的就是一個未過時的時間戳(C5 設置的時間戳)。C4 只好從第一步開始重試。
注意,即使 C4 的 GETSET 操做對 key 進行了修改,這對將來也沒什麼影響。

這裏假設鎖key對應的value沒有實際業務意義,不然會有問題,並且其實其value也確實不該該用在業務中。

爲了讓這個加鎖算法更健壯,得到鎖的客戶端應該經常檢查過時時間以避免鎖因諸如 DEL 等命令的執行而被意外解開,由於客戶端失敗的狀況很是複雜,不只僅是崩潰這麼簡單,還多是客戶端由於某些操做被阻塞了至關長時間,緊接着 DEL 命令被嘗試執行(但這時鎖卻在另外的客戶端手上)。


GETSET命令

語法:
GETSET key value

功能:
將給定 key 的值設爲 value ,並返回 key 的舊值(old value)。當 key 存在但不是字符串類型時,返回一個錯誤。

時間複雜度:
O(1)

返回值:
返回給定 key 的舊值;當 key 沒有舊值時,也便是, key 不存在時,返回 nil 。

 

 

 

Redis的字典(dict)rehash過程解析

Redis的內存存儲結構是個大的字典存儲,也就是咱們一般說的哈希表。Redis小到能夠存儲幾萬記錄的CACHE,大到能夠存儲幾千萬甚至上億的記錄(看內存而定),這充分說明Redis做爲緩衝的強大。Redis的核心數據結構就是字典(dict),dict在數據量不斷增大的過程當中,會遇到HASH(key)碰撞的問題,若是DICT不夠大,碰撞的機率增大,這樣單個hash 桶存儲的元素會越來愈多,查詢效率就會變慢。若是數據量從幾千萬變成幾萬,不斷減少的過程,DICT內存卻會形成沒必要要的浪費。Redis的dict在設計的過程當中充分考慮了dict自動擴大和收縮,實現了一個稱之爲rehash的過程。使dict出發rehash的條件有兩個:

1)總的元素個數 除 DICT桶的個數獲得每一個桶平均存儲的元素個數(pre_num),若是 pre_num > dict_force_resize_ratio,就會觸發dict 擴大操做。dict_force_resize_ratio = 5。

2)在總元素 * 10 < 桶的個數,也就是,填充率必須<10%, DICT便會進行收縮,讓total / bk_num 接近 1:1。

 

總結,Redis的rehash動做是一個內存管理和數據管理的一個核心操做,因爲Redis主要使用單線程作數據管理和消息效應,它的rehash數據遷移過程採用的是漸進式的數據遷移模式,這樣作是爲了防止rehash過程太長堵塞數據處理線程。並無採用memcached的多線程遷移模式。關於memcached的rehash過程,之後再作介紹。從redis的rehash過程設計的很巧,也很優雅。在這裏值得注意的是,redis在find數據的時候,是同時查找正在遷移的ht[0]和被遷移的ht[1]。防止遷移過程數據命不中的問題。

相關文章
相關標籤/搜索