1. 程式人生 > 實用技巧 >非關係資料庫之redis入門到實戰(4)Redis基本操作

非關係資料庫之redis入門到實戰(4)Redis基本操作

第一章、redis入門

1,redis是什麼

redis是一種支援Key-Value等多種資料結構的儲存系統。可用於快取,事件釋出或訂閱,高速佇列等場景。該資料庫使用ANSI C語言編寫,支援網路,提供字串,雜湊,列表,佇列,集合結構直接存取,基於記憶體,可持久化。

2,支援的語言

3,redis的應用場景有哪些

1,會話快取(最常用)2,訊息佇列,比如支付3,活動排行榜或計數4,釋出,訂閱訊息(訊息通知)5,商品列表,評論列表等

4,redis資料型別

*Redis*一共支援五種資料類:string(字串),hash(雜湊),list(列表),set(集合)和zset(sorted set有序集合)。

(1)字串(字串)

它是redis的最基本的資料型別,一個鍵對應一個值,需要注意是一個鍵值最大儲存512MB。

(2)hash(雜湊)

redis hash是一個鍵值對的集合,是一個string型別的field和value的對映表,適合用於儲存物件

(3)表(列表)

是redis的簡單的字串列表,它按插入順序排序

(4)組(集合)

是字串型別的無序集合,也不可重複

(5)zset(sorted set有序集合)

是string型別的有序集合,也不可重複有序集合中的每個元素都需要指定一個分數,根據分數對元素進行升序排序,如果多個元素有相同的分數,則以字典序進行升序排序,sorted set因此非常適合實現排名

5,redis的服務相關的命令

slect#選擇資料庫(資料庫編號0-15)退出#退出連線資訊#獲得服務的資訊與統計monitor#實時監控config get#獲得服務配置flushdb#刪除當前選擇的資料庫中的keyflushall#刪除所有資料庫中的鍵

6,redis的釋出與訂閱

redis的釋出與訂閱(釋出/訂閱)是它的一種訊息通訊模式,一方傳送資訊,一方接收資訊。下圖是三個客戶端同時訂閱同一個頻道

下圖是有新資訊傳送給頻道1時,就會將訊息傳送給訂閱它的三個客戶端

7,redis的持久化

redis持久有兩種方式:快照(快照),僅附加檔案(AOF)

快照(快照)

1,將儲存在記憶體的資料以快照的方式寫入二進位制檔案中,如預設dump.rdb中2,儲存900 1

#900秒內如果超過1個Key被修改,則啟動快照儲存3,儲存300 10

#300秒內如果超過10個Key被修改,則啟動快照儲存4,儲存60 10000

#60秒內如果超過10000個重點被修改,則啟動快照儲存

僅附加檔案(AOF)

1,使用AOF持久時,服務會將每個收到的寫命令通過寫函式追加到檔案中(appendonly.aof)2,AOF持久化儲存方式引數說明appendonly yes

#開啟AOF持久化儲存方式appendfsync always

#收到寫命令後就立即寫入磁碟,效率最差,效果最好appendfsync everysec

#每秒寫入磁碟一次,效率與效果居中appendfsync no

#完全依賴作業系統,效率最佳,效果沒法保證

8,redis的效能測試

自帶相關測試工具

實際測試同時執行100萬的請求

第二章、redis實戰

1、redis簡介

Redis 是一個開源的 使用 ANSI C 語言編寫、支援網路、可基於記憶體亦可持久化的日誌 型、Key-Value 資料庫。

2、儲存操作

redis的key-value需要特別說明下,key只能為字串型別,但value可以為字串,hash表,列表(list),集合(set),有序集合(sorted set)等多種資料型別,這也是redis與memcached的區別。memcached也是key-value資料庫,但是key,value都只能是字串型別。

1、redis的key

Redis 的 key 是字串型別,但是 key 中不能包括邊界字元 ,由於 key 不是 binary safe 的字串,所以像"my key"和"mykey\n"這樣包含空格和換行的 key 是不允許的。

2、key的相關操作
  • exitskey檢測指定key是否存在,返回1表示存在,0不存在

  • delkey1key2…keyN刪除給定key,返回刪除key的數目,0表示給定key都不存在

  • typekey 返回給定 key 值的型別。返回 none 表示 key 不存在,string 字元型別,list 連結串列 型別 set 無序集合型別…

  • keyspattern 返回匹配指定模式的所有 key。如:keys * 返回所有key

  • randomkey返回從當前資料庫中隨機選擇的一個 key,如果當前資料庫是空的,返回空串 rename oldkey

  • newkey重新命名一個 key,如果 newkey 存在,將會被覆蓋,返回 1 表示成功, 0 失敗。可能是 oldkey 不存在或者和 newkey 相同。

  • renamenxoldkey newkey 同上,但是如果 newkey 存在返回失敗。expire key seconds 為 key 指定過期時間,單位是秒。返回 1 成功,0 表示 key 已經設定過過 期時間或者不存在。

  • ttlkey 返回設定過過期時間key的剩餘過期秒數。-1表示key不存在或者未設定過期時間。 select db-index 通過索引選擇資料庫,預設連線的資料庫是 0,預設資料庫數是 16 個。返回 1 表示成功,0 失敗。

  • movekeydb-index將key從當前資料庫移動到指定資料庫。返回 1表示成功。0表示key 不存在或者已經在指定資料庫中 。key的大小最大為512MB。

3、字串

string 是最基本的型別,而且 string 型別是二進位制安全的。意思是 redis 的 string 可以包含任何資料。比如 jpg 圖片或者序列化的物件。從內部實現來看其實 string 可以看作 byte 3陣列,最大上限是 1G 位元組。

4、String操作命令
  • setkey value 設定 key 對應 string 型別的值,返回 1 表示成功,0 失敗。

  • setnxkey value 如果 key 不存在,設定 key 對應 string 型別的值。如果 key 已經存在,返 回0。

  • getkey 獲取 key 對應的 string 值,如果 key 不存在返回 nil

  • getsetkey value 先獲取 key 的值,再設定 key 的值。如果 key 不存在返回 nil。

  • mgetkey1 key2 … keyN 一次獲取多個 key 的值,如果對應 key 不存在,則對應返回 nil。

  • msetkey1 value1 … keyN valueN 一次設定多個 key 的值,成功返回 1 表示所有的值都設定 了,失敗返回 0 表示沒有任何值被設定。

  • msetnxkey1 value1 … keyN valueN 一次設定多個 key 的值,但是不會覆蓋已經存在的 key

  • incrkey 對 key 的值做++操作,並返回新的值。注意 incr 一個不是 int 的 value 會返回錯誤,incr一個不存在的 key,則設定 key 值為 1。

  • decrkey 對 key 的值做–操作,decr 一個不存在 key,則設定 key 值為-1。

  • incrbykey integer 對 key 加上指定值 ,key 不存在時候會設定 key,並認為原來的 value 是0。

  • decrbykey integer 對key減去指定值。decrby完全是為了可讀性,我們完全可以通過incrby 一個負值來實現同樣效果,反之一樣。set說明

    - //設定值和獲取值
    127.0.0.1:7000> set b 1234
    OK
    127.0.0.1:7000> get b
    "1234"

    //再次設定值,之前的值會被修改
    127.0.0.1:7000> set b 1dfff
    OK
    127.0.0.1:7000> get b
    "1dfff"

    //nx如果b已經存在,則直接返回失敗,不存在則設定成功。
    127.0.0.1:7000> set b fsdf nx
    (nil)
    127.0.0.1:7000> get b
    "1dfff"

    //xx如果b存在,則設定成功,如果b不存在則設定失敗,這相當於修改一個值。
    127.0.0.1:7000> set b ffff xx
    OK
    127.0.0.1:7000> get b
    "ffff"
    127.0.0.1:7000>

    //設定過期時間,多次設定與最後一次為準。相當於修改
    127.0.0.1:7000> set f ffff ex 10
    OK
    //獲取過期還剩多少
    127.0.0.1:7000> ttl f
    (integer) 6
    127.0.0.1:7000> ttl f
    (integer) 3
    127.0.0.1:7000>

    原子操作incr如果遇到需要i++這型別的操作我們程式碼裡面一般會如下:

虛擬碼如下:

int a= get key
a= a + 1
set key a;
這樣是不安全的,因為多個執行緒同事操作時,會導致結果不一致。

上面分析了,i++ 操作,那麼redis為我們提供了原子性的i++操作。

127.0.0.1:7000> set a 1
OK
127.0.0.1:7000> INCR a
(integer) 2
127.0.0.1:7000> incr a
(integer) 3
127.0.0.1:7000> get a
"3"

設定一個a=1,現在需要將a+1,那麼呼叫incr a會自動為我們做++操作,decr a會自動做減減操作。incrby a 2 這個表示a直接加2操作,而incr為每次自動+1,incrby為指定加多少。

bitmapRedis從2.2.0版本開始新增了setbit,getbit,bitcount等幾個bitmap相關命令。雖然是新命令,但是並沒有新增新的資料型別,因為setbit等命令只不過是在set上的擴充套件,所以bigmap還是一種字串型別

在一臺2010MacBook Pro上,offset為232-1(分配512MB)需要~300ms,offset為230-1(分配128MB)需要~80ms,offset為228-1(分配32MB)需要~30ms,offset為226-1(分配8MB)需要8ms

指令 SETBIT key offset value複雜度 O(1)設定或者清空key的value(字串)在offset處的bit值(只能只0或者1)。

offset的最大值為10億,2的23-1方

5、hash(雜湊)

hash是一個string型別的field和value的對映表。新增,刪除操作都是O(1)(平均)。hash特別適合用於儲存物件。相對於將物件的每個欄位存成單個string型別。將一個物件儲存在hash型別中會佔用更少的記憶體,並且可以更方便的存取整個物件。省記憶體的原因是新建一個hash物件時開始是用zipmap(又稱為small hash)來儲存的。這個zipmap其實並不是hash table,但是zipmap相比正常的hash實現可以節省不少hash本身需要的一些元資料儲存開銷。儘管zipmap的新增,刪除,查詢都是O(n),但是由於一般物件的field數量都不太多,所以使用zipmap也是很快的,也就是說新增刪除平均還是O(1)。如果field或者value的大小超出一定限制後,redis會在內部自動將zipmap替換成正常的hash實現.這個限制可以在配置檔案中指定。hash-max-zipmap-entries64#配置欄位最多64個hash-max-zipmap-value512#配置value最大為512位元組

6、hash 型別資料操作指令簡介
  • hsetkey field value 設定 hash field 為指定值,如果 key 不存在,則建立

  • hgetkey field 獲取指定的hash field。

  • hmget key filed1…fieldN 獲取全部指定的 hash filed。

  • hmsetkey filed1 value1 … filedN valueN 同時設定 hash 的多個 field。

  • hincrbykey field integer 將指定的 hash filed 加上指定值。成功返回 hash filed 變更後的 值。

  • hexistskey field 檢測指定 field 是否存在。

  • hdelkey field 刪除指定的 hash field。

  • hlenkey 返回指定 hash 的 field 數量。

  • hkeyskey 返回 hash 的所有 field。 hvals key 返回 hash 的所有 value。 hgetall 返回hash的所有filed和value

//使用hash來儲存資料。user是key,由於value是hash,所以還有一個key,value健值對。
127.0.0.1:7000> hset user id 1
(integer) 1
127.0.0.1:7000> hset user userName xiaobao
(integer) 1
127.0.0.1:7000> hset user password ssssss
(integer) 1
//獲取key為user的hash表中的id的值。
127.0.0.1:7000> hget user id
"1"
//獲取hash表中的所有key
127.0.0.1:7000> hkeys user
1) "id"
2) "userName"
3) "password"
//獲取hash表中所有的value
127.0.0.1:7000> HVALS user
1) "1"
2) "xiaobao"
3) "ssssss"
127.0.0.1:7000>
7、list(列表)

list是一個連結串列結構,可以理解為每個子元素都是string型別的雙向連結串列。主要功能是push、pop獲取一個範圍的所有值等。操作中 key理解為連結串列的名字。

8、List 型別資料操作指令簡介

lpushkey string 在key對應list的頭部新增字串元素,返回 1表示成功,0表示key存 在且不是 list 型別。rpushkey string 在 key 對應 list 的尾部新增字串元素。llenkey 返回 key 對應 list 的長度,如果 key 不存在返回 0,如果 key 對應型別不是 list 返回錯誤。lrangekey start end 返回指定區間內的元素,下標從 0 開始,負值表示從後面計算,-1 表示 倒數第一個元素 ,key 不存在返回空列表。ltrimkeystartend擷取list指定區間內元素,成功返回1,key不存在返回錯誤。lset key index value設定 list 中指定下標的元素值,成功返回 1,key 或者下標不存在返回 錯誤。lremkeycountvalue從 List 的頭部(count正數)或尾部(count負數)刪除一定數量(count) 匹配 value 的元素,返回刪除的元素數量。count為0時候刪除全部。lpop key從list的頭部刪除並返回刪除元素。如果key對應list不存在或者是空返回nil, 如果 key 對應值不是 list 返回錯誤。rpop key從list的尾部刪除並返回刪除元素。blpopkey1 … keyN timeout 從左到右掃描,返回對第一個非空 list 進行 lpop 操作並返回, 比如 blpop list1 list2 list3 0 ,如果 list 不存在 list2,list3 都是非空則對 list2 做 lpop 並返回從 list2 中刪除的元素。如果所有的 list 都是空或不存在,則會阻塞 timeout 秒,timeout為0表示一直阻塞。當阻塞時,如果有 client對key1…keyN中的任意key 進行 push 操作,則第一在這個 key 上被阻塞的 client 會立即返回。如果超時發生,則返回 nil。有點像 unix 的 select 或者 poll。brpop同blpop,一個是從頭部刪除一個是從尾部刪除。

//從key為list的左邊push一個1.
127.0.0.1:7000> lpush list 1
(integer) 1
127.0.0.1:7000> lpush list 2
(integer) 2
//檢視list佇列長度
127.0.0.1:7000> llen list
(integer) 2

//從右邊連續push 3 4 5 6
127.0.0.1:7000> rpush list 3 4 5 6
(integer) 6
//返回列表從0到6個元素
127.0.0.1:7000> LRANGE list 0 6
1) "2"
2) "1"
3) "3"
4) "4"
5) "5"
6) "6"
//從佇列頭彈出值並刪除
127.0.0.1:7000> lpop list
"2"
127.0.0.1:7000> lpop list
"1"
127.0.0.1:7000> lpop list
"3"
127.0.0.1:7000> lpop list
"4"
整個集合就是一個入棧出棧的操作,記住集合它是一個雙向連結串列就OK了。
9、set(集合)

set是無序集合,最大可以包含(2 的 32 次方-1)個元素,set 是不能有重複元素,如果存入重複元素不會報錯,但是set裡面還是隻有一個。set 的是通過 hash table 實現的, 所以新增,刪除,查詢的複雜度都是 O(1)。hash table 會隨著新增或者刪除自動的調整大小,需要注意的是調整hash table大小時候需要同步(獲取寫鎖)會阻塞其他讀寫操作,可能不久後就會改用跳錶(skip list)來實現。跳錶已經在sorted sets 中使用了。關於set集合型別除了基本的新增刪除操作,其它有用的操作還包含集合的取並集 (union),交集(intersection), 差集(difference)。通過這些操作可以很容易的實現 SNS 中的好友推薦和 blog 的 tag 功能。

10、set 型別資料操作指令簡介

saddkey member 新增一個 string 元素到 key 對應 set 集合中,成功返回1,如果元素以及 在集合中則返回 0,key 對應的 set 不存在則返回錯誤。sremkey member 從 key 對應 set 中移除指定元素,成功返回 1,如果 member 在集合中不 存在或者 key 不存在返回 0,如果 key 對應的不是 set 型別的值返回錯誤。spopkey count 刪除並返回key對應set中隨機的一個元素,如果set是空或者key不存在返回nil。srandmemberkey count 同spop,隨機取set中的一個元素,但是不刪除元素。smemberskey 返回key對應set的所有元素,結果是無序的。smovesrckey dstkey member 從srckey對應set中移除member並新增到dstkey對應set中,整個操作是原子的。成功返回 1,如果 member 在 srckey 中不存在返回 0,如果 key 不是 set 型別返回錯誤。scardkey返回set的元素個數,如果set是空或者key不存在返回 0。sismemberkey member 判斷member是否在set中,存在返回1,0表示不存在或者key不存在。sinterkey1 key2 … keyN 返回所有給定 key 的交集。sinterstoredstkey key1 … keyN 返回所有給定 key 的交集,並儲存交集存到 dstkey 下。sunionkey1 key2 … keyN 返回所有給定 key 的並集。sunionstoredstkey key1 … keyN 返回所有給定 key 的並集,並儲存並集到 dstkey 下。sdiffkey1 key2 … keyN 返回所有給定 key 的差集。sdiffstoredstkey key1 … keyN 返回所有給定 key 的差集,並儲存差集到 dstkey 下。

//存入元素
127.0.0.1:7000> sadd set 1
(integer) 1
127.0.0.1:7000> sadd set 2
(integer) 1
//1已經存在了,再存入1則返回0了。
127.0.0.1:7000> sadd set 1
(integer) 0
//彈出key為set的總的2個元素,彈出後刪除元素 
127.0.0.1:7000> spop set 2
1) "1"
2) "2"
//沒有元素的情況下返回空
127.0.0.1:7000> spop set 1
(empty list or set)

127.0.0.1:7000> sadd set 1 2 3 4 5 6 6 6
(integer) 6
//隨機彈出10個元素,不刪除
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> srandmember set 1
1) "6"

//返回set的所有元素,無序的
127.0.0.1:7000> smembers set3
1) "2"
2) "3"
3) "5"
127.0.0.1:7000> srandmember set 10
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:7000> sadd set1 2 3 5 10 20
(integer) 5
//求set,set2兩個key的並集
127.0.0.1:7000> sunion set set1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "10"
8) "20"

//求set set1的交集
127.0.0.1:7000> sinter set set1
1) "2"
2) "3"
3) "5"

//求兩個交集,返回的結果儲存到set3裡面
127.0.0.1:7000> sinterstore set3 set set1
(integer) 3
127.0.0.1:7000> SRANDMEMBER set3 10
1) "2"
2) "3"
3) "5"
11、有序集合

sorted set 是有序集合,它在 set 的基礎上增加了一個順序屬性,這一屬性在新增修 改元素的時候可以指定 ,每次指定後,會自動重新按新的值調整順序 。可以理解了有兩列 的 mysql 表,一列存 value,一列存順序。操作中key理解為sorted set的名字。

12、Sorted Set 型別資料操作指令簡介

zaddkey score member 新增元素到集合,元素在集合中存在則更新對應 score。zremkey member 刪除指定元素,1 表示成功,如果元素不存在返回 0。zincrbykey incr member 增加對應 member 的 score 值,然後移動元素並保持 skip list 保持有 序。返回更新後的 score 值。zrankkey member 返回指定元素在集合中的排名(下標 ),集合中元素是按 score 從小到大 排序的。zrevrankkey member 同上,但是集合中元素是按 score 從大到小排序。zrangekey start end 類似 lrange 操作從集合中去指定區間的元素。返回的是有序結果zrevrangekey start end 同上,返回結果是按 score 逆序的。zrangebyscorekey min max 返回集合中 score 在給定區間的元素。zcountkey min max 返回集合中 score 在給定區間的數量。zcardkey 返回集合中元素個數。zscorekey element 返回給定元素對應的 score。zremrangebyrankkey min max 刪除集合中排名在給定區間的元素 。zremrangebyscorekey min max 刪除集合中 score 在給定區間的元素。

//儲存一個有序set,sortset為key,1表示分數,a是我們的值
127.0.0.1:7000> zadd sortset 1 a
(integer) 1
127.0.0.1:7000> zadd sortset 2 b
(integer) 1
//檢視b的下標
127.0.0.1:7000> zrank sortset b
(integer) 1

//返回0,1個元素
127.0.0.1:7000> zrange sortset 0 1
1) "a"
2) "b"
3、redis運維
1、統計生產上比較大的key
./redis-cli -p 7000 --bigkeys
2、檢視key的詳細資訊

//檢視key為d的詳細資訊,檢視key的詳細資訊(從這裡可以看出key佔用的儲存空間挺大的,需要儲存很多資訊)

127.0.0.1:7000> DEBUG OBJECT d
Value at:0x7fb7cf526240 refcount:1 encoding:embstr serializedlength:5 lru:6748523 lru_seconds_idle:537
3、分頁檢視redis中的key
//檢視key
127.0.0.1:7000> SCAN 0
1) "17"
2) 1) "d"
   2) "kye"

127.0.0.1:7000> SCAN 17
1) "0"
2) 1) "dfff"
   2) "ff" 
   3) "ddd"  

第一次迭代使用 0 作為遊標, 表示開始一次新的迭代。第二次迭代使用的是第一次迭代時返回的遊標, 也即是命令回覆第一個元素的值17.從上面的示例可以看到, SCAN 命令的回覆是一個包含兩個元素的陣列, 第一個陣列元素是用於進行下一次迭代的新遊標, 而第二個陣列元素則是一個數組, 這個陣列中包含了所有被迭代的元素。 在第二次呼叫 SCAN 命令時, 命令返回了遊標 0 , 這表示迭代已經結束, 整個資料集(collection)已經被完整遍歷過了。

以 0 作為遊標開始一次新的迭代, 一直呼叫 SCAN 命令, 直到命令返回遊標 0 , 我們稱這個過程為一次完整遍歷(full iteration)。

4、模糊查詢key
127.0.0.1:7000> scan 0 match t*
1) "0"
2) 1) "t"
   2) "ttt"

127.0.0.1:7000> scan 0 match t* count 2
1) "3"
2) 1) "t"   

scan 0 match t* 與keys t* 還是有差別的,因為scan 是帶分頁的,而keys 是沒有分頁的,遍歷了所有的key。

在線上不建議使用keys來查詢key。

5、效能查詢
redis-benchmark -n 10000
6、密碼登陸

連結後沒有許可權操作:(error) NOAUTH Authentication required

redis-cli -h 127.0.0.1 -p 70000 -a password
如果忘記輸入密碼,可以在進入的時候使用
auth password
4、redis快取淘汰策略

redis淘汰策略配置,在redis.conf檔案檢視:maxmemory-policy voltile-lru 支援熱配置

1、redis 提供 6種資料淘汰策略:
  • voltile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰

  • volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰

  • volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰

  • allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰

  • allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰

  • no-enviction(驅逐):禁止驅逐資料(預設策略)選擇策略規則

如果資料呈現冪律分佈,也就是一部分資料訪問頻率高,一部分資料訪問頻率低,則使用allkeys-lru如果資料呈現平等分佈,也就是所有的資料訪問頻率都相同,則使用allkeys-randomvolatile-lru策略和volatile-random策略適合我們將一個Redis例項既應用於快取和又應用於持久化儲存的時候,然而我們也可以通過使用兩個Redis例項來達到相同的效果,將key設定過期時間實際上會消耗更多的記憶體,因此我們建議使用allkeys-lru策略從而更有效率的使用記憶體

5、主從,哨兵模式
1、主從設定

進入到將要設定的從的客戶端或者在配置檔案裡面下入

$ redis-cli -p 7000
127.0.0.1:7000> slaveof 192.168.110.101 6379

即可將當前7000這個redis服務設定為一個從服務。

2、取消從服務
127.0.0.1:7000> SLAVEOF no one

redis設定了主從伺服器後,從會自動備份主的資料,但是從伺服器對外提供只能讀,不能寫。

3、sentinel

哨兵是分散式應用,可以啟動多個,指定不同埠即可。哨兵與哨兵之間不需要互相配置,哨兵會根據自己監控的主服務做判斷,如果是監控的是同一個主服務,則他們會互相自動建立連線,互相通訊。

哨兵配置監控時只需要配置master地址即可,但是不代表它不監控從節點。sentinel連線上主節點後,會自動從主節點獲取到該節點的從節點資訊然後進行監控。

對於redis-sentinel 程式, 你可以用以下命令來啟動 Sentinel 系統:

redis-sentinel /path/to/sentinel.conf

對於 redis-server 程式, 你可以用以下命令來啟動一個執行在 Sentinel 模式下的 Redis 伺服器:

redis-server /path/to/sentinel.conf --sentinel

sentinel.conf配置如下:

sentinel monitor mymaster 127.0.0.1 7001 2
sentinel config-epoch mymaster 1
port 17000

建立了一個監控monitor, 自定義一個名稱為mymaster,redis主伺服器的IP地址,埠號,需要投票的數量為2.設定哨兵的埠為17000sentinel操作流程:

如果主伺服器掛了,哨兵在指定的時間內檢測到,會在從伺服器中選一個出來做為主節點,然後把其他所有節點全部設定為從節點。然後在把之前掛掉的主給從sentinel中剔除去。注意:

第一行最後面的那個數字為2,需要至少啟動2個sentinel,才能完成投票,如果只啟動一個sentinel,但是配置為2,當主redis掛掉的時候,sentinel會發起投票,因為只有一個sentinel,也就只有一票,這個時候sentinel就不能做主從切換了。對於客戶端來說,是不能直接連線sentinel只能連線主或者從。Java客戶端連線sentinel對redis操作。

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Autowired
    private RedisProperties redisProperties;
    
    @Bean
    public RedisConnectionFactory jedisConnectionFactory() {
        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
        sentinelConfig.master(redisProperties.getSentinel().getMaster());
        sentinelConfig.setSentinels(createSentinels(redisProperties.getSentinel()));
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig);
        jedisConnectionFactory.setPassword(redisProperties.getPassword());
        return jedisConnectionFactory;
    }
    
    @Bean
    @DependsOn("jedisConnectionFactory")
    public StringRedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        return new StringRedisTemplate(jedisConnectionFactory);
    }
    
    private List<RedisNode> createSentinels(RedisProperties.Sentinel sentinel) {
        List<RedisNode> nodes = new ArrayList();
        for (String node : StringUtils
                .commaDelimitedListToStringArray(sentinel.getNodes())) {
            try {
                String[] parts = StringUtils.split(node, ":");
                Assert.state(parts.length == 2, "Must be defined as 'host:port'");
                nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
            } catch (RuntimeException ex) {
                throw new IllegalStateException(
                        "Invalid redis sentinel " + "property '" + node + "'", ex);
            }
        }
        return nodes;
    }

}
可以看出來,操作sentinel時,不是直接操作sentinel的,而是通過sentinel找到主服務的IP地址和埠,然後再根據IP地址和埠去連線reids。
6、持久化

通常 Redis 將資料儲存在記憶體中或虛擬記憶體中,它是通過以下兩種方式實現對資料的持久化。持久化即使儲存到硬碟上,如果儲存到記憶體中,電腦重啟後記憶體就會被清空。

1、RDB(快照模式)

這種方式就是將記憶體中資料以快照的方式寫入到二進位制檔案中,預設的檔名為dump.rdb。客戶端也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主執行緒中儲存快照的,由於redis是用一個主執行緒來處理所有客戶端的請求,這種方式會阻塞所有客戶端請求,所以不推薦使用,bgsave是fork一個子程序來做的,就是後臺執行,不阻塞執行。另一點需要注意的是,每次快照持久化都是將記憶體資料完整寫入到磁碟一次,並不是增量的只同步增量資料。如果資料量大的話,寫操作會比較多,必然會引起大量的磁碟IO操作,可能會嚴重影響效能。

redis會自動備份,而不需要我們自己手動的去呼叫save來儲存備份。通過配置檔案可以看出:vi /usr/local/etc/redis.conf

save 900 1 //900秒裡面發生1次操作則執行一次save。
save 300 10 //300秒了發生了10次操作則執行一次save
save 60 10000 //60秒裡面發生了1萬次操作則執行一次save。

注意:由於快照方式是在一定間隔時間做一次的,所以如果 redis意外當機的話,就會丟失最後一次快照後的所有資料修改。

2、AOF(日誌追加)

這種方式redis會將每一個收到的寫命令都通過write函式追加到檔案中(預設appendonly.aof)。當redis重啟時會通過重新執行檔案中儲存的寫命令來在記憶體中重建整個資料庫的內容。當然由於作業系統會在核心中快取write做的修改,所以可能不是立即寫到磁碟上。這樣的持久化還是有可能會丟失部分修改。不過我們可以通過配置檔案告訴redis我們想要通過fsync函式強制作業系統寫入到磁碟的時機。有三種方式如下(預設是:每秒fsync一次)

appendonly yes //啟用日誌追加持久化方式
//appendfsync always //每次收到寫命令就立即強制寫入磁碟,最慢的,但是保證完全的持久化,不推薦使用
appendfsync everysec //每秒鐘強制寫入磁碟一次,在效能和持久化方面做了很好的折中,推薦
//appendfsync no //完全依賴作業系統,效能最好,持久化沒保證
3、AOF重寫

AOF會自動優化檔案。比如:

set key aaaset key bbbset key ddd上面三個操作最終庫裡面資料為:key=ddd,那就簡單了額,AOF會只需要儲存set key ddd命令就OK了,其他兩條命令可以忽略了,這樣就可以達到優化儲存的效果。

AOF 重寫和 RDB 建立快照一樣,都巧妙地利用了寫時複製機制。以下是 AOF 重寫的執行步驟:

Redis 執行 fork() ,現在同時擁有父程序和子程序。子程序開始將新 AOF 檔案的內容寫入到臨時檔案。對於所有新執行的寫入命令,父程序一邊將它們累積到一個記憶體快取中,一邊將這些改動追加到現有 AOF 檔案的末尾: 這樣即使在重寫的中途發生停機,現有的 AOF 檔案也還是安全的。當子程序完成重寫工作時,它給父程序傳送一個訊號,父程序在接收到訊號之後,將記憶體快取中的所有資料追加到新 AOF 檔案的末尾。現在 Redis 原子地用新檔案替換舊檔案,之後所有命令都會直接追加到新 AOF 檔案的末尾。

4、恢復模式

資料恢復是記憶體資料被清空了,需要從硬碟中的備份檔案重新載入到記憶體中。在電腦重啟後,如果兩種持久化模式都開啟了,則會優先從AOF中恢復資料,然後才是RDB模式。

5、兩種方式的優缺點

RDB優點:

  1. RDB 可以最大化 Redis 的效能:父程序在儲存 RDB 檔案時唯一要做的就是 fork 出一個子程序,然後這個子程序就會處理接下來的所有儲存工作,父程序無須執行任何磁碟 I/O 操作。

  2. RDB 在恢復大資料集時的速度比 AOF 的恢復速度要快。

  3. RDB 非常適用於災難恢復(disaster recovery):它只有一個檔案,並且內容都非常緊湊,可以(在加密後)將它傳送到別的資料中心RDB缺點:

  4. 如果你需要儘量避免在伺服器故障時丟失資料,那麼 RDB 不適合你。 雖然 Redis 允許你設定不同的儲存點(save point)來控制儲存 RDB 檔案的頻率, 但是, 因為RDB 檔案需要儲存整個資料集的狀態, 所以它並不是一個輕鬆的操作。 因此你可能會至少 5 分鐘才儲存一次 RDB 檔案。 在這種情況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的資料。

  5. 每次儲存 RDB 的時候,Redis 都要 fork() 出一個子程序,並由子程序來進行實際的持久化工作。 在資料集比較龐大時, fork() 可能會非常耗時,造成伺服器在某某毫秒內停止處理客戶端; 如果資料集非常巨大,並且 CPU 時間非常緊張的話,那麼這種停止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,資料的耐久性都不會有任何損失。AOF優點

  6. 使用 AOF 持久化會讓 Redis 變得非常耐久(much more durable):你可以設定不同的 fsync 策略,比如無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的預設策略為每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的效能,並且就算髮生故障停機,也最多隻會丟失一秒鐘的資料( fsync 會在後臺執行緒執行,所以主執行緒可以繼續努力地處理命令請求)。

  7. AOF 檔案是一個只進行追加操作的日誌檔案(append only log), 因此對 AOF 檔案的寫入不需要進行 seek , 即使日誌因為某些原因而包含了未寫入完整的命令(比如寫入時磁碟已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。

  8. Redis 可以在 AOF 檔案體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 檔案包含了恢復當前資料集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在建立新 AOF 檔案的過程中,會繼續將命令追加到現有的 AOF 檔案裡面,即使重寫過程中發生停機,現有的 AOF 檔案也不會丟失。 而一旦新 AOF 檔案建立完畢,Redis 就會從舊 AOF 檔案切換到新 AOF 檔案,並開始對新 AOF 檔案進行追加操作

  9. AOF 檔案有序地儲存了對資料庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式儲存, 因此 AOF 檔案的內容非常容易被人讀懂, 對檔案進行分析(parse)也很輕鬆。 匯出(export) AOF 檔案也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 檔案未被重寫, 那麼只要停止伺服器, 移除 AOF 檔案末尾的 FLUSHALL 命令, 並重啟 Redis , 就可以將資料集恢復到 FLUSHALL 執行之前的狀態。AOF缺點

  10. 對於相同的資料集來說,AOF 檔案的體積通常要大於 RDB 檔案的體積。

  11. 根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在一般情況下, 每秒 fsync 的效能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。

  12. AOF 在過去曾經發生過這樣的 bug : 因為個別命令的原因,導致 AOF 檔案在重新載入時,無法將資料集恢復成儲存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引起過這樣的 bug 。) 測試套件裡為這種情況添加了測試: 它們會自動生成隨機的、複雜的資料集, 並通過重新載入這些資料來確保一切正常。 雖然這種 bug 在 AOF 檔案中並不常見, 但是對比來說, RDB 幾乎是不可能出現這種 bug 的。

7、事務

Redis 事務可以一次執行多個命令, 並且帶有以下兩個重要的保證:

  • 批量操作在傳送 EXEC 命令前被放入佇列快取。

  • 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。

  • 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。一個事務從開始到執行會經歷以下三個階段:

  • 開始事務。

  • 命令入隊。

  • 執行事務。

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED

redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

DISCARD :取消事務,放棄執行事務塊內的所有命令。

8、redis keyspace鍵通知

keyspace有啥用? 請看下面回答

  1. 設定了生存時間的Key,在過期時能不能給一個通知?

  2. 如何使用 Redis 來實現定時任務?

  3. key監聽事件,key的刪除,新增.之類等等.

    在 Redis 裡面有一些事件,比如鍵到期、鍵被刪除等。然後我們可以通過配置一些東西來讓 Redis 一旦觸發這些事件的時候就往特定的 Channel 推一條訊息。大致的流程就是我們給 Redis 的某一個 db 設定過期事件,使其鍵一旦過期就會往特定頻道推訊息,我在自己的客戶端這邊就一直消費這個頻道就好了。以後一來一條定時任務,我們就把這個任務狀態壓縮成一個鍵,並且過期時間為距這個任務執行的時間差。那麼當鍵一旦到期,就到了任務該執行的時間,Redis 自然會把過期訊息推去,我們的客戶端就能接收到了。這樣一來就起到了定時任務的作用。

第一步:需要開啟事件通知

開啟所有的事件
redis-cli> config set notify-keyspace-events KEA

開啟keyspace Events
redis-cli> config set notify-keyspace-events KA

開啟keyspace 所有List 操作的 Events
redis-cli> config set notify-keyspace-events Kl

redis-cli> config set notify-keyspace-events Ex  // 其中Ex表示鍵事件通知裡面的key過期事件,每當有過期鍵被刪除時,會發送通知

或者在redis.conf配置檔案中,找到notify-keyspace-events “”表示什麼都不做,後面加上值即為開啟.修改後需要重啟redis服務

注意:預設情況下config在生產環境是不開啟的,所以在生產上使用config會提示不存在的問題. config在redis.conf配置檔案裡面,去掉config前面的註解就可以使用了.

第二步:訂閱通知事件

127.0.0.1:7000> psubscribe __keyevent@0__:expired
1

0表示第0個數據庫,expired表示訂閱過期事件.keyevent@0:expired 整個值為過期事件的topic.訂閱到的資料只有key,沒有value哦.

9、叢集

Redis 叢集是一個提供在多個Redis間節點間共享資料的程式集。Redis叢集並不支援處理多個keys的命令,因為這需要在不同的節點間移動資料,從而達不到像Redis那樣的效能,在高負載的情況下可能會導致不可預料的錯誤.

Redis 叢集通過分割槽來提供一定程度的可用性,在實際環境中當某個節點宕機或者不可達的情況下繼續處理命令.Redis 叢集的優勢:

  • 自動分割資料到不同的節點上。

  • 整個叢集的部分節點失敗或者不可達的情況下能夠繼續處理命令。

    10、Redis 叢集的資料分片

    Redis 叢集沒有使用一致性hash, 而是引入了雜湊槽的概念.

Redis 叢集有16384個(2的14次方)雜湊槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽.叢集的每個節點負責一部分hash槽,舉個例子,比如當前叢集有3個節點,那麼:

  • 節點 A 包含 0 到 5500號雜湊槽.

  • 節點 B 包含5501 到 11000 號雜湊槽.

  • 節點 C 包含11001 到 16384號雜湊槽.這種結構很容易新增或者刪除節點. 比如如果我想新添加個節點D, 我需要從節點 A, B, C中得部分槽到D上. 如果我像移除節點A,需要將A中得槽移到B和C節點上,然後將沒有任何槽的A節點從叢集中移除即可. 由於從一個節點將雜湊槽移動到另一個節點並不會停止服務,所以無論新增刪除或者改變某個節點的雜湊槽的數量都不會造成叢集不可用的狀態.為了使得叢集在一部分節點下線或者無法與叢集的大多數(majority)節點進行通訊的情況下, 仍然可以正常運作, Redis 叢集對節點使用了主從複製功能: 叢集中的每個節點都有 1 個至 N 個複製品(replica), 其中一個複製品為主節點(master), 而其餘的 N-1 個複製品為從節點(slave)。

注意:節點 A 、B 、C 的例子中, 如果節點 B 下線了, 那麼叢集將無法正常執行, 因為叢集找不到節點來處理 5501 號至 11000號的雜湊槽。A,C節點也將不可用了哦。為了解決上面說的B下線了, 導致整個叢集不可以用了,我們需要使用叢集的另外一個功能,主從複製功能。為A,B,C,新增一個或者多個從節點A1,B1,C1,這樣B節點掛了,B1節點會自動升級為主節點,接替B的工作,為叢集工作。這個很像哨兵模式,其實就是哨兵模式。在redis的叢集模組中,添加了哨兵模式,主節點掛了從節點會自動切換為主節點。

整個叢集模式如圖:

架構細節:

  1. 所有的redis節點彼此互聯(PING-PONG機制),內部使用二進位制協議優化傳輸速度和頻寬.

  2. 節點的fail是通過叢集中超過半數的節點檢測失效時才生效.

  3. 客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可

  4. redis-cluster把所有的物理節點對映到[0-16383]slot上,cluster 負責維護node<->slot<->value

    10、Redis 一致性保證

    Redis並不能保證資料的強一致性. 這意味這在實際中叢集在特定的條件下可能會丟失寫操作.

第一個原因是因為叢集是用了非同步複製. 寫操作過程:

  • 客戶端向主節點B寫入一條命令.

  • 主節點B向客戶端回覆命令狀態.

  • 主節點將寫操作複製給他得從節點 B1, B2 和 B3.主節點對命令的複製工作發生在返回命令回覆之後, 因為如果每次處理命令請求都需要等待複製操作完成的話, 那麼主節點處理命令請求的速度將極大地降低 —— 我們必須在效能和一致性之間做出權衡。注意:Redis 叢集可能會在將來提供同步寫的方法。 Redis 叢集另外一種可能會丟失命令的情況是叢集出現了網路分割槽, 並且一個客戶端與至少包括一個主節點在內的少數例項被孤立。

舉個例子 假設叢集包含 A 、 B 、 C 、 A1 、 B1 、 C1 六個節點, 其中 A 、B 、C 為主節點, A1 、B1 、C1 為A,B,C的從節點, 還有一個客戶端 Z1 假設叢集中發生網路分割槽,那麼叢集可能會分為兩方,大部分的一方包含節點 A 、C 、A1 、B1 和 C1 ,小部分的一方則包含節點 B 和客戶端 Z1 .

Z1仍然能夠向主節點B中寫入, 如果網路分割槽發生時間較短,那麼叢集將會繼續正常運作,如果分割槽的時間足夠讓大部分的一方將B1選舉為新的master,那麼Z1寫入B中得資料便丟失了.

注意, 在網路分裂出現期間, 客戶端 Z1 可以向主節點 B 傳送寫命令的最大時間是有限制的, 這一時間限制稱為節點超時時間(node timeout), 是 Redis 叢集的一個重要的配置選項:

port 7000   //redis埠
cluster-enabled yes   //開啟叢集
cluster-config-file nodes.conf   //叢集配置檔案
cluster-node-timeout 5000   //叢集節點超時設定
appendonly yes   //開啟aof持久化
10、mac叢集搭建

要讓叢集正常工作至少需要3個主節點,在這裡我們要建立6個redis節點,其中三個為主節點,三個為從節點,對應的redis節點的ip和埠對應關係如下(為了簡單演示都在同一臺機器上面)

1、安裝redis
brew install redis

由於配置了.bash_profile變數,所以可以在任何地方直接啟動redis1.直接啟動

redis-server

2.帶配置檔案啟動

redis-server /Users/baowenwei/service/redis/7000/redis7000.conf

建立6個資料夾7000,7001,…;在每個資料夾裡面建立一個配置檔案,redis7000.conf,redis7001.conf在每個redis.conf裡面配置如下:7000.conf

port 7000
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7000
cluster-enabled yes

7001.conf

port 7001
dbfilename dump.rdb
dir /Users/baowenwei/service/redis/7001
cluster-enabled yes
…

由於叢集的指令碼是使用ruby寫的,所以還需要安裝ruby。

brew install ruby.

安裝ruby後,就可以使用ruby安裝ruby操作redis的redis包了。

gam install redis -v 3.2 //安裝完ruby後,gam命令就自己有了。

由於我使用的是brew在mac上面安裝的redis,所以沒有原始碼包,現在需要下載原始碼包,從官網下載原始碼包到本地。進入原始碼包裡面cd ~/service/redis/redis-3.2.11(根據個人自己的路徑來看),執行

make //編譯打包的意思。
編譯後可以從src裡面看到redis-trib.rb這個檔案了。
使用cp redis-trib.rb /usr/local/bin/redis-trib ///usr/local/bin這個目錄已經配置到環境變數的pash下面了, 這樣就可以在任何地方直接使用redis-trib了。

以此啟動7000—7005節點。

redis-server /Users/baowenwei/service/redis/7000/redis7000.conf

建立叢集

redis-trib create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

create表示建立,選項 --replicas 1 表示我們希望為叢集中的每個主節點建立一個從節點。。

create表示建立,選項 --replicas 1 表示我們希望為叢集中的每個主節點建立一個從節點。。

進入客戶端操作了。

$ redis-cli -c -p 7002
127.0.0.1:7002> set 777 ddd
-> Redirected to slot [6787] located at 127.0.0.1:7001
OK
127.0.0.1:7001> set ffff sddd
-> Redirected to slot [14956] located at 127.0.0.1:7002
OK
127.0.0.1:7002> set ffff sddd
OK
127.0.0.1:7002>

可以看到777這個key計算後應該存放到7001下面的卡曹,這時候會切換到7001節點上去,然後你需要重新執行一邊命令,之前的並沒有執行成功哦。

注意事項:在第一次建立叢集前,不能有dump.rdb檔案

11、redis穿透

快取穿透是指查詢一個根本不存在的資料,快取層和儲存層都不會命中,但是出於容錯的考慮,如果從儲存層查不到資料則不寫入快取層。

1、防穿透

穿透過程

  • 快取層不命中

  • 儲存層不命中,所以不將空結果寫回快取

  • 返回空結果快取穿透將導致不存在的資料每次請求都要到儲存層去查詢,失去了快取保護後端儲存的意義,這就是redis穿透。

2、解決方案1:

快取空物件會有兩個問題:第一,空值做了快取,意味著快取層中存了更多的鍵,需要更多的記憶體空間 (如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。第二,快取層和儲存層的資料會有一段時間視窗的不一致,可能會對業務有一定影響。例如過期時間設定為5分鐘,如果此時儲存層添加了這個資料,那此段時間就會出現快取層和儲存層資料的不一致,此時可以利用訊息系統或者其他方式清除掉快取層中的空物件。

3、解決方案2:

布隆過濾器攔截

4、快取熱點 key 重建優化

開發人員使用快取 + 過期時間的策略既可以加速資料讀寫,又保證資料的定期更新,這種模式基本能夠滿足絕大部分需求。但是有兩個問題如果同時出現,可能就會對應用造成致命的危害:

當前 key 是一個熱點 key( 例如一個熱門的娛樂新聞),併發量非常大。重建快取不能在短時間完成,可能是一個複雜計算,例如複雜的 SQL、多次 IO、多個依賴等。

在快取失效的瞬間,有大量執行緒來重建快取,造成後端負載加大,甚至可能會讓應用崩潰。

熱點 key 失效後大量執行緒重建快取要解決這個問題也不是很複雜,但是不能為了解決這個問題給系統帶來更多的麻煩,所以需要制定如下目標:

  • 減少重建快取的次數

  • 資料儘可能一致

  • 較少的潛在危險

12、互斥鎖 (mutex key)

此方法只允許一個執行緒重建快取,其他執行緒等待重建快取的執行緒執行完,重新從快取獲取資料即可

(1) 從 Redis 獲取資料,如果值不為空,則直接返回值,否則執行下面的步驟(2) 如果 set(nx 和 ex) 結果為 true,說明此時沒有其他執行緒重建快取,那麼當前執行緒執行快取構建邏輯。(2.2) 如果 setnx(nx 和 ex) 結果為 false,說明此時已經有其他執行緒正在執行構建快取的工作,那麼當前執行緒將休息指定時間 ( 例如這裡是 50 毫秒,取決於構建快取的速度 ) 後,重新執行函式,直到獲取到資料。

13、redis雪崩

雪崩是指如果redis突然掛掉了,所有請求都打到了db上面,這個時候有可能會把db給打垮掉。

預防和解決快取雪崩問題,可以從以下三個方面進行著手。

  1. 保證快取層服務高可用性。和飛機都有多個引擎一樣,如果快取層設計成高可用的,即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如前面介紹過的 Redis Sentinel 和 Redis Cluster 都實現了高可用。

  2. 依賴隔離元件為後端限流並降級。無論是快取層還是儲存層都會有出錯的概率,可以將它們視同為資源。作為併發量較大的系統,假如有一個資源不可用,可能會造成執行緒全部 hang 在這個資源上,造成整個系統不可用。降級在高併發系統中是非常正常的:比如推薦服務中,如果個性化推薦服務不可用,可以降級補充熱點資料,不至於造成前端頁面是開天窗。

  3. 在實際專案中,我們需要對重要的資源 ( 例如 Redis、 MySQL、 HBase、外部介面 ) 都進行隔離,讓每種資源都單獨執行在自己的執行緒池中,即使個別資源出現了問題,對其他服務沒有影響。但是執行緒池如何管理,比如如何關閉資源池,開啟資源池,資源池閥值管理,這些做起來還是相當複雜的,這裡推薦一個 Java 依賴隔離工具 Hystrix(https://github.com/Netflix/Hystrix),如下圖所示。

    Hystrix不再處於主動開發中,並且當前處於維護模式。

    Hystrix(1.5.18版)足夠穩定,可以滿足Netflix現有應用程式的需求。同時,我們的重點已轉向對應用程式的實時效能做出反應的自適應性實現,而不是預先配置的設定(例如,通過自適應併發限制)。對於像Hystrix這樣有意義的情況,我們打算繼續將Hystrix用於現有應用程式,併為新的內部專案利用諸如resilience4j之類的開放式活動專案。我們開始建議其他人也這樣做。

    Netflix Hystrix現在正式處於維護模式,對整個社群具有以下期望:Netflix將不再主動審查問題,合併請求併發布新版本的Hystrix。我們已經根據1891年的最終版本釋出了Hystrix(1.5.18),以便Maven Central中的最新版本與Netflix內部使用的最新已知穩定版本(1.5.11)保持一致。如果社群成員有興趣獲得Hystrix的所有權並將其移回活動模式,請聯絡[email protected]

    多年來,Hystrix為Netflix和社群提供了良好的服務,向維護模式的過渡絕不表示Hystrix的概念和想法不再有價值。相反,Hystrix啟發了許多偉大的想法和專案。我們感謝Netflix和整個社群中的每個人多年來為Hystrix所做的所有貢獻。

    介紹

    Hystrix是一個延遲和容錯庫,旨在隔離對遠端系統,服務和第三方庫的訪問點,停止級聯故障,並在不可避免發生故障的複雜分散式系統中實現彈性。

    完整檔案

    有關完整的文件,示例,操作詳細資訊和其他資訊,請參見Wiki

    有關API,請參見Javadoc

    通訊

    它有什麼作用?

    1)延遲和容錯

    停止級聯故障。後備和正常降級。無法快速快速恢復。

    使用斷路器隔離執行緒和訊號量。

    2)實時操作

    實時監控和配置更改。監視服務和財產變更在整個機隊中擴散後立即生效。

    在幾秒鐘內收到警報,做出決定,影響更改並檢視結果。

    3)併發

    並行執行。併發請求快取。通過請求摺疊自動進行批處理。

    你好,世界!

    要隔離的程式碼包裝在HystrixCommand的run()方法內,類似於以下程式碼:

    公共 類 CommandHelloWorld  擴充套件 HystrixCommand < String > {
    
        私有 最終 字串名稱;
    
        公共 CommandHelloWorld(字串 名稱){
             超(HystrixCommandGroupKey 。工廠。阿斯基(“ ExampleGroup ”));
            這個。名稱=名稱
        }
    
        @Override 
        受保護的 字串 run(){
             返回 “ Hello ”  +名稱+  “!”;
        }
    }
    

    該命令可以這樣使用:

    字串 s =  new  CommandHelloWorld(“ Bob ”)。執行();
    Future < String > s =  新的 CommandHelloWorld(“ Bob ”)。佇列();
    Observable < String > s =  新的 CommandHelloWorld(“ Bob ”)。觀察();
    

    有關更多示例和資訊,請參見“如何使用”部分。

    可以在hystrix-examples模組中找到示例原始碼。

    二進位制檔案

    可以在http://search.maven.org上找到有關Maven,Ivy,Gradle等的二進位制檔案和依賴項資訊。

    更改歷史記錄和版本號=>CHANGELOG.md

    Maven的示例:

    < 依存關係 >
        < groupId > com.netflix.hystrix </ groupId >
        < artifactId > hystrix-core </ artifactId >
        < 版本 > xyz </ 版本 >
    </ 依賴 >
    

    對於常春藤:

    < 依賴 org = “ com.netflix.hystrix ” 名稱 = “ hystrix核心”  rev = “ xyz ” />
    

    如果您需要下載jar而不是使用構建系統,請使用所需的版本建立一個Maven pom檔案,如下所示:

    <?xml 版本 = “ 1.0 ”?>
    < project  xmlns = “ http://maven.apache.org/POM/4.0.0 ”  xmlns :xsi = “ http://www.w3.org/2001/XMLSchema-instance ”  xsi :schemaLocation = “ http:/ /maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd “ >
    	< modelVersion > 4.0.0 </ modelVersion >
    	< groupId > com.netflix.hystrix.download </ groupId >
    	< artifactId > hystrix下載</ artifactId >
    	< 版本 > 1.0-SNAPSHOT </ 版本 >
    	< name >用於下載hystrix-core和依賴項的簡單POM </ name >
    	< url > http://github.com/Netflix/Hystrix </ url >
    	< 依賴項 >
    		< 依存關係 >
    			< groupId > com.netflix.hystrix </ groupId >
    			< artifactId > hystrix-core </ artifactId >
    			< 版本 > xyz </ 版本 >
    			< 範圍 />
    		</ 依賴 >
    	</ 依賴 >
    </ project >
    

    然後執行:

    mvn -f download-hystrix-pom.xml dependency:copy-dependencies
    

    它將hystrix-core-*。jar及其依賴項下載到./target/dependency/中。

    您需要Java 6或更高版本。

    建立

    建立:

    $ git clone [email protected]:Netflix/Hystrix.git
    $ cd Hystrix/
    $ ./gradlew build
    

    有關構建的更多詳細資訊,請參見Wiki的“入門”頁面。

    執行演示

    要執行演示應用程式,請執行以下操作:

    $ git clone [email protected]:Netflix/Hystrix.git
    $ cd Hystrix/
    ./gradlew runDemo
    

    您將看到類似於以下內容的輸出:

    Request => GetUserAccountCommand[SUCCESS][8ms], GetPaymentInformationCommand[SUCCESS][20ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][101ms], CreditCardCommand[SUCCESS][1075ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][2ms], GetPaymentInformationCommand[SUCCESS][22ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][130ms], CreditCardCommand[SUCCESS][1050ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][19ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][145ms], CreditCardCommand[SUCCESS][1301ms]
    Request => GetUserAccountCommand[SUCCESS][4ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][93ms], CreditCardCommand[SUCCESS][1409ms]
    
    #####################################################################################
    # CreditCardCommand: Requests: 17 Errors: 0 (0%)   Mean: 1171 75th: 1391 90th: 1470 99th: 1486 
    # GetOrderCommand: Requests: 21 Errors: 0 (0%)   Mean: 100 75th: 144 90th: 207 99th: 230 
    # GetUserAccountCommand: Requests: 21 Errors: 4 (19%)   Mean: 8 75th: 11 90th: 46 99th: 51 
    # GetPaymentInformationCommand: Requests: 21 Errors: 0 (0%)   Mean: 18 75th: 21 90th: 24 99th: 25 
    #####################################################################################
    
    Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][16ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][51ms], CreditCardCommand[SUCCESS][922ms]
    Request => GetUserAccountCommand[SUCCESS][12ms], GetPaymentInformationCommand[SUCCESS][12ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][68ms], CreditCardCommand[SUCCESS][1257ms]
    Request => GetUserAccountCommand[SUCCESS][10ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][78ms], CreditCardCommand[SUCCESS][1295ms]
    Request => GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS][6ms], GetPaymentInformationCommand[SUCCESS][11ms], GetUserAccountCommand[FAILURE, FALLBACK_SUCCESS, RESPONSE_FROM_CACHE][0ms]x2, GetOrderCommand[SUCCESS][153ms], CreditCardCommand[SUCCESS][1321ms]
    

    該演示模擬了4種不同的HystrixCommand實現,這些實現在多執行緒環境中具有故障,延遲,超時和重複呼叫。

    它從HystrixCommandMetrics記錄HystrixRequestLog的結果和指標。

    儀表板

    該專案的hystrix-dashboard元件已棄用,移至Netflix-Skunkworks / hystrix-dashboard。請參閱自述檔案以獲取更多詳細資訊,包括重要的安全注意事項。

    錯誤和反饋

    有關錯誤,問題和討論,請使用GitHub Issues

    執照

    版權所有2013 Netflix,Inc.

    根據Apache許可版本2.0(“許可”)許可;除非遵守許可,否則不得使用此檔案。您可以在以下位置獲得許可的副本:

    http://www.apache.org/licenses/LICENSE-2.0

    除非適用法律要求或書面同意,否則根據“許可”分發的軟體將按“原樣”分發,沒有任何明示或暗示的保證或條件。請參閱許可證,以瞭解許可證下管理許可權和限制的特定語言。

第三章、redis面試

1、Redis 持久化機制

Redis是一個支援持久化的記憶體資料庫,通過持久化機制把記憶體中的資料同步到硬碟檔案來保證資料持久化。當Redis重啟後通過把硬碟檔案重新載入到記憶體,就能達到恢復資料的目的。實現:單獨建立fork()一個子程序,將當前父程序的資料庫資料複製到子程序的記憶體中,然後由子程序寫入到臨時檔案中,持久化的過程結束了,再用這個臨時檔案替換上次的快照檔案,然後子程序退出,記憶體釋放。

RDB是Redis預設的持久化方式。按照一定的時間週期策略把記憶體的資料以快照的形式儲存到硬碟的二進位制檔案。即Snapshot快照儲存,對應產生的資料檔案為dump.rdb,通過配置檔案中的save引數來定義快照的週期。( 快照可以是其所表示的資料的一個副本,也可以是資料的一個複製品。)AOF:Redis會將每一個收到的寫命令都通過Write函式追加到檔案最後,類似於MySQL的binlog。當Redis重啟是會通過重新執行檔案中儲存的寫命令來在記憶體中重建整個資料庫的內容。當兩種方式同時開啟時,資料恢復Redis會優先選擇AOF恢復。

2、快取雪崩、快取穿透、快取預熱、快取更新、快取降級等問題
一、快取雪崩

快取雪崩我們可以簡單的理解為:由於原有快取失效,新快取未到期間(例如:我們設定快取時採用了相同的過期時間,在同一時刻出現大面積的快取過期),所有原本應該訪問快取的請求都去查詢資料庫了,而對資料庫CPU和記憶體造成巨大壓力,嚴重的會造成資料庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。解決辦法:大多數系統設計者考慮用加鎖( 最多的解決方案)或者佇列的方式保證來保證不會有大量的執行緒對資料庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層儲存系統上。還有一個簡單方案就是將快取失效時間分散開

二、快取穿透

快取穿透是指使用者查詢資料,在資料庫沒有,自然在快取中也不會有。這樣就導致使用者查詢的時候,在快取中找不到,每次都要去資料庫再查詢一遍,然後返回空(相當於進行了兩次無用的查詢)。這樣請求就繞過快取直接查資料庫,這也是經常提的快取命中率問題。解決辦法;最常見的則是採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉,從而避免了對底層儲存系統的查詢壓力。另外也有一個更為簡單粗暴的方法,如果一個查詢返回的資料為空(不管是資料不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,最長不超過五分鐘。通過這個直接設定的預設值存放到快取,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問資料庫,這種辦法最簡單粗暴。5TB的硬碟上放滿了資料,請寫一個演算法將這些資料進行排重。如果這些資料是一些32bit大小的資料該如何解決?如果是64bit的呢?

對於空間的利用到達了一種極致,那就是Bitmap和布隆過濾器(Bloom Filter)。Bitmap: 典型的就是雜湊表缺點是,Bitmap對於每個元素只能記錄1bit資訊,如果還想完成額外的功能,恐怕只能靠犧牲更多的空間、時間來完成了。

布隆過濾器(推薦)就是引入了k(k>1)k(k>1)個相互獨立的雜湊函式,保證在給定的空間、誤判率下,完成元素判重的過程。它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。Bloom-Filter演算法的核心思想就是利用多個不同的Hash函式來解決“衝突”。Hash存在一個衝突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少衝突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那麼該元素肯定不在集合中。只有在所有的Hash函式告訴我們該元素在集合中時,才能確定該元素存在於集合中。這便是Bloom-Filter的基本思想。Bloom-Filter一般用於在大資料量的集合中判定某元素是否存在。受提醒補充:快取穿透與快取擊穿的區別快取擊穿:指一個key非常熱點,大併發集中對這個key進行訪問,當這個key在失效的瞬間,仍然持續的大併發訪問就穿破快取,轉而直接請求資料庫。解決方案;在訪問key之前,採用SETNX(set if not exists)來設定另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key。

三、快取預熱

快取預熱這個應該是一個比較常見的概念,相信很多小夥伴都應該可以很容易的理解,快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣就可以避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!解決思路:1、直接寫個快取重新整理頁面,上線時手工操作下;2、資料量不大,可以在專案啟動的時候自動進行載入;3、定時重新整理快取;

四、快取更新

除了快取伺服器自帶的快取失效策略之外(Redis預設的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的快取淘汰,常見的策略有兩種:(1)定時去清理過期的快取;(2)當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新資料並更新快取。兩者各有優劣,第一種的缺點是維護大量快取的key是比較麻煩的,第二種的缺點就是每次使用者請求過來都要判斷快取失效,邏輯相對比較複雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。

五、快取降級

當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關實現人工降級。降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。以參考日誌級別設定預案:(1)一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;(3)錯誤:比如可用率低於90%,或者資料庫連線池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;(4)嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級。

服務降級的目的,是為了防止Redis服務故障,導致資料庫跟著一起發生雪崩問題。因此,對於不重要的快取資料,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接返回預設值給使用者。

熱點資料和冷資料是什麼熱點資料,快取才有價值對於冷資料而言,大部分資料可能還沒有再次訪問到就已經被擠出記憶體,不僅佔用記憶體,而且價值不大。頻繁修改的資料,看情況考慮使用快取對於上面兩個例子,壽星列表、導航資訊都存在一個特點,就是資訊修改頻率不高,讀取通常非常高的場景。對於熱點資料,比如我們的某IM產品,生日祝福模組,當天的壽星列表,快取以後可能讀取數十萬次。再舉個例子,某導航產品,我們將導航資訊,快取以後可能讀取數百萬次。資料更新前至少讀取兩次,快取才有意義。這個是最基本的策略,如果快取還沒有起作用就失效了,那就沒有太大價值了。那存不存在,修改頻率很高,但是又不得不考慮快取的場景呢?有!比如,這個讀取介面對資料庫的壓力很大,但是又是熱點資料,這個時候就需要考慮通過快取手段,減少資料庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點資料,但是又不斷變化,此時就需要將資料同步儲存到Redis快取,減少資料庫壓力。

3、Memcache與Redis的區別都有哪些?

1)、儲存方式 Memecache把資料全部存在記憶體之中,斷電後會掛掉,資料不能超過記憶體大小。 Redis有部份存在硬碟上,redis可以持久化其資料2)、資料支援型別 memcached所有的值均是簡單的字串,redis作為其替代者,支援更為豐富的資料型別 ,提供list,set,zset,hash等資料結構的儲存3)、使用底層模型不同 它們之間底層實現方式 以及與客戶端之間通訊的應用協議不一樣。 Redis直接自己構建了VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求。4). value 值大小不同:Redis 最大可以達到 512M;memcache 只有 1mb。5)redis的速度比memcached快很多6)Redis支援資料的備份,即master-slave模式的資料備份。

4、單執行緒的redis為什麼這麼快

(一)純記憶體操作(二)單執行緒操作,避免了頻繁的上下文切換(三)採用了非阻塞I/O多路複用機制

5、redis的資料型別,以及每種資料型別的使用場景

回答:一共五種

(一)String

這個其實沒啥好說的,最常規的set/get操作,value可以是String也可以是數字。一般做一些複雜的計數功能的快取。

(二)hash

這裡value存放的是結構化的物件,比較方便的就是操作其中的某個欄位。博主在做單點登入的時候,就是用這種資料結構儲存使用者資訊,以cookieId作為key,設定30分鐘為快取過期時間,能很好的模擬出類似session的效果。

(三)list

使用List的資料結構,可以做簡單的訊息佇列的功能。另外還有一個就是,可以利用lrange命令,做基於redis的分頁功能,效能極佳,使用者體驗好。本人還用一個場景,很合適—取行情資訊。就也是個生產者和消費者的場景。LIST可以很好的完成排隊,先進先出的原則。

(四)set

因為set堆放的是一堆不重複值的集合。所以可以做全域性去重的功能。為什麼不用JVM自帶的Set進行去重?因為我們的系統一般都是叢集部署,使用JVM自帶的Set,比較麻煩,難道為了一個做一個全域性去重,再起一個公共服務,太麻煩了。另外,就是利用交集、並集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。

(五)sorted set

sorted set多了一個權重引數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。

6、Redis 內部結構

dict 本質上是為了解決演算法中的查詢問題(Searching)是一個用於維護key和value對映關係的資料結構,與很多語言中的Map或dictionary類似。 本質上是為了解決演算法中的查詢問題(Searching)sds sds就等同於char * 它可以儲存任意二進位制資料,不能像C語言字串那樣以字元’\0’來標識字串的結 束,因此它必然有個長度欄位。skiplist (跳躍表) 跳錶是一種實現起來很簡單,單層多指標的連結串列,它查詢效率很高,堪比優化過的二叉平衡樹,且比平衡樹的實現,quicklistziplist 壓縮表 ziplist是一個編碼後的列表,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,redis的過期策略以及記憶體淘汰機制redis採用的是定期刪除+惰性刪除策略。

7、為什麼不用定時刪除策略?

定時刪除,用一個定時器來負責監視key,過期則自動刪除。雖然記憶體及時釋放,但是十分消耗CPU資源。在大併發請求下,CPU要將時間應用在處理請求,而不是刪除key,因此沒有采用這一策略.

8、定期刪除+惰性刪除是如何工作的呢?

定期刪除,redis預設每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只採用定期刪除策略,會導致很多key到時間沒有刪除。於是,惰性刪除派上用場。也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除。

9、採用定期刪除+惰性刪除就沒其他問題了麼?

不是的,如果定期刪除沒刪除key。然後你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的記憶體會越來越高。那麼就應該採用記憶體淘汰機制在redis.conf中有一行配置

maxmemory-policy volatile-lru1

10、該配置就是配記憶體淘汰策略的(什麼,你沒配過?好好反省一下自己)

volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰no-enviction(驅逐):禁止驅逐資料,新寫入操作會報錯ps:如果沒有設定 expire 的key, 不滿足先決條件(prerequisites); 那麼 volatile-lru, volatile-random 和 volatile-ttl 策略的行為, 和 noeviction(不刪除) 基本上一致。

11、Redis 為什麼是單執行緒的

官方FAQ表示,因為Redis是基於記憶體的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器記憶體的大小或者網路頻寬。既然單執行緒容易實現,而且CPU不會成為瓶頸,那就順理成章地採用單執行緒的方案了(畢竟採用多執行緒會有很多麻煩!)Redis利用佇列技術將併發訪問變為序列訪問1)絕大部分請求是純粹的記憶體操作(非常快速)

2)採用單執行緒,避免了不必要的上下文切換和競爭條件3)非阻塞IO優點:1.速度快,因為資料存在記憶體中,類似於HashMap,HashMap的優勢就是查詢和操作的時間複雜度都是O(1)

  1. 支援豐富資料型別,支援string,list,set,sorted set,hash3.支援事務,操作都是原子性,所謂的原子性就是對資料的更改要麼全部執行,要麼全部不執行

4、豐富的特性:可用於快取,訊息,按key設定過期時間,過期後將會自動刪除如何解決redis的併發競爭key問題

同時有多個子系統去set一個key。這個時候要注意什麼呢? 不推薦使用redis的事務機制。因為我們的生產環境,基本都是redis叢集環境,做了資料分片操作。你一個事務中有涉及到多個key操作的時候,這多個key不一定都儲存在同一個redis-server上。因此,redis的事務機制,十分雞肋。(1)如果對這個key操作,不要求順序: 準備一個分散式鎖,大家去搶鎖,搶到鎖就做set操作即可(2)如果對這個key操作,要求順序: 分散式鎖+時間戳。 假設這會系統B先搶到鎖,將key1設定為{valueB 3:05}。接下來系統A搶到鎖,發現自己的valueA的時間戳早於快取中的時間戳,那就不做set操作了。以此類推。(3) 利用佇列,將set方法變成序列訪問也可以redis遇到高併發,如果保證讀寫key的一致性對redis的操作都是具有原子性的,是執行緒安全的操作,你不用考慮併發問題,redis內部已經幫你處理好併發的問題了。

12、Redis 叢集方案應該怎麼做?都有哪些方案?

1.twemproxy,大概概念是,它類似於一個代理方式, 使用時在本需要連線 redis 的地方改為連線 twemproxy, 它會以一個代理的身份接收請求並使用一致性 hash 演算法,將請求轉接到具體 redis,將結果再返回 twemproxy。缺點: twemproxy 自身單埠例項的壓力,使用一致性 hash 後,對 redis 節點數量改變時候的計算值的改變,資料無法自動移動到新的節點。

2.codis,目前用的最多的叢集方案,基本和 twemproxy 一致的效果,但它支援在 節點數量改變情況下,舊節點資料可恢復到新 hash 節點

3.redis cluster3.0 自帶的叢集,特點在於他的分散式演算法不是一致性 hash,而是 hash 槽的概念,以及自身支援節點設定從節點。具體看官方文件介紹。

13、有沒有嘗試進行多機redis 的部署?如何保證資料一致的?

主從複製,讀寫分離一類是主資料庫(master)一類是從資料庫(slave),主資料庫可以進行讀寫操作,當發生寫操作的時候自動將資料同步到從資料庫,而從資料庫一般是隻讀的,並接收主資料庫同步過來的資料,一個主資料庫可以有多個從資料庫,而一個從資料庫只能有一個主資料庫。

14、對於大量的請求怎麼樣處理

redis是一個單執行緒程式,也就說同一時刻它只能處理一個客戶端請求;redis是通過IO多路複用(select,epoll, kqueue,依據不同的平臺,採取不同的實現)來處理多個客戶端請求的

15、Redis 常見效能問題和解決方案?

(1) Master 最好不要做任何持久化工作,如 RDB 記憶體快照和 AOF 日誌檔案(2) 如果資料比較重要,某個 Slave 開啟 AOF 備份資料,策略設定為每秒同步一次(3) 為了主從複製的速度和連線的穩定性, Master 和 Slave 最好在同一個區域網內(4) 儘量避免在壓力很大的主庫上增加從庫(5) 主從複製不要用圖狀結構,用單向連結串列結構更為穩定,即: Master <- Slave1 <- Slave2 <-Slave3…

16、講解下Redis執行緒模型

檔案事件處理器包括分別是套接字、 I/O 多路複用程式、 檔案事件分派器(dispatcher)、 以及事件處理器。使用 I/O 多路複用程式來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。當被監聽的套接字準備好執行連線應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時,與操作相對應的檔案事件就會產生, 這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。I/O 多路複用程式負責監聽多個套接字, 並向檔案事件分派器傳送那些產生了事件的套接字。工作原理:1)I/O 多路複用程式負責監聽多個套接字, 並向檔案事件分派器傳送那些產生了事件的套接字。儘管多個檔案事件可能會併發地出現, 但 I/O 多路複用程式總是會將所有產生事件的套接字都入隊到一個佇列裡面, 然後通過這個佇列, 以有序(sequentially)、同步(synchronously)、每次一個套接字的方式向檔案事件分派器傳送套接字: 當上一個套接字產生的事件被處理完畢之後(該套接字為事件所關聯的事件處理器執行完畢), I/O 多路複用程式才會繼續向檔案事件分派器傳送下一個套接字。如果一個套接字又可讀又可寫的話, 那麼伺服器將先讀套接字, 後寫套接字.

17、為什麼Redis的操作是原子性的,怎麼保證原子性的?

對於Redis而言,命令的原子性指的是:一個操作的不可以再分,操作要麼執行,要麼不執行。Redis的操作之所以是原子性的,是因為Redis是單執行緒的。Redis本身提供的所有API都是原子操作,Redis中的事務其實是要保證批量操作的原子性。多個命令在併發中也是原子性的嗎?不一定, 將get和set改成單命令操作,incr 。使用Redis的事務,或者使用Redis+Lua==的方式實現.

18、Redis事務

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的Redis會將一個事務中的所有命令序列化,然後按順序執行。1.redis 不支援回滾“Redis 在事務失敗時不進行回滾,而是繼續執行餘下的命令”, 所以 Redis 的內部可以保持簡單且快速。2.如果在一個事務中的命令出現錯誤,那麼所有的命令都不會執行;3.如果在一個事務中出現執行錯誤,那麼正確的命令會被執行。注:redis的discard只是結束本次事務,正確命令造成的影響仍然存在.

1)MULTI命令用於開啟一個事務,它總是返回OK。 MULTI執行之後,客戶端可以繼續向伺服器傳送任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有佇列中的命令才會被執行。2)EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。 當操作被打斷時,返回空值 nil 。3)通過呼叫DISCARD,客戶端可以清空事務佇列,並放棄執行事務, 並且客戶端會從事務狀態中退出。4)WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為。 可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。

19、Redis實現分散式鎖

Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係Redis中可以使用SETNX命令實現分散式鎖。將 key 的值設為 value ,當且僅當 key 不存在。 若給定的 key 已經存在,則 SETNX 不做任何動作

解鎖:使用del key命令就能釋放鎖解決死鎖:1)通過Redis中expire()給鎖設定最大持有時間,如果超過,則Redis來幫我們釋放鎖。2) 使用 setnx key “當前系統時間+鎖持有的時間”和getset key “當前系統時間+鎖持有的時間”組合的命令就可以實現