1.Redis
1.1 初識Redis
1.1.1 Redis特性
1.1.1.1 快
1.1.1.2 持久化
AOF + RDB
1.1.1.3 多種數據結構
String、Hash、Set、List、SortedSet、BitMap(位圖)、HyperLogLog(超小內存唯一計數)、Geo(地理信息)
1.1.1.4 多種客戶端語言
Java、python、Ruby
1.1.1.5 功能豐富
發佈訂閱、lua腳本、事務、pipeline
1.1.1.6 主從複製
1.1.1.7 高可用、分佈式(集羣)
1.1.2 Redis使用場景
緩存系統、計數器、排行榜、社交網絡、簡易消息隊列、實時系統(布隆過濾器)
1.1.3 Redis常用數據類型
- String:最基本的類型,最大512M,二進制安全(jpg圖片、序列化的對象等)[單key單value]
Redis 的字符串是動態字符串,是可以修改的字符串,內部結構實現上類似於 Java 的ArrayList,採用預分配冗餘空間的方式來減少內存的頻繁分配,內存爲當前字符串實際分配的空間一般要高於實際字符串長度。當字符串長度小於 1M 時,擴容都是加倍現有的空間,如果超過 1M,擴容時一次只會多擴 1M 的空間。需要注意的是字符串最大長度爲 512M。
使用場景:記錄用戶頁面訪問量、緩存基本數據、分佈式Id生成器
------------鍵值對------------- > set key value //設置鍵值對,重複設置會更新key > get key // > exists key (integer) 1 > del key (integer) 1 > strlen key //獲取value的長度 > append key valu //在原先的value後面追加值 ------------批量鍵值對------------- (可以批量對多個字符串進行讀寫,節省網絡耗時開銷。) > mset k1 v1 k2 v2 k3 v3 > mget k1 k2 k3 > msenx k1 v1 k2 v2 //當且僅當key都不存在時,設置成功 ------------過期和 set 命令擴展------------- > set key value > expire key 5 # 5s 後過期 > setex key 5 value # 5s 後過期,等價於 set+expire > setnx key value# 如果 name 不存在就執行 set 創建 (integer) 1 > setnx key value (integer) 0 # 因爲 name 已經存在,所以 set 創建不成功 ---------------------計數------------------- > set age 30 > incr age //自增值 decr key // 自減 (integer) 31 > incrby age 5 //decrby key 值 //-值 (integer) 36 -------------------獲取----------------------- getrange key startIdx endIdx //獲取內容,包括起始位置和終止位置 setrang key startIdx value //從指定位置開始寫
還有一個需要特別注意的地方是如果一個key已經設置了過期時間,然後你調用了set 方法修改了它,它的過期時間會消失。
- Hash :
key:fileld : value ;MapMap結構
Redis 的Hash相當於 Java 語言裏面的 HashMap,它是無序字典。內部通過數組 + 鏈表實現。第一維 hash 的數組位置碰撞時,就會將碰撞的元素使用鏈表串接起來。不同的是,Redis 的字典的值只能是字符串,另外它們擴容 方式不一樣,因爲Java 的HashMap 在字典很大時,擴容是個耗時的操作,需要一次性全部 擴容。Redis爲了高性能,不能堵塞服務,所以採用了漸進式擴容策略。漸進式 擴容會在 擴容的同時,保留新舊兩個hash 結構,查詢時會同時查詢兩個hash 結構,然後在後續的定時任務中以及 hash 的子指令中,循序漸進地將舊 hash 的內容一點點遷移到新的 hash 結構中。當 hash 移除了最後一個元素之後,該數據結構自動被刪除,內存被回收。
hset sunpeipei age 22 //設置修改年齡 hget sunpei age //獲取年齡 hmset sunpei name "sunpei" age 23 phone "153..."//批量設置 hmget sunpei name age hgetall sunpei //獲取全部key和v hlen sunpei //獲取value有多少鍵值對 hexists sunpei name //判斷key存在否 hkeys hash名 //獲取所有的key hvals 哈希名 //獲取所有的value hincrby hash名 k 數 //將指定的值+數 【整數】 hsetnx sunpei age 22 //當且僅當元素不存在時設置成功
- List : 列表,按照插入順序排序[先進後出][單值多value]
Redis 的列表相當於 Java 語言裏面的 LinkedList,注意它是鏈表而不是數組。這意味着list 的插入和刪除操作非常快,時間複雜度爲 O(1),但是索引定位很慢,時間複雜度爲O(n)。當列表彈出了最後一個元素之後,該數據結構自動被刪除,內存被回收。
Redis 的列表結構常用來做異步隊列使用。將需要延後處理的任務結構體序列化成字符
串塞進 Redis 的列表,另一個線程從這個列表中輪詢數據進行處理。
Redis 底層存儲的不是一個簡單的 linkedlist,而是稱之爲快速鏈表 quicklist 的一個結構。
首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是 ziplist,也即是壓縮列表。它將所有的元素緊挨着一起存儲,分配的是一塊連續的內存。當數據量比較多的時候纔會改成 quicklist。因爲普通的鏈表需要的附加指針空間太大,會比較浪費空間,而且會加重內存的碎片化。所以 Redis 將鏈表和 ziplist 結合起來組成了 quicklist。也就是將多個ziplist 使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗餘。
Tips:
LPUSH+LPOP =棧
LPUSH+RPOP =隊列
LPUSH+ LTRIM = 固定數量的列表
LPUSH +BRPOP =消息隊列
--------------右邊進左邊出:隊列------------- > rpush books python java golang // lpush 列表名 值 (integer) 3 > lpop books //正序彈出一個元素 "python" > lpop books "java" > lpop books "golang" -----------右邊進右邊出:棧--------------- > rpush books python java golang (integer) 3 > rpop books //倒序彈出一個元素 "golang" > rpop books "java" > rpop books "python" -----------慢操作(慎用)--------------- > lindex 列表名 索引 //獲取指定索引的元素 > lrange 列表名 startIdx endIdx //獲取指定數據【包括起始索引和結束索引】 > ltrim 列表名 starIdx endIdx //截取指定範圍的值再賦值給key 【包括起止索引】 > lset 列表名 index value // > linsert 列表名 before/after v1 v2 //在v1前/後面插入v2 -----------其他--------------- > llen 列表名 //獲取列表的長度 > lrem 列表名 個數 值 //刪除n個值 > rpoplpush 源列表 目的列表 //將原列表表的最後一個元素添加到目的列表的第一位
- Set :無序集合,不予許重複
sadd friends lilei //成功返回 1 sadd friends lilei //重複返回 0 不可重複設置 smembers set名 //查看元素,返回結果無序 sismember set名 value //查看該集合中是否包含該元素 1-包含 0-不包含 scard set名 //獲取集合元素個數 srem set名 值 //移除某元素 srandmember set名 個數 //隨機獲取指定個數值 spop set名 //隨機出棧 smove set1名 set2名 v //將集合1中的某值移動到集合2中 sdiff set1 set2 //在集合1中且不再集合2中的元素 sinter set1 set2 //集合1和集合2的交集部分 sunion set1 set2 //集合1和集合2的並集 //用戶關注人 、粉絲 可以求交集、差集、並集操作【共同操作、共同愛好】
Tips:
SADD :標籤
SPOP + SRANDMEMBER : 隨機數
SADD +SINTER :社交
- Sorted Set : 有序結合,不允許重複 【在set的基礎上加了一個分數】
zset 可能是 Redis 提供的最爲特色的數據結構,它類似於 Java 的 SortedSet 和 HashMap 的結合體,一方面它是一個 set,保證了內部value 的唯一性,另一方面它可以給每個 value 賦予一個 score,代表這個 value 的排序權重。它的內部實現用的是一種叫着「跳躍列表」的數據結構。
zset 要支持隨機的插入和刪除,所以它不好使用數組來表示。我們先看一個普通的
鏈表結構。
我們需要這個鏈表按照 score 值進行排序。這意味着當有新元素需要插入時,要定位到
特定位置的插入點,這樣纔可以繼續保證鏈表是有序的。通常我們會通過二分查找來找到插入點,但是二分查找的對象必須是數組,只有數組纔可以支持快速位置定位,鏈表做不到。
「跳躍列表」之所以「跳躍」,是因爲內部的元素可能「身兼數職」,比如上圖中間的
這個元素,同時處於 L0、L1 和 L2 層,可以快速在不同層次之間進行「跳躍」。
定位插入點時,先在頂層進行定位,然後下潛到下一級定位,一直下潛到最底層找到合
適的位置,將新元素插進去。你也許會問,那新插入的元素如何纔有機會「身兼數職」呢?跳躍列表採取一個隨機策略來決定新元素可以兼職到第幾層。
首先 L0 層肯定是 100% 了,L1 層只有 50% 的概率,L2 層只有 25% 的概率,L3
層只有 12.5% 的概率,一直隨機到最頂層 L31 層。絕大多數元素都過不了幾層,只有極少數元素可以深入到頂層。
zadd 集合名 分數1 值1 分數2 值2 //添加數據 zrange key startIdx endIdx 【withscores】 //遍歷集合 【帶分數】 zrangebyscore 集合名 【(】開始socre 【(】 結束score 【limit 開始idx 個數】//獲取指定範圍的元素 zrem 集合名 元素 // 刪除指定元素 zcard 集合名 //統計集合中有多少元素 zcount 集合名 開始score 結束score //統計得分在指定範圍的元素有多少個,包括邊界 zrank 集合名 k1 //獲取元素的下標 zcore 集合名 元素 //獲取元素的分數 zrevrank 集合名 元素 //逆序獲取元素的下標 zrevrange 集合名 開始idx 結束idx //逆序獲取元素 zrevrangebyscore 集合名 結束分數 開始分數 //獲取指定分數範圍的集合
- HyperLogLog: 用於計數
- Geo:存儲地理位置信息
1.1.4 單線程
單線程的理解:
Redis一次只會執行一個命令,因此要避免長命令(keys、flush、flushdb、slow lua script、multi/exec、operate、big value)
單線程爲什麼這麼快?
- 基於內存
- 非阻塞IO(IO多路複用)
- 避免線程切換
1.2 常用API
1.2.1 通用命令
keys //keys * keys h[h-l]* keys jav? config get * //獲取配置
命令 |
時間複雜度 |
keys |
O(n) |
dbsize |
O(1) |
exists、hexists、hlen |
O(1) |
del 、hdel |
O(1) |
expire、ttl、persist |
O(1) |
type |
O(1) |
get、set、del、hget、hset、hdel |
O(1) |
incr、decr、incrby、decrby、incrbyflocat、hincrby、hincrbyflocat |
O(1) |
setnx(add)、set xx(update)、hsetnx |
O(1) |
mget、mset、hmget、hmset、 |
O(n)[1次網絡時間+n次命令時間] |
getset(設置新值、返回舊值)、append、strlen(中文) |
O(1) |
getrange、setrange |
O(1) |
hgetall、hvals、hkeys |
O(n) |
rpush、lpush |
O(1-n) |
linsert |
O(n) |
lpop、rpop、 |
O(1) |
lrem、ltrim |
O(n) |
lindex、lrange(包含end)、 |
O(n) |
llen |
O(1) |
lset、 |
O(n) |
blpop、brpop |
O(1) |
sadd、srem |
O(1) |
scard、sismember、srandmember、spop |
O(1) |
smembers |
O(n) |
sdiff、sinter、sunion、 |
集合間操作 |
zadd |
O(lgn) |
zrem、zscore、zincrby、zcard |
O(1) |
zrange、zrangebyscore、zcount、zremrangebyrank、zremrangebyscore |
O(log(n)+m) |
zinterstore、zunionstore |
1.3 數據結構&內部編碼
這樣設計的目的: 空間換時間? 時間換空間
Redis內置對象結構:
1.4 持久化
1.4.1 RDB
觸發機制:save、bgsave、自動觸發
文件策略:新文件覆蓋舊文件
1.4.1.1 save命令
1.4.1.2 bgsave命令
1.4.1.3 save VS bgsave
1.4.1.4 自動生成
自動觸發:達到配置、shutdown、全量複製
推薦配置:關閉自動save、開啓文件壓縮、開啓文件校驗
1.4.1.5 總結
- save會阻塞Redis,bgsave不會長時間阻塞(fork時阻塞)
- bgsave會fork出子進程
1.4.2 AOF
1.4.2.1 策略
推薦使用第二種:最多丟失1s數據
1.4.2.2 AOF重寫
重寫的作用:減少硬盤佔用量、加快恢復速度
1.4.2.3 AOF重寫實現方式
注意:重寫是基於內存重寫,不是基於舊的AOF文件;
min-size:第一次重寫最小內存
percentage:下一次重寫的條件
重寫條件:(同時滿足)
1.當期內存 > 最小內存
2.(當期尺寸-上次尺寸)/上次尺寸 > 增長率
no-appendfsync-on-rewrite :yes 在往緩存區寫重寫內容時,是否停止寫aof;性能 VS 數據(如果重寫失敗,會導致aof丟失部分數據),推薦使用yes,提高性能;
1.4.3 RDB VS AOF
RDB建議:主節點關閉RDB、從節點打開RDB,便於備份數據;
1.4.4 持久化存在的問題
1.4.4.1 fork
latest_fork_usec :上次fork花費的時間
1.4.4.2 子進程的開銷
Linux在2.6版本後,優化了內存頁(內存頁更大),然而這對Redis來說並不是一個好消息;echo:nerver > /sys/kernel/mm/transparent_hugepage/enable
1.4.4.3 AOF追加阻塞
2.Redis安裝
2.1 linux安裝
下載壓縮包:http://download.redis.io/releases/
// 下載 wget http://download.redis.io/releases/+版本 //解壓 tar -zxvf 文件名 // 安裝gcc[c編譯器]: yum install gcc-c++ // 安裝Redis 【redis目錄下】 make // 出錯用 : make MALLOC=libc make install // 開啓守護線程 daemonize yes // 運行Redis redis-server 配置文件 // 查詢線程信息 ps -ef|grep "redis" // 連接客戶端: redis-cli -h host-p 端口號
2.2 常用操作
性能測試:
redis-benchmark
切換庫:
select 編號 【從0開始,默認16個庫】
查看當前數據庫key的數量:
DBSIZE
查看當前庫所有的key
keys * // keys k? 精確查找
清空當前庫
flushdb
清空全庫
flushall
判斷某個key是否存在
exists key //0-不存在 1-存在
剪切key到另一個庫
move key db //當前庫就不存在了
給key設置過期時間
expire key 10 //過期時間
查看key還有多久過期
ttl key //-1 永久有效,-2 已過期
查看key的類型
type key
2.3 配置
2.3.1 配置文件
redis.conf 配置項說明如下: 1.daemonize no Redis默認不是以守護進程的方式運行,可以通過該配置項修改, 使用yes啓用守護進程 2. pidfile /var/run/redis.pid 當Redis以守護進程方式運行時,Redis默認會把pid 寫入/var/run/redis.pid文件,可以通過pidfile指定 3. port 6379 指定Redis監聽端口,默認端口爲6379, 4. bind 127.0.0.1 綁定的主機地址 5. timeout 300 當客戶端閒置多長時間後關閉連接,如果指定爲0,表示關閉該功能 6. loglevel verbose 指定日誌記錄級別,Redis總共支持四個級別:debug、verbose、notice、 warning,默認爲verbose 7. logfile stdout 日誌記錄方式,默認爲標準輸出,如果配置Redis爲守護進程方式運行, 而這裏又配置爲日誌記錄方式爲標準輸出,則日誌將會發送給/dev/null 8. databases 16 Redis支持多個數據庫,並且每個數據庫的數據是隔離的不能共享, 並且基於單機纔有,如果是集羣就沒有數據庫的概念 。 設置數據庫的數量,默認數據庫爲0,可以使用SELECT <dbid>命令 在連接上指定數據庫id 9. save <seconds> <changes> 指定在多長時間內,有多少次更新操作,就將數據同步到數據文件, 可以多個條件配合 save "" //關閉RDB Redis默認配置文件中提供了三個條件: save 900 1 save 300 10 save 60 10000 分別表示900秒(15分鐘)內有1個更改,300秒(5分鐘)內有10個更改以及 60秒內有10000個更改。 10. rdbcompression yes 指定存儲至本地數據庫時是否壓縮數據,默認爲yes, Redis採用LZF壓縮,如果爲了節省CPU時間,可以關閉該選項, 但會導致數據庫文件變的巨大 11. dbfilename dump.rdb 指定本地數據庫文件名,默認值爲dump.rdb 12. dir ./ 指定本地數據庫存放目錄 13. slaveof <masterip> <masterport> 設置當本機爲slave服務時,設置master服務的IP地址及端口, 在Redis啓動時,它會自動從master進行數據同步 14. masterauth <master-password> 當master服務設置了密碼保護時,slav服務連接master的密碼 15. requirepass foobared 設置Redis連接密碼,如果配置了連接密碼, 客戶端在連接Redis時需要通過AUTH <password>命令提供密碼,默認關閉 16. maxclients 12 設置同一時間最大客戶端連接數,默認無限制, Redis可以同時打開的客戶端連接數爲Redis進程可以打開的最大文件描述符 數,如果設置 maxclients 0,表示不作限制。當客戶端連接數到達限制時, Redis會關閉新的連接並向客戶端返回max number of clients reached 錯誤信息 17. maxmemory <bytes> 指定Redis最大內存限制,Redis在啓動時會把數據加載到內存中, 達到最大內存後,Redis會先嚐試清除已到期或即將到期的Key, 當此方法處理 後,仍然到達最大內存設置,將無法再進行寫入操作, 但仍然可以進行讀取操作。Redis新的vm機制,會把Key存放內存, Value會存放在swap區 18. appendonly no 指定是否在每次更新操作後進行日誌記錄,Redis在默認情況下是異步的 把數據寫入磁盤,如果不開啓,可能會在斷電時導致一段時間內的數據丟失。 因爲 redis本身同步數據文件是按上面save條件來同步的,所以有的數據會 在一段時間內只存在於內存中。默認爲no 19. appendfilename appendonly.aof 指定更新日誌文件名,默認爲appendonly.aof 20. appendfsync everysec 指定更新日誌條件,共有3個可選值: no:表示等操作系統進行數據緩存同步到磁盤(快) always:表示每次更新操作後手動調用fsync()將數據寫到磁盤(慢,安全) everysec:表示每秒同步一次(折衷,默認值) 21. vm-enabled no 指定是否啓用虛擬內存機制,默認值爲no,簡單的介紹一下, VM機制將數據分頁存放,由Redis將訪問量較少的頁即冷數據 swap到磁盤上,訪問多的頁面由磁盤自動換出到內存中 22. 虛擬內存文件路徑,默認值爲/tmp/redis.swap,不可多個Redis實例共享 vm-swap-file /tmp/redis.swap 23. 將所有大於vm-max-memory的數據存入虛擬內存,無論vm-max-memory設置多小,所有索引數據都是內存存儲的(Redis的索引數據 就是keys),也就是說,當vm-max-memory設置爲0的時候,其實是所有value都存在於磁盤。默認值爲0 vm-max-memory 0 24. Redis swap文件分成了很多的page,一個對象可以保存在多個page上面,但一個page上不能被多個對象共享,vm-page-size是要根據存儲的 數據大小來設定的,作者建議如果存儲很多小對象,page大小最好設置爲32或者64bytes;如果存儲很大大對象,則可以使用更大的page,如果不 確定,就使用默認值 vm-page-size 32 25. 設置swap文件中的page數量,由於頁表(一種表示頁面空閒或使用的bitmap)是在放在內存中的,,在磁盤上每8個pages將消耗1byte的內存。 vm-pages 134217728 26. 設置訪問swap文件的線程數,最好不要超過機器的核數,如果設置爲0,那麼所有對swap文件的操作都是串行的,可能會造成比較長時間的延遲。默認值爲4 vm-max-threads 4 27. 設置在向客戶端應答時,是否把較小的包合併爲一個包發送,默認爲開啓 glueoutputbuf yes 28. 指定在超過一定的數量或者最大的元素超過某一臨界值時,採用一種特殊的哈希算法 hash-max-zipmap-entries 64 hash-max-zipmap-value 512 29. 指定是否激活重置哈希,默認爲開啓(後面在介紹Redis的哈希算法時具體介紹) activerehashing yes 30. 指定包含其它的配置文件,可以在同一主機上多個Redis實例之間使用同一份配置文件,而同時各個實例又擁有自己的特定配置文件 include /path/to/local.conf
2.3.4 Redis安裝腳本
#!/bin/bash #檢查用戶是否已經存在 ############################## ## 描述:創建用戶,安裝Redis ## 作者:孫培 ## 時間:2019-09-17 ############################## set -o nounset set -o errexit readonly redisDir="/opt/cachecloud/redis" readonly redisTarGz="redis-3.0.7.tar.gz" checkExist(){ #查看passwd裏是否有用戶記錄 local num=`cat /etc/passwd | grep -w ${1}|wc -l` if [ ${num} -ge 1 ] then echo "==>>當前用戶已經存在,是否覆蓋已有數據?:[y/n]" read replace if [ ${replace} == "y" ] then echo "==>>刪除已存在用戶: ${1}" userdel -r "${1}" createUser "${1}" init "${1}" return 0 fi else createUser "${1}" init "${1}" fi return 0 } #創建用戶 createUser() { # 添加用戶 useradd -m -d /home/${1} -s /bin/bash ${1} # 添加密碼 echo "==>> 請輸入用戶密碼" passwd ${1} # 密碼保存的有效天數 chage -M 9999 ${1} echo "==>> 用戶${1}創建成功!" } # 初始化 init() { # 創建目錄 mkdir -p /opt/cachecloud/data mkdir -p /opt/cachecloud/conf mkdir -p /opt/cachecloud/logs mkdir -p /opt/cachecloud/redis mkdir -p /tmp/cachecloud echo "==>>初始化文件目錄" # 爲用戶授權 chown -R ${1}:${1} /opt/cachecloud chown -R ${1}:${1} /tmp/cachecloud echo "==>>爲${1}用戶授權" } # 安裝redis installRedis() { # 提示語: echo "===>>開始安裝Redis," # 安裝GCC yum install -y gcc # 創建目錄 mkdir -p ${redisDir} && cd ${redisDir} # 下載解壓目錄 wget http://download.redis.io/releases/${redisTarGz} && mv ${redisTarGz} redis.tar.gz && tar zxvf redis.tar.gz --strip-component=1 # 安裝Redis make && make install if [ $? == 0 ] then echo "==>> Redis安裝成功,Redis默認安裝目錄:${redisDir},Redis默認版本:${redisTarGz},如有需要,請修改腳本" #授權 chown -R $1:$1 ${redisDir} #配置環境變量 export PATH=$PATH:${redisDir}/src return 0 fi echo "==>>Redis安裝失敗,原因:Redis已經存在" } username=$1 checkExist "${username}" installRedis "${username}"
3. Redis
3.1 慢查詢
Redis會將慢查詢命令保存在內存隊列中,慢查詢數據不會持久化;
slowlog get [n] //獲取慢查詢列表 slowlog len //獲取慢查詢隊列長度 slowlog reset //清理慢查詢隊列 //動態修改 config set slowlog-max-len 1000 //慢查詢隊列長度 config set slowlog-log-slower-than 1000 //單位微妙:即1毫秒
tip:
- slowlog-log-slower-than 默認10ms,通常設置1ms
- slowlog-max-len 不要太小,通常設置1000左右
- 查詢聲明週期:1.發送請求 2.請求排隊 3.執行請求(慢查詢) 4,返回請求
- 定期持久化慢查詢
3.2 pipeline
1次請求時間 = 1次網絡請求時間(發送請求+接收請求) + 命令排隊時間+執行時間
注意:Redis執行命令的時間是微妙級別、所以Redis的瓶頸是在網絡時間上;北京到上海的一次網 絡請求大概需要花費13毫秒。
pipeline就是爲了節省網絡開銷,一次執行多條命令;
3.2.1 pipeline VS m操作
原生的M操作是原子性的,pipeline是非原子性的;
3.3 Hyperloglog
pfadd key element //添加元素 pfcount key :計算總數 pfmerge destkey sourcekey [sourcekey..] //合併多個key
Tips:
- 是否容忍錯誤 (錯誤率:0.81)
- 是否需要單條數據
3.1 Redis事務
3.1.1 常用命令:
①標記開始一個事務:MULTI
②取消事務:DISCARD
③執行所有的事務:EXEC
④監視一個(或多個)key,如果在事務執行前這個key被改動,那麼事務被打斷:WATCH key [key...]
⑤取消WATCH命令多所有key的監視: UNWATCH
redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> DISCARD OK redis 127.0.0.1:6379> get k1 (nil) redis 127.0.0.1:6379> EXEC 1) OK redis 127.0.0.1:6379> get k1 "v1"
3.1.2 特性:
①冤頭債主:
部分支持事務,成功的操作不會受錯誤操作的影響;
redis 127.0.0.1:6379> set k1 v1 QUEUED redis 127.0.0.1:6379> getset k2 (error) ERR wrong number of arguments for 'getset' command redis 127.0.0.1:6379> set k3 v3 QUEUED redis 127.0.0.1:6379> exec 1) OK 3) OK redis 127.0.0.1:6379> get k3 "v3"
②WATCH鎖
WATCH鎖,底層由樂觀鎖實現;
redis 127.0.0.1:6379> get k1 "800" //設置監視鎖 redis 127.0.0.1:6379> watch k1 OK //開啓事務 redis 127.0.0.1:6379> multi OK // 修改k1爲29 redis 127.0.0.1:6379> set k1 29 QUEUED /----------- 別的線程---------------- redis 127.0.0.1:6379> get k1 "800" redis 127.0.0.1:6379> set k1 200 OK /----------- 別的線程---------------- //執行失敗 redis 127.0.0.1:6379> exec (nil) //修改值失敗 redis 127.0.0.1:6379> get k1 "200"
3.4 發佈訂閱
Redis消息隊列特點:1. 無法獲取歷史消息 2.消息是非搶佔式的(都可以收到)
3.4.1 訂閱消息
//訂閱多個消息 redis 127.0.0.1:6379> subscribe cctv-1 cctv-2 //訂閱多個消息 psubscribe cctv*
3.4.2 發佈消息
publish cctv-1 xinweblianbo
3.3 主從複製
作用:數據副本(高可用)、擴展讀性能(讀寫分離)
3.3.1 主從配置
3.3.1.1 命令
查看當前節點信息:
info replication
作爲從節點:【重啓無效】
slaveof 主機IP 主機端口號 slaveof no one // 恢復爲主節點,舊數據不會清除,跟隨新主後會清空數據
3.3.1.2 配置
slaveof ip port slave-read-only yes //從節點只讀
3.3.2 情景分析
情景一:先寫入數據,後進行主從備份,之前的數據會不會被同步?
主從備份前的數據也會被同步
情景二:從機是否可以寫數據?
從機只支持讀取數據,不支持寫數據
情景三:主機宕機後,從機是否會選舉爲主機?主機恢復後,從機是否可以正常工作?
主機宕機後,從機不會進行選舉;
主機恢復後,從機正常工作;
情景四:從機宕機重啓後是否可以正常工作?
命令式:重啓會從機不會識別主機,需要重新執行命令;
配置型:重啓後從機會識別主機
3.3.3 薪火相傳
①:中間的節點還是屬於從節點,不支持寫操作;
②:中間節點宕機,最後的從節點也無法正常工作;中間節點恢復後需要重新執行slaveof命令
3.3.4 反客爲主
原主機宕機 -->從機執行 slaveof no one -->選出新的主機【剩餘的從機不會更新主機(忠心不二);誰執行這條命令誰就會變爲主機(謀權篡位)】
原主機恢復---> 新主機不會禪讓(反客爲主) ---> 叢機會跟隨原主機(堅貞不渝)
3.3.5 全量複製
全量複製開銷:
1.bgsave時間
2.RDB文件網絡傳輸時間
3.從節點清空數據時間
4.從節點加載RDB文件時間
5.從節點AOF重寫時間(如果從節點開啓了AOF功能)
3.3.6 部分複製
3.3.7 複製運維
- 讀寫分離:主從數據複製延遲、讀到過期數據(3.2已經解決)
- 配置不一樣:例如:maxmemory配置不一致,數據結構優化參數不一致(hash-max-ziplist-entries)
- 規避全量複製:第一次全量複製(不可避免)、runId不一致、複製緩衝區不足(默認1M,可增大該值)
- 複製風暴:假如Master掛了很多Slave,Master重啓後,會進行大量全量複製、單機器故障,該機器上有大批主節點(主機分散、從機晉升)
3.3.8 優缺點
優點:1.數據備份 2、讀寫分離,減輕主機壓力
缺點:1.手動故障轉移 2.寫能力、存儲能力有限
3.4 哨兵模式
3.4.1 哨兵原理
新建配置文件:sentinel.conf
添加配置:
sentinel monitor mastser-name(區分不同的組) 主機IP 主機端口 投票數(達到該票時就會選舉爲主機) daemonized yesse port 端口號 logfile "${port}.log" ###########以下配置會重寫################## sentinel down-after-milliseconds 組名 30000(30s,超過該時間,就認爲該節點下線) sentinel parallel-syncs 組名 1(新主機成立後,一次可以有幾個從機複製主機) sentinel failover-timeout 組名 180000(故障轉移時間)
啓動哨兵: /Redis/src目錄下
redis-sentinel sentinel.conf【配置文件位置】
命令查看:
redis-cli -p 26379 //哨兵是一個特殊的 info //查看哨兵信息
3.4.2 客戶端
3.4.2.1 客戶端如何連接Master
step1: 客戶端需要提供哨兵集合和組名,遍歷哨兵集合,找到一個可用的哨兵
step2:通過組名獲取master地址
step3: 驗證節點的信息
step4:如果發生故障轉移,哨兵通過發佈訂閱模式通知客戶端
3.4.3 哨兵內部定時任務
3.4.4 主客觀下線
從節點:因爲不涉及故障轉移,固只需要主觀下線就行;
3.4.5 領導者選舉
3.4.6 故障轉移
1.面試題:
1.1 爲什麼要用Redis:
高性能:Redis讀取速率高於DB查找
高併發:緩解數據庫壓力
1.2 Redis存在的問題
1.3 Redis的文件事件處理器
1.4 Redis過期策略
1.4.1 惰性刪除 && 定期刪除
定期刪除:每隔100ms,就會隨機抽取一些設置了過期時間的key,j檢查其是否過期,如果過期,就刪除;
惰性刪除:在獲取key時,Redis會檢查這個key是否設置過期時間以及是否過期,如果已經過期則刪除;
其餘:走內存淘汰機制
1.4.2 內存淘汰機制
如果Redis內存佔用過多,就會進行內存淘汰,對應的策略:
策略一:內存不足時,拒絕寫入;(noeviction)
策略二:內存不足時,刪除最近最少使用的key[全量key];推薦使用(allkeys-lru)
策略三:內存不足時,隨機刪除;(allkeys-random)
策略四:內存不足時,刪除最近最少使用的key【設置了過期時間的key】;(volatile-lru)
策略五:內存不足時,隨機刪除;【設置了過期時間的key】 (volatile-random)
策略六:內存不足時,優先刪除過期時間早的key; (volatile-ttl)
1.4.3 LRU算法
public class MyLRU<K,V> extends LinkedHashMap<K,V>{ /** * 緩存的容量 */ private final int CACHE_SIZE; /** * LinkedHashMap的一個構造函數,當參數accessOrder爲true時,即會按照訪問順序排序,最近訪問的放在最前,最早訪問的放在後面 * public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { * super(initialCapacity, loadFactor); * this.accessOrder = accessOrder; * } * @param cacheSize */ public MyLRU(int cacheSize) { super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); CACHE_SIZE = cacheSize; } /** * 過期策略: * LinkedHashMap自帶的判斷是否刪除最老的元素方法,默認返回false,即不刪除老數據 * 我們要做的就是重寫這個方法,當滿足一定條件時刪除老數據 * @param eldest * @return */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > CACHE_SIZE; } }
1.5 緩存雪崩
1.5.1 緩存雪崩
緩存崩潰 -->數據庫崩潰 -->系統不可用
1.5.2 解決策略
事前:保證Redis的高可用 : Redis集羣
事中:本地ehcache+限流組件
事後:根據Redis持久化文件,恢復Redis集羣
1.6 緩存穿透
穿透:Redis->DB ->穿透
根本原因:數據庫中不存在的數據不會回寫到Redis中,進而每次都請求數據庫
解決方案:查詢不到數據時,就寫空值到緩存;
1.7 緩存和數據庫雙寫的一致性問題
1.7.1 雙寫的策略
讀數據:讀取數據時,先從Redis獲取,獲取不到時,再查數據庫,並將結果回寫數據庫。
更新數據:更新數據時,先刪除緩存,再更新數據庫。
更新數據時刪除緩存原因:懶加載機制,頻繁更新不等於頻繁查詢,只有查詢的時候才放入緩存;
1.7.2
3