Redis是什麼
redis是一個高性能的key-value數據庫,它是徹底開源免費的,並且redis是一個NOSQL類型數據庫,是爲了解決高併發、高擴展,大數據存儲等一系列的問題而產生的數據庫解決方案,是一個非關係型的數據庫。可是,它也是不能替代關係型數據庫,只能做爲特定環境下的擴充。redis的出現,很大程度補償了memcached這類key/value存儲的不足,在部分場合能夠對關係數據庫起到很好的補充做用。redis
Redis的特色:
1.速度快數據庫
- Redis的全部數據都是存放在內存中的,因此把數據放在內存中是Redis速度快的最主要緣由。
- Redis是用C語言實現的,通常來講C語言實現的程序「距離」操做系統更近,執行速度相對會更快。
- Redis使用了單線程架構,預防了多線程可能產生的競爭問題。
2.redis提供了豐富的數據結構編程
它主要提供了5種數據結構:string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。數組
3.豐富的功能緩存
除了5種數據結構,Redis還提供了許多額外的功能:tomcat
- 提供了鍵過時功能,能夠用來實現緩存。
- 提供了發佈訂閱功能,能夠用來實現消息系統。
- 支持Lua腳本功能,能夠利用Lua創造出新的Redis命令。
- 提供了簡單的事務功能,能在必定程度上保證事務特性。
- 提供了流水線(Pipeline)功能,這樣客戶端能將一批命令一次性傳到Redis,減小了網絡的開銷。
4.簡單穩定安全
Redis的簡單主要表如今三個方面。服務器
- Redis的源碼不多。
- Redis使用單線程模型,這樣不只使得Redis服務端處理模型變得簡單,並且也使得客戶端開發變得簡單。
- Redis不須要依賴於操做系統中的類庫(例如Memcache須要依賴libevent這樣的系統類庫),Redis本身實現了事件處理的相關功能。
Redis雖然很簡單,可是不表明它不穩定。維護的上千個Redis爲例,沒有出現過由於Redis自身bug而宕掉的狀況。
5.客戶端語言多網絡
Redis提供了簡單的TCP通訊協議,不少編程語言能夠很方便地接入到Redis,而且因爲Redis受到社區和各大公司的普遍承認,因此支持Redis的客戶端語言也很是多,幾乎涵蓋了主流的編程語言,例如Java、PHP、Python、C、C++、Nodejs等。
6.持久化
一般看,將數據放在內存中是不安全的,一旦發生斷電或者機器故障,重要的數據可能就會丟失,所以Redis提供了兩種持久化方式:RDB和AOF,便可以用兩種策略將內存的數據保存到硬盤中(如圖所示)這樣就保證了數據的可持久性。
7.主從複製
Redis提供了複製功能,實現了多個相同數據的Redis副本(如圖所示),複製功能是分佈式Redis的基礎。
Redis的工做流程
從圖上能夠看出,當一個客戶端訪問服務器的時候,客戶端請求會先到達Nginx,由Nginx負責對數據進行分發,上傳到多個服務器,當用戶訪問到tomcat1的時候,會進行登錄驗證並將session放入session管理中,使用Redis管理session的好處就是當第二次客戶端登錄後再進行操做,這時極可能到達tomcat2服務器,這時候tomcat2會從Redis中尋找session,從而避免了session只在一個服務器中形成第二次讀取session發現爲空的問題。
在shiro中,shiro也提供了一個分佈式session的管理功能,但使用Redis更能集中管理。
Redis數據類型及使用場景
前面也提到了Redis支持五種數據類型,下面詳細介紹一下這5種數據類型級使用場景
一、string
簡單介紹一下,Strings數據類型是最經常使用、簡單的key-value類型,普通的key/ value 存儲均可以歸爲此類。value不只能夠是字符串,也能夠是數字。由於是二進制安全的,因此你徹底能夠把一個圖片文件的內容做爲string來存儲。Redis的string能夠徹底實現目前memcached的功能,而且效率更高。除了提供與 Memcached 同樣的get、set、incr、decr 等操做外,Redis還額外提供了下面一些操做:
- 獲取字符串長度
- 往字符串append內容
- 設置和獲取字符串的某一段內容
- 設置及獲取字符串的某一位(bit)
- 批量設置一系列字符串的內容
經常使用命令:
set,get,decr,incr,mget 等。
應用場景:
- 應用 Memcached和CKV的全部場景。字符串和數字直接存取。結構化數據須要先序列化,再set到value;相應的,get到value後須要反序列化。
- 能夠利用redis的INCR、INCRBY、DECR、DECRBY等指令來實現原子計數的效果。便可以用來實現業務上的統計計數需求。也可用於實現idmaker,即生成全局惟一的id。
- 存放session key,實現一個分佈式session系統。Redis的key能夠方便地設置過時時間,用於實現session key的自動過時。驗證skey時先根據uid路由到對應的redis,如取不到skey,則表示skey已過時,須要從新登陸;如取到skey且校驗經過則升級此skey的過時時間便可。
- Set nx或SetNx,僅當key不存在時才Set。能夠用來選舉Master或實現分佈式鎖:全部Client不斷嘗試使用SetNx master myName搶注Master,成功的那位不斷使用Expire刷新它的過時時間。若是Master掛掉了key就會失效,剩下的節點又會發生新一輪搶奪。
- 藉助redis2.6開始支持的lua腳本,能夠實現更安全的2種分佈式鎖:一種適用於各進程競爭但老是單個進程獲取鎖並處理的場景。除非原處理進程掛掉於是鎖過時纔會被其它進程獲取到鎖。無須主動解鎖。經過get、expire/pexpire、setnx ex| px的lua腳本實現;一種適用於各進程競爭獲取鎖並處理的場景。經過set nx ex| px獲取鎖,用完須要經過先get判斷再del釋放鎖,不然在鎖過時以前不能獲取到鎖。
- GetSet, 設置新值,返回舊值。好比實現一個計數器,能夠用GetSet獲取計數並重置爲0。
- GetBit/SetBit/BitOp/BitCount, BitMap的玩法,好比統計今天的獨立訪問用戶數時,每一個註冊用戶都有一個offset,他今天進來的話就把他那個位設爲1,用BitCount就能夠得出今天的總人數。
- Append/SetRange/GetRange/StrLen,對文本進行擴展、替換、截取和求長度,對特定數據格式很是有用。
實現方式:
String在redis內部存儲默認就是一個字符串,被redisObject所引用,當遇到incr,decr等操做時會轉成數值型進行計算,此時redisObject的encoding字段爲int。
二、List
簡單介紹一下,List是一個雙向鏈表,支持雙向的Pop/Push,江湖規矩通常從左端Push,右端Pop——LPush/RPop,並且還有Blocking的版本BLPop/BRPop,客戶端能夠阻塞在那直到有消息到來。還有RPopLPush/ BRPopLPush,彈出來返回給client的同時,把本身又推入另外一個list,LLen獲取列表的長度。還有按值進行的操做:LRem(按值刪除元素)、LInsert(插在某個值的元素的先後),複雜度是O(N),N是List長度,由於List的值不惟一,因此要遍歷所有元素,而Set只要O(log(N))。
按下標進行的操做:下標從0開始,隊列從左到右算,下標爲負數時則從右到左。LSet ,按下標設置元素值。LIndex,按下標返回元素。LRange,不一樣於POP直接彈走元素,只是返回列表內一段下標的元素,是分頁的最愛。LTrim,限制List的大小,好比只保留最新的20條消息。複雜度也是O(N),其中LSet的N是List長度,LIndex的N是下標的值,LRange的N是start的值+列出元素的個數,由於是鏈表而不是數組,因此按下標訪問其實要遍歷鏈表,除非下標正好是隊頭和隊尾。LTrim的N是移除元素的個數。
經常使用命令:
lpush,rpush,lpop,rpop,lrange等。
應用場景:
- 各類列表,好比twitter的關注列表、粉絲列表等,最新消息排行、每篇文章的評論等也能夠用Redis的list結構來實現。
- 消息隊列,能夠利用Lists的PUSH操做,將任務存在Lists中,而後工做線程再用POP操做將任務取出執行。這裏的消息隊列並無ack機制,若是消費者把任務給Pop走了又沒處理完就死機了怎麼辦?解決方法之一是加多一個sorted set,分發的時候同時發到list與sorted set,以分發時間爲score,用戶把任務作完了以後要用ZREM消掉sorted set裏的job,而且定時從sorted set中取出超時沒有完成的任務,從新放回list。另外一個作法是爲每一個worker多加一個的list,彈出任務時改用RPopLPush,將job同時放到worker本身的list中,完成時用LREM消掉。若是集羣管理(如zookeeper)發現worker已經掛掉,就將worker的list內容從新放回主list。
- 利用LRANGE能夠很方便的實現list內容分頁的功能。
- 取最新N個數據的操做:LPUSH用來插入一個內容ID,做爲關鍵字存儲在列表頭部。LTRIM用來限制列表中的項目數最多爲5000。若是用戶須要的檢索的數據量超越這個緩存容量,這時才須要把請求發送到數據庫。
實現方式:
Redis list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。
三、Set
簡單介紹一下,set是一種無序的集合,集合中的元素沒有前後順序,不重複。將重複的元素放入Set會自動去重。
經常使用命令:
sadd,spop,smembers,sunion等。
應用場景:
- 某些須要去重的列表,而且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
- 能夠存儲一些集合性的數據,好比在微博應用中,能夠將一個用戶全部的關注人存在一個集合中,將其全部粉絲存在一個集合。Redis還爲集合提供了求交集、並集、差集等操做,能夠很是方便的實現如共同關注、共同喜愛、二度好友等功能,對上面的全部集合操做,你還可使用不一樣的命令選擇將結果返回給客戶端仍是存集到一個新的集合中。又好比QQ有一個社交功能叫作「好友標籤」,你們能夠給你的好友貼標籤,好比「大美女」、「土豪」、「歐巴」等等,這裏也能夠把每個用戶的標籤都存儲在一個集合之中。
- 想要知道某些特定的註冊用戶或IP地址,他們到底有多少訪問了某個頁面,能夠這樣實現:SADD page:day1:。想知道特定用戶的數量,使用SCARD page:day1:。 須要測試某個特定用戶是否訪問了這個頁面?SISMEMBER page:day1:。
實現方式:
set 的內部實現是一個 value永遠爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的緣由。
四、zest
簡單介紹一下,有序集合,相比set,元素放入集合時還要提供該元素的分數,可根據分數自動排序。
經常使用命令:
zadd,zrange,zrem,zcard等
使用場景:
- 存放一個有序的而且不重複的集合列表,好比twitter 的public timeline能夠以發表時間做爲score來存儲,這樣獲取時就是自動按時間排好序的。
- 能夠作帶權重的隊列,好比普通消息的score爲1,重要消息的score爲2,而後工做線程能夠選擇按score的倒序來獲取工做任務。讓重要的任務優先執行。
- 排行榜相關:ZADD leaderboard。 獲得前100名高分用戶很簡單:ZREVRANGE leaderboard 0 99。用戶的全球排名也類似,只須要執行:ZRANK leaderboard。
- 新聞按照用戶投票和時間排序,ZADD時的score = points / time^alpha, 這樣用戶的投票會相應的把新聞挖出來,但時間會按照必定的指數將新聞埋下去。
- 過時項目處理:使用unix時間做爲關鍵字,用來保持列表可以按時間排序。對current_time和time_to_live進行檢索,完成查找過時項目的艱鉅任務。另外一項後臺任務使用ZRANGE...WITHSCORES進行查詢,刪除過時的條目。
實現方式:
Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是全部的成員,排序依據是HashMap裏存的score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。
五、Hash
簡單介紹一下,Hash存的是字符串和字符串值之間的映射。Hash將對象的各個屬性存入Map裏,能夠只讀取/更新對象的某些屬性。這樣有些屬性超長就讓它一邊呆着不動,另外不一樣的模塊能夠只更新本身關心的屬性而不會互相併發致使覆蓋衝突。
經常使用命令:
hget,hset,hgetall 等。
應用場景:
- 存放結構化數據,好比用戶信息。在Memcached或CKV中,對於用戶信息好比用戶的暱稱、年齡、性別、積分等,咱們須要先序列化後存儲爲一個字符串的值,這時候在須要修改其中某一項時,一般須要將全部值取出反序列化後,修改某一項的值,再序列化存儲回去。這樣不只增大了開銷,也不適用於一些可能併發操做的場合(好比兩個併發的操做都須要修改積分)。而Redis的Hash結構可使你像在數據庫中Update一個屬性同樣只修改某一項屬性值
- Key是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取均可以直接經過其內部Map的Key(Redis裏稱內部Map的key爲field), 也就是經過key(用戶ID) + field(屬性標籤) 就能夠操做對應屬性數據了,既不須要重複存儲數據,也不會帶來序列化和併發修改控制的問題。3. 不過這裏須要注意,Redis提供了接口(hgetall)
- 能夠直接取到所有的屬性數據,可是若是內部Map的成員不少,那麼涉及到遍歷整個內部Map的操做,因爲Redis單線程模型的緣故,這個遍歷操做可能會比較耗時,而對其它客戶端的請求徹底不響應,這點須要格外注意。
- 可用來建索引。好比User對象,除了id有時還要按name來查詢,能夠建一個Key爲user:name:id的Hash,在插入User對象時(set user:101{"id":101,"name":"calvin"}), 順便往這個hash插入一條(hset user:name:id calvin 101),這時calvin做爲hash裏的一個key,值爲101。按name查詢的時候,用hgetuser:name:id calvin 就能從名爲calvin的key裏取出id。假如須要使用多種索引來查找某條數據時可使用,一個hash key搞定,避免使用多個string key存放索引值。
- HINCRBY一樣可用於實現idmaker。相對string類型的idmaker每個類型須要一個key,hash類型的用一個key便可。
實現方式:
Redis Hash對應Value內部實際就是一個HashMap,這裏會有2種不一樣實現,這個Hash的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,對應的value redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。
今日就分享到這啦,若是任何問題或者建議,歡迎留言交流。