最近針對中鐵一局項目,跟事業部討論以後須要咱們的KF平臺可以接入一些開源的數據庫,因而這兩天研究了一下Redis的原理。html
1. Redis的數據存儲原理及簡述
1.1Redis簡述
Redis是一個基於內存且支持持久化的key-value的NoSQL數據庫,其中每一個key和value都是使用對象表示的,具備以如下特徵:多樣數據類型、持久化、主從同步。它支持存儲的value類型包括string(字符串)、list(鏈表)、hash(哈希)、set(集合)和zset(有序集合)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操做,並且這些操做都是原子性的。前端
爲了保證效率,Redis數據都是緩存在內存中,且Redis有一個很重要的特色就是它能夠實現持久化數據,經過兩種方式能夠實現數據持久化:使用RDB快照的方式,將內存中的數據不斷寫入磁盤;或使用相似MySQL的AOF日誌方式,記錄每次更新的日誌。前者性能較高,可是可能會引發必定程度的數據丟失;後者相反。 Redis支持將數據同步到多臺從數據庫上,這種特性對提升讀取性能很是有益。node
Redis3.0版本容許單點故障,它沒有中心節點,各個節點地位同樣,擴展性很好,節點間的採用二進制通訊,節點與客戶端採用ascII協議通訊。redis
綜上所述,Redis做爲一個典型的非關係型數據庫,Redis可用於緩存、數據庫、消息中間件。它十分適合存儲少、訪問量巨大的場景,全部數據所有in-memory保證了數據的高速訪問。數據庫
1.2Redis存儲原理及實現方式
Redis內部使用一個redisObject對象來表示全部的Key和Value,redisObject最主要的信息如圖所示:centos
type表明一個value對象具體是何種數據類型數組
encoding是不一樣數據類型在redis內部的存儲方式,好比:type=string表明value存儲的是一個普通字符串,那麼對應的encoding能夠是raw或者是int,若是是int則表明實際redis內部是按數值型類存儲和表示這個字符串的,固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。緩存
vm字段,只有打開了Redis的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的。安全
五種數據類型的使用和內部實現方式:ruby
1)String
經常使用命令:set/get/decr/incr/mget等;
應用場景:String是最經常使用的一種數據類型,普通的key/value存儲均可以歸爲此類;
實現方式:String在redis內部存儲默認就是一個字符串,被redisObject所引用,當遇到incr、decr等操做時會轉成數值型進行計算,此時redisObject的encoding字段爲int。
2)Hash
經常使用命令:hget/hset/hgetall等
應用場景:咱們要存儲一個用戶信息對象數據,其中包括用戶ID、用戶姓名、年齡和生日,經過用戶ID咱們但願獲取該用戶的姓名或者年齡或者生日;
實現方式:Redis的Hash實際是內部存儲的Value爲一個HashMap,並提供了直接存取這個Map成員的接口。如圖2所示,Key是用戶ID, value是一個Map。這個Map的key是成員的屬性名,value是屬性值。這樣對數據的修改和存取均可以直接經過其內部Map的Key(Redis裏稱內部Map的key爲field), 也就是經過 key(用戶ID) + field(屬性標籤) 就能夠操做對應屬性數據。當前HashMap的實現有兩種方式:當HashMap的成員比較少時Redis爲了節省內存會採用相似一維數組的方式來緊湊存儲,而不會採用真正的HashMap結構,這時對應的value的redisObject的encoding爲zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding爲ht。
3)List
經常使用命令:lpush/rpush/lpop/rpop/lrange等;
應用場景:Redis list的應用場景很是多,也是Redis最重要的數據結構之一,好比twitter的關注列表,粉絲列表等均可以用Redis的list結構來實現;
實現方式:Redis list的實現爲一個雙向鏈表,便可以支持反向查找和遍歷,更方便操做,不過帶來了部分額外的內存開銷,Redis內部的不少實現,包括髮送緩衝隊列等也都是用的這個數據結構。
4)Set
經常使用命令:sadd/spop/smembers/sunion等;
應用場景:Redis set對外提供的功能與list相似是一個列表的功能,特殊之處在於set是能夠自動排重的,當你須要存儲一個列表數據,又不但願出現重複數據時,set是一個很好的選擇,而且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的;
實現方式:set 的內部實現是一個 value永遠爲null的HashMap,實際就是經過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的緣由。
5)Sorted Set
經常使用命令:zadd/zrange/zrem/zcard等;
應用場景:Redis sorted set的使用場景與set相似,區別是set不是自動有序的,而sorted set能夠經過用戶額外提供一個優先級(score)的參數來爲成員排序,而且是插入有序的,即自動排序。當你須要一個有序的而且不重複的集合列表,那麼能夠選擇sorted set數據結構,好比twitter 的public timeline能夠以發表時間做爲score來存儲,這樣獲取時就是自動按時間排好序的。
實現方式:Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證數據的存儲和有序,HashMap裏放的是成員到score的映射,而跳躍表裏存放的是全部的成員,排序依據是HashMap裏存的score,使用跳躍表的結構能夠得到比較高的查找效率,而且在實現上比較簡單。
1.3 Redis持久化
Redis雖然是基於內存的存儲系統,可是它自己是支持內存數據持久化的,並且提供兩種主要的持久化策略:RDB快照和AOF日誌。
RDB快照:Redis支持將當前數據的快照存成一個數據文件的持久化機制。
可是一個持續寫入的數據庫如何生成快照呢?Redis藉助了fork命令的copy on write機制。在生成快照時,將當前進程fork出一個子進程,而後在子進程中循環全部的數據,將數據寫成爲RDB文件。
咱們能夠經過Redis的save指令來配置RDB快照生成的時機,好比你能夠配置當10分鐘之內有100次寫入就生成快照,也能夠配置當1小時內有1000次寫入就生成快照,也能夠多個規則一塊兒實施。這些規則的定義就在Redis的配置文件中,你也能夠經過Redis的CONFIG SET命令在Redis運行時設置規則,不須要重啓Redis。
Redis的RDB文件不會壞掉,由於其寫操做是在一個新進程中進行的,當生成一個新的RDB文件時,Redis生成的子進程會先將數據寫到一個臨時文件中,而後經過原子性rename系統調用將臨時文件重命名爲RDB文件,這樣在任什麼時候候出現故障,Redis的RDB文件都老是可用的。同時,Redis的RDB文件也是Redis主從同步內部實現中的一環。
可是,咱們能夠很明顯的看到,RDB有他的不足,就是一旦數據庫出現問題,那麼咱們的RDB文件中保存的數據並非全新的,從上次RDB文件生成到Redis停機這段時間的數據所有丟掉了。在某些業務下,這是能夠忍受的,咱們也推薦這些業務使用RDB的方式進行持久化,由於開啓RDB的代價並不高。可是對於另一些對數據安全性要求極高的應用,沒法容忍數據丟失的應用,RDB就無能爲力了,因此Redis引入了另外一個重要的持久化機制:AOF日誌。
AOF日誌:AOF日誌的全稱是append only file,從名字上咱們就能看出來,它是一個追加寫入的日誌文件。
通常數據庫的binlog不一樣的是,AOF文件是可識別的純文本,它的內容就是一個個的Redis標準命令。固然,並非發送發Redis的全部命令都要記錄到AOF日誌裏面,只有那些會致使數據發生修改的命令纔會追加到AOF文件。
那麼每一條修改數據的命令都生成一條日誌,那麼AOF文件是否是會很大?答案是確定的,AOF文件會愈來愈大,因此Redis又提供了一個功能,叫作AOF rewrite。其功能就是從新生成一份AOF文件,新的AOF文件中一條記錄的操做只會有一次,而不像一份老文件那樣,可能記錄了對同一個值的屢次操做。其生成過程和RDB相似,也是fork一個進程,直接遍歷數據,寫入新的AOF臨時文件。在寫入新文件的過程當中,全部的寫操做日誌仍是會寫到原來老的AOF文件中,同時還會記錄在內存緩衝區中。當重完操做完成後,會將全部緩衝區中的日誌一次性寫入到臨時文件中。而後調用原子性的rename命令用新的AOF文件取代老的AOF文件。
AOF是一個寫文件操做,其目的是將操做日誌寫到磁盤上,因此它也一樣會遇到咱們上面說的寫操做的5個流程。那麼寫AOF的操做安全性又有多高呢。實際上這是能夠設置的,在Redis中對AOF調用write(2)寫入後,什麼時候再調用fsync將其寫到磁盤上,經過appendfsync選項來控制,下面appendfsync的三個設置項,安全強度逐漸變強。
1)appendfsync no
當設置appendfsync爲no的時候,Redis不會主動調用fsync去將AOF日誌內容同步到磁盤,因此這一切就徹底依賴於操做系統的調試了。對大多數Linux操做系統,是每30秒進行一次fsync,將緩衝區中的數據寫到磁盤上。
2)appendfsync everysec
當設置appendfsync爲everysec的時候,Redis會默認每隔一秒進行一次fsync調用,將緩衝區中的數據寫到磁盤。可是當這一次的fsync調用時長超過1秒時。Redis會採起延遲fsync的策略,再等一秒鐘。也就是在兩秒後再進行fsync,這一次的fsync就無論會執行多長時間都會進行。這時候因爲在fsync時文件描述符會被阻塞,因此當前的寫操做就會阻塞。因此結論就是,在絕大多數狀況下,Redis會每隔一秒進行一次fsync。在最壞的狀況下,兩秒鐘會進行一次fsync操做。這一操做在大多數數據庫系統中被稱爲group commit,就是組合屢次寫操做的數據,一次性將日誌寫到磁盤。
3)appednfsync always
當設置appendfsync爲always時,每一次寫操做都會調用一次fsync,這時數據是最安全的,固然,因爲每次都會執行fsync,因此其性能也會受到影響。
1.4Redis的內存管理機制
Redis的內存管理機制主要經過源碼中的zmalloc.h和zmalloc.c兩個文件來實現的。Redis爲了方便內存的管理,在分配一塊內存以後,會將這塊內存的大小存入內存的頭部。
如圖所示,real_ptr是redis調用malloc後返回的指針。redis將內存的大小size存入頭部,size所佔據的內存大小是已知的,爲size_t類型的長度,而後返回ret_ptr。當須要釋放內存的時候,ret_ptr被傳給內存管理程序。經過ret_ptr,程序能夠很容易的計算出real_ptr的值,而後將real_ptr傳給free釋放內存。
Redis經過定義一個數組來記錄全部的內存分配狀況,這個數組的長度爲ZMALLOC_MAX_ALLOC_STAT.數組的每個元素表明當前程序所分配的內存塊的個數,且內存塊的個數,且內存塊的大小爲該元素的下標。
在源碼中,這個數組爲zmalloc_allocations。zmalloc_allocations[16]表明已經分配的長度爲16bytes的內存塊的個數。zmalloc.c中有一個靜態變量used_memory用來記錄當前分配的內存總大小。因此,總的來看,Redis採用的是包裝的mallc/free,相較於Memcached的內存管理方法來講,要簡單不少。
2. Redis的數據類型
Redis支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
2.1 String(字符串)
string 是 redis 最基本的類型,是二進制安全的,意思是 redis 的 string 能夠包含任何數據。好比jpg圖片或者序列化的對象,string 類型的值最大能存儲 512MB。
在以上實例中咱們使用了 Redis 的 SET 和 GET 命令。鍵爲 name,對應的值爲 runoob。
2.2 Hash(哈希)
Redis hash 是一個鍵值(key=>value)對集合, 是一個 string 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。
DEL runoob 用於刪除前面測試用過的 key,否則會報錯:(error) WRONGTYPE Operation against a key holding the wrong kind of value。實例中咱們使用了 Redis HMSET, HGET 命令,HMSET 設置了兩個 field=>value 對, HGET 獲取對應 field 對應的 value。
每一個 hash 能夠存儲 232 -1 鍵值對(40多億)。
2.3 List(列表)
Redis 列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)。
列表最多可存儲 232 - 1 元素 (4294967295, 每一個列表可存儲40多億)。
2.4 Set(集合)
Redis的Set是string類型的無序集合。集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
注意:以上實例中 rabitmq 添加了兩次,但根據集合內元素的惟一性,第二次插入的元素將被忽略。集合中最大的成員數爲 232 - 1(4294967295, 每一個集合可存儲40多億個成員)。
2.5 zset(sorted set:有序集合)
Redis zset 和 set 同樣也是string類型元素的集合,且不容許重複的成員。
不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
zset的成員是惟一的,但分數(score)卻能夠重複。
zadd 命令
添加元素到集合,元素在集合中存在則更新對應score
3. Redis基本安裝過程及簡單使用
本次安裝是在vmware虛擬機下,使用的是centos6.9x64版本的Linux系統。
3.1.在官網http://www.redis.cn/download.html下載Redis壓縮包(見附件)
3.2.把redis軟件上傳到虛擬機中/rdtar/中:
3.3.對壓縮包進行解壓並make編譯:
3.4打開redis文件夾中的src目錄,找到redis.server和redis.cli文件:
其中前者是服務端文件,後者是客戶端文件。
3.5建立redis運行目錄並將redis-server和redis-cli複製到運行目錄中:
3.6將redis文件夾下的redis.conf配置文件也拷貝到redis運行目錄下:
3.7前端運行redis-server:
3.8後臺運行redis-server時講配置文件redis.conf中daemonize設置爲yes:
3.9 redis-server的後臺啓動:
3.10 redis-cli啓動而且簡單使用:
4. Redis簡單集羣
Redis Cluster是一個實現了分佈式且容許單點故障的Redis高級版本,它沒有中心節點,各個節點地位一致,具備線性可伸縮的功能。Redis Cluster的分佈式存儲結構,其中節點與節點之間經過二進制協議進行通信,節點與客戶端之間經過ascii協議進行通訊,在數據的放置策略上,Redis Cluster將整個key的數值域分紅16384個哈希槽。每一個節點上能夠存儲一個或多個哈希槽,也就是說當前Redis Cluster支持的最大節點就是16384
4.1Redis簡單集羣下所需的計算機資源要求
Redis集羣至少須要3個節點,要保證集羣的高可用,須要每一個節點都有從節點,也就是備份節點,因此Redis集羣至少須要6臺服務器。由於沒有那麼多服務器,也啓動不了那麼多虛擬機,所在這裏搭建的是僞分佈式集羣,即一臺服務器虛擬運行6個redis實例,修改端口號爲(9001-9006),固然實際生產環境的Redis集羣搭建和這裏是同樣的。
redis3.0版本以前只支持單例模式,在3.0版本及之後才支持集羣。redis集羣採用P2P模式,是徹底去中心化的,不存在中心節點或者代理節點;redis集羣也沒有統一的入口的,客戶端(client)鏈接集羣的時候鏈接集羣中的任意節點(node)便可,集羣內部的節點是相互通訊的(PING-PONG機制),每一個節點都是一個redis實例;每一個Redis集羣理論上最多能夠有16384個節點。
4.2Redis簡單集羣搭建過程
建立文件夾
咱們計劃集羣中 Redis 節點的端口號爲9001-9006 ,端口號即集羣下各redis實例文件夾。數據存放在端口號/data文件夾中。
複製執行腳本
把安裝好的單機版redis bin目錄下的執行腳本複製到redis集羣文件夾下
複製配置文件
把解壓目錄下的redis配置文件redis.conf複製一份到各端口實例文件夾中
修改各端口實例中的配置文件選項
啓動 9001-9006 六個實例節點
查看啓動服務
根據節點建立集羣
Redis 5.0開始再也不使用ruby搭建集羣,而是直接使用客戶端命令 redis-cli 來建立。客戶端命令:
./redis-cli --cluster create 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003 127.0.0.1:9004 127.0.0.1:9005 127.0.0.1:9006 --cluster-replicas 1
鏈接測試