redis原理及應用
一、redis來源
二、數據類型
三、主流的應用場景
四、特性
五、補充
一、 redis來源
作者:Salvatore Sanfilippo (antirez),男,意大利人.
需求: 一個訪客信息追蹤網站,網站可以通 過 JavaScript 腳本,將訪客的 IP 地 址、所屬國家、閱覽器信息、被訪問頁 面的地址等數據傳送給 LLOOGG. com 。 然后 LLOOGG.com 會將這些瀏覽數 據通過 web 頁面實時地展示給用戶, 並儲存起最新的 5 至 10,000 條瀏覽 記錄以便進行查閱。
redis解決方案
每當某個被追蹤的網站新增一條 瀏覽記錄時, LLOOGG.com 就會將這條新的瀏覽記錄推入 (push)到與該網站相對應的列表里面,當列表的 長度超過用戶指定的最大長度時,程序每向 列表推入一條新的記錄,就需要從列表中彈出(pop)一條最舊的記錄。
現在已經被廣泛使用:
Twitter 使用 Redis 來儲存用戶時間線(user timeline)。
StackOverflow 使用 Redis 來進行緩存和消息分發。
Pinterest 使用 Redis 來構建關注模型(follow model)和興趣圖譜(interest graph)。
Flickr 使用 Redis 來構建隊列。
Github 使用 Redis 作為持久化的鍵值對數據庫,並使用 Resque 來實現消息隊列。
新浪微博使用 Redis 來實現計數器、反向索引、排行榜、消息 隊列,並儲存用戶關系。
知乎使用 Redis 來進行計數、緩存、消息分發和任務調度。
掌上醫訊使用redis來進行緩存,分布式鎖等。
阿里雲、百度雲、Amazon、 RedisLab 等公司都提供了基於 Redis 的應用服務。
二、支持的數據類型
Redis 所有的數據結構都是以唯一的 key 字符串作為名稱,然后通過這個唯一 key 值來獲取相應的 value 數據。不同類型的數據結構的差異就在於 value 的結構不一樣。
a.字符串
常用命令 SET key value [NX|XX] 保存值
SETNX key value 命令僅在鍵 key 不存在的情況下,才進行設置操作
SETXX key value 命令僅在鍵 key 存在的情況下,才進行設置操作
APPEND key value
STRLEN key
get key
SETRANGE key index value 根據索引設置值
GETRANGE key start end
字符串結構使用非常廣泛,一個常見的用途就是緩存用戶信息。我們將用戶信息結構體使用 JSON 序列化成字符串,然后將序列化后的 字符串塞進 Redis 來緩存。同樣,取用戶信息會經過一次反序列化的過程。
struct SDS<T> {
T capacity; // 數組容量
T len; // 數組長度
byte flags; // 特殊標識位,
byte[] content; // 數組內內容
}
Redis 的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似於 Java 的 ArrayList,采用預分配冗余空間的方式來減少內存的頻繁分配,如圖中所示,內部為當前字符串實際分配的空間 capacity 一般要高於實際字符串長度 len。當字符串長度小於 1M 時,擴容都是加倍現有的空間,如果超過 1M,擴容時一次只會多擴 1M 的空間。需要注意的是字符串最大長度為 512M。
存儲二進制數據相關的命令
SETBIT key index value
GETBIT key index
BITCOUNT key [start] [end]
b 列表
Redis 的列表相當於 Java 語言里面的 LinkedList,注意它是鏈表而不是數組。這意味着 list 的插入和刪除操作非常快
常用的命令有 LPUSH key value [value ...]
LPOP key
RPUSH key value [value ...]
RPOP key
LLEN key
LINDEX key index
LRANGE key start stop
LSET key index value
LREM key count value
LTRIM key start stop
阻塞彈出
BLPOP key [key ...] timeout
Redis 的列表結構常用來做異步隊列使用。將需要延后處理的任務結構體序列化成字符串塞進 Redis 的列表,另一個線程從這個列表中輪詢數據進行處理
hash 字典
Redis 的字典相當於 Java 語言里面的 HashMap,它是無序字典。內部實現結構上同 Java 的 HashMap 也是一致的
常用指令:HSET key field value
HGET key field
HEXISTS key field
HDEL key field [field ...]
HLEN key
hash 結構也可以用來存儲用戶信息,不同於字符串一次性需要全部序列化整個對象,hash 可以對用戶結構中的每個字段單獨存儲。這樣當我們需要獲取用戶信息時可以進行部分獲取。而以整個字符串的形式去保存用戶信息的話就只能一次性全部讀取,這樣就會比較浪費網絡流量...
集合(set)
Redis 的集合相當於 Java 語言里面的 HashSet,它內部的鍵值對是無序的唯一的。它的內部實現相當於一個特殊的字典,字典中所有的 value 都是一個值NULL
。
用戶可以速地向集合添加元素,或者從集合里面 刪除元素,也可以對多個集合進行集合運算操作,比 如計算並集、交集和差集。
SDIFF key [key ...] 計算所有給定集合的差集,並返回結果。
SDIFF key [key ...] 計算所有給定集合的差集,,並將結果儲存到 destkey 。
SINTER key [key ...] 計算所有給定集合的交集,並返回結果
SINTERSTORE destkey key [key ...] 計算所有給定集合的交集,並將結果儲存到 destkey
SUNION key [key ...] 計算所有給定集合的並集,並返回結果
SUNIONSTORE destkey key [key ...] 計算所有給定集合的並集,並將結果儲存到 destkey
使用集合實現共同關注功能
使用集合可以實現商品篩選功能
有序集合
有序集合和集合一樣,都可以包含任意數量的、各不相同的元素( element),不同於集合的是,有序集 合的每個元素都關聯着一個浮點數格式的分 值(score),並且有序集合會按照分 值,以從小到大的順序 來排列有序集合中的各個元素。 雖然有序集合中的每個元素都必 須是各不相同的,但元素的分 值並沒有這一限制,換句話來說,兩個不 同元素的分值可以是相同的。
ZADD key score element [[score element] [score element] ...]
ZREM key element [element ...]
ZSCORE key element 返回分值
ZINCRBY key increment element 添加分值 ZINCRBY fruits-price 1.5 西瓜 3.5
ZCARD key 返回有序集合包含的元素數量
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 獲取指定分值范圍內的升序元素
ZCOUNT key min max 計算給定分值范圍內的元素數量
zset 可以用來存粉絲列表,value 值是粉絲的用戶 ID,score 是關注時間。我們可以對粉絲列表按關注時間進行排序
ZUNIONSTORE destkey numkeys key [key ...] 計算多個集合的並集
eg:水果的銷售情況:
HyperLogLog
需求:記錄網站每天獲得的獨立 IP 數量
集合方式:
SADD '2018.9.10::unique::ip' ip 加入IP
SCARD '20148:9:10:unique::ip' 計算獨立IP數量
集合實現的問題
使用字符串來儲存每個 IPv4 地址最多需要耗費 15 字節(格式為 'XXX.XXX.XXX.XXX' ,比如 '202.189.128.186')。下表給給出了不同用戶量使用內存的數量
隨着集合記錄的 IP 越來越多,消耗的內存也會越來越多。 另外如果要儲存 IPv6 地址的話,需要的內存還會更多一些。為了解決此類問題,Redis 提供了 HyperLogLog 數據結構就是用來解決這種統計問題的。
HyperLogLog 提供了兩個指令 pfadd 和 pfcount
pfadd key value 增加對象
pfcount key 獲取計數
HyperLogLog 是不精確的去重計數方案,雖然不精確但是也不是非常不精確,標准誤差是 0.81%,重點是省空間,總共占用的內存是12k.關於該算法的介紹https://blog.csdn.net/firenet1/article/details/77247649
發明這個算法的牛人,名字叫Philippe Flajolet 。
pfmerge 用於將多個 pf 計數值累加在一起形成一個新的 pf 值。
比如獲取兩個網站的合並起來的獨立ip。
主流的應用場景
1、緩存
2、分布式鎖
3、 消息列隊
4、位圖
在我們平時開發過程中,會有一些 bool 型數據需要存取,比如用戶一年的簽到記錄,簽了是 1,沒簽是 0,要記錄 365 天。如果使用普通的 key/value,每個用戶要記錄 365 個,當用戶上億的時候,需要的存儲空間是驚人的。 為了解決這個問題,Redis 提供了位圖數據結構,這樣每天的簽到記錄只占據一個位,365 天就是 365 個位,46 個字節 (一個稍長一點的字符串) 就可以完全容納下,這就大大節約了存儲空間。
5、HyperLogLog
6、geoHash 附近的人
- geoadd 為成員增加某個地理位置的坐標
- geodist 獲取兩個成員之間的地理位置的距離,可以設置距離單位 m:米 ,km:千米,ft:英尺 ,mi:英里
- geohash 獲取某個地理位置的hash值
- geopos 獲取某個成員的地理位置的坐標
- georadius 根據給定地理位置坐標獲取指定范圍內的地理位置集合
- WITHCOORD:傳入WITHCOORD參數,則返回結果會帶上匹配位置的經緯度。
- WITHDIST:傳入WITHDIST參數,則返回結果會帶上匹配位置與給定地理位置的距離。
- ASC|DESC:默認結果是未排序的,傳入ASC為從近到遠排序,傳入DESC為從遠到近排序。
- WITHHASH:傳入WITHHASH參數,則返回結果會帶上匹配位置的hash值。
- COUNT count:傳入COUNT參數,可以返回指定數量的結果。
- georadiusbymember 根據給定成員獲取指定范圍內的地理位置集合,相對於georadius命令,使用起來比較方便.參數基本一樣
7、排行榜
8、關注點贊
四、特性
1、快
2、支持多種數據類型
3、過期鍵功能
4、支持持久化
5、管道
6、支持主從模式
7、sentinel高可用
8、Cluster集群
快!!!內存存儲,不受IO到硬盤IO速度限制 速度極快!
采用了非阻塞 I/O 多路復用機制 極大增加訪問速度。
過期鍵功能
設置生存時間 EXPIRE 命令和 PEXPIRE 命令。 SETEX 命令 PSETEX 命令
設置過期時間 EXPIREAT 命令和 PEXPIREAT 命令。
查看剩余生存時間 TTL 命令和 PTTL 命令。
刪除生存時間或過期時間 PERSIST 命令。
應用:1、自動更新的緩存
2、自動刷新的排行榜:在有序集合中,通過給日排行榜設置生存時間,我們可以 讓 Redis 在每個新的一天開始時,自動刪除舊的排行榜。
過期刪除機制:
redis 會將每個設置了過期時間的 key 放入到一個獨立的字典中,以后會定時遍歷這個字典來刪除到期的 key。除了定時遍歷之外,它還會使用惰性策略來刪除過期的 key,所謂惰性策略就是在客戶端訪問這個 key 的時候,redis 對 key 的過期時間進行檢查,如果過期了就立即刪除。定時刪除是集中處理,惰性刪除是零散處理。
定時掃描:
Redis 默認會每秒進行十次過期掃描,過期掃描不會遍歷過期字典中所有的 key,而是采用了一種簡單的貪心策略。
1、從過期字典中隨機 20 個 key;
2、 刪除這 20 個 key 中已經過期的 key;
3、 如果過期的 key 比率超過 1/4,那就重復步驟 1;
注意:如果有大批量的 key 過期,要給過期時間設置一個隨機范圍,而不能全部在同一時間過期。否則可能會系統資源不足,造成卡頓。
支持持久化
Redis 的數據全部在內存里,如果突然宕機,數據就會全部丟失,因此必須有一種機制來保證 Redis 的數據不會因為故障而丟失,這種機制就是 Redis 的持久化機制。
如果我們僅僅是將 Redis 用作緩存的話,那么這種數據丟失帶來的問題並不是非常大,我們只需要重 啟機器,然后再次將數據放到 緩存里面就可以了;但如果我 們將 Redis 用作數據庫的話,那么這種數據 丟失就不能接受了。
Redis 的持久化機制有兩種:
第一種是快照 RDB,全量備份 記錄數據
那么 Redis 服務器在什么時候才會創建 RDB 文件呢?
在 Redis 服務器創建 RDB 文件的情況中,以下三種是最常 見的:
1. 服務器執行客戶端發送的 SAVE 命令; 手動 阻塞 快
2. 服務器執行客戶端發送的 BGSAVE 命令;手動 不阻塞 慢
3. 使用 save 配置選項設置的自動保存條件被滿足,服務器自動執行 BGSAVE 。自動 不阻塞
BGSAVE 命令不會㐀成服務器阻塞的原因在於:
1. 當 Redis 服務器接收到 BGSAVE 命令的時候,它不會自己來創建 RDB 文件,而是通過 fork() 來生 成一個子進程,然后由子進程負責創建 RDB 文件,而自己則繼續處理客戶端的命令請求;
2. 當子進程創建好 RDB 文件並退出時,它會向父進程(也即是負責處理命令請求的 Redis 服務器)發 送一個信號,告知它 RDB 文件已經創建完畢;
3. 最后 Redis 服務器(父進程)接收子進程創建的 RDB 文件,BGSAVE 執行完畢。
自動BGSAVE的條件
save 900 1
save 300 10
save 60 10000
表示“如果距離上一次創建 RDB 文件已經過去了 900 秒,並且服務器的所有數據庫總共已經發生了 不少於 1 次修改,那么執行 BGSAVE 命令”。
RDB持久化策略的不足:
因 為創建 RDB 文件需要將服務器所有數據庫的數據都保存起來, 這是一個非常耗費資源和時間的操作,所以服務器需要隔一段時間才創建一個新的 RDB 文件,也即 是說,創建 RDB 文件的操作不能執行得過於頻繁,否則就會嚴重地影響服務器的性能。 比如說,在 save 配置選項的默認設置下,即使有超過 10000 次修改操作發生,服務器也至少會間隔 一分鍾才創建下一個 RDB 文件: save 900 1 save 300 10 save 60 10000 如果在等待創建下一個 RDB 文件的過程中,服務器遭遇了意外停機,那么用 戶將丟失最后一次創建 RDB 文件之后,數據庫發生的所有修改。
第二種是 AOF 日志 增量備份 記錄指令
AOF 持久化有一個巨大的優勢,那就是,用戶可以根據自己的需要對 AOF 持 久化進行調整,讓 Redis 在遭遇意外停機時不丟失任何數據,或者只丟失一秒鍾數據,這比 RDB 持 久化遭遇意外停機時,丟失的數據要少得多。
配置 相關配置
appendonly yes no#開啟AOF模式
appendfilename "appendonly.aof" #保存數據的AOF文件名稱
appendfsync always everysec no
原理
AOF 持久化保存數據庫數據的方法是:每當有修改數據 庫的命令被執行時,服務器就會將被執行的命 令寫入到 AOF 文件的末尾。下次服務啟動時還原。
AOF重寫-----給文件瘦身
AOF 重寫的觸發
有兩種方法可以觸發 AOF 重寫:
1. 客戶端向服務器發送 BGREWRITEAOF 命令。
2. 通過設置配置選項來讓服務器自動執行 BGREWRITEAOF 命令,它們分別是:
• auto-aof-rewrite-min-size ,觸發 AOF 重寫所需的最小體積:只有在 AOF 文件的體積 大於等於 size 時,服務器才會考慮是否需要進行 AOF 重寫。這個選項用於避免對體積過小的 AOF 文件進行重寫。
• auto-aof-rewrite-percentage ,指定觸發重寫所需的 AOF 文件體積百分比:當 AOF 文件的體積大於 auto-aof-rewrite-min-size 指定的體積,並且超過上一次重寫之后的 AOF 文件 體積的 percent% 時,就會觸發 AOF 重寫。(如果服務器剛剛啟動不久,還沒有進行過 AOF 重 寫,那么使用服務器啟動時載入的 AOF 文件的體積來作為基准值。)將這個值設置為 0 表示關 閉自動 AOF 重寫。
管道
在一般情況下, 用戶每執行一個 Redis 命令,客戶端與服務器都需要進行一次通信:客戶端會將命令 請求發送給服務器,而服務器則會將執行命令所得的結果返回給客戶端。 當程序執行一些復雜的操作時, 客戶端可能需要執行多個命令, 並與服務器進行多次通信。
Redis 的流水線功能允許客戶端一次將多個命令請求發送給服務器, 並將被執行的多個命令請求的結 果在一個命令回復中全部返回 給客戶端, 使用這個功能可以有效地減少客 戶端在執行多個命令時需要 與服務器進行通信的次數。
支持主從模式
為了分擔讀壓力,Redis支持主從復制,Redis的主從結構可以采用一主多從或者級聯結構,Redis主從復制可以根據是否是全量分為全量同步和增量同步。
Redis 的復制(replication)功能允許用戶根據一個 Redis 服務器來創建任意多個該服務器的復制品,其 中被復制的服務器為主服務器(master),而通過復制創建出來的服務器復制品則為從服務器(slave)。 主從服務器兩者擁有相同的數據庫數據:只要主從服務器之間的網絡連接正常,主服務器就會一直將 發生在自己身上的數據更新同步 給從服務器,從而一直保證主從服務器的數據相同。
Redis 提供了兩種方法來為某個主服務器創建從服務器:
1. 使用 SLAVEOF 命令,比如向一個服務器發送 SLAVEOF 127.0.0.1 6379 ,可以讓接收到該命令的服務器變為 127.0.0.1:6379 的從服務器。 在將一個服務器設置成從服務器之后,可以通過向它發送 SLAVEOF no one 來讓它變回一個主 服務器(數據庫已有的數據會被保留)。
2. 在啟動服務器時,通過設置 slaveof 配置選項來讓服務器成為指定 服務器的從服務器。
我配置的服務器分布:
6376 | 6377 | 6378 | 6379 |
master | slave | slave | slave |
配置並且啟動主節點:
port 6376
daemonize yes
logfile "6376.log"
dbfilename "dump_6376.rdb"
dir "/var/redis/data/"
啟動並重啟從節點
port 6377
daemonize yes
logfile "6377.log"
dbfilename "dump-6377.rdb"
dir "/var/redis/data/"
slaveof 127.0.0.1 6376 // 從屬主節點
查看服務狀態,登陸客戶端,查看主從關系 INFO replication
sentinel高可用
監視主從服務器,並在主服務器下線時自動進行故障轉移.
它負責持續監控主從節點的健康,當主節點掛掉時,自動選擇一個最優的從節點切換為主節點。客戶端來連接集群時,會首先連接 sentinel,通過 sentinel 來查詢主節點的地址,然后再去連接主節點進行數據交互。當主節點發生故障時,客戶端會重新向 sentinel 要地址,sentinel 會將最新的主節點地址告訴客戶端。如此應用程序將無需重啟即可自動完成節點切換。比如上圖的主節點掛掉后,集群將可能自動調整為下圖所示結構。
部署sentinel集群
sentinel26379 | sentinel26380 | sentinel26381 |
修改sentinel.conf文件
// Sentinel節點的端口
port 26379
dir /var/redis/data/
logfile "26379.log"
// 當前Sentinel節點監控 127.0.0.1:6379 這個主節點
// 2代表判斷主節點失敗至少需要2個Sentinel節點節點同意
// mymaster是主節點的別名
sentinel monitor mymaster 127.0.0.1 6376 2
//每個Sentinel節點都要定期PING命令來判斷Redis數據節點和其余Sentinel節點是否可達,如果超過30000毫秒且沒有回復,則判定不可達
sentinel down-after-milliseconds mymaster 30000
//故障轉移超時時間為180000毫秒
sentinel failover-timeout mymaster 180000
啟動sentinel節點
redis-sentinel sentinel.conf
確認登陸客戶端確認:
redis-cli -h 127.0.0.1 -p 26379 INFO Sentinel
測試
相關指令 sentinel masters
sentinel slaves mymaster
Redis Sentinel
的以下幾個功能。
- 監控:
Sentinel
節點會定期檢測Redis
數據節點和其余Sentinel
節點是否可達。 - 通知:
Sentinel
節點會將故障轉移通知給應用方。 - 主節點故障轉移:實現從節點晉升為主節點並維護后續正確的主從關系。
- 配置提供者:在
Redis Sentinel
結構中,客戶端在初始化的時候連接的是Sentinel
節點集合,從中獲取主節點信息。
消息丟失
Redis 主從采用異步復制,意味着當主節點掛掉時,從節點可能沒有收到全部的同步消息,這部分未同步的消息就丟失了。如果主從延遲特別大,那么丟失的數據就可能會特別多。Sentinel 無法保證消息完全不丟失,但是也盡可能保證消息少丟失。它有兩個選項可以限制主從延遲過大。
min-slaves-to-write 1
min-slaves-max-lag 10
第一個參數表示主節點必須至少有一個從節點在進行正常復制,否 則就停止對外寫服務,喪失可用性。 何為正常復制,何為異常復制?這個就是由第二個參數控制的,它的單位是秒,表示如果 10s 沒有收到從節點的反饋,就意味着從節點同步不正常,要么網絡斷開了,要么一直沒有給反饋。
Cluster集群
------復制特性可以創建指定服務器的復制品,這些復制品可以用於擴展系統處理讀請求的能力。
------Redis Sentinel 可以在復制特性的基礎上,通過監視主從服務器並在主服務器故障時執行自動故
障轉移來保證系統的可用性。
寫能力不足怎么辦??????
分片技術
集群使用分片來擴展數據庫的容量,並將命令請求的負載交給不同的節點來分擔。
集群將整個數據庫分為 16384 個槽(slot),所有鍵都屬於這 16384 個槽的其中一個,計算鍵 key 屬於哪個槽的公式為 slot_number = crc16(key) % 16384 ,其中 crc16 為 16 位的循環冗余校驗和函數。
集群中的每個主節點都可以處理 0 個至 16384 個槽,當 16384 個槽都有某個節點在負責處理時,集群進入上線狀態,並開始處理客戶端發送的數據命令請求。
比如說,如果我們有三個主節點 7000 、 7001 和 7002 ,那么我們可以:
- 將槽 0 至 5460 指派給節點 7000 負責處理;
- 將槽 5461 至 10922 指派給節點 7001 負責處理;
- 將槽 10923 至 16383 指派給節點 7002 負責處理;
這樣就可以將 16384 個槽平均地指派給三個節點負責處理。
請求轉向:
對於一個被指派了槽的主 節點來說,這個主節點只會處理屬於指派給自己的槽的命令請求。如果一個節點接收到了和自己處理的槽無關的命令請求,那么節點會向客戶端返回一個轉向錯誤(redirection error),告訴客戶端,哪個節點才是負責處理這條命令的,之后客戶端需要根據錯誤中包含的地址和端口號重新向正確的 節點發送命令請求。
集群搭建:
1、創建多個節點
2、為每個節點指派槽,並將多個節點連接起來,組成一個集群
3、當集群數據庫的 16384 個槽都有節點在處理時,集群進入上線狀態。
五、補充
1、應用場景不同,配置不同。
緩存:關閉aof化,rdb持久化視情況而定 ,開啟最大內存設置(否則為物理內存)
數據庫:開啟 aof ,開啟rdb 關閉最大內存設置