日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

深入学习Redis

發(fā)布時(shí)間:2025/3/21 数据库 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入学习Redis 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • Redis
    • 前言
    • 一、概念和基礎(chǔ)
      • 概念
      • 優(yōu)勢
      • 官方資料
      • 使用場景
    • 二、數(shù)據(jù)類型:5種基礎(chǔ)的數(shù)據(jù)類型
      • Redis數(shù)據(jù)結(jié)構(gòu)簡介
      • 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)詳解
        • String字符串
        • List列表
        • Set集合
        • Hash散列
        • Zset有序集合
    • 三、特殊的數(shù)據(jù)類型
      • 三種特殊類型講解
        • HyperLogLog
        • Bitmap
        • Geospatial
    • 四、數(shù)據(jù)庫
      • 常用指令
      • 過期數(shù)據(jù)的刪除策略
        • 定時(shí)刪除
        • 惰性刪除
        • 定期刪除
    • 五、持久化
      • RDB持久化
        • 觸發(fā)方式
        • RDB的優(yōu)缺點(diǎn)
      • AOF持久化
        • 實(shí)現(xiàn)AOF
        • 配置文件
        • 重寫機(jī)制
      • 重啟加載
    • 六、事務(wù)
    • 七、主從復(fù)制
      • 舊版復(fù)制功能的實(shí)現(xiàn)
        • 復(fù)制
        • 命令傳播
      • 新版復(fù)制功能的實(shí)現(xiàn)
        • 部分重同步
        • 復(fù)制的完整流程
      • 心跳檢測
    • 八、哨兵
    • 九、集群
      • 集群的作用
      • 集群的搭建
        • 執(zhí)行Redis命令搭建集群
        • 使用Ruby腳本搭建集群
      • 集群設(shè)計(jì)
      • 數(shù)據(jù)結(jié)構(gòu)
      • 集群命令的實(shí)現(xiàn)
      • 實(shí)踐須知
        • 集群伸縮
        • ASK錯誤

Redis

前言

Redis是一種支持key-value等多種數(shù)據(jù)結(jié)構(gòu)的存儲系統(tǒng),通過在內(nèi)存中讀取數(shù)據(jù),大大提高了數(shù)據(jù)的讀取速度,作為一個(gè)緩存中間件,是實(shí)現(xiàn)網(wǎng)站高并發(fā)以及高可用不可或缺的一部分。可應(yīng)用于緩存,事件發(fā)布或者訂閱,高速隊(duì)列等場景,支持網(wǎng)絡(luò),提供字符串、哈希表、列表、隊(duì)列、集合結(jié)構(gòu)直接存儲,基于內(nèi)存并可持久化。

一、概念和基礎(chǔ)

概念

Redis是一款基于內(nèi)存的高速緩存數(shù)據(jù)庫,全稱為:Remote Dictionary Server(遠(yuǎn)程數(shù)據(jù)服務(wù)),使用C語言編寫,支持豐富的數(shù)據(jù)類型。

Redis與其他key-value緩存產(chǎn)品有以下幾個(gè)特點(diǎn):

  • Redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保存在磁盤,重啟的時(shí)候再次加載進(jìn)行使用
  • Redis不僅僅支持簡單的key-value類型數(shù)據(jù),同時(shí)還提供list、set、zset以及hash等數(shù)據(jù)結(jié)構(gòu)的存儲
  • Redis支持?jǐn)?shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。

優(yōu)勢

  • 性能極高 — Redis能讀的速度是110000次/s,寫的速度是81000次/s
  • 原子性 — Redis所有操作都是原子性的,同時(shí)Redis還支持對幾個(gè)操作全并之后的原子性執(zhí)行
  • 豐富的特性 — Redis支持publish/subscribe,通知,key過期等等特性。
  • 持久化 — Redis支持RDB,AOF等持久化方式
  • 發(fā)布訂閱 — Redis支持發(fā)布/訂閱模式
  • 分布式 — Redis Cluster 集群

官方資料

Redis官網(wǎng):http://redis.io/

Redis官方文檔:http://redis.io/documentation

Redis下載:http://redis.io/download

參考資料:
http://redisdoc.com/index.html
https://www.cnblogs.com/kismetv/p/8654978.html#t41
https://www.pdai.tech/md/outline/x-outline.html#nosql-db—redis%E8%AF%A6%E8%A7%A3


使用場景

熱點(diǎn)數(shù)據(jù)的緩存

緩存是Redis中最常見的應(yīng)用場景,Redis讀寫性能優(yōu)異,在高并發(fā)服務(wù)中成為首選的緩存組件,并且Redis支持事務(wù),能保證數(shù)據(jù)的一致性。

限時(shí)活動

Redis中可以使用expire命令射在一個(gè)鍵的生存時(shí)間,過期之后Redis會自動刪除,利用這一特性可以應(yīng)用在限時(shí)搶購活動、獲取手機(jī)驗(yàn)證碼等常見業(yè)務(wù)場景。

計(jì)數(shù)器相關(guān)問題

Redis中有incr命令可以實(shí)現(xiàn)原子性的遞增,可以運(yùn)用于高并發(fā)的秒殺活動、分布式序列號的生成以及其他限制次數(shù)的業(yè)務(wù)中。

分布式鎖

Redis中有setnx命令,該命令全寫為:“set if not exists”,如果不存在則設(shè)置成功并返回1,否則返回0。在分布式集群系統(tǒng)中,一個(gè)定時(shí)任務(wù)可能會在多個(gè)機(jī)器上運(yùn)行,為了保持?jǐn)?shù)據(jù)的一致性,可以先在定時(shí)任務(wù)中運(yùn)用setnx命令設(shè)置一個(gè)鎖,如果成功設(shè)置則執(zhí)行任務(wù),否則說明任務(wù)已經(jīng)開始執(zhí)行,從而避免多個(gè)任務(wù)同時(shí)執(zhí)行對數(shù)據(jù)產(chǎn)生影響。該分布式鎖常運(yùn)用于大型秒殺系統(tǒng)。

延時(shí)操作

在電商系統(tǒng)中,當(dāng)用戶下單,訂單產(chǎn)生之后會占用庫存,可以設(shè)置一個(gè)時(shí)效檢驗(yàn)用戶是否已經(jīng)付款購買,如果超時(shí)則讓該單據(jù)失效,同時(shí)還原庫存。在Redis2.8.0版本之后還提供了Keyspace Notifications功能,運(yùn)行客戶端訂閱Pub/Sub頻道,接受Redis數(shù)據(jù)集的變化事件。通過上述我們就可以解決實(shí)際應(yīng)用的問題,當(dāng)訂單產(chǎn)生時(shí),設(shè)置一個(gè)key,同時(shí)設(shè)置15分鐘后過期,在后臺實(shí)現(xiàn)一個(gè)監(jiān)聽器,監(jiān)聽key的時(shí)效,key失效后仍沒有完成訂單則取消訂單。

點(diǎn)贊、好友等相互關(guān)系的存儲

Redis利用集合的一些命令,如求交集、并集、差集等。

在微信朋友圈中,每個(gè)用戶的好友存入一個(gè)集合中,很容易實(shí)現(xiàn)求出兩個(gè)人的共同好友,并將共同好友的信息呈現(xiàn)在朋友圈中。


二、數(shù)據(jù)類型:5種基礎(chǔ)的數(shù)據(jù)類型

Redis數(shù)據(jù)結(jié)構(gòu)簡介

Redis所有的key(鍵)都是字符串,基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)包括String、List、Set、Zset、Hash。每種結(jié)構(gòu)都至少有兩種編碼,這樣的好處在于:一方面接口與實(shí)現(xiàn)實(shí)現(xiàn)了分離,需要增加或改變內(nèi)部編碼時(shí),用戶不受影響,另一方面可以根據(jù)不同的應(yīng)用場景切換內(nèi)部編碼,提高效率。

結(jié)構(gòu)類型結(jié)構(gòu)存儲的值結(jié)構(gòu)的讀寫能力
String字符串二進(jìn)制安全的,可以包含任何數(shù)據(jù),比如jpg格式的圖片或者可以序列化的對象,一個(gè)鍵最大能存儲512MB對整個(gè)字符串或字符串的一部分進(jìn)行操作;對整數(shù)或浮點(diǎn)數(shù)進(jìn)行自增或自減操作;
List列表本質(zhì)是鏈表,鏈表上的每個(gè)節(jié)點(diǎn)都包含一個(gè)字符串鏈表的兩端都可以進(jìn)行push和pop操作,讀取單個(gè)或多個(gè)數(shù)據(jù)
Set集合包含字符串的無序集合并且存儲的值唯一,集合是通過哈希表實(shí)現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是 O(1)。字符串的集合,包含基礎(chǔ)的方法有看是否存在添加、獲取、刪除;還包含計(jì)算交集、并集、差集等
Zset有序集合包含鍵值對的無序散列表,是一個(gè) string 類型的field 和 value 的映射表,hash 特別適合用于存儲對象。添加、獲取、刪除單個(gè)元素
Hash散列表string 類型元素的集合,且不允許重復(fù)的成員,不同的是每個(gè)元素都會關(guān)聯(lián)一個(gè) double 類型的分?jǐn)?shù)。redis 正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。字符串成員與浮點(diǎn)數(shù)分?jǐn)?shù)之間的有序映射;元素的排列順序由分?jǐn)?shù)的大小決定;包含方法有添加、獲取、刪除單個(gè)元素以及根據(jù)分值范圍或成員來獲取元素

基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)詳解

String字符串

String是redis中最基本的數(shù)據(jù)類型,一個(gè)key對應(yīng)一個(gè)value。

  • 命令使用

    命令簡述使用
    SET將字符串值 value 關(guān)聯(lián)到 key,如果 key 已經(jīng)持有其他值, SET 就覆寫舊值, 無視類型SET key value
    SETNX“Set If Not Exists”的縮寫,鍵key 不存在的情況,將key設(shè)為 valule,并返回1,若鍵 key 已經(jīng)存在, 則 SETNX 命令不做任何動作,并返回0SETNX key value
    SETEX將鍵 key 的值設(shè)置為 value , 并將鍵 key 的生存時(shí)間設(shè)置為 seconds 秒鐘。如果鍵 key 已經(jīng)存在, 那么 SETEX 命令將覆蓋已有的值SETEX key seconds value
    PSETEX這個(gè)命令和 SETEX 命令相似, 但它以毫秒為單位設(shè)置 key 的生存時(shí)間, 而不是像 SETEX 命令那樣以秒為單位進(jìn)行設(shè)置PSETEX key milliseconds value
    GET返回與鍵 key 相關(guān)聯(lián)的字符串值GET key
    GETSET將鍵 key 的值設(shè)為 value , 并返回鍵 key 在被設(shè)置之前的舊值GETSET key value
    APPEND如果鍵 key 已經(jīng)存在并且它的值是一個(gè)字符串, APPEND 命令將把 value 追加到鍵 key 現(xiàn)有值的末尾。如果key不存在,則該命令與SET,命令效果相同APPEND key value
    INCR為鍵 key 儲存的數(shù)字值加上一。如果鍵 key 不存在, 那么它的值會先被初始化為 0 , 然后再執(zhí)行 INCR 命令。如果鍵 key 儲存的值不能被解釋為數(shù)字, 那么 INCR 命令將返回一個(gè)錯誤INCR key
    INCRBY為鍵 key 儲存的數(shù)字值加上增量 incrementINCRBY key increment
    INCRBYFLOAT為鍵 key 儲存的值加上浮點(diǎn)數(shù)增量 incrementINCRBYFLOAT key increment
    DECR為鍵 key 儲存的數(shù)字值減去一。如果鍵 key 不存在, 那么鍵 key 的值會先被初始化為 0 , 然后再執(zhí)行 DECR 操作。如果鍵 key 儲存的值不能被解釋為數(shù)字, 那么 DECR 命令將返回一個(gè)錯誤DECR key
    DECRBY將鍵 key 儲存的整數(shù)值減去減量 decrementDECRBY key decrement
    MSET同時(shí)為多個(gè)鍵設(shè)置值。MSET key value [key value …]
    MGET返回給定的一個(gè)或多個(gè)字符串鍵的值。MGET key [key …]
  • 實(shí)戰(zhàn)場景

    • 緩存:把常用消息,字符串,照片等信息存入Redis中,把Redis當(dāng)作緩存層,Mysql作為持久層,以減輕Musql的讀寫壓力
    • 計(jì)數(shù)器:Redis是單線程模型,一個(gè)命令執(zhí)行完才會執(zhí)行下一個(gè)。
    • Session:Spring Session + Redis實(shí)現(xiàn)Session共享。

List列表

實(shí)質(zhì)為鏈表,用雙端鏈表實(shí)現(xiàn)

  • 命令使用

    命令簡述使用
    LPUSH/RPUSH將一個(gè)或多個(gè)值 value 插入到列表 key 的表頭(表尾)LPUSH /RPUSH key value [value …]
    LPUSHX將值 value 插入到列表 key 的表頭,當(dāng)且僅當(dāng) key 存在并且是一個(gè)列表。和 LPUSH 命令相反,當(dāng) key 不存在時(shí) LPUSHX命令什么也不做。LPUSHX key value
    LPOP/RPOP移除并返回列表 key 的頭(尾)元素。LPOP /RPOP key
    RPOPLPUSH將列表 source 中的最后一個(gè)元素(尾元素)彈出,并返回給客戶端。 將 source 彈出的元素插入到列表 destination ,作為 destination 列表的的頭元素。RPOPLPUSH source destination
    LREM根據(jù)參數(shù) count 的值,移除列表中與參數(shù) value 相等的元素;count > 0: 從表頭開始向表尾搜索,移除與value相等的元素,數(shù)量為count; count < 0 : 從表尾開始向表頭搜索,移除與 value 相等的元素,數(shù)量為 count 的絕對值。count = 0 : 移除表中所有與 value 相等的值。LREM key count value
    LLEN返回列表 key 的長度。LLEN key
    LINDEX返回列表 key 中,下標(biāo)為 index 的元素。LINDEX key index
    LSET將列表 key 下標(biāo)為 index 的元素的值設(shè)置為 value 。LSET key index value
    LRANGE返回列表 key 中指定區(qū)間內(nèi)的元素,區(qū)間以偏移量 start 和 stop 指定。LRANGE key start stop
    LTRIM對一個(gè)列表進(jìn)行修剪(trim),就是說,讓列表只保留指定區(qū)間內(nèi)的元素,不在指定區(qū)間之內(nèi)的元素都將被刪除。LTRIM key start stop
    BLPOP它是 LPOP key 命令的阻塞版本,當(dāng)給定列表內(nèi)沒有任何元素可供彈出的時(shí)候,連接將被 BLPOP 命令阻塞,直到等待超時(shí)或發(fā)現(xiàn)可彈出元素為止。BLPOP key [key …] timeout
  • 列表的使用技巧

    • lpush+lpop=Stack(棧)
    • lpush+rpop=Queue(隊(duì)列)
    • lpush+ltrim=Capped Collection(有限列表)
    • push+brpop=Message Queue(消息隊(duì)列)
  • 實(shí)戰(zhàn)場景

    • 朋友圈:lpush新的動態(tài),lpop展示新的動態(tài)
    • 消息隊(duì)列:利用RPOPLPUSH實(shí)現(xiàn)一個(gè)安全的消息隊(duì)列,不僅返回一個(gè)消息同時(shí)將這個(gè)消息備份到一個(gè)備份列表中,如果使用LPUSH將消息放入隊(duì)列,而另一個(gè)客戶端中 BRPOP取出隊(duì)列,該隊(duì)列方式是不安全的,如果一個(gè)客戶端取出消息后崩潰,而未處理完的消息也將因此丟失
    • 事件提醒:有時(shí)候?yàn)榱说却粋€(gè)新的元素到達(dá)數(shù)據(jù)中,需要使用輪詢的方式對數(shù)據(jù)進(jìn)行探查,另一種更好的方式是,使用系統(tǒng)提供的阻塞原語,在新元素到達(dá)時(shí)立即進(jìn)行處理,而新元素沒達(dá)到時(shí),一直阻塞,避免輪詢占用資源

    Set集合

    Redis 的 Set 是 String 類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。

    通過哈希表實(shí)現(xiàn)的,所以添加,刪除,查找的復(fù)雜度都是 O(1)。

    • 命令使用

      命令簡述使用
      SADD將一個(gè)或多個(gè) member 元素加入到集合 key 當(dāng)中,已經(jīng)存在于集合的 member 元素將被忽略SADD key member [member …]
      SISMEMBER判斷 member 元素是否集合 key 的成員;如果 member 元素是集合的成員,返回 1 。 如果 member 元素不是集合的成員,或 key 不存在,返回 0SISMEMBER key member
      SPOP移除并返回集合中的一個(gè)隨機(jī)元素。SPOP key
      SRANDMEMBER只提供 key 參數(shù)時(shí),返回一個(gè)元素; 如果提供了 count 參數(shù),那么返回一個(gè)數(shù)組;如果集合為空,返回空數(shù)組SRANDMEMBER key [count]
      SREM移除集合 key 中的一個(gè)或多個(gè) member 元素,不存在的 member 元素會被忽略。SREM key member [member …]
      SMOVE將 member 元素從 source 集合移動到 destination 集合。SMOVE source destination member
      SCARD返回集合 key 的基數(shù)(集合中元素的數(shù)量)。SCARD key
      SMEMBERS返回集合 key 中的所有成員。SMEMBERS key
      SINTER返回一個(gè)集合的全部成員,該集合是所有給定集合的交集。不存在的 key 被視為空集。當(dāng)給定集合當(dāng)中有一個(gè)空集時(shí),結(jié)果也為空集(根據(jù)集合運(yùn)算定律)。SINTER key [key …]
      SINTERSTORE這個(gè)命令類似于 SINTER命令,但它將結(jié)果保存到 destination 集合,而不是簡單地返回結(jié)果集。SINTERSTORE destination key [key …]
      SUNION返回一個(gè)集合的全部成員,該集合是所有給定集合的并集。SUNION key [key …]
      SDIFF返回一個(gè)集合的全部成員,該集合是所有給定集合之間的差集。SDIFF key [key …]
    • 實(shí)戰(zhàn)場景

      • 標(biāo)簽(tag):給用戶添加標(biāo)簽,或者用戶給消息添加標(biāo)簽,這樣有同一標(biāo)簽或者類似標(biāo)簽的可以給推薦關(guān)注的事或者關(guān)注的人
      • 點(diǎn)贊,或點(diǎn)踩,收藏等,可以放到set中實(shí)現(xiàn)

Hash散列

一個(gè) string 類型的 field(字段) 和 value(值) 的映射表,hash 特別適合用于存儲對象。

  • 命令使用

    命令簡述使用
    HSET將哈希表 hash 中域 field 的值設(shè)置為 value 。如果給定的哈希表并不存在, 那么一個(gè)新的哈希表將被創(chuàng)建并執(zhí)行 HSET 操作。如果域 field 已經(jīng)存在于哈希表中, 那么它的舊值將被新值 value 覆蓋。HSET hash field value
    HSETNX當(dāng)且僅當(dāng)域 field 尚未存在于哈希表的情況下, 將它的值設(shè)置為 value ;如果給定域已經(jīng)存在于哈希表當(dāng)中, 那么命令將放棄執(zhí)行設(shè)置操作;如果哈希表 hash 不存在, 那么一個(gè)新的哈希表將被創(chuàng)建并執(zhí)行 HSETNX 命令HSETNX hash field value
    HGETHGET 命令在默認(rèn)情況下返回給定域的值。HGET hash field
    HEXISTS檢查給定域 field 是否存在于哈希表 hash 當(dāng)中。HEXISTS hash field
    HDEL刪除哈希表 key 中的一個(gè)或多個(gè)指定域,不存在的域?qū)⒈缓雎浴?/td>HDEL key field [field …]
    HLEN返回哈希表 key 中域的數(shù)量。HLEN key
    HINCRBY為哈希表 key 中的域 field 的值加上增量 increment 。HINCRBY key field increment
    HMSET同時(shí)將多個(gè) field-value (域-值)對設(shè)置到哈希表 key 中。HMSET key field value [field value …]
    HMGET返回哈希表 key 中,一個(gè)或多個(gè)給定域的值。HMGET key field [field …]
    HVALS返回哈希表 key 中所有域的值。HVALS key
    HGETALL返回哈希表 key 中,所有的域和值。HGETALL key
  • 實(shí)戰(zhàn)場景

    • 緩存:可以更方便更直觀地維護(hù)緩存信息,如果用戶信息等,并且比String更節(jié)省空間,方便管理。

Zset有序集合

Redis 有序集合和集合一樣也是 string 類型元素的集合,且不允許重復(fù)的成員。不同的是每個(gè)元素都會關(guān)聯(lián)一個(gè) double 類型的分?jǐn)?shù)。redis 正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。

  • 命令使用

    命令簡述使用
    ZADD將一個(gè)或多個(gè) member 元素及其 score 值加入到有序集 key 當(dāng)中。ZADD key score member [[score member] [score member] …]
    ZSCORE返回有序集 key 中,成員 member 的 score 值。ZSCORE key member
    ZINCRBY為有序集 key 的成員 member 的 score 值加上增量 increment 。ZINCRBY key increment member
    ZREVRANGE返回有序集 key 中,指定區(qū)間內(nèi)的成員。ZREVRANGE key start stop [WITHSCORES]
    ZREM移除有序集 key 中的一個(gè)或多個(gè)成員,不存在的成員將被忽略。ZREM key member [member …]
    ZRANK返回有序集 key 中成員 member 的排名。其中有序集成員按 score 值遞增(從小到大)順序排列。ZRANK key member
    ZREVRANK返回有序集 key 中成員 member 的排名。其中有序集成員按 score 值遞減(從大到小)排序。ZREVRANK key member
    ZREMRANGEBYRANK移除有序集 key 中,指定排名(rank)區(qū)間內(nèi)的所有成員。ZREMRANGEBYRANK key start stop
    ZREMRANGEBYSCORE移除有序集 key 中,所有 score 值介于 min 和 max 之間(包括等于 min 或 max )的成員。ZREMRANGEBYSCORE key min max
  • 實(shí)戰(zhàn)場景

    • 排行榜:可以應(yīng)用于一些需要排序的排行榜中,例如常見的微博熱搜等。

三、特殊的數(shù)據(jù)類型

三種特殊類型講解

除了上文中的五種基礎(chǔ)數(shù)據(jù)類型,還有三種特殊的數(shù)據(jù)類型分別是 **HyperLogLog(基數(shù)統(tǒng)計(jì)), Bitmaps (位圖) 和 geospatial (地理位置)。

HyperLogLog

Redis 2.8.9 版本更新:新增Hyperloglog 數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)存入后無法取出,只能用于基數(shù)的統(tǒng)計(jì)

  • 什么是基數(shù)?

    例如,A={1, 3, 5, 7, 5, 7, 8,}那么基數(shù)(不重復(fù)的元素)為5,基數(shù)集為{1,3,5,7,8};基數(shù)估計(jì)就是在誤差可接受的范圍內(nèi),快速計(jì)算基數(shù)。

  • HyperLogLog 基數(shù)統(tǒng)計(jì)用來解決什么問題

    作為一個(gè)高級不精確去重的數(shù)據(jù)結(jié)構(gòu),它常常用于統(tǒng)計(jì)數(shù)據(jù)時(shí)去重的操作。特點(diǎn)是可以利用極小的內(nèi)存空間完成獨(dú)立總數(shù)的統(tǒng)計(jì),比如注冊 IP 數(shù)、每日訪問 IP 數(shù)、頁面實(shí)時(shí)UV、在線用戶數(shù),共同好友數(shù)等。

  • 優(yōu)勢體現(xiàn)在哪?

    一個(gè)大型網(wǎng)站中需要統(tǒng)計(jì)IP數(shù),粗略計(jì)算一個(gè)IP消耗10字節(jié),100萬的IP就是15MB,而HyperLogLog在Redis中每個(gè)鍵占用的內(nèi)容都是12字節(jié),理論存儲近似接近264個(gè)值,它基于一個(gè)基數(shù)估算的算法,只能比較準(zhǔn)確的估算出基數(shù),仍然會存在一定不可避免的誤差,但一個(gè)帶有0.81%誤差的近似值在實(shí)際應(yīng)用場景也是可以接受的。

  • 命令使用

    命令簡述使用返回值
    PFADD將任意數(shù)量的元素添加到指定的 HyperLogLog 里面PFADD key element [element …]整數(shù)回復(fù): 如果 HyperLogLog 的內(nèi)部儲存被修改了, 那么返回 1 , 否則返回 0
    PFCOUNT命令作用于單個(gè)鍵時(shí), 返回儲存在給定鍵的 HyperLogLog 的近似基數(shù), 如果鍵不存在, 那么返回 0PFCOUNT key [key …]返回的可見集合(observed set)基數(shù)并不是精確值, 而是一個(gè)帶有 0.81% 標(biāo)準(zhǔn)錯誤(standard error)的近似值。
    PFMERGE將多個(gè) HyperLogLog 合并(merge)為一個(gè) HyperLogLog , 合并后的 HyperLogLog 的基數(shù)接近于所有輸入 HyperLogLog 的可見集合(observed set)的并集。PFMERGE destkey sourcekey [sourcekey …]字符串回復(fù):返回 OK

Bitmap

itmap 即位圖數(shù)據(jù)結(jié)構(gòu),都是操作二進(jìn)制位來進(jìn)行記錄,只有0 和 1 兩個(gè)狀態(tài)。

  • Bitmap位存儲可以解決什么問題?

    由于只能存儲0和1兩個(gè)數(shù)據(jù),所以它適用于一些只需記錄狀態(tài)而不需要記錄具體數(shù)值的數(shù)據(jù),如用戶登錄狀態(tài)等。

  • 命令使用

    命令簡述使用返回值
    SETBIT對 key 所儲存的字符串值,設(shè)置或清除指定偏移量上的位(bit);位的設(shè)置或清除取決于 value 參數(shù),可以是 0 也可以是 1 ;當(dāng) key 不存在時(shí),自動生成一個(gè)新的字符串值。字符串會進(jìn)行伸展(grown)以確保它可以將 value 保存在指定的偏移量上,當(dāng)字符串值進(jìn)行伸展時(shí),空白位置以 0 填充。SETBIT key offset value指定偏移量原來儲存的位
    GETBIT對 key 所儲存的字符串值,獲取指定偏移量上的位(bit),當(dāng) offset 比字符串值的長度大,或者 key 不存在時(shí),返回 0 。GETBIT key offset字符串值指定偏移量上的位(bit)
    BITCOUNT計(jì)算給定字符串中,被設(shè)置為 1 的比特位的數(shù)量。BITCOUNT key [start] [end]被設(shè)置為 1 的位的數(shù)量。
    BITPOS返回位圖中第一個(gè)值為 bit 的二進(jìn)制位的位置。BITPOS key bit [start] [end]整數(shù)回復(fù)。
    BITOP對一個(gè)或多個(gè)保存二進(jìn)制位的字符串 key 進(jìn)行位元操作,并將結(jié)果保存到 destkey 上。operation 可以是 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種: BITOP AND destkey key [key ...] :對一個(gè)或多個(gè) key 求邏輯并,并將結(jié)果保存到 destkey ;BITOP OR destkey key [key ...] :對一個(gè)或多個(gè) key 求邏輯或,并將結(jié)果保存到 destkey ; BITOP XOR destkey key [key ...] :對一個(gè)或多個(gè) key 求邏輯異或,并將結(jié)果保存到 destkey ; BITOP NOT destkey key:對給定 key 求邏輯非,并將結(jié)果保存到 destkeyBITOP operation destkey key [key …]保存到 destkey 的字符串的長度,和輸入 key 中最長的字符串長度相等。
  • 實(shí)戰(zhàn)應(yīng)用

    • 使用Bitmap實(shí)現(xiàn)用戶上線次數(shù)的統(tǒng)計(jì)

      假設(shè)我們希望記錄網(wǎng)站上用戶的上線頻率,可以通過SETBIT key offset value 和 BITCOUNT key [start] [end]來實(shí)現(xiàn)。

      比如,當(dāng)用戶某一天上線時(shí),我們就使用SETBIT key offset value ,將用戶名username作為key,將那天的上線日作為offset參數(shù),并將這個(gè)offset賦值value為1

      當(dāng)需要計(jì)算某用戶的上線次數(shù)時(shí),就使用 BITCOUNT key [start] [end]命令,執(zhí)行:BITCOUNT username計(jì)算得出結(jié)果就是該用戶的總上線天數(shù)。

      性能分析

      上述案例中,即使網(wǎng)站運(yùn)行10年,每個(gè)用戶存儲的信息占用的空間也不過10*365bit,也就是每個(gè)用戶456字節(jié),對于這種大小的數(shù)據(jù)處理速度相當(dāng)快速。


Geospatial

Redis 3.2 .0版本更新:新增Geospatial 數(shù)據(jù)結(jié)構(gòu),可以用于推算地理位置的信息

常用命令

  • GEOADD key longitude latitude member [longitude latitude member …]

    GEOADD 命令以標(biāo)準(zhǔn)的 x,y 格式接受參數(shù), 所以用戶必須先輸入經(jīng)度, 然后再輸入緯度。 GEOADD 能夠記錄的坐標(biāo)是有限的: 非常接近兩極的區(qū)域是無法被索引的。 精確的坐標(biāo)限制由 EPSG:900913 / EPSG:3785 / OSGEO:41001 等坐標(biāo)系統(tǒng)定義, 具體如下:

    • 有效的經(jīng)度介于 -180 度至 180 度之間。
    • 有效的緯度介于 -85.05112878 度至 85.05112878 度之間。
    127.0.0.1:6379> geoadd china:city 144.05 22.52 shenzhen 120.16 30.24 hangzhou 108.96 34.26 xian (integer) 3
  • GEOPOS key member [member …]

    從鍵里面返回所有給定位置元素的位置(經(jīng)度和緯度)。

    因?yàn)?GEOPOS 命令接受可變數(shù)量的位置元素作為輸入, 所以即使用戶只給定了一個(gè)位置元素, 命令也會返回?cái)?shù)組回復(fù)。

    GEOPOS 命令返回一個(gè)數(shù)組, 數(shù)組中的每個(gè)項(xiàng)都由兩個(gè)元素組成: 第一個(gè)元素為給定位置元素的經(jīng)度, 而第二個(gè)元素則為給定位置元素的緯度。 當(dāng)給定的位置元素不存在時(shí), 對應(yīng)的數(shù)組項(xiàng)為空值。

    127.0.0.1:6379> GEOPOS china:city shenzhen hangzhou 1) 1) "144.05000120401382446"2) "22.5200000879503861" 2) 1) "120.1600000262260437"2) "30.2400003229490224"
  • GEODIST key member1 member2 [unit]

    返回兩個(gè)給定位置之間的距離。

    指定單位的參數(shù) unit 必須是以下單位的其中一個(gè):

    • m 表示單位為米。
    • km 表示單位為千米。
    • mi 表示單位為英里。
    • ft 表示單位為英尺。

    如果用戶沒有顯式地指定單位參數(shù), 那么 GEODIST 默認(rèn)使用米作為單位。

    127.0.0.1:6379> GEODIST china:city shenzhen hangzhou "2524417.0471"
  • GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]

    以給定的經(jīng)緯度為中心, 返回鍵包含的位置元素當(dāng)中, 與中心的距離不超過給定最大距離的所有位置元素。

    在給定以下可選項(xiàng)時(shí), 命令會返回額外的信息:

    • WITHDIST : 在返回位置元素的同時(shí), 將位置元素與中心之間的距離也一并返回。 距離的單位和用戶給定的范圍單位保持一致。
    • WITHCOORD : 將位置元素的經(jīng)度和維度也一并返回。
    • WITHHASH : 以 52 位有符號整數(shù)的形式, 返回位置元素經(jīng)過原始 geohash 編碼的有序集合分值。 這個(gè)選項(xiàng)主要用于底層應(yīng)用或者調(diào)試, 實(shí)際中的作用并不大。

    命令默認(rèn)返回未排序的位置元素。 通過以下兩個(gè)參數(shù), 用戶可以指定被返回位置元素的排序方式:

    • ASC : 根據(jù)中心的位置, 按照從近到遠(yuǎn)的方式返回位置元素。
    • DESC : 根據(jù)中心的位置, 按照從遠(yuǎn)到近的方式返回位置元素。

    在默認(rèn)情況下, GEORADIUS 命令會返回所有匹配的位置元素。 雖然用戶可以使用 COUNT <count> 選項(xiàng)去獲取前 N 個(gè)匹配元素, 但是因?yàn)槊钤趦?nèi)部可能會需要對所有被匹配的元素進(jìn)行處理, 所以在對一個(gè)非常大的區(qū)域進(jìn)行搜索時(shí), 即使只使用 COUNT 選項(xiàng)去獲取少量元素, 命令的執(zhí)行速度也可能會非常慢。 但是從另一方面來說, 使用 COUNT 選項(xiàng)去減少需要返回的元素?cái)?shù)量, 對于減少帶寬來說仍然是非常有用的。

    127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km 1) "xian" 2) "hangzhou" 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist 1) 1) "xian"2) "483.8340" 2) 1) "hangzhou"2) "977.5143" 127.0.0.1:6379> geoadd china:city 118.76 32.04 manjing 112.55 37.86 taiyuan 123.43 41.80 shenyang (integer) 3 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist count 2 1) 1) "xian"2) "483.8340" 2) 1) "manjing"2) "864.9816" 127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 2 1) 1) "xian"2) "483.8340"3) 1) "108.96000176668167114"2) "34.25999964418929977" 2) 1) "manjing"2) "864.9816"3) 1) "118.75999957323074341"2) "32.03999960287850968"
  • GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count]

    這個(gè)命令和 GEORADIUS 命令一樣, 都可以找出位于指定范圍內(nèi)的元素, 但是 GEORADIUSBYMEMBER 的中心點(diǎn)是由給定的位置元素決定的, 而不是像 GEORADIUS 那樣, 使用輸入的經(jīng)度和緯度來決定中心點(diǎn)。

    127.0.0.1:6379> GEORADIUSBYMEMBER china:city taiyuan 1000 km withcoord withdist 1) 1) "manjing"2) "859.5256"3) 1) "118.75999957323074341"2) "32.03999960287850968" 2) 1) "taiyuan"2) "0.0000"3) 1) "112.54999905824661255"2) "37.86000073876942196" 3) 1) "xian"2) "514.2264"3) 1) "108.96000176668167114"2) "34.25999964418929977"

四、數(shù)據(jù)庫

常用指令

命令簡述使用返回值
EXISTS檢查給定 key 是否存在EXISTS key若 key 存在,返回 1 ,否則返回 0
TYPE返回 key 所儲存的值的類型TYPE keynone (key不存在) 、string (字符串) 、list (列表) 、set (集合) 、zset (有序集) 、hash (哈希表) 、stream (流)
RENAME將 key 改名為 newkey ,當(dāng) key 和 newkey 相同,或者 key 不存在時(shí),返回一個(gè)錯誤。RENAME key newkey改名成功時(shí)提示 OK ,失敗時(shí)候返回一個(gè)錯誤。
RENAMENX當(dāng)且僅當(dāng) newkey 不存在時(shí),將 key 改名為 newkey ;當(dāng) key 不存在時(shí),返回一個(gè)錯誤RENAMENX key newkey修改成功時(shí),返回 1 ; 如果 newkey 已經(jīng)存在,返回 0 。
MOVE將當(dāng)前數(shù)據(jù)庫的 key 移動到給定的數(shù)據(jù)庫 db 當(dāng)中,如果當(dāng)前數(shù)據(jù)庫(源數(shù)據(jù)庫)和給定數(shù)據(jù)庫(目標(biāo)數(shù)據(jù)庫)有相同名字的給定 key ,或者 key 不存在于當(dāng)前數(shù)據(jù)庫,那么 MOVE 沒有任何效果MOVE key db移動成功返回 1 ,失敗則返回 0 。
DEL刪除給定的一個(gè)或多個(gè) key ,不存在的 key 會被忽略。DEL key [key …]被刪除 key 的數(shù)量
DBSIZE返回當(dāng)前數(shù)據(jù)庫的 key 的數(shù)量。DBSIZE當(dāng)前數(shù)據(jù)庫的 key 的數(shù)量。
KEYS查找所有符合給定模式 pattern 的 key , 比如說: KEYS * 匹配數(shù)據(jù)庫中所有 key ;KEYS h?llo 匹配 hello , hallo 和 hxllo 等; KEYS h*llo 匹配 hllo 和 heeeeello 等; KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hilloKEYS pattern符合給定模式的 key 列表。
FLUSHDB清空當(dāng)前數(shù)據(jù)庫中的所有 key,此命令從不失敗FLUSHDB總是返回 OK 。
FLUSHALL清空整個(gè) Redis 服務(wù)器的數(shù)據(jù)(刪除所有數(shù)據(jù)庫的所有 key ),此命令從不失敗FLUSHALL總是返回 OK 。
SELECT切換到指定的數(shù)據(jù)庫,數(shù)據(jù)庫索引號 index 用數(shù)字值指定,以 0 作為起始索引值,默認(rèn)使用 0 號數(shù)據(jù)庫SELECT indexOK
SWAPDB db1 db2對換指定的兩個(gè)數(shù)據(jù)庫, 使得兩個(gè)數(shù)據(jù)庫的數(shù)據(jù)立即互換SWAPDB db1 db2OK

設(shè)置鍵的生存時(shí)間或過期時(shí)間

通過EXPIRE 或者PEXPIRE命令,客戶端可以以秒或者毫秒精度為數(shù)據(jù)庫中的某個(gè)鍵設(shè)置生存時(shí)間(Time To Live,后面簡稱TTL),在經(jīng)過了指定的時(shí)間后服務(wù)器就會刪除生存時(shí)間為0的鍵。

127.0.0.1:6379> SET key value OK 127.0.0.1:6379> EXPIRE key 5 //設(shè)置過期時(shí)間為5s (integer) 1 127.0.0.1:6379> get key //5s之內(nèi) "value" 127.0.0.1:6379> get key //5s后 (nil)

在之前提到的SETEX命令可以在設(shè)置一個(gè)字符串鍵的同時(shí)為鍵設(shè)置過期時(shí)間,這個(gè)命令是一個(gè)限制了類型的命令,只適用于字符串類型的數(shù)據(jù),但SETEX命令設(shè)置過期時(shí)間的原理與EXPIRE命令設(shè)置過期時(shí)間的原理是完全一致的。

TTL 和PTTL命令可以接收一個(gè)待用生存時(shí)間或者過期時(shí)間的鍵,返回這個(gè)鍵的剩余生存時(shí)間。

127.0.0.1:6379> EXPIRE key 1000 (integer) 1 127.0.0.1:6379> TTL key (integer) 996

EXPIREAT key timestamp命令可以設(shè)置鍵key的過期時(shí)間為timestamp指定的秒數(shù)時(shí)間戳

PEXPIREAT key timestamp命令可以設(shè)置鍵key的過期時(shí)間為timestamp指定的毫秒數(shù)時(shí)間戳

雖然有多種不同的單位以及不同形式的設(shè)置命令,但實(shí)際上EXPIRE PEXPIRE EXPIREAT都是基于PEXPIREAT來實(shí)現(xiàn)的,所以客戶端中無論執(zhí)行的是上面四個(gè)指令中的哪一個(gè),經(jīng)過轉(zhuǎn)換后,執(zhí)行的效果與PEXPIREAT指令效果相同。

保存設(shè)置時(shí)間

RedisDB結(jié)構(gòu)的expires字典保存了數(shù)據(jù)庫中所有鍵的過期時(shí)間,稱之為過期字典

  • 過期字典的鍵是一個(gè)指針,這個(gè)指針指向鍵空間的某個(gè)鍵對象,即某個(gè)數(shù)據(jù)庫的鍵
  • 過期字典的值是一個(gè)long long類型的整數(shù),這個(gè)整數(shù)保存了鍵指向的數(shù)據(jù)庫鍵的過期時(shí)間——一個(gè)毫秒精度的UNIX時(shí)間戳

移除過期時(shí)間

PERSIST命令可以移除一個(gè)鍵的過期時(shí)間

127.0.0.1:6379> EXPIRE key 1989 (integer) 1 127.0.0.1:6379> TTL key (integer) 1987 127.0.0.1:6379> PERSIST key (integer) 1 127.0.0.1:6379> ttl key (integer) -1

PERSIST命令就是PEXPIREAT命令的反操作,PERSIST命令在過期字典中查找給定的鍵,并接觸鍵和值(過期時(shí)間)在過期字典中的關(guān)聯(lián)。


過期數(shù)據(jù)的刪除策略

如果一個(gè)鍵過期了,那么它什么時(shí)候會被刪除呢?

這個(gè)問題有三種可能的答案,分別代表了三種不同的過期刪除策略:

  • 定時(shí)刪除:在設(shè)置鍵的過期時(shí)間時(shí),創(chuàng)建一個(gè)定時(shí)器(timer),讓定時(shí)器在鍵過期時(shí)間來臨時(shí),立刻執(zhí)行對鍵的刪除
  • 惰性刪除:放任鍵過期不管,但是每次從鍵空間中獲取鍵,都會檢查取得的鍵是否過期,如果過期,就刪除該鍵,否則就返回該鍵
  • 定期刪除:每隔一段時(shí)間,程序就對數(shù)據(jù)庫進(jìn)行一次檢查,刪除里面的過期鍵,至于要刪除多少過期鍵,檢查多少個(gè)數(shù)據(jù)庫,如何檢查(因?yàn)閿?shù)據(jù)量龐大時(shí),不可能每個(gè)數(shù)據(jù)都進(jìn)行檢查,要設(shè)計(jì)算法隨機(jī)檢查),則由算法決定。

以上三種刪除策略中,第一、三種屬于主動刪除,第二種則屬于被動刪除。


定時(shí)刪除

定時(shí)刪除策略對內(nèi)存是最友好的,屬于用時(shí)間換空間的一種策略,通過使用定時(shí)器,定時(shí)刪除鍵會保證過期鍵盡快被刪除,并釋放過期鍵占用的內(nèi)存。

另一方面,定時(shí)刪除策略的缺點(diǎn)是,它對CPU時(shí)間是最不友好的,在過期鍵數(shù)量龐大時(shí),刪除過期鍵會占用一部分CPU處理時(shí)間,內(nèi)存不緊張但是CPU壓力大時(shí),采用這種策略無疑會對服務(wù)器響應(yīng)時(shí)間和吞吐量產(chǎn)生相當(dāng)明顯的負(fù)面影響。

如果有大量的請求命令在等待服務(wù)器處理,而此時(shí)服務(wù)器當(dāng)前并不缺少內(nèi)存, 那么服務(wù)器應(yīng)該優(yōu)先將CPU占用分配給請求處理,而不是用在刪除過期鍵上。

除此之外,創(chuàng)建一個(gè)定時(shí)器需要用到Redis服務(wù)器中的時(shí)間事件,當(dāng)前時(shí)間事件的實(shí)現(xiàn)方式——無序鏈表,查找一個(gè)事件的時(shí)間復(fù)雜度為O(N),并不能高效地處理時(shí)間事件。

因此要讓服務(wù)器創(chuàng)建大量的定時(shí)器來執(zhí)行定時(shí)刪除策略,在現(xiàn)階段是不現(xiàn)實(shí)的。

惰性刪除

惰性刪除策略對CPU時(shí)間來說是最友好的:程序只會在取出鍵的時(shí)候才會對鍵進(jìn)行過期檢查,這可以保證刪除鍵的操作只會在非做不可的情況下才進(jìn)行,并且刪除的目標(biāo)僅限于當(dāng)前鍵,并不是大量地刪除,這個(gè)策略不會讓CPU在刪除無關(guān)的鍵上花費(fèi)過多時(shí)間。

但是這種策略的確定是,它對內(nèi)存是最不友好的,如果數(shù)據(jù)庫中有相當(dāng)龐大數(shù)量的過期鍵并且占著大量的內(nèi)存,而這些鍵如果不被訪問到的話,它可能永遠(yuǎn)不會被刪除,它所占據(jù)的內(nèi)存就永遠(yuǎn)不會被釋放,服務(wù)器的內(nèi)存空間會被這些垃圾數(shù)據(jù)一直占據(jù)吞噬,服務(wù)器內(nèi)存也會越來越緊張。

舉個(gè)例子,對于一些和事件有關(guān)的數(shù)據(jù),比如日志,在某個(gè)時(shí)間點(diǎn)之后,它的訪問量就會大大減少,甚至不再訪問,如果這類過期數(shù)據(jù)一直堆積在數(shù)據(jù)庫中,那么造成的影響肯定是非常嚴(yán)重的。

定期刪除

從上述對兩種刪除策略的討論后得知,這兩種策略都是相當(dāng)極端的,在同樣極端的情況下會造成嚴(yán)重的后果。

而定期刪除策略是這兩種策略的一種整合和折中:

  • 定期刪除策略每隔一段時(shí)間執(zhí)行一次過期鍵刪除,并通過限制刪除操作執(zhí)行的時(shí)間和頻率來減少操作對CPU的影響
  • 除此之外,定期刪除過期鍵,有效地減少了過期鍵帶來的內(nèi)存浪費(fèi),避免了內(nèi)存泄漏的危險(xiǎn)

定期刪除策略的難點(diǎn)是確定刪除操作執(zhí)行時(shí)長和頻率:

  • 如果刪除得太頻繁或執(zhí)行時(shí)間過長,定期刪除策略會退化成定時(shí)刪除策略
  • 如果刪除操作執(zhí)行的太少或者執(zhí)行時(shí)間過少,又會跟惰性刪除策略一樣出現(xiàn)內(nèi)存浪費(fèi)的情況

因此,如果采用定期刪除策略,服務(wù)器必須根據(jù)情況,合理設(shè)置刪除操作的時(shí)間和執(zhí)行頻率。


五、持久化

Redis作為一個(gè)緩存組件為什么需要持久化?

Redis是一個(gè)基于內(nèi)存的數(shù)據(jù)庫,如果服務(wù)出現(xiàn)宕機(jī)的情況,數(shù)據(jù)將全部丟失,通常的解決方案是通過后端數(shù)據(jù)庫恢復(fù),但是后端數(shù)據(jù)庫如常見的Mysql數(shù)據(jù)庫有性能瓶頸,如果是大量丟失數(shù)據(jù)的恢復(fù),會對數(shù)據(jù)庫造成相當(dāng)大的壓力,開銷大效率低下,所以對于Redis實(shí)現(xiàn)數(shù)據(jù)持久化是相當(dāng)重要的,在出現(xiàn)數(shù)據(jù)丟失災(zāi)難中可以避免從后端數(shù)據(jù)庫恢復(fù)數(shù)據(jù)。

Redis提供了多種持久化方式

  • RDB持久化可以在指定的時(shí)間間隔里生成數(shù)據(jù)集的快照(point-in-time-snapshot)并保存到磁盤上,由于是某一時(shí)刻的快照,所以快照中的數(shù)據(jù)要早于或等于內(nèi)存中的數(shù)據(jù)
  • AOF持久化記錄服務(wù)器執(zhí)行的所有寫操作命令并以aof格式保存在磁盤上,并在服務(wù)器啟動時(shí),通過執(zhí)行這些命令來還原數(shù)據(jù)集。AOF文件中的命令全部以Redis協(xié)議的格式來保存,新命令會追加到文件的末尾。Redis還可以使用BGREWRITEAOF命令來重寫文件,去除一些不影響最終數(shù)據(jù)結(jié)果的命令,這樣保證AOF文件保存的數(shù)據(jù)集占用內(nèi)存不會過大
  • Redis還可以同時(shí)使用AOF和RDB來實(shí)現(xiàn)持久化,這是Redis4.0版本推出的,官方也支持在實(shí)際開發(fā)中使用這種用法。簡單來說,內(nèi)存快照以一定的頻率執(zhí)行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作。在這種情況下,快照不用很頻繁地執(zhí)行,避免了頻繁的fork對主線程的影響,避免主線程阻塞,也不需要記錄所有操作了,因此不會出現(xiàn)文件過大的問題,也可以避免重寫開銷過大。
  • 虛擬內(nèi)存(VM)方式存儲,從Redis Version2.4開始,官方就明確表示不再使用,Version 3.2版本中更找不到關(guān)于虛擬內(nèi)存(VM)的任何配置范例,Redis的主要作者Salvatore Sanfilippo還專門寫了一篇論文,來反思Redis對虛擬內(nèi)存(VM)存儲技術(shù)的支持問題。

RDB持久化

RDB 就是 Redis DataBase 的縮寫,中文名為快照/內(nèi)存快照,RDB持久化是把當(dāng)前進(jìn)程數(shù)據(jù)生成快照保存到磁盤上的過程

觸發(fā)方式

觸發(fā)RDB持久化的方式有兩種,分別是手動觸發(fā)和自動觸發(fā)

  • 手動觸發(fā)

    手動觸發(fā)分別對應(yīng)save和bgsave命令

    • save命令

      阻塞當(dāng)前Redis服務(wù)器主線程,直至RDB過程完成位置,當(dāng)數(shù)據(jù)占用內(nèi)存較大時(shí),這個(gè)過程會造成相當(dāng)長時(shí)間的阻塞,并且如果出現(xiàn)服務(wù)器宕機(jī),這個(gè)過程中寫入的所有數(shù)據(jù)都會丟失,線上環(huán)境中慎重使用!

    • bgsave命令

      Redis進(jìn)程執(zhí)行fork操作時(shí)會創(chuàng)建子進(jìn)程,RDB持久化過程由子進(jìn)程負(fù)責(zé),完成后自動結(jié)束。這個(gè)過程中的保存工作全部由子進(jìn)程完成,主進(jìn)程無需進(jìn)行任何磁盤的I/O操作。但是如果數(shù)據(jù)集相當(dāng)龐大時(shí),fork可能會非常耗時(shí),并且在CPU緊張時(shí),服務(wù)器可能會在某時(shí)刻停止處理服務(wù)器,這種停止情況甚至可能長達(dá)整整一秒。

      bgsave命令執(zhí)行期間,客戶端的save命令和bgsave命令會被服務(wù)器拒絕,因?yàn)橐乐贡4婷钪g產(chǎn)生競爭條件。

      其次bgsave和bgrewriteaof兩個(gè)命令不能同時(shí)執(zhí)行:

      • 如果bgsave命令正在執(zhí)行,那么客戶端bgrewriteaof命令會延遲到bgsave命令完成后執(zhí)行
      • 如果bgrewriteaof命令正在執(zhí)行,那么客戶端發(fā)送的bgsave命令會被拒絕

    bgsave流程圖如下圖所示:

    具體流程如下:

    • Redis客戶端執(zhí)行bgsave命令或者自動觸發(fā)bgsave命令
    • 主進(jìn)程判斷是否已經(jīng)存在已經(jīng)在執(zhí)行的子進(jìn)程,如果存在直接返回,父進(jìn)程繼續(xù)處理命令請求,等待子進(jìn)程完成快照任務(wù)后的信號;
    • 如果不存在正在執(zhí)行的子進(jìn)程,那么就調(diào)用fork函數(shù)創(chuàng)建一個(gè)新的子進(jìn)程進(jìn)程持久化,fork過程是一個(gè)阻塞的,創(chuàng)建完子進(jìn)程主進(jìn)程即可執(zhí)行其他操作
    • 子進(jìn)程先將數(shù)據(jù)寫到臨時(shí)的rdb文件,待快照數(shù)據(jù)寫入完成后再原子替換舊的rdb文件
    • 發(fā)送信號給主進(jìn)程,通知主進(jìn)程rdb持久化已經(jīng)完成,主進(jìn)程更新相關(guān)的統(tǒng)計(jì)信息。
  • 自動觸發(fā)

    以下四種情況會自動觸發(fā)

    • redis.conf中配置的save m n,即在m秒中發(fā)生了n次數(shù)據(jù)修改,則會觸發(fā)bgsave命令生成rdb文件
    • 主從復(fù)制時(shí),從節(jié)點(diǎn)要從主節(jié)點(diǎn)中進(jìn)行全量復(fù)制也會觸發(fā)bgsave操作,生成當(dāng)時(shí)的快照發(fā)送到從節(jié)點(diǎn),進(jìn)行主從復(fù)制的數(shù)據(jù)同步
    • 執(zhí)行debug reload命令重新加載reids時(shí)也會觸發(fā)bgsave操作
    • 默認(rèn)情況下執(zhí)行shutdown命令,如果沒有開啟AOF持久化,也會自動觸發(fā)bgsave操作

    redis.conf配置RDB持久化

    快照周期:內(nèi)存快照雖然可以通過技術(shù)人員手動執(zhí)行save和bgsave命令來執(zhí)行,但是實(shí)際生產(chǎn)環(huán)境下都會設(shè)置周期執(zhí)行條件

    • Redis中默認(rèn)的周期設(shè)置

      # 周期性執(zhí)行條件的設(shè)置格式為 save <seconds> <changes># 默認(rèn)的設(shè)置為: save 900 1 //900s中有一條key信息發(fā)生變化執(zhí)行快照 save 300 10 //300s中有十條key信息發(fā)生變化執(zhí)行快照 save 60 10000 //60s中有10000條key信息發(fā)生變化執(zhí)行快照# 以下設(shè)置方式為關(guān)閉RDB快照功能 save ""
    • 其他配置信息

      #文件名稱 dbfilename "dump-6379.rdb"#端口號 port 6379#文件保存路徑 dir /www/server/redis/data/redis_cache# 如果持久化出錯,主進(jìn)程是否停止寫入 stop-writes-on-bgsave-error yes#是否壓縮 rdbcompression yes#導(dǎo)入時(shí)進(jìn)行檢查 rdbchecksum yes

      dbfilename:RDB文件在磁盤上的名稱。

      dir:RDB文件的存儲路徑。默認(rèn)設(shè)置為“./”,也就是Redis服務(wù)的主目錄。

      stop-writes-on-bgsave-error:上文提到的在快照進(jìn)行過程中,主進(jìn)程照樣可以接受客戶端的任何寫操作的特性,是指在快照操作正常的情況下。如果快照操作出現(xiàn)異常(例如操作系統(tǒng)用戶權(quán)限不夠、磁盤空間寫滿等等)時(shí),Redis就會禁止寫操作。這個(gè)特性的主要目的是使運(yùn)維人員在第一時(shí)間就發(fā)現(xiàn)Redis的運(yùn)行錯誤,并進(jìn)行解決。一些特定的場景下,您可能需要對這個(gè)特性進(jìn)行配置,這時(shí)就可以調(diào)整這個(gè)參數(shù)項(xiàng)。該參數(shù)項(xiàng)默認(rèn)情況下值為yes,如果要關(guān)閉這個(gè)特性,指定即使出現(xiàn)快照錯誤Redis一樣允許寫操作,則可以將該值更改為no。

      rdbcompression:該屬性將在字符串類型的數(shù)據(jù)被快照到磁盤文件時(shí),啟用LZF壓縮算法。Redis官方的建議是請保持該選項(xiàng)設(shè)置為yes,因?yàn)椤癷t’s almost always a win”。

      rdbchecksum:從RDB快照功能的version 5 版本開始,一個(gè)64位的CRC冗余校驗(yàn)編碼會被放置在RDB文件的末尾,以便對整個(gè)RDB文件的完整性進(jìn)行驗(yàn)證。這個(gè)功能大概會多損失10%左右的性能,但獲得了更高的數(shù)據(jù)可靠性。所以如果您的Redis服務(wù)需要追求極致的性能,就可以將這個(gè)選項(xiàng)設(shè)置為no。


    深入理解RDB

    • 在生產(chǎn)環(huán)境中為Redis開辟的內(nèi)存區(qū)域都比較大,那么將內(nèi)存中的數(shù)據(jù)同步到硬盤的過程可能就會持續(xù)較長的時(shí)間,而實(shí)際情況中,這段時(shí)間都會收到數(shù)據(jù)的讀寫請求,那么如何保持?jǐn)?shù)據(jù)一致性呢?

      RDB的核心思想是Copy-On-Write,來保證進(jìn)行快照操作的時(shí)間里需要壓縮寫入磁盤的的數(shù)據(jù)在內(nèi)存中不會發(fā)生變化。正常的快照操作中,一方面Redis主進(jìn)程會fork一個(gè)快照子進(jìn)程來做持久化,保持Redis不會停止對客戶端的數(shù)據(jù)請求處理,另一方面這段時(shí)間內(nèi)的數(shù)據(jù)變化會以副本的方式放在一個(gè)新的內(nèi)存區(qū)域,與此同時(shí)bgsave進(jìn)程會將該副本寫入rdb文件,等待快照操作執(zhí)行完畢后才會同步到原來的數(shù)據(jù)存儲內(nèi)存區(qū)域。

    • 在進(jìn)行快照操作的這段時(shí)間里,如果服務(wù)器發(fā)生宕機(jī)怎么辦?

      快照操作過程中不會影響上一次的備份數(shù)據(jù)rdb文件,Redis在進(jìn)行快照操作的時(shí)候會創(chuàng)建一個(gè)臨時(shí)文件進(jìn)行數(shù)據(jù)寫入,操作成功之后才會將這個(gè)臨時(shí)文件覆蓋掉上一次的備份文件。而如果快照操作中服務(wù)器發(fā)生宕機(jī),服務(wù)器重啟時(shí)將以上次的備份文件作為數(shù)據(jù)恢復(fù)參考,而服務(wù)器宕機(jī)之前更新的數(shù)據(jù)則無法恢復(fù)會丟失。

    • 參照上述問題,服務(wù)器發(fā)生宕機(jī)會導(dǎo)致數(shù)據(jù)丟失,那么可以一秒做一次快照而減少數(shù)據(jù)丟失量嗎?

      對于快照來說,所謂的“連拍”就是連續(xù)地進(jìn)行快照持久化,這樣做,快照的間隔時(shí)間很短,確實(shí)可以減少丟失數(shù)據(jù)的數(shù)量。

      那么快照間隔時(shí)間是不是可以縮短到很短呢?因?yàn)槊看慰煺詹僮鞫际怯蒪gsave子進(jìn)程去完成的,并不會影響主進(jìn)程對客戶端請求的處理,也不會阻塞主進(jìn)程。

      其實(shí)這種想法是錯誤的,雖然bgsave子進(jìn)程執(zhí)行時(shí)不會阻塞主進(jìn)程,但是如果頻繁地執(zhí)行全量快照,也會帶來其他的開銷:

      • 頻繁將全量數(shù)據(jù)寫入磁盤,這樣大量數(shù)據(jù)的IO操作本身就會對磁盤造成相當(dāng)大的壓力,并且多個(gè)快照競爭有限的磁盤寬帶,前一個(gè)快照還沒執(zhí)行完,后一個(gè)快照又開始進(jìn)行了,從而使得快照操作越來越慢,但是新的快照又開始進(jìn)行了,從而導(dǎo)致惡性循環(huán)。
      • bgsave子進(jìn)程需要通過fork操作會從主進(jìn)程中創(chuàng)建出來,雖然子進(jìn)程創(chuàng)建過后不會阻塞主進(jìn)程,但是fork子進(jìn)程這個(gè)操作本身是會阻塞主進(jìn)程的,而且如果主進(jìn)程已經(jīng)被大量請求占用,而此時(shí)又頻繁地創(chuàng)建子進(jìn)程,這樣也是會阻塞主進(jìn)程的,只是方式與save有所不同,但本質(zhì)都是一樣的,這樣又回到了原來的問題。

RDB的優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn)
    • RDB文件是某個(gè)時(shí)間節(jié)點(diǎn)的快照,默認(rèn)使用LZF算法進(jìn)行壓縮,壓縮后的文件體積遠(yuǎn)小于于內(nèi)存大小,適合備份、全量復(fù)制等場景。
    • Redis加載RDB文件恢復(fù)數(shù)據(jù)遠(yuǎn)遠(yuǎn)快于AOF,因此RDB非常適用于災(zāi)難恢復(fù)(Disaster Recovery)。
  • 缺點(diǎn)
    • RDB方式實(shí)時(shí)性不夠,無法做到秒級的數(shù)據(jù)持久化,雖然Redis允許設(shè)置不同的保存條件來控制保存RDB文件的頻率,但是RDB文件需要做的是全量復(fù)制需要保存整個(gè)數(shù)據(jù)集,所以它并不是一個(gè)輕松的過程,當(dāng)服務(wù)器故障發(fā)生宕機(jī),可能因此丟失相當(dāng)多的數(shù)據(jù)。
    • 每次進(jìn)行快照時(shí),主進(jìn)程需要fork()出一個(gè)子進(jìn)程,由子進(jìn)程來完成持久化工作。在數(shù)據(jù)集比較龐大時(shí),fork()過程都可能會非常耗時(shí),造成服務(wù)器某時(shí)刻停止對服務(wù)器的請求處理。

AOF持久化

Redis是寫后日志,先執(zhí)行命令,將數(shù)據(jù)寫入內(nèi)存,再記錄日志,日志里記錄的是Redis收到的每一條命令

為什么采用寫后日志?

  • 避免額外的檢查開銷:Redis在想AOF里面記錄日志時(shí),并不會先去執(zhí)行對命令的語法檢測,所以先記錄日志再執(zhí)行命令的話,日志中有可能記錄下錯誤的命令,當(dāng)Redis利用日志恢復(fù)數(shù)據(jù)時(shí),可能就會出錯。
  • 不會阻塞當(dāng)前的寫操作

當(dāng)也同樣存在風(fēng)險(xiǎn):

  • 如果命令執(zhí)行完即將寫入日志時(shí),服務(wù)器宕機(jī),這些數(shù)據(jù)操作未能寫入日志,則這些數(shù)據(jù)將丟失。

實(shí)現(xiàn)AOF

AOF日志記錄Redis的每個(gè)寫操作,因此不需要觸發(fā),具體步驟分為命令追加append,文件寫入write和文件同步sync以及文件重寫rewrite

  • 命令追加append

    當(dāng)AOF持久化功能開啟后,服務(wù)器在執(zhí)行完一個(gè)寫命令之后,會將寫命令追加到服務(wù)器的aof_buf緩沖區(qū),而不是直接寫入文件,主要是避免了每次寫命令直接寫入磁盤,導(dǎo)致磁盤IO操作成為Redis負(fù)載的瓶頸。

    命令追加的格式是Redis命令請求的協(xié)議格式,它是一種純文本格式,有兼容性好,可讀性強(qiáng),容易處理等優(yōu)點(diǎn)。

  • 文件寫入write和文件同步sync

    Redis提供了多種AOF緩存區(qū)的文件同步策略,策略涉及到操作系統(tǒng)的write函數(shù)和fsync函數(shù),為了提高文件寫入效率,現(xiàn)代操作系統(tǒng)中,當(dāng)用戶調(diào)用write函數(shù)將數(shù)據(jù)寫入文件時(shí),操作系統(tǒng)常會將數(shù)據(jù)暫存到一個(gè)內(nèi)存緩沖區(qū)中,當(dāng)緩沖區(qū)被存滿或者達(dá)到一定時(shí)限后才會寫入磁盤中,這樣的操作雖然提高了效率但是也存在安全隱患,如果服務(wù)器宕機(jī),內(nèi)存緩沖區(qū)的數(shù)據(jù)將全部丟失。因此系統(tǒng)里也提供了fsync函數(shù)等同步函數(shù),可以強(qiáng)制將緩沖區(qū)的數(shù)據(jù)寫入磁盤,以此保證數(shù)據(jù)的安全性。

    AOF持久化中aof_buf緩存區(qū)的文件同步策略由appendfsync參數(shù)控制,各個(gè)參數(shù)含義如下:

    • always:命令寫入aof_buf后立刻調(diào)用系統(tǒng)的fsync函數(shù)同步到AOF文件,fsync操作完成后線程返回。這種情況下,aof_buf緩沖區(qū)已然失去作用,每次寫命令都要同步到AOF文件中,磁盤IO成為性能瓶頸,嚴(yán)重降低了Redis的性能;即便是用固態(tài)硬盤,每秒也只能處理幾萬個(gè)請求,而且會大大降低硬盤的壽命。
    • no:命令寫入aof_buf后調(diào)用系統(tǒng)write操作,不對AOF文件進(jìn)行fsync同步,同步操作由操作系統(tǒng)負(fù)責(zé),通常同步周期為30s。這種情況下,同步操作完全由系統(tǒng)控制,文件的同步時(shí)間變得不可控,而且緩沖區(qū)堆積的數(shù)據(jù)會很多,安全性無法得到保證。
    • everysec:命令寫入aof_buf后調(diào)用系統(tǒng)的write,write完成后線程返回;fsync同步文件操作由專門的線程每秒調(diào)用一次。everysec是前述兩種策略的折中,是性能和數(shù)據(jù)安全性的平衡,實(shí)際開發(fā)中我們會優(yōu)先選擇這種策略。

  • 文件重寫rewrite

    Redis執(zhí)行的寫命令越來越大,AOF文件也會越來越大,過大的文件會影響服務(wù)器的運(yùn)行,也會使文件恢復(fù)時(shí)用時(shí)過長。

    文件重寫是指定期對AOF文件進(jìn)行重寫,減少AOF文件的體積。Redis通過創(chuàng)建一個(gè)新的AOF文件來替換現(xiàn)有的AOF,新舊兩個(gè)AOF文件保存的數(shù)據(jù)相同,但新AOF文件沒有了冗余命令。

    文件重寫之所以可以壓縮AOF文件原因在于:

    • 過期的數(shù)據(jù)不再寫入文件

    • 無效的命令不再寫入文件:如有些數(shù)據(jù)被重復(fù)設(shè)值,有些數(shù)據(jù)被刪除了等待

    • 多條命令可以合并為一個(gè):如sadd key value1,sadd key value2,sadd key value3可以合并為sadd key value1 value2 value3,不過為了防止單條命令過大造成客戶端緩沖區(qū)溢出,對于list,set,hash,zset類型的key,并不一定只使用一條命令,而是以某個(gè)常量為界將命令拆分為多條。這個(gè)常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定義,不可更改,3.0版本中值是64。

      #define REDIS_AOF_REWRITE_ITEMS_PRE_CMD 64

配置文件

#文件名稱 appendfilename "appendonly-6379.aof"# appendonly參數(shù)開啟AOF持久化 appendonly yes# 同步策略 # appendfsync always appendfsync everysec # appendfsync no# aof重寫期間是否同步 no-appendfsync-on-rewrite no# 重寫觸發(fā)配置 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb# 加載aof出錯如何處理 aof-load-truncated yes# 文件重寫策略 aof-rewrite-incremental-fsync yes

要特別注意:no-appendfsync-on-rewrite:always和everysec的設(shè)置會使真正的I/O操作高頻度的出現(xiàn),甚至?xí)霈F(xiàn)長時(shí)間的卡頓情況,這個(gè)問題出現(xiàn)在操作系統(tǒng)層面上,所有靠工作在操作系統(tǒng)之上的Redis是沒法解決的。為了盡量緩解這個(gè)情況,Redis提供了這個(gè)設(shè)置項(xiàng),保證在完成fsync函數(shù)調(diào)用時(shí),不會將這段時(shí)間內(nèi)發(fā)生的命令操作放入操作系統(tǒng)的Page Cache(這段時(shí)間Redis還在接受客戶端的各種寫操作命令)。


重寫機(jī)制

  • AOF重寫會阻塞嗎?

    AOF重寫過程是由后臺進(jìn)程bgrewriteaof完成的,主線程需要fork出子進(jìn)程,這個(gè)過程會占用主進(jìn)程內(nèi)存,所以如果頻繁進(jìn)行重寫是會造成主線程阻塞的。

  • AOF日志何時(shí)會重寫?

    auto-aof-rewrite-min-size:表示運(yùn)行AOF重寫文件的最小大小,默認(rèn)為64MB

    auto-aof-rewrite-percentage:這個(gè)值的計(jì)算方式是,當(dāng)前aof文件大小比上次重寫后的aof文件大小的差值與上次重寫后aof文件大小的比值;percentage = (last - now) / last

  • 重寫日志時(shí),有新的數(shù)據(jù)寫入怎么做?

    關(guān)于文件重寫,要特別注意兩點(diǎn):

    • 重寫由子進(jìn)程進(jìn)行
    • 重寫階段Redis執(zhí)行的寫命令需要追加到新的AOF文件中,為此Redis引入了aof_rewrite_buf緩存區(qū)

    ?

    ? 對比上圖,文件重寫的流程如下:

    • 1>執(zhí)行AOF請求

      如果當(dāng)前進(jìn)程正在執(zhí)行bgrewriteaof,則返回請求,請求不執(zhí)行

      如果當(dāng)前進(jìn)程正在執(zhí)行bgsave,則重寫命令延遲到bgsave完成之后進(jìn)行

    • 2>父進(jìn)程fork創(chuàng)建子進(jìn)程,開銷相當(dāng)于bgsave創(chuàng)建子進(jìn)程的開銷

    • 3.1>主進(jìn)程fork操作完成后繼續(xù)相應(yīng)其他命令

      所有修改命令依然寫入aof_buf緩沖區(qū)根據(jù)appendfsync參數(shù)采取策略同步到磁盤,保證原有數(shù)據(jù)同步

    • 3.2>fork操作運(yùn)用寫時(shí)復(fù)制基數(shù),子進(jìn)程只能共享fork操作時(shí)的內(nèi)存數(shù)據(jù)

      由于父進(jìn)程依然響應(yīng)命令,會有新的數(shù)據(jù)寫入,Redis使用aof_rewrite_buf重寫緩沖區(qū)來保存這部分?jǐn)?shù)據(jù),防止新生成的文件生成期間丟失這部分?jǐn)?shù)據(jù)。

    • 4>子進(jìn)程按照命令合并規(guī)則寫入到新的AOF文件

      每次批量寫入的硬盤數(shù)據(jù)量由aof-rewrite-incremental-fsync參數(shù)控制,默認(rèn)為32MB,防止單次寫入數(shù)據(jù)造成磁盤IO阻塞

    • 5.1>新的AOF文件寫入完成后,發(fā)送信號給主進(jìn)程,通知主進(jìn)程更新統(tǒng)計(jì)信息

    • 5.2>父進(jìn)程將aof_rewrite_buf重寫緩沖區(qū)新寫入的數(shù)據(jù)更新到新的AOF文件中

    • 5.3>使用新的AOF文件代替舊的AOF文件

    總結(jié)

    1.父進(jìn)程fork子進(jìn)程完成AOF文件重寫

    2.父進(jìn)程將新寫入的數(shù)據(jù)保存到aof_buf和aof_rewrite_buf緩沖區(qū),子進(jìn)程重寫完畢后從aof_rewrite_buf緩存區(qū)將新的數(shù)據(jù)寫入新的AOF文件

    3.新的AOF文件替代舊的AOF文件

  • 為什么AOF重寫不復(fù)用舊的AOF文件?

    1.父子進(jìn)程寫同一個(gè)文件會產(chǎn)生競爭關(guān)系,影響了父進(jìn)程的性能

    2.如果AOF重寫失敗,會污染原本的AOF文件,無法再作為數(shù)據(jù)恢復(fù)的參考


重啟加載

AOF和RDB文件都可以用于服務(wù)器重啟時(shí)的數(shù)據(jù)恢復(fù)。下面展示Redis持久化文件加載流程:

流程說明:

1)AOF持久化開啟且存在AOF文件時(shí),優(yōu)先加載AOF文件。

2)AOF關(guān)閉或者AOF文件不存在時(shí),加載RDB文件。

3)加載AOF/RDB文件成功后,Redis啟動成功。

4)AOF/RDB文件存在錯誤時(shí),Redis啟動失敗并打印錯誤信息。

那么為什么會優(yōu)先加載AOF呢?因?yàn)锳OF保存的數(shù)據(jù)更完整,通過上面的分析我們知道AOF基本上最多損失1s的數(shù)據(jù)。


六、事務(wù)

Redis通過MULTI,EXEC,WATCH,DISCARD等命令來實(shí)現(xiàn)事務(wù)Transaction功能。事務(wù)體哦概念股了一種將多個(gè)命令請求打包,然后一次性按順序地執(zhí)行多個(gè)命令的機(jī)制,并且事務(wù)執(zhí)行期間,服務(wù)器不會中斷事務(wù)而去改去執(zhí)行其他客戶端的命令請求,它會將事務(wù)中的所有命令都執(zhí)行完畢,然后 才去執(zhí)行其他客戶端的命令請求。

以下是一個(gè)事務(wù)執(zhí)行的過程,該事務(wù)從一個(gè)MULTI開始,接著將多個(gè)操作命令放入事務(wù)中,然后最后EXEC將事務(wù)提交給服務(wù)器執(zhí)行。

127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set key value QUEUED 127.0.0.1:6379(TX)> get key QUEUED 127.0.0.1:6379(TX)> set key otherValue QUEUED 127.0.0.1:6379(TX)> get key QUEUED 127.0.0.1:6379(TX)> EXEC 1) OK 2) "value" 3) OK 4) "otherValue"

特別注意

  • 事務(wù)是一個(gè)單獨(dú)的隔離操作:事務(wù)中的所有命令都會序列化,按順序地執(zhí)行,事務(wù)執(zhí)行過程中也不會被其他客戶端發(fā)來的命令請求打斷
  • 事務(wù)是一個(gè)原子操作:事務(wù)中的命令要么全部被執(zhí)行,要么全部不執(zhí)行

EXEC命令復(fù)制觸發(fā)并執(zhí)行事務(wù)中的所有命令

  • 如果客戶端使用MULTI開啟一個(gè)事務(wù)后,因?yàn)閿嗑€導(dǎo)致EXEC沒有執(zhí)行,那么事務(wù)中的所有命令都不會被執(zhí)行
  • 而如果EXEC成功執(zhí)行,那么事務(wù)中所有命令都會被執(zhí)行

當(dāng)使用AOF持久化時(shí),Redis會使用單個(gè)write命令將事務(wù)寫入磁盤中,如果Redis服務(wù)器宕機(jī),那么只有部分事務(wù)命令會成功寫入磁盤。

如果Redis重新啟動發(fā)現(xiàn)了AOF文件有這樣的問題,那么它會退出并匯報(bào)一個(gè)錯誤。

使用redis-check-aof可以修復(fù)這一問題,它會移除AOF文件中不完整事務(wù)信息,以保證服務(wù)器順利啟動。


放棄事務(wù)

當(dāng)執(zhí)行DISCARD命令時(shí),事務(wù)會被放棄,事務(wù)隊(duì)列清空,并且客戶端從事務(wù)狀態(tài)退出:

127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set key value QUEUED 127.0.0.1:6379(TX)> DISCARD OK 127.0.0.1:6379> get key "otherValue"

WATCH命令

WATCH命令是一個(gè)樂觀鎖Optimistic Locking,它可以在EXEC命令執(zhí)行之前,監(jiān)視任意順序的數(shù)據(jù)庫鍵,并在執(zhí)行EXEC命令時(shí),檢查被監(jiān)視的值是否已經(jīng)被修改,如果是,服務(wù)器將拒絕執(zhí)行事務(wù),并向客戶端代表事務(wù)執(zhí)行已經(jīng)失敗的回復(fù)。

redis> set key oldValue OK redis> watch key OK redis> MULTI OK redis(TX)> set key newValue QUEUED redis(TX)> EXEC (nil) //事務(wù)失敗

為什么上述事務(wù)失敗了呢?

時(shí)間客戶端A客戶端B
T1SET key oldValue
T2WATCH key
T3MULTI
T4SET key newValue
T5SET key otherValue
T6EXEC

由上圖所知,T5時(shí)刻,在客戶端A執(zhí)行EXEC命令前,key值已經(jīng)被客戶端B修改,此時(shí)服務(wù)器發(fā)現(xiàn)被監(jiān)視的鍵key的值已經(jīng)被修改了,所以服務(wù)器會拒絕執(zhí)行客戶端A的事務(wù),并向客戶端A返回空回復(fù)。

上述客戶端A的事務(wù)是不安全的,服務(wù)器會拒絕執(zhí)行客戶端提交的不安全的事務(wù),以保證數(shù)據(jù)的一致性。

上述這種形式的鎖叫做樂觀鎖,是一種強(qiáng)大的鎖機(jī)制。

同時(shí)可以使用UNWATCH命令取消對所有鍵key的監(jiān)視,注意!不是取消對單個(gè)或著幾個(gè)鍵的監(jiān)視,是取消所有。


事務(wù)的ACID性質(zhì)

  • 原子性

    事務(wù)中具有原子性是指:事務(wù)中的多個(gè)操作當(dāng)作一個(gè)整體來執(zhí)行,服務(wù)器要么執(zhí)行所有操作,要么一個(gè)操作都不執(zhí)行。

    以下展示的是一個(gè)成功執(zhí)行的事務(wù),所有命令都被執(zhí)行:

    127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> get key QUEUED 127.0.0.1:6379(TX)> set key value QUEUED 127.0.0.1:6379(TX)> get key QUEUED 127.0.0.1:6379(TX)> EXEC 1) (nil) 2) OK 3) "value"

    與此相反,以下展示了一個(gè)執(zhí)行錯誤的事務(wù),這個(gè)事務(wù)因?yàn)槊钊腙?duì)時(shí)錯誤而被服務(wù)器拒絕執(zhí)行,事務(wù)中的所有命令都不會被執(zhí)行:

    127.0.0.1:6379> set key value OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set key newValue QUEUED 127.0.0.1:6379(TX)> gett key (error) ERR unknown command `gett`, with args beginning with: `key`, 127.0.0.1:6379(TX)> EXEC (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get key "value"

    Redis的事務(wù)與傳統(tǒng)的關(guān)系型數(shù)據(jù)庫事務(wù)的最大區(qū)別就在于,Redis不支持事務(wù)回滾機(jī)制rollback,即使隊(duì)列中某個(gè)命令執(zhí)行期間出現(xiàn)了錯誤,整個(gè)事務(wù)也不會回滾,而是繼續(xù)執(zhí)行下去。

    在下面的例子中,SADD命令執(zhí)行期間發(fā)生了錯誤,后續(xù)的命令也會繼續(xù)執(zhí)行下去,而且之前執(zhí)行的事務(wù)也不會受到影響

    127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set key newValue QUEUED 127.0.0.1:6379(TX)> SADD key ERROR QUEUED 127.0.0.1:6379(TX)> get key QUEUED 127.0.0.1:6379(TX)> EXEC 1) OK 2) (error) WRONGTYPE Operation against a key holding the wrong kind of value 3) "newValue"

    Redis的作者在事務(wù)功能的文檔中解釋說,不支持事務(wù)回滾是因?yàn)檫@種復(fù)雜的功能與Redis追求簡單高效的設(shè)計(jì)初衷不相符,并且他認(rèn)為Redis事務(wù)執(zhí)行時(shí)的錯誤都是因?yàn)槌绦蝈e誤產(chǎn)生的,這種錯誤通常只會出現(xiàn)在開發(fā)環(huán)境中,而很少在實(shí)際的生成環(huán)境中出現(xiàn),所以他認(rèn)為沒有必要為Redis開發(fā)事務(wù)回滾功能。


  • 一致性

    事務(wù)具有一致性是指:如果數(shù)據(jù)庫在執(zhí)行事務(wù)之前是一致的,那么事務(wù)執(zhí)行之后無論事務(wù)是否執(zhí)行成功,數(shù)據(jù)庫也應(yīng)該仍然是一致的。

    一致性是指數(shù)據(jù)庫符合數(shù)據(jù)庫本身的定義和要求,沒有包含非法或者無效的錯誤數(shù)據(jù)

    Redis通過謹(jǐn)慎的錯誤檢測和簡單的設(shè)計(jì)來保證事務(wù)的一致性。

  • 隔離性

    事務(wù)的隔離性指的是,即使數(shù)據(jù)庫中有多個(gè)事務(wù)并發(fā)執(zhí)行,各個(gè)事務(wù)也不會互相影響,并且在并發(fā)情況下執(zhí)行的事務(wù)和串行執(zhí)行的事務(wù)產(chǎn)生的結(jié)果完全相同。

    因?yàn)镽edis使用單線程的方式來執(zhí)行事務(wù),并且服務(wù)器保證,在事務(wù)執(zhí)行期間,其他客戶端不會中斷該事務(wù),因此Redis的事務(wù)總是以串行的方式執(zhí)行,并且事務(wù)也是具有隔離性的。

  • 耐久性

    事務(wù)的耐久性指定是,當(dāng)一個(gè)事務(wù)執(zhí)行完畢時(shí),執(zhí)行這個(gè)事務(wù)所得的結(jié)果已經(jīng)被保存到永久性存儲介質(zhì)中,即使服務(wù)器執(zhí)行完事務(wù)后發(fā)生宕機(jī),執(zhí)行事務(wù)的結(jié)果也不會丟失。

    因?yàn)镽edis的事務(wù)不過是簡單地用隊(duì)列包裹起了一組Redis命令,使之成為一個(gè)整體,Redis并沒有為事務(wù)提供任何額外的持久化功能,所以Redis事務(wù)中的耐久性是基于Redis所使用的持久化模式。

    • 當(dāng)服務(wù)器在無持久化的內(nèi)存模式運(yùn)作時(shí),事務(wù)不具有耐久性,一旦服務(wù)器停機(jī),包括事務(wù)數(shù)據(jù)在內(nèi)的所有服務(wù)器數(shù)據(jù)都將丟失。
    • 當(dāng)服務(wù)器在RDB持久化模式運(yùn)作下,服務(wù)器只會在特定的保存條件被滿足時(shí),才會執(zhí)行bgsave命令,對數(shù)據(jù)庫進(jìn)行保存操作,并且異步執(zhí)行的bgsave不能保證事務(wù)數(shù)據(jù)被第一時(shí)間保存的硬盤里面,因此RDB持久化模式下的事務(wù)也不具有耐久性
    • 當(dāng)服務(wù)器運(yùn)行在AOF持久化模式下,并且appendfsync參數(shù)的值為always時(shí),程序總會執(zhí)行命令之后調(diào)用同步sync函數(shù),將數(shù)據(jù)真正保存到硬盤里面,這種配置下的事務(wù)是具有耐久性
    • 當(dāng)服務(wù)器運(yùn)行在AOF持久化模式下,并且appendfsync參數(shù)的值為everysec時(shí),程序會每秒同步一次命令數(shù)據(jù)到硬盤中,如果服務(wù)器發(fā)生宕機(jī),可能會造成事務(wù)數(shù)據(jù)丟失,這種配置下的事務(wù)也不具有耐久性
    • 當(dāng)服務(wù)器運(yùn)行在AOF持久化模式下,并且appendfsync參數(shù)的值為no時(shí),程序會交由操作系統(tǒng)為決定何時(shí),將命令數(shù)據(jù)同步到硬盤中,因?yàn)槭聞?wù)數(shù)據(jù)可能在等待同步中丟失,這種配置下的事務(wù)也不具有耐久性

七、主從復(fù)制

在Redis中,用戶可以通過執(zhí)行SLAVEOF命令或者設(shè)置slaveof配置,讓一個(gè)服務(wù)器去復(fù)制(replicate)另一個(gè)服務(wù)器,被復(fù)制的的服務(wù)器叫主服務(wù)器(master),對主服務(wù)器進(jìn)行復(fù)制的服務(wù)器叫從服務(wù)器(slave)。

進(jìn)行復(fù)制的主從服務(wù)器雙方的數(shù)據(jù)庫都將保存相同的數(shù)據(jù),概念上將這種現(xiàn)象稱為“數(shù)據(jù)庫狀態(tài)一致”。


舊版復(fù)制功能的實(shí)現(xiàn)

Redis2.8版本之前的復(fù)制功能分為同步(sync)和命令傳播(command propagate)兩個(gè)操作。

  • 同步用于將從服務(wù)器的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器當(dāng)前所處的數(shù)據(jù)庫狀態(tài)
  • 命令傳播用于當(dāng)主服務(wù)器數(shù)據(jù)庫的狀態(tài)被修改,導(dǎo)致主從服務(wù)器數(shù)據(jù)庫狀態(tài)不一致,讓主從服務(wù)器數(shù)據(jù)庫回到一致狀態(tài)。

復(fù)制

當(dāng)從服務(wù)器復(fù)制主服務(wù)器時(shí),需要先進(jìn)行執(zhí)行同步操作,從服務(wù)器需要通過向主服務(wù)器發(fā)送SYNC命令來完成,以下是SYNC命令的執(zhí)行步驟:

  • 從服務(wù)器向主服務(wù)器發(fā)送SYNC命令

  • 收到SYNC命令的主服務(wù)器執(zhí)行BGSAVE命令,在后于生成RDB文件,并使用一個(gè)緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行的所有寫命令。

  • 當(dāng)主服務(wù)器的BGSAVE命令執(zhí)行完畢時(shí),主服務(wù)器會將BGSAVE生成的RDB文件發(fā)送給從服務(wù)器,從服務(wù)器接收并載入RDB文件,將自己數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器執(zhí)行BGSAVE命令時(shí)的數(shù)據(jù)庫狀態(tài)。

  • 主服務(wù)器將記錄在緩沖區(qū)里面所有寫命令發(fā)送給從服務(wù)器,從服務(wù)器執(zhí)行這些寫命令,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器數(shù)據(jù)庫當(dāng)前所處的狀態(tài)。

    同步完整過程:

命令傳播

在同步操作執(zhí)行完畢之后,主從服務(wù)器兩者的數(shù)據(jù)庫達(dá)到一致,每當(dāng)主服務(wù)器執(zhí)行客戶端發(fā)送的寫入命令時(shí),數(shù)據(jù)庫就會被修改,主從服務(wù)器數(shù)據(jù)庫狀態(tài)不一致。

為了讓主從服務(wù)器回到一致狀態(tài),主服務(wù)器需要對從服務(wù)器執(zhí)行命令傳播操作:主服務(wù)器會將自己執(zhí)行的寫命令,發(fā)送給從服務(wù)器執(zhí)行相同的寫命令之后,主從服務(wù)器再次回到一致狀態(tài)。


舊版復(fù)制的缺陷:

斷線后重新復(fù)制:處于命令傳播階段的主從服務(wù)器因?yàn)榫W(wǎng)絡(luò)問題斷開了連接終止了復(fù)制,但從服務(wù)器重寫連接主服務(wù)器后會重新進(jìn)行復(fù)制,但是這種復(fù)制是全量復(fù)制,開銷相當(dāng)大。主從服務(wù)器斷線期間,主服務(wù)器執(zhí)行的寫命令可能多或少,但是服務(wù)器為了彌補(bǔ)這一小部分缺失的數(shù)據(jù),就要主從服務(wù)器重寫執(zhí)行一次SYNC,這種做法無疑是低效的。


新版復(fù)制功能的實(shí)現(xiàn)

為了解決舊版復(fù)制在處理斷線重復(fù)值情況的低效率問題,Redis從2.8版本之后推出了PSYNC命令來代替SYNC命令執(zhí)行復(fù)制操作。

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)兩種模式:

  • 完整重同步用于處理初次復(fù)制情況,完整重同步的執(zhí)行步驟和SYNC命令的執(zhí)行步驟基本一樣,通過主服務(wù)器創(chuàng)建并發(fā)送RDB文件,以及向從服務(wù)器發(fā)送保存在緩沖區(qū)里面的寫命令來進(jìn)行同步
  • 部分重同步則用于處理斷線后重復(fù)值的情況:當(dāng)從服務(wù)器斷線后重新連接主服務(wù)器,只復(fù)制斷開期間主服務(wù)器寫入的數(shù)據(jù)即可,不需要再做一次完全重同步。


部分重同步

部分重同步功能由以下三個(gè)部分構(gòu)成:

  • 主從服務(wù)器的復(fù)制偏移量(replication offset)
  • 主服務(wù)器的復(fù)制積壓緩沖區(qū)(replication backlog)
  • 服務(wù)器的運(yùn)行ID(run ID)

復(fù)制偏移量

執(zhí)行復(fù)制的主從服務(wù)器都會分別維護(hù)一個(gè)復(fù)制偏移量:

  • 主服務(wù)器每次向從服務(wù)器傳播N個(gè)字節(jié)的數(shù)據(jù)時(shí),就會將自己的復(fù)制偏移量加入N
  • 從服務(wù)器收到主服務(wù)器傳播來的N個(gè)字節(jié)的數(shù)據(jù)時(shí),也會將自己的復(fù)制偏移量加入N

通過對比主從服務(wù)器的復(fù)制偏移量,程序可以很容易地知道主從服務(wù)器是否處于一致。


復(fù)制積壓緩沖區(qū)

復(fù)制積壓緩沖區(qū)是由主服務(wù)器維護(hù)的一個(gè)固定長度(fixed-size)先進(jìn)先出(FIFO)隊(duì)列組成,默認(rèn)存儲大小為1MB。

當(dāng)主服務(wù)器進(jìn)行命令傳播時(shí),不僅會將寫命令發(fā)送給所有從服務(wù)器,還會將寫命令入隊(duì)到復(fù)制積壓緩沖區(qū)。

因此,主服務(wù)器的復(fù)制積壓緩沖區(qū)里面會保存著一部分最近播放的寫命令,并且復(fù)制緩沖區(qū)會為隊(duì)列中的每個(gè)字節(jié)記錄相應(yīng)的復(fù)制偏移量。當(dāng)從服務(wù)器重寫連接上主服務(wù)器之后,從服務(wù)器會通過PSYNC將自己的復(fù)制偏移量offset發(fā)送給主服務(wù)器,主服務(wù)器會根據(jù)這個(gè)復(fù)制偏移量來決定對從服務(wù)器執(zhí)行何種同步操作:

  • 如果offset偏移量之后的數(shù)據(jù)仍然存在于復(fù)制積壓緩沖區(qū)中,那么主服務(wù)器將對從服務(wù)器進(jìn)行部分重同步操作。
  • 相反,如果offset偏移量之后的數(shù)據(jù)已經(jīng)不存在于復(fù)制積壓緩沖區(qū),那么主服務(wù)器將對從服務(wù)器執(zhí)行完整重同步操作。

根據(jù)需要調(diào)整復(fù)制積壓緩沖區(qū)的大小

Redis為復(fù)制積壓緩沖區(qū)設(shè)置的默認(rèn)大小為1MB,如果主服務(wù)器需要執(zhí)行大量的寫操作,或者從服務(wù)器斷線后重連接的時(shí)間較長,那么這個(gè)值可能并不合適,這個(gè)值設(shè)置不得當(dāng),可能會讓從服務(wù)器重新連接主服務(wù)器后,讓主服務(wù)器判定從服務(wù)器需要進(jìn)行完整重同步,那么PSYNC命令的部分重同步模式就不能正常發(fā)揮作用。

復(fù)制積壓緩沖區(qū)的最小大小可以根據(jù)公式reconnect_second*write_size_per_second來估算,reconnect_second為重新連接所需要時(shí)間,write_size_per_second為主服務(wù)器平均每秒寫入的命令數(shù)據(jù)量,然后在此基礎(chǔ)上將這個(gè)大小翻倍,即可滿足大部分?jǐn)嗑€情況下重連后都能用部分重同步。

可參考:repl-backlog-size = 2*reconnect_second*write_size_per_second


服務(wù)器運(yùn)行ID

  • 每個(gè)Redis服務(wù)器無論主從都會自己的運(yùn)行ID

  • 運(yùn)行ID在服務(wù)器啟動時(shí),自動生成,由40個(gè)隨機(jī)的十六進(jìn)制字符組成。

    當(dāng)從服務(wù)器對主服務(wù)器進(jìn)行初次復(fù)制時(shí),主服務(wù)器會將自己的運(yùn)行ID傳送給從服務(wù)器,而從服務(wù)器也會將這個(gè)運(yùn)行ID保存起來。

    當(dāng)從服務(wù)器斷線重連上一個(gè)主服務(wù)器時(shí),會向主服務(wù)器將之前保存的運(yùn)行ID:

    • 如果從服務(wù)器保存的運(yùn)行ID與當(dāng)前連接的主服務(wù)器運(yùn)行ID相同,則說明之前連接的就是這個(gè)服務(wù)器,主服務(wù)器可以嘗試執(zhí)行部分重同步。
    • 反之,則說明之前連接的主服務(wù)器不是當(dāng)前連接的服務(wù)器,主服務(wù)器會對從服務(wù)器執(zhí)行完整同步操作。

PSYNC命令的實(shí)現(xiàn)

PSYNC命令的調(diào)用有兩種方式:

  • 如果從服務(wù)器以前沒有復(fù)制過任何服務(wù)器,或者之前執(zhí)行過SLAVEOF no one命令,那么從服務(wù)器在開始依次新的復(fù)制時(shí)將對主服務(wù)器發(fā)送PSYNC ? -1命令,主動請求主服務(wù)器進(jìn)行完整重同步。
  • 相反,從服務(wù)器已經(jīng)復(fù)制過某個(gè)主服務(wù)器的數(shù)據(jù),那么從服務(wù)器在開始一次新的復(fù)制前,會向主服務(wù)器發(fā)送PSYNC <runid> <offset>命令,runid是上次復(fù)制的主服務(wù)器的運(yùn)行ID,而offset是當(dāng)前從服務(wù)器的復(fù)制偏移量,接受到這個(gè)命令的主服務(wù)器會根據(jù)這兩個(gè)參數(shù)來決定對從服務(wù)器執(zhí)行哪種同步操作。

根據(jù)情況,收到PSYNC的主服務(wù)器回向從服務(wù)器返回以下的三種回復(fù)中的其中一種:

  • 如果主服務(wù)器返回+FULLRESYNC <runid> <offset>,表示主服務(wù)器將與從服務(wù)器執(zhí)行完整重同步操作,runid是這個(gè)主服務(wù)器的運(yùn)行ID,而從服務(wù)器會將這個(gè)運(yùn)行ID保存起來,在下次發(fā)送PSYNC命令時(shí)使用,而offset是主服務(wù)器當(dāng)前的復(fù)制偏移量,從服務(wù)器會將這個(gè)值作為自己的初始化偏移量
  • 如果主服務(wù)器返回+CONTINUE,那么表示主服務(wù)器將與從服務(wù)器執(zhí)行部分重同步操作,從服務(wù)器只需要等候主服務(wù)器將自己缺少的那部分?jǐn)?shù)據(jù)發(fā)送過來完成同步即可。
  • 如果主服務(wù)器返回-ERR,那么表示主服務(wù)器的版本低于2.8識別不了PSYNC。

復(fù)制的完整流程

通過向從服務(wù)器發(fā)送SLAVEOF命令,可以讓一個(gè)從服務(wù)器去復(fù)制主服務(wù)器:SLAVEOF <master_ip> <master_port>

步驟一:設(shè)置主服務(wù)器的地址和端口

從服務(wù)器將客戶端給定的主服務(wù)器IO地址以及端口保存到服務(wù)器狀態(tài)里的masterhost屬性和masterport屬性里面。

需要注意的是:SLAVEOF命令是一個(gè)異步任務(wù),在完成masterhost屬性和masterport屬性的設(shè)置工作之后,從服務(wù)器將發(fā)送SLAVEOF命令的客戶端返回OK,表示復(fù)制指令已經(jīng)被接受,但是真正的復(fù)制工作是在OK返回之后才開始真正執(zhí)行的。

步驟二:建立套接字連接

在SLAVEOF命令執(zhí)行后,從服務(wù)器會根據(jù)命令所設(shè)置的IP地址以及端口號,創(chuàng)建連向主服務(wù)器的套接字(socket)連接。

如果連接成功,那么從服務(wù)器將會為這個(gè)套接字關(guān)聯(lián)一個(gè)專門用于處理復(fù)制工作的文件事件處理器,這個(gè)處理器將負(fù)責(zé)執(zhí)行后續(xù)的復(fù)制工作,比如接受RDB文件以及接受主服務(wù)器傳播過來的寫命令。

主服務(wù)器在接受從服務(wù)器的套接字連接之后,將會該套接字創(chuàng)建相應(yīng)的客戶端狀態(tài),并將從服務(wù)器看作是一個(gè)連接到主服務(wù)器的客戶端來對待,此時(shí)從服務(wù)器同時(shí)具有服務(wù)器和客戶端兩個(gè)身份,而接下來的復(fù)制工作都會以從服務(wù)器向主服務(wù)器發(fā)送命令請求的形式來執(zhí)行,因此理解“從服務(wù)器是主服務(wù)器的客戶端”這點(diǎn)相當(dāng)重要。

步驟三:發(fā)送PING命令

從服務(wù)器成為主服務(wù)器的客戶端之后,做的第一件事情就是向主服務(wù)器發(fā)送一個(gè)PING命令。

PING命令有以下作用:

  • 因?yàn)橹鲝姆?wù)器創(chuàng)建了套接字連接之后未進(jìn)行過任何通信,所以先要檢查套接字的讀寫狀態(tài)是否正常。
  • 檢查主服務(wù)器是否能正常處理命令請求。

從服務(wù)器發(fā)送PING命令之后會遇到以下三種情況:

  • 主服務(wù)器向從服務(wù)器返回一個(gè)命令回復(fù),但從服務(wù)器未能在限定時(shí)間里讀取命令回復(fù)的內(nèi)容,則表示主從服務(wù)器之間的網(wǎng)絡(luò)連接狀態(tài)不佳,此時(shí)需要從服務(wù)器斷開連接并重新建立連向主服務(wù)器的套接字
  • 主服務(wù)器返回一個(gè)錯誤,表示主服務(wù)器暫時(shí)無法處理從服務(wù)器的處理請求,不能執(zhí)行之后的復(fù)制工作,此時(shí)需要從服務(wù)器斷開連接并重新建立連向主服務(wù)器的套接字。
  • 如果從服務(wù)器成功讀取到PONG回復(fù),那么表示主從服務(wù)器的連接狀態(tài)正常,可以繼續(xù)執(zhí)行以下的復(fù)制操作。

步驟四:身份驗(yàn)證

從服務(wù)器收到主服務(wù)器返回的PONG之后,下一步要進(jìn)行的就是是否需要進(jìn)行身份驗(yàn)證:

  • 如果從服務(wù)器設(shè)置了masterauth,那么需要進(jìn)行
  • 反之,則不需要進(jìn)行

需要進(jìn)行身份驗(yàn)證的情況下,從服務(wù)器向主服務(wù)器發(fā)送一條AUTH命令,命令的參數(shù)為從服務(wù)器中masterauth參數(shù)的值。

從服務(wù)器身份驗(yàn)證階段可能遇到的情況有以下幾種:

  • 主服務(wù)器沒有設(shè)置requirepass選項(xiàng),而且從服務(wù)器也沒有設(shè)置masterauth,那么主服務(wù)器將繼續(xù)從服務(wù)器發(fā)送的命令,復(fù)制操作可以繼續(xù)執(zhí)行
  • 如果從服務(wù)器通過AUTH命令發(fā)送的密碼與主服務(wù)器requirepass所設(shè)置的密碼相同,則繼續(xù)執(zhí)行復(fù)制操作,反之,主服務(wù)器將返回一個(gè)invalid password錯誤
  • 如果主服務(wù)器設(shè)置了requirepass但是從服務(wù)器沒有設(shè)置masterauth,那么主服務(wù)器會返回NOAUTH錯誤
  • 如果主服務(wù)器沒有設(shè)置requirepass,但是從服務(wù)器設(shè)置了masterauth,那么主服務(wù)器將返回no password is set錯誤

所有錯誤都會讓從服務(wù)器終止當(dāng)前的復(fù)制工作,并重新創(chuàng)建套接字開始重新進(jìn)行驗(yàn)證,直至身份驗(yàn)證通過或者從服務(wù)器放棄復(fù)制為止。

步驟五:發(fā)送端口信息

身份驗(yàn)證過后,從服務(wù)器將執(zhí)行REPLCONF listening-port <port-number>,向主服務(wù)器發(fā)送從服務(wù)器的監(jiān)聽端口號。

主服務(wù)器接受該命令之后會將端口號記錄在slave_listening_port屬性中。目前該屬性的唯一作用就是主服務(wù)器執(zhí)行INFO replication命令時(shí)打印出從服務(wù)器的端口號信息。

步驟六:同步

這一步中,從服務(wù)器將向主服務(wù)器發(fā)送PSYNC命令,執(zhí)行同步操作,將自己的數(shù)據(jù)更新至與主服務(wù)器數(shù)據(jù)庫當(dāng)前所處狀態(tài)

步驟七:命令傳播

完成同步之后,主從服務(wù)器會進(jìn)入命令傳播階段,主服務(wù)器會一直將執(zhí)行的寫命令發(fā)送給從服務(wù)器,從服務(wù)器會一直接受并執(zhí)行主服務(wù)器發(fā)送來的命令,以此保證主從服務(wù)器數(shù)據(jù)庫的一致性。


心跳檢測

在命令傳播階段,從服務(wù)器會默認(rèn)以每秒一次的頻率向主服務(wù)器發(fā)送命令:REPLCONF ACK <replication_offset>

其中replication_offset是當(dāng)前從服務(wù)器的復(fù)制偏移量,發(fā)送REPLCONF ACK <replication_offset>命令對主從復(fù)制有以下三個(gè)作用:

  • 檢測主從服務(wù)器的網(wǎng)絡(luò)連接狀態(tài)
  • 輔助實(shí)現(xiàn)min-slaves參數(shù)
  • 檢測命令丟失

以下將介紹這三個(gè)作用的具體實(shí)現(xiàn)

檢測主從服務(wù)器的網(wǎng)絡(luò)連接狀態(tài)

主從服務(wù)器通過發(fā)送和接收REPLCONF ACK <replication_offset>命令來檢查兩者之間的網(wǎng)絡(luò)連接是否正常:如果主服務(wù)器超過一秒鐘沒有接收到REPLCONF ACK <replication_offset>命令,那么主服務(wù)器就知道從服務(wù)器的連接狀態(tài)出現(xiàn)問題了。

通過向主服務(wù)器發(fā)送INFO replication命令,在列出的從服務(wù)器列表中的lag一欄中,會看到對應(yīng)的從服務(wù)器上一次向主服務(wù)器發(fā)送REPLCONF ACK <replication_offset>命令距離現(xiàn)在過了多少秒:

在一般情況下,lag值應(yīng)該在0秒和1秒之間跳動,如果超過1秒,則說明主從服務(wù)器可能出現(xiàn)問題。

輔助實(shí)現(xiàn)min-slaves選項(xiàng)

Redis的min-slaves-to-write和min-slaves-max-lag兩個(gè)選項(xiàng)可以防止主服務(wù)器在不安全的情況下執(zhí)行寫命令。

min-slaves-to-write 3 min-slaves-max-lag 10

以上配置中,如果從服務(wù)器數(shù)量少于3個(gè)或者三個(gè)從服務(wù)器的延遲(lag)值都大于10秒時(shí),則主服務(wù)器將拒絕執(zhí)行寫命令。

檢測命令丟失

如果因?yàn)榫W(wǎng)絡(luò)故障,主服務(wù)器傳播給從服務(wù)器的寫命令在中途丟失,那么從服務(wù)器向主服務(wù)器發(fā)送REPLCONF ACK <replication_offset>命令,主服務(wù)器發(fā)現(xiàn)從服務(wù)器當(dāng)前復(fù)制偏移量少于自己的偏移量,然后就會根據(jù)從服務(wù)器提交的偏移量在復(fù)制積壓緩沖區(qū)里找到從服務(wù)器缺失的數(shù)據(jù),并將這些數(shù)據(jù)重新發(fā)送給從服務(wù)器。

主服務(wù)器向從服務(wù)器補(bǔ)發(fā)缺失數(shù)據(jù)這一操作的原理與部分重同步操作的原理相似,兩者的區(qū)別在于,補(bǔ)發(fā)缺失數(shù)據(jù)是在主從服務(wù)器沒有斷開連接的情況下執(zhí)行的,而部分重同步是主從服務(wù)器斷線后重連執(zhí)行的,


八、哨兵

哨兵(Sentinel)是Redis高可用性(High Availability)解決方案:由一個(gè)或多個(gè)Sentinel實(shí)例組成的哨兵系統(tǒng)可以監(jiān)視多個(gè)主服務(wù)器以及這些主服務(wù)器屬下的從服務(wù)器,并在被監(jiān)視的主服務(wù)器進(jìn)行下線狀態(tài)后,進(jìn)行故障轉(zhuǎn)移,讓某個(gè)從服務(wù)器升級為主服務(wù)器,代替已下線的主服務(wù)器繼續(xù)處理請求命令。

哨兵實(shí)現(xiàn)的功能:

  • 監(jiān)控(Monitoring):哨兵會不斷檢查主節(jié)點(diǎn)和從節(jié)點(diǎn)是否正常運(yùn)作
  • 自動故障轉(zhuǎn)移(Automaitc Failover):當(dāng)主節(jié)點(diǎn)不能正常工作時(shí),哨兵會開始自動轉(zhuǎn)移故障,將失效主節(jié)點(diǎn)的其中一個(gè)從節(jié)點(diǎn)升級為主節(jié)點(diǎn),并讓其他從節(jié)點(diǎn)復(fù)制新的主節(jié)點(diǎn)
  • 配置提供者(Configuration Provider):客戶端初始化時(shí),通過連接哨兵來獲得當(dāng)前Redis服務(wù)的主節(jié)點(diǎn)地址
  • 通知(Notification):哨兵可以將故障轉(zhuǎn)移的結(jié)果發(fā)送給客戶端

以下是一個(gè)哨兵系統(tǒng)監(jiān)視服務(wù)器的例子:

其中,雙環(huán)的server1是當(dāng)前的主服務(wù)器,單環(huán)的表示主服務(wù)器的三個(gè)從服務(wù)器,哨兵系統(tǒng)監(jiān)視著四個(gè)服務(wù)器。

此時(shí)server1出現(xiàn)故障,進(jìn)入下線狀態(tài),三個(gè)從服務(wù)器的復(fù)制操作將被終止,并且哨兵系統(tǒng)會監(jiān)察到server1已下線。

當(dāng)server1的下線時(shí)長超過了用戶設(shè)定的下線時(shí)長上限時(shí),哨兵系統(tǒng)就會對server1進(jìn)行故障轉(zhuǎn)移

  • 首先,哨兵系統(tǒng)會挑選server1的下屬的其中之一從服務(wù)器,并將這個(gè)從服務(wù)器升級為主服務(wù)器。
  • 哨兵系統(tǒng)會向其他的服務(wù)器發(fā)送新的復(fù)制指令,讓他們成為新的主服務(wù)器的從服務(wù)器,當(dāng)所有從服務(wù)器都開始復(fù)制新的主服務(wù)器時(shí),故障轉(zhuǎn)移操作執(zhí)行完畢。
  • 另外,哨兵系統(tǒng)還會監(jiān)視已下線的server1,并在它重新上線時(shí),將它設(shè)置為新的主服務(wù)器的從服務(wù)器。


啟動并初始化Sentinel

啟動一個(gè)Sentinel:

redis-sentinel sentinel.conf

當(dāng)一個(gè)Sentinel啟動時(shí),它需要執(zhí)行以下步驟:

  • 初始化服務(wù)器
  • 將普通Redis服務(wù)器使用的代碼替換成Sentinel專用代碼
  • 初始化Sentinel狀態(tài)
  • 根據(jù)給定的配置文件,初始化Sentinel的監(jiān)視主服務(wù)器列表
  • 創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接

初始化服務(wù)器

Sentinel本質(zhì)上就是一個(gè)運(yùn)行在特殊模式下的Redis服務(wù)器,這個(gè)模式叫做哨兵模式。啟動Sentinel的第一步就是初始化一個(gè)普通的Redis服務(wù)器。不過Sentinel執(zhí)行的工作跟普通的Redis服務(wù)器執(zhí)行的工作不一樣,所以兩者的初始化過程也不完全相同。

例如,Sentinel服務(wù)器并不使用數(shù)據(jù)庫,所以初始化Sentinel不會載入RDB文件或者AOF文件。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-M2QS5iXy-1631271000165)(C:/Users/Supreme%20honor/Desktop/NoteBook/redis21.jpg)]

使用Sentinel專用代碼

啟動Sentinel的第二個(gè)步驟就是將一部分普通Redis服務(wù)器使用的代碼進(jìn)行替換。

例如普通Redis服務(wù)器使用redis.h/REDIS_SERVERPORT常量值作為服務(wù)器端口

# define REDIS_SERVERPORT 6379

而Sentinel服務(wù)器使用sentinel.c/REDIS_SENTINEL_PORT常量作為服務(wù)器端口

# define REDIS_SENTINEL_PORT 26379

并且Sentinel會使用sentinel.c/sentinelcmds作為服務(wù)器的專用命令表,sentinelcmds命令表也解釋了為什么Sentinel模式下,Redis服務(wù)器不能執(zhí)行諸如SET,DEL,DBSIZE等命令,因?yàn)榉?wù)器載入的時(shí)候就沒有載入這些命令。

Sentinel模式下客戶端可以執(zhí)行的全部命令有:PING SENTINEL INFO SUBCRIBE UNSUBSCRIBE PSUBSCRIBE PUNSUBSCRIBE

初始化Sentinel狀態(tài)

服務(wù)器 初始化一個(gè)sentinel.c/sentinelState結(jié)構(gòu)(簡稱:Sentinel狀態(tài)),這個(gè)結(jié)構(gòu)中保存了服務(wù)器中所有和Sentinel有關(guān)的狀態(tài)信息。

struct sentinelState {//當(dāng)前紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移uint64_t current_epoch;//保存了所有被sentinel監(jiān)視的主服務(wù)器//字典的鍵是主服務(wù)器的名字,值是指向主服務(wù)器的指針dict *masters;//是否進(jìn)入TILT模式 int tilt;//正在執(zhí)行的腳本數(shù)量int running_scripts;//最后一次執(zhí)行時(shí)間處理器的時(shí)間mstime_t previous_time;//進(jìn)入TILT模式的時(shí)間mastime_t tilt_start_time;//一個(gè)FIFO隊(duì)列。包含了所有需要執(zhí)行的用戶腳本list *scripts_queue; }

初始化Sentinel狀態(tài)的masters屬性

如上述代碼可知,sentinelState結(jié)構(gòu)體中的masters字典記錄了所有被Sentinel監(jiān)視的主服務(wù)器的相關(guān)信息。

其中:

  • 字典的鍵是被監(jiān)視的主服務(wù)器的名稱

  • 字典的值是被監(jiān)視的主服務(wù)器對應(yīng)的sentinel.c/sentinelRedisInstance結(jié)構(gòu)。

    每一個(gè)sentinel.c/sentinelRedisInstance都代表一個(gè)被Sentinel監(jiān)視的Redis服務(wù)器實(shí)例,這個(gè)實(shí)例可以是主服務(wù)器,從服務(wù)器或另外一個(gè)哨兵Sentinel。

    typedef struct sentinelRedisInstance {//標(biāo)識值,記錄了實(shí)例的類型,以及該實(shí)例的狀態(tài)int flags;//實(shí)例的名稱//主服務(wù)器的名稱在配置文件中設(shè)置//從服務(wù)器以及Sentinel的名稱由Sentinel自動設(shè)置//格式為 ip:portchar *name;//運(yùn)行IDchar *runid;//配置紀(jì)元,實(shí)現(xiàn)故障轉(zhuǎn)移uint64_t config_epoch;//實(shí)例的地址sentinelAddr * addr;//SENTINEL down-after-milliseconds選項(xiàng)設(shè)定的值//實(shí)例無響應(yīng)多少秒之后才會被判定為主觀下線mstime_t down_after_period;//SENTINEL monitor <master-name> <ip> <port> <quorum>//判斷該實(shí)例客觀下線需要的的支持投票數(shù)量int quorum;//SENTINEL parallel-synuc <master-name> <number>選項(xiàng)的值//在執(zhí)行故障轉(zhuǎn)移操作時(shí),可以同時(shí)對新的主服務(wù)器進(jìn)行同步的從服務(wù)器數(shù)量int parallel_syncs;//SENTINEL failover-timeout <master-name> <ms>選項(xiàng)的值//刷新故障遷移狀態(tài)的最大時(shí)限mstime_t failover_timeout;//... }sentinelRedisInstance;typedef struct sentinelAddr {char *ip;int port; }sentinelAddr;

    創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接

    初始化Sentinel的最后一步是創(chuàng)建連向被監(jiān)視的主服務(wù)器的網(wǎng)絡(luò)連接,Sentinel將成為主服務(wù)器的客戶端,它可以向主服務(wù)器發(fā)送命令,并從命令回復(fù)中獲取相關(guān)的信息。

    對于每個(gè)被Sentinel監(jiān)視的主服務(wù)器來說,Sentinel會創(chuàng)建兩個(gè)連向主服務(wù)器的異步網(wǎng)絡(luò)連接:

    • 一個(gè)是命令連接,這個(gè)連接用于向主服務(wù)器發(fā)送命令,并接受命令回復(fù)。
    • 另一個(gè)是訂閱連接,這個(gè)連接用于訂閱主服務(wù)器的__sentinel__:hello頻道

    為什么要有兩個(gè)連接?

    • Redis目前的發(fā)布與訂閱功能中,被發(fā)送的信息都不會在Redis服務(wù)器中保存,如果這條信息發(fā)送時(shí),要接受信息的客戶端不在線或者掉線,那么這個(gè)客戶端就會丟失該信息,因此為了不丟失__sentinel__:hello頻道的信息,Sentinel必須專門用另外一個(gè)訂閱連接來接受該頻道的信息。
    • 除了訂閱頻道之外,Sentinel還必須向被它監(jiān)視的主服務(wù)器發(fā)送命令,以此來與主服務(wù)器進(jìn)行通訊,所有Sentinel還必須向主服務(wù)器創(chuàng)建命令連接。
    • 因?yàn)镾entinel需要和多個(gè)實(shí)例創(chuàng)建多個(gè)網(wǎng)絡(luò)連接,所以Sentinel使用的是異步連接。

獲取主服務(wù)器信息

Sentinel默認(rèn)會以每十秒一次的頻率,通過連接向被監(jiān)視的主服務(wù)器發(fā)送INFO命令,并通過分析INFO命令的回復(fù)來獲取主服務(wù)器的當(dāng)前信息。

通過分析主服務(wù)器返回的INFO命令回復(fù),Sentinel可以獲得以下兩方面信息:

  • 一方面是關(guān)于主服務(wù)器 本身的信息,包括run_id域記錄的服務(wù)器運(yùn)行ID,以及role域記錄的服務(wù)器角色。
  • 另一方面是關(guān)于主服務(wù)器屬下的所有從服務(wù)器的信息,每一個(gè)從服務(wù)器由一個(gè)slave字符串開頭的行記錄,每行中會顯示從服務(wù)器的IP地址和port端口號,根據(jù)這些信息,Sentinel無需用戶提供從服務(wù)器的地址信息就可以自動發(fā)現(xiàn)從服務(wù)器。

根據(jù)上述信息,Sentinel對主服務(wù)器的實(shí)例結(jié)構(gòu)進(jìn)行更新,例如,主服務(wù)器重啟后的運(yùn)行ID與之前保存的運(yùn)行ID不同,Sentinel會檢測到該情況,對實(shí)例結(jié)構(gòu)的運(yùn)行ID進(jìn)行更新。

獲取從服務(wù)器信息

當(dāng)Sentinel發(fā)現(xiàn)主服務(wù)器有新的從服務(wù)器出現(xiàn)時(shí),Sentinel除了會為這個(gè)新的從服務(wù)器創(chuàng)建新的實(shí)例結(jié)構(gòu)之外,還會創(chuàng)建連接到從服務(wù)器的命令連接和訂閱連接。

創(chuàng)建命令連接之后,Sentinel會以十秒一次的頻率通過向從服務(wù)器發(fā)送INFO命令,并獲得以下內(nèi)容的回復(fù):

  • 從服務(wù)器的運(yùn)行ID
  • 從服務(wù)器的角色role
  • 主服務(wù)器的IP地址master_host,以及主服務(wù)器的端口號master_port
  • 主從服務(wù)器的連接狀態(tài)master_link_status
  • 從服務(wù)器的優(yōu)先級slave_priority
  • 從服務(wù)器的復(fù)制偏移量slave_repl_offset

根據(jù)這些信息,Sentinel會對從服務(wù)器的實(shí)例結(jié)構(gòu)進(jìn)行更新。

向主從服務(wù)器發(fā)送信息

默認(rèn)情況下,Sentinel會以每兩秒一次的頻率通過命令連接向所有被監(jiān)視的主服務(wù)器和從服務(wù)器發(fā)送以下格式命令:

PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

分別記錄Sentinel和其監(jiān)視的主服務(wù)器的IP地址,端口號,運(yùn)行ID以及當(dāng)前的配置紀(jì)元。

接收來自主從服務(wù)器的的頻道信息

當(dāng)Sentinel與一個(gè)主服務(wù)器或從服務(wù)器建立起訂閱連接之后,Sentinel就會通過訂閱連接向服務(wù)器發(fā)送以下命令:

SUBCRIBE _sentinel_:hello

Sentinel對_sentinel_:hello頻道的訂閱會一直持續(xù)到Sentinel與服務(wù)器的連接斷開為止。

每個(gè)與Sentinel連接到服務(wù)器,Sentinel既通過命令連接向服務(wù)器的_sentinel_:hello頻道發(fā)送信息,又通過訂閱連接從服務(wù)器的_sentinel_:hello頻道接收信息。

當(dāng)一個(gè)Sentinel從_sentinel_:hello頻道接收到信息之后,會對該信息進(jìn)行分析,提取出Sentinel IP Sentinel post Sentinel runID等八個(gè)參數(shù)信息,并進(jìn)行以下檢查:

  • 如果信息中的運(yùn)行ID與接收信息的運(yùn)行ID相同,則說明是自己發(fā)送的,丟棄該信息,不予處理。
  • 反之,說明這條信息是由監(jiān)視同一個(gè)服務(wù)器的其他Sentinel發(fā)來的,接收信息的Sentinel會根據(jù)信息重點(diǎn)各種參數(shù),而對主服務(wù)器的實(shí)例結(jié)構(gòu)進(jìn)行調(diào)整更新。

更新sentinels字典

Sentinel為主服務(wù)器創(chuàng)建的實(shí)例結(jié)構(gòu)中的sentinels字典,不僅保存Sentinel本身,還有所有同樣監(jiān)視這個(gè)主服務(wù)器的其他Sentienl資料。

創(chuàng)建連向其他Sentinel的命令連接

當(dāng)Sentinel通過頻道信息發(fā)現(xiàn)了一個(gè)新的Sentinel時(shí),它不僅為會新Sentinel在sentinels字典中創(chuàng)建對應(yīng)的實(shí)例結(jié)構(gòu),還會創(chuàng)建一個(gè)連向新Sentinel的命令連接,而新的Sentinel也會創(chuàng)建連接到這個(gè)Sentinel的命令連接,從而讓哨兵系統(tǒng)中的多個(gè)Sentinel形成相互連接的網(wǎng)絡(luò)。

使用命令連接的各個(gè)Sentinel通過命令請求來進(jìn)行信息交換。

Sentinel之間不會創(chuàng)建訂閱連接

Sentinel在連接主從服務(wù)器時(shí)會創(chuàng)建命令連接和訂閱連接,但是在連接Sentinel時(shí)只會創(chuàng)建命令連接,這是因?yàn)镾entinel需要通過接收主從服務(wù)器發(fā)來的頻道信息發(fā)現(xiàn)未知的Sentinel,所以才需要創(chuàng)建訂閱連接,而互相已知的Sentinel則只需要通過命令連接進(jìn)行通訊即可。

檢測主觀下線狀態(tài)

在默認(rèn)情況下,Sentinel會以每秒一次的頻率向其他創(chuàng)建了命令連接的實(shí)例(主從服務(wù)器、其他Sentinel在內(nèi))發(fā)送PING命令,通過實(shí)例返回的回復(fù)的來判斷實(shí)例是否在線。

服務(wù)器對PING命令的有效回復(fù)是以下三種的其中一種:

  • +PONG
  • -LOADING錯誤
  • -MASTERDOWN錯誤

如果服務(wù)器返回了除以上三種之外的其他回復(fù),又或者在指定時(shí)間內(nèi)沒有回復(fù)PING命令,則Sentinel認(rèn)為服務(wù)器返回的回復(fù)無效。

一個(gè)服務(wù)器在master-down-after-milliseconds毫秒內(nèi)一直返回?zé)o效信息則會被Sentinel判定為主觀下線。

檢查客觀下線狀態(tài)

當(dāng)Sentinel將一個(gè)主服務(wù)器判定為主觀下線之后,為了確認(rèn)這個(gè)主服務(wù)器是否真的下線,它會向其他監(jiān)視這一主服務(wù)器的其他Sentinel進(jìn)行詢問,當(dāng)Sentinel從其他Sentinel接收到足夠的已下線判斷時(shí),Sentinel就會將這個(gè)主服務(wù)器判定為客觀下線,并進(jìn)行故障轉(zhuǎn)移。

發(fā)送SENTINEL is-master-down-by-addr <ip> <port> <current-epoch> <runid>命令向其他Sentinel詢問意見。

參數(shù)意義
ip被Sentinel判定為主觀下線的主服務(wù)器的IP地址
port被Sentinel判定為主觀下線的主服務(wù)器的端口號
current_epochSentinel當(dāng)前的配置紀(jì)元,用于選舉領(lǐng)頭Sentienl
runid可以是* 符號或者是Sentinel的運(yùn)行ID:* 符號表示命令僅僅用于主服務(wù)器的客觀下線狀態(tài),而Sentinel的運(yùn)行ID用于選舉領(lǐng)頭Sentinel

目標(biāo)Sentinel接收SENTINEL is-master-down-addr命令

當(dāng)一個(gè)Sentinel(目標(biāo)Sentinel)接收到另一個(gè)Sentinel(源Sentinel)發(fā)來的SENTINEL is-master-down-addr命令時(shí),目標(biāo)Sentinel會分析并取出命令請求中的各個(gè)參數(shù),并根據(jù)主服務(wù)器的IP和端口號,判斷主服務(wù)器是否已經(jīng)下線,然后向源Sentinel返回SENTINEL is-master-down-by <down_state> <leader_runid> <leader_epoch>命令

參數(shù)意義
down_state返回目標(biāo)Sentinel對服務(wù)器的檢查結(jié)果,1表示主服務(wù)器已經(jīng)下線,0表示主服務(wù)器未下線
leader_runid可以是* 符號或者目標(biāo)Sentinel的局部領(lǐng)頭Sentinel的運(yùn)行ID,* 符號表示主服務(wù)器的下線狀態(tài),而局部領(lǐng)頭Sentinel 的運(yùn)行ID則用于選舉領(lǐng)頭Sentinel
leader_epoch目標(biāo)Sentinel的局部領(lǐng)頭Sentinel的配置紀(jì)元,用于選舉領(lǐng)頭Sentinel,僅在leader_runid不為* 時(shí)有效,如果leader_runid的值為* ,那么leader_epoch的值為0

舉例:一個(gè)目標(biāo)Sentinel返回SENTINEL is-master-down-by <1> <*> <0>命令給源Sentinel,則說明目標(biāo)Sentinel同意主服務(wù)器已經(jīng)下線。

源Sentinel接收SENTINEL is-master-down-by命令

源Sentinel根據(jù)其他目標(biāo)Sentinel發(fā)回的SENTINEL is-master-down-by命令,Sentinel統(tǒng)計(jì)其他Sentinel同意主服務(wù)器下線的數(shù)量,當(dāng)這一數(shù)量達(dá)到配置指定的判斷客觀下線所需數(shù)量時(shí),Sentinel就會將主服務(wù)器實(shí)例結(jié)構(gòu)的flags屬性的SRI_O_DOWN標(biāo)識打開,表示該服務(wù)器已經(jīng)下線。

客觀下線的判斷條件

Sentinel配置文件中寫入了sentinel monitor mymaster 127.0.0.1 6379 2——配置的含義是:該哨兵節(jié)點(diǎn)監(jiān)控192.168.92.128:6379這個(gè)主節(jié)點(diǎn),該主節(jié)點(diǎn)的名稱是mymaster,最后的2的含義與主節(jié)點(diǎn)的故障判定有關(guān):至少需要2個(gè)哨兵節(jié)點(diǎn)同意,才能判定主節(jié)點(diǎn)故障并進(jìn)行故障轉(zhuǎn)移。

選舉領(lǐng)頭Sentinel

當(dāng)一個(gè)主服務(wù)器被判斷為客觀下線,監(jiān)視這個(gè)下線的主服務(wù)器的各個(gè)Sentinel回進(jìn)行協(xié)商,選出一個(gè)領(lǐng)頭Sentinel,并由領(lǐng)頭Sentinel對下線的主服務(wù)器進(jìn)行故障轉(zhuǎn)移操作。

以下是Redis選舉領(lǐng)頭Sentinel的規(guī)則和方法:

故障轉(zhuǎn)移

在選舉出領(lǐng)頭Sentinel之后,領(lǐng)頭Sentinel將對已下線的主服務(wù)器進(jìn)行故障轉(zhuǎn)移操作:

  • 從已下線的主服務(wù)器屬性的從服務(wù)器中挑選一個(gè)轉(zhuǎn)換為主服務(wù)器
  • 讓已下線的主服務(wù)器屬性的其他從服務(wù)器改為復(fù)制新的主服務(wù)器
  • 將已下線的主服務(wù)器設(shè)置為新的主服務(wù)器的從服務(wù)器,當(dāng)它重新連接上來時(shí)就會成為新的主服務(wù)器的從服務(wù)器

挑選新的主服務(wù)器

在已下線的主服務(wù)器屬下的所有從服務(wù)器中,挑選一個(gè)狀態(tài)良好,數(shù)據(jù)完整的從服務(wù)器,然后向它發(fā)送SLAVEOF no one命令,將這個(gè)從服務(wù)器轉(zhuǎn)換為主服務(wù)器。

新的主服務(wù)器是如何挑選的呢?

領(lǐng)頭Sentinel回將已下線的主服務(wù)器的所有從服務(wù)器保存到一個(gè)列表中,如何進(jìn)行一項(xiàng)一項(xiàng)地篩選:

  • 刪除列表中所有處于下線或者斷線的從服務(wù)器,保證列表中的服務(wù)器都是在線狀態(tài)良好的

  • 刪除列表中所有最近五秒內(nèi)沒有回復(fù)過領(lǐng)頭Sentinel的INFO命令的從服務(wù)器,保證列表中都是最近進(jìn)行成功通訊的服務(wù)器

  • 刪除所有與已下線主服務(wù)器斷開連接超過down-after-millisecond * 10 毫秒的從服務(wù)器,保證列表中的從服務(wù)器都沒有過早地與主服務(wù)器斷開連接,以此保證數(shù)據(jù)完整。

  • 從以上淘汰中存留下來的服務(wù)器,會根據(jù)復(fù)制偏移量來繼續(xù)進(jìn)行篩選,(復(fù)制偏移量最大的從服務(wù)就是保存著最新數(shù)據(jù)的服務(wù)器);

    如果復(fù)制偏移量不可用,則會根據(jù)服務(wù)器的runID來進(jìn)行選擇,選擇runID小的服務(wù)器成為主服務(wù)器。

修改從服務(wù)器的復(fù)制目標(biāo)

當(dāng)新的主服務(wù)器出現(xiàn)之后,領(lǐng)頭Sentinel下一步做的就是,讓其他從服務(wù)器去復(fù)制新的主服務(wù)器,可以通過向從服務(wù)器發(fā)送SLAVEOF實(shí)現(xiàn)。

將舊的主服務(wù)器變成從服務(wù)器

故障轉(zhuǎn)移操作最后要做的就是將已下線的主服務(wù)器設(shè)置為新的主服務(wù)器的從服務(wù)器。

當(dāng)已下線的主服務(wù)器重新連接后Sentinel就會向其發(fā)送SLAVEOF命令,使其成為新的主服務(wù)器的從服務(wù)器,如下圖所示:

Sentinel自動故障轉(zhuǎn)移的一致性特質(zhì)

Sentinel自動故障轉(zhuǎn)移使用Raft算法來選舉領(lǐng)頭Sentinel,從而確保在一個(gè)給定的紀(jì)元里面,只有一個(gè)領(lǐng)頭產(chǎn)生。

這表示同一個(gè)紀(jì)元中,不會有兩個(gè)Sentinel同時(shí)被選為領(lǐng)頭,并且各個(gè)Sentinel在同一個(gè)紀(jì)元中,只會對一個(gè)領(lǐng)頭進(jìn)行投票。

更高的配置紀(jì)元總是優(yōu)于較低的紀(jì)元,因此每個(gè)Sentinel都會主動使用更新的紀(jì)元來代替自己的配置。

可以這樣說,我們將Sentinel配置看作一個(gè)帶有版本號的狀態(tài),一個(gè)狀態(tài)會以最后寫入者的方式保留下來,當(dāng)一個(gè)有著比較舊的配置的Sentinel接收到其他Sentinel發(fā)來的版本更新的配置時(shí),就會將自己的配置進(jìn)行更新。

Sentinel狀態(tài)的持久化

Sentinel 的狀態(tài)會被持久化在 Sentinel 配置文件里面。

每當(dāng) Sentinel 接收到一個(gè)新的配置, 或者當(dāng)領(lǐng)頭 Sentinel 為主服務(wù)器創(chuàng)建一個(gè)新的配置時(shí), 這個(gè)配置會與配置紀(jì)元一起被保存到磁盤里面。

這意味著停止和重啟 Sentinel 進(jìn)程都是安全的。


九、集群

上述的高可用方案:持久化,主從復(fù)制和哨兵,但這些方案仍然存在不足,其中主要的問題就是存儲能力受單機(jī)限制,以及無法實(shí)現(xiàn)寫操作的負(fù)載均衡。

集群的作用

集群,即Redis Cluster,是Redis3.0開始引入的分布式存儲方案。

集群有多個(gè)節(jié)點(diǎn)(Node)組成,Redis的數(shù)據(jù)分布在這些節(jié)點(diǎn)中。集群中的節(jié)點(diǎn)分為主節(jié)點(diǎn)和從節(jié)點(diǎn):主節(jié)點(diǎn)負(fù)責(zé)讀寫請求和集群信息的維護(hù),從節(jié)點(diǎn)進(jìn)行主節(jié)點(diǎn)數(shù)據(jù)和狀態(tài)信息的復(fù)制。

集群的作用歸納為以下兩點(diǎn):

  • 數(shù)據(jù)分區(qū)(數(shù)據(jù)分片)

    集群將數(shù)據(jù)分散到多個(gè)節(jié)點(diǎn),一方面突破了Redis單機(jī)內(nèi)存大小的限制,另一方面每個(gè)節(jié)點(diǎn)都可以對外提供讀寫服務(wù),極大提高了集群的響應(yīng)能力。

  • 高可用

    集群支持主從復(fù)制和主節(jié)點(diǎn)的自動故障轉(zhuǎn)移,轉(zhuǎn)移機(jī)制與哨兵機(jī)制類似;當(dāng)某一節(jié)點(diǎn)出現(xiàn)故障時(shí),集群仍然可以對外提供服務(wù)。


集群的搭建

集群的搭建有兩種方式:(1)手動執(zhí)行Redis命令,一步步完成搭建;(2)使用Ruby腳本搭建。兩者原理相同,后者對前者使用到的Redis命令進(jìn)行封裝打包。

執(zhí)行Redis命令搭建集群

集群的搭建分為四步:

  • 啟動節(jié)點(diǎn):將節(jié)點(diǎn)以集群模式啟動,此時(shí)節(jié)點(diǎn)是獨(dú)立的,沒有建立與其他節(jié)點(diǎn)的連接
  • 節(jié)點(diǎn)握手:讓各個(gè)獨(dú)立的節(jié)點(diǎn)連接成一個(gè)網(wǎng)絡(luò)
  • 分配槽:將16384個(gè)槽分配給各個(gè)主節(jié)點(diǎn)
  • 指定主從關(guān)系

啟動節(jié)點(diǎn)

集群節(jié)點(diǎn)的啟動依然是使用redis-server命令,但需要以集群模式啟動,以下是節(jié)點(diǎn)的配置文件

#redis-6379.conf dbfilename "dump-6379.rdb" port 6379 daemonize no rdbcompression yes rdbchecksum yes save 10 2 appendonly yes appendfsync always appendfilename "appendonly-6379.aof" bind 127.0.0.1 #logfile "/www/server/redis/redis-6379.log" databases 16 cluster-enabled yes cluster-config-file "nodes-6379.conf" cluster-node-timeout 10000

cluster-enabled yes:Redis實(shí)例可以分為單機(jī)模式standAlone和集群模式cluster,這個(gè)設(shè)置可以開啟節(jié)點(diǎn)的集群模式

集群模式下的節(jié)點(diǎn),其redis-mode為cluster,如下圖所示:

cluster-config-file:指定了集群配置文件的位置,每個(gè)節(jié)點(diǎn)運(yùn)行過程中會維護(hù)一份集群配置文件;當(dāng)集群信息發(fā)生變化,集群中的所有節(jié)點(diǎn)會將最新信息更新到該配置文件中;當(dāng)節(jié)點(diǎn)重啟時(shí)會讀取該配置文件,獲取集群信息。Redis節(jié)點(diǎn)以集群模式啟動時(shí),會首先尋找是否有集群信息文件,如果有則使用文件中的配置啟動,如果沒有,則初始化配置并將配置保存到文件中。

編輯好配置文件后,通過redis-server命令啟動節(jié)點(diǎn):

redis-server redis-6379.conf

節(jié)點(diǎn)啟動以后,通過cluster nodes命令可以查看節(jié)點(diǎn)的情況,如下圖所示。

其中返回值第一項(xiàng)表示節(jié)點(diǎn)id,由40個(gè)16進(jìn)制字符串組成,集群模式下的節(jié)點(diǎn)的run_id與單機(jī)模式下的節(jié)點(diǎn)run_id有所不同,Redis每次啟動都會重新創(chuàng)建run_id,但是集群模式下只會在初始時(shí)創(chuàng)建一次,然后保存到集群配置文件中,之后節(jié)點(diǎn)重啟會從配置文件從讀取,而不再重新創(chuàng)建。

需要注意的是:啟動節(jié)點(diǎn)階段,節(jié)點(diǎn)之間是沒有主從關(guān)系的,因此節(jié)點(diǎn)中不需要添加slaveof配置。

節(jié)點(diǎn)握手

節(jié)點(diǎn)啟動后是互相獨(dú)立的,并不知道其他節(jié)點(diǎn)存在,因此集群模式中需要進(jìn)行節(jié)點(diǎn)握手,將獨(dú)立的節(jié)點(diǎn)組成一個(gè)網(wǎng)絡(luò)。

節(jié)點(diǎn)握手使用cluster meet {ip} {port}命令實(shí)現(xiàn)。

分配槽

在Redis集群中,借助槽實(shí)現(xiàn)數(shù)據(jù)分區(qū),集群有16384個(gè)槽,槽是數(shù)據(jù)管理和遷移的基本單位,當(dāng)數(shù)據(jù)庫中的16384個(gè)槽分配了節(jié)點(diǎn),集群處于上線狀態(tài)(ok),如果有一個(gè)槽沒有分配節(jié)點(diǎn),則集群處于下線狀態(tài)(fail)。

redis-cli -p 7000 cluster addslots {0..5461} redis-cli -p 7001 cluster addslots {5462..10922} redis-cli -p 7002 cluster addslots {10923..16383}

此時(shí)查看集群狀態(tài),顯示所有槽分配完畢,集群進(jìn)入上線狀態(tài):

指定主從關(guān)系

集群中指定關(guān)系不再使用slaveof命令,而是使用cluster replicate run_id

例如:

redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4 redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1

至此,集群搭建完畢。


使用Ruby腳本搭建集群

在{REDIS_HOME}/src目錄下有一個(gè)redis-trib.rb文件,這是一個(gè)Ruby腳本,可以實(shí)現(xiàn)集群的自動搭建。

安裝Ruby環(huán)境

輸入以下命令

apt-get install ruby #安裝ruby環(huán)境 gem install redis #gem是ruby的包管理工具

啟動節(jié)點(diǎn)

redis-server redis-6379.conf

搭建集群

redis-trib.rb腳本提供了眾多命令,其中create用于搭建集群:

redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1

-replicas 1:表示每個(gè)主節(jié)點(diǎn)有一個(gè)從節(jié)點(diǎn);多個(gè){id:port}表示節(jié)點(diǎn)地址,前面的做主節(jié)點(diǎn),后面的做從節(jié)點(diǎn)。

注意:使用redis-trib.rb腳本搭建集群時(shí),要求節(jié)點(diǎn)不能包含任何槽和數(shù)據(jù),否則會報(bào)以下錯誤:

執(zhí)行創(chuàng)建命令之后,腳本會給出創(chuàng)建集群的計(jì)劃,如下圖所示,計(jì)劃包括哪些節(jié)點(diǎn)是主節(jié)點(diǎn),哪些是從節(jié)點(diǎn),以及如何分配槽。

#是否執(zhí)行計(jì)劃 Can I set the above configuration?(type 'yes' to accept):yes

輸入yes執(zhí)行計(jì)劃,至此,集群搭建完畢。


集群設(shè)計(jì)

設(shè)計(jì)集群方案時(shí),需要考慮以下因素:

  • 高可用要求:根據(jù)故障自動轉(zhuǎn)移原理,至少需要3個(gè)主節(jié)點(diǎn)才能完成故障轉(zhuǎn)移,且三個(gè)主節(jié)點(diǎn)應(yīng)在不同的物理機(jī)上,每個(gè)主節(jié)點(diǎn)至少需要一個(gè)從節(jié)點(diǎn),主從節(jié)點(diǎn)應(yīng)在不同的物理機(jī)上,因此高可用集群至少需要6個(gè)節(jié)點(diǎn)來支持。
  • 數(shù)據(jù)量和訪問量:估算應(yīng)用需要的數(shù)據(jù)量和總訪問量,結(jié)合每個(gè)主節(jié)點(diǎn)的容量和能承受的訪問量(可以通過benchmark估算),計(jì)算所需的主節(jié)點(diǎn)個(gè)數(shù)。
  • 節(jié)點(diǎn)數(shù)量限制:Redis官方給出的節(jié)點(diǎn)數(shù)量限制是1000,主要是考慮節(jié)點(diǎn)間通信帶來的消耗。實(shí)際應(yīng)用中需要避免大量集群,如果節(jié)點(diǎn)數(shù)量不足以滿足應(yīng)用對Redis數(shù)據(jù)量和訪問量的要求,可以考慮:(1)業(yè)務(wù)分割,大集群劃分為多個(gè)小集群;(2)減少不必要的數(shù)據(jù);(3)調(diào)整過期數(shù)據(jù)刪除策略。
  • 適度冗余:Redis可以在不影響集群服務(wù)的情況下適度增加節(jié)點(diǎn),保證數(shù)據(jù)容冗余。

數(shù)據(jù)結(jié)構(gòu)

節(jié)點(diǎn)需要專門的數(shù)據(jù)結(jié)構(gòu)來存儲集群的狀態(tài)。所謂集群的狀態(tài),是一個(gè)很大的概念,包括:集群是否處于上線狀態(tài),集群中有哪些節(jié)點(diǎn),節(jié)點(diǎn)的主從狀態(tài),槽指派的分布等。

節(jié)點(diǎn)為了存儲集群狀態(tài)而提供的數(shù)據(jù)結(jié)構(gòu)中,最關(guān)鍵的是clusterNode和clusterState結(jié)構(gòu),前者記錄集群中一個(gè)節(jié)點(diǎn)的狀態(tài),后者記錄了集群作為一個(gè)整體的狀態(tài)。

每個(gè)節(jié)點(diǎn)都會使用一個(gè)clusterNode結(jié)構(gòu)來記錄自己的狀態(tài),并為集群中所有的節(jié)點(diǎn)創(chuàng)建一個(gè)clusterNode結(jié)構(gòu),以此記錄其他節(jié)點(diǎn)的狀態(tài):

struct clusterNode {//節(jié)點(diǎn)創(chuàng)建時(shí)間mstime_t ctime;//節(jié)點(diǎn)名稱char name[REDIS_CLUSTER_NAMELEN];//節(jié)點(diǎn)的ip和端口號char ip[REDIS_IP_STR_LEN];int port;//節(jié)點(diǎn)標(biāo)識:整型,每個(gè)bit都代表了不同狀態(tài),如節(jié)點(diǎn)的主從狀態(tài)、是否在線、是否在握手等int flags;//配置紀(jì)元:故障轉(zhuǎn)移時(shí)起作用,類似于哨兵的配置紀(jì)元uint64_t configEpoch;//槽在該節(jié)點(diǎn)中的分布:占用16384/8個(gè)字節(jié),16384個(gè)比特;每個(gè)比特對應(yīng)一個(gè)槽:比特值為1,則該比特對應(yīng)的槽在節(jié)點(diǎn)中;比特值為0,則該比特對應(yīng)的槽不在節(jié)點(diǎn)中unsigned char slots[16384/8];//節(jié)點(diǎn)中槽的數(shù)量int numslots;//... };

除了上述字段,clusterNode還包含了節(jié)點(diǎn)連接、主從復(fù)制、故障發(fā)現(xiàn)和轉(zhuǎn)移需要的信息等。

clusterState

typedef struct clusterState {//自身節(jié)點(diǎn)clusterNode *myself;//配置紀(jì)元uint64_t currentEpoch;//集群狀態(tài):在線還是下線int state;//集群中至少包含一個(gè)槽的節(jié)點(diǎn)數(shù)量int size;//哈希表,節(jié)點(diǎn)名稱->clusterNode節(jié)點(diǎn)指針dict *nodes;//槽分布信息:數(shù)組的每個(gè)元素都是一個(gè)指向clusterNode結(jié)構(gòu)的指針;如果槽還沒有分配給任何節(jié)點(diǎn),則為NULLclusterNode *slots[16384]; };

集群命令的實(shí)現(xiàn)

cluster meet

通過向節(jié)點(diǎn)A發(fā)送cluster meet命令,客戶端可以讓接收命令的節(jié)點(diǎn)A將另一個(gè)節(jié)點(diǎn)B添加到節(jié)點(diǎn)A 所在的集群中。

CLUSTER MEET <ip> <port>

收到命令的節(jié)點(diǎn)A將與節(jié)點(diǎn)B進(jìn)行握手,以此確定彼此的存在,并為將來進(jìn)一步的通信打好基礎(chǔ)。具體步驟:

  • 節(jié)點(diǎn)A為節(jié)點(diǎn)B創(chuàng)建一個(gè)clusterNode結(jié)構(gòu)來存儲節(jié)點(diǎn)B的信息,并將該結(jié)構(gòu)添加到自己的clusterState.nodes字典里。
  • 之后,節(jié)點(diǎn)A根據(jù)CLUSTER MEET <ip> <port>命令中指定的IP地址和端口號,向節(jié)點(diǎn)B發(fā)送一條MEET消息。
  • 節(jié)點(diǎn)B接收到節(jié)點(diǎn)A發(fā)送的MEET消息,節(jié)點(diǎn)B為節(jié)點(diǎn)A創(chuàng)建一個(gè)clusterNode結(jié)構(gòu),并將該結(jié)構(gòu)添加到自己的clusterState.nodes字典里。
  • 之后節(jié)點(diǎn)B向節(jié)點(diǎn)A返回一條PONG消息。
  • 節(jié)點(diǎn)A將接收到節(jié)點(diǎn)B返回的PONG消息,通過這條消息,節(jié)點(diǎn)A可以得知節(jié)點(diǎn)B已經(jīng)成功地接收自己的MEET消息。
  • 之后節(jié)點(diǎn)A將向節(jié)點(diǎn)B返回一條PING消息。
  • 節(jié)點(diǎn)B將接收到節(jié)點(diǎn)A返回的PING消息,通過這條消息,節(jié)點(diǎn)B可以得知節(jié)點(diǎn)A已經(jīng)成功接收了自己返回的PONG消息,至此,握手完成。


cluster addslots

集群中槽的分配信息,存儲在clusterNode的slots數(shù)組中和clusterState的slots數(shù)組中,兩個(gè)數(shù)組之間的區(qū)別是,前者存儲的該節(jié)點(diǎn)中分配了哪些槽,而后者存儲的每個(gè)槽所指向的節(jié)點(diǎn),即集群中所有槽分別分布在哪個(gè)節(jié)點(diǎn)。

cluster addslots命令接收一個(gè)或多個(gè)槽作為參數(shù),例如在A節(jié)點(diǎn)上執(zhí)行cluster addslots {0,1989}命令,是將編號為0-1989的槽分配給A節(jié)點(diǎn),具體執(zhí)行步驟如下:

  • 遍歷槽,檢查它們0-1989是否都沒有分配節(jié)點(diǎn),如果有一個(gè)槽已經(jīng)分配,則命令執(zhí)行失敗;檢查方法是遍歷槽在clusterState.slots[]中對應(yīng)的值是否為NULL值。
  • 遍歷槽,將其分配給節(jié)點(diǎn)A,將clusterNode.slots[]中對應(yīng)的比特修改為1,以及clusterState.slots[]中對應(yīng)的指針指向節(jié)點(diǎn)A。
  • 執(zhí)行完畢后,通過節(jié)點(diǎn)通信機(jī)制通知其他節(jié)點(diǎn),所有節(jié)點(diǎn)都會知道0-1989的槽分配給了節(jié)點(diǎn)A。

實(shí)踐須知

集群伸縮

實(shí)際場景中常常需要對集群進(jìn)行伸縮,如果訪問量增大時(shí),集群的擴(kuò)容操作。Redis集群可以在不影響對外服務(wù)的情況下對集群進(jìn)行伸縮;其核心是槽遷移:修改槽與節(jié)點(diǎn)之間的關(guān)系,實(shí)現(xiàn)槽在節(jié)點(diǎn)中的遷移。例如,如果槽均勻分配在三個(gè)節(jié)點(diǎn)中,現(xiàn)需要新增一個(gè)節(jié)點(diǎn),則需要從3個(gè)節(jié)點(diǎn)中取出一部分槽分配給新的節(jié)點(diǎn),從而實(shí)現(xiàn)槽的重新分配。

新增節(jié)點(diǎn)

  • 啟動節(jié)點(diǎn)
  • 節(jié)點(diǎn)握手
  • 遷移槽,使用redis-trib.rb 的reshard(重新分區(qū))工具實(shí)現(xiàn),reshard自動化程度很高,只需要輸入redis-trib.rb reshard ip:port即可自動實(shí)現(xiàn)槽遷移。
  • 指定主從關(guān)系

減少節(jié)點(diǎn)

  • 遷移槽,使用reshard將需要刪除的節(jié)點(diǎn)的槽均勻遷移到其他節(jié)點(diǎn)上
  • 下線節(jié)點(diǎn):使用redis-trib.rb del-node工具,先下線從節(jié)點(diǎn)再下線主節(jié)點(diǎn)。

ASK錯誤

當(dāng)客戶端向源節(jié)點(diǎn)發(fā)送一個(gè)與數(shù)據(jù)庫有關(guān)的命令,并且命令要處理的數(shù)據(jù)庫鍵剛好就屬于正在被遷移的槽時(shí):

  • 源節(jié)點(diǎn)會現(xiàn)在自己的數(shù)據(jù)庫中查找指定的鍵,如果找到就執(zhí)行客戶端發(fā)送的命令。
  • 相反,如果源節(jié)點(diǎn)沒有在數(shù)據(jù)庫中找到指定的鍵,則這個(gè)鍵有可能已經(jīng)被遷移到了其他節(jié)點(diǎn),此時(shí)源節(jié)點(diǎn)將向客戶端返回一個(gè)ASK錯誤,指引客戶端轉(zhuǎn)向正在導(dǎo)入槽的目標(biāo)節(jié)點(diǎn),并且再次發(fā)送之前要執(zhí)行的命令。

客戶端收到ASK錯誤后,從中讀取目標(biāo)節(jié)點(diǎn)的地址信息,并向目標(biāo)節(jié)點(diǎn)重新發(fā)送請求,就像收到MOVED錯誤時(shí)一樣。但是二者有很大區(qū)別:ASK錯誤說明數(shù)據(jù)正在遷移,不知道何時(shí)遷移完成,因此重定向是臨時(shí)的,SMART客戶端不會刷新slots緩存;MOVED錯誤重定向則是(相對)永久的,SMART客戶端會刷新slots緩存。


參考文獻(xiàn):

《Redis設(shè)計(jì)與實(shí)現(xiàn)》
《Redis開發(fā)與運(yùn)維》

以上

創(chuàng)作不易,如果文章對你有幫助,留個(gè)三連再走吧。

如果不足或錯誤歡迎評論指正。

總結(jié)

以上是生活随笔為你收集整理的深入学习Redis的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

91精品视频免费观看 | 麻豆国产精品永久免费视频 | 丁香影院在线 | 在线电影av| 中文字幕精品一区二区精品 | 日本久久免费电影 | 亚洲三级毛片 | 91精品国产一区二区在线观看 | 国产精品久久久久久久久久久杏吧 | 在线免费日韩 | 精品久久1 | 国产99re| 欧美国产日韩在线视频 | 免费看一级特黄a大片 | 色噜噜狠狠色综合中国 | www.av免费观看 | 国产精品成人自产拍在线观看 | 欧美日韩国产高清视频 | 日韩一区二区三区视频在线 | 日韩欧美一区视频 | av福利网址导航大全 | 四虎国产精品成人免费4hu | 日韩精品久久久久久久电影竹菊 | 日韩在线免费不卡 | 国产精品毛片一区视频播 | 99久久er热在这里只有精品66 | 在线看片成人 | 国产97av| 亚洲日本va中文字幕 | 99久久免费看 | 久久久久免费观看 | 91桃色免费观看 | 九九热在线观看 | 日韩影视大全 | 免费在线看v | 国产黄色免费看 | 97超碰人人在线 | 在线观看的黄色 | 日韩一级黄色av | 日本视频不卡 | 国产视频久久久 | 狠狠网| 黄色aa久久 | www.国产在线 | 国产精品综合在线 | 亚洲精品人人 | 久久男人影院 | 色的网站在线观看 | 亚洲欧美日韩一区二区三区在线观看 | 久久99深爱久久99精品 | 国产亚洲aⅴaaaaaa毛片 | 热久久精品在线 | 香蕉视频91| 久久久久久久久久久影视 | 成人av手机在线 | 成人h在线观看 | 狠狠躁天天躁 | 深夜福利视频一区二区 | 久草视频99 | 精品国产观看 | 久久成人午夜视频 | 色狠狠综合天天综合综合 | 免费日韩一级片 | 国产又粗又硬又爽视频 | 精品在线视频一区二区三区 | 久久视频在线 | 欧美一级电影在线观看 | 伊人中文在线 | 狠狠干夜夜操天天爽 | www.夜色.com| 日韩成人免费在线 | 精品亚洲视频在线 | 欧美国产高清 | 免费观看久久久 | 久草在线电影网 | 国产精品你懂的在线观看 | 波多野结衣在线播放一区 | 毛片在线播放网址 | 久久久久久久亚洲精品 | 久草视频在线资源 | 99色在线视频 | 国产专区视频在线 | 亚洲第一区在线观看 | 四虎影视精品永久在线观看 | 久久精品国产99国产 | 日韩精品中文字幕在线播放 | 四虎永久精品在线 | 福利视频一二区 | 丁香电影小说免费视频观看 | 亚洲高清在线 | 91精品国产99久久久久久红楼 | 99热这里只有精品久久 | 久久躁日日躁aaaaxxxx | 91av在| 成片免费观看视频 | 天堂网av 在线 | av免费电影在线观看 | 久久免费国产视频 | 成人毛片一区 | 色噜噜狠狠色综合中国 | 黄色一及电影 | 欧美日韩在线观看不卡 | 久久久久女人精品毛片 | 天海翼一区二区三区免费 | 四虎在线免费观看视频 | 欧美日韩中文另类 | 成年人免费观看国产 | 免费在线色电影 | 亚洲精品小视频 | 国产视频精品在线 | 国产黄色特级片 | 亚洲永久精品在线观看 | 国产v在线观看 | 成人av.com| 天天操天天吃 | 日本深夜福利视频 | 精品视频在线观看 | 国产麻豆精品久久 | 水蜜桃亚洲一二三四在线 | 欧美肥妇free| 国产精品免费久久久久久久久久中文 | 亚洲欧美国产视频 | 日韩av电影中文字幕在线观看 | 国产成人精品亚洲日本在线观看 | 蜜臀久久99精品久久久无需会员 | 国产精品久久久久久一二三四五 | 国外调教视频网站 | 久久久久国产精品厨房 | 麻豆视频国产精品 | 久久99精品久久只有精品 | 国产成人精品在线观看 | 91在线国内视频 | 欧美成人精品欧美一级乱 | 亚洲精品视| 精品免费99久久 | 免费日韩在线 | 又长又大又黑又粗欧美 | 日本成人中文字幕在线观看 | 伊人色**天天综合婷婷 | 97在线免费视频 | 午夜少妇av | 日本最新高清不卡中文字幕 | 国产精品欧美在线 | 水蜜桃亚洲一二三四在线 | 精品一二 | 久草手机视频 | 久久国产精品一国产精品 | 久久久99国产精品免费 | 久久精品—区二区三区 | 一区二区三区免费播放 | 丁香九月激情 | 五月婷婷在线观看视频 | 亚洲一区二区高潮无套美女 | 三级av免费观看 | 精品国产欧美一区二区三区不卡 | 美女黄网久久 | 国产麻豆剧果冻传媒视频播放量 | 日本69hd| 欧美视屏一区二区 | 在线观看亚洲精品 | 97超在线视频 | 精品字幕 | 久久99欧美 | 96亚洲精品久久 | 精品一区二区三区久久 | 国产精品亚洲片在线播放 | 亚洲免费在线播放视频 | 精品视频资源站 | 精品国产一区二区三区免费 | 日韩中文字幕第一页 | 天天干天天草天天爽 | 激情视频在线观看网址 | 美女精品久久久 | 免费黄色小网站 | 麻豆精品视频在线观看免费 | 国产精品亚州 | 久久国内免费视频 | 日本黄色大片儿 | 在线成人短视频 | 亚洲丝袜一区二区 | 9ⅰ精品久久久久久久久中文字幕 | 中文字幕成人在线 | 美女网站在线播放 | 一区二区三区免费播放 | 色中色综合 | 亚洲理论在线 | 久久久污 | 欧美精品天堂 | 日韩av午夜在线观看 | 夜夜躁日日躁狠狠久久av | 一区中文字幕 | 免费观看十分钟 | 四虎永久国产精品 | 天天综合网天天综合色 | 午夜久久久久久久 | 91九色在线视频 | av电影中文字幕 | 五月婷婷丁香 | 欧美成人xxxxxxxx | 81精品国产乱码久久久久久 | 4p变态网欧美系列 | www99久久 | 欧美日韩亚洲在线观看 | 日本久久视频 | 黄色av免费看 | 在线视频免费观看 | 一区二区 不卡 | 毛片基地黄久久久久久天堂 | 国产手机免费视频 | 国产精品自产拍在线观看桃花 | 九月婷婷色 | 精品亚洲二区 | 久久久官网 | 91污在线观看 | 婷婷久操 | 日日夜夜天天人人 | 99精品免费久久久久久日本 | 中文字幕在线视频第一页 | 久久撸在线视频 | 福利电影一区二区 | 久久免费黄色网址 | 亚洲激情免费 | 色操插 | 国产艹b视频 | 久久精品中文 | 国产在线黄 | 亚洲国产精品va在线看黑人 | 国产黄大片在线观看 | 免费在线a | 黄色日本片 | 黄色精品一区 | 国产欧美久久久精品影院 | 视频三区 | 国产精品99久久久精品免费观看 | 美女视频久久黄 | 999久久久 | 天天干天天干天天射 | 狠狠色伊人亚洲综合网站野外 | 亚洲天堂色婷婷 | 日韩成人免费在线观看 | 日韩激情中文字幕 | 成+人+色综合 | 97视频免费在线 | 亚洲网站在线看 | 五月天久久久久久 | 亚洲色图美腿丝袜 | 美女av免费看 | 九九免费在线观看视频 | 日本在线观看一区二区三区 | 91九色视频在线观看 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 黄色大全视频 | 国产精品久久久久久久久久久久冷 | 免费看片网站91 | 日韩综合一区二区三区 | 欧美日韩精品在线免费观看 | 国产午夜精品久久久久久久久久 | 亚洲高清在线精品 | 亚洲高清视频在线观看 | 中文字幕日本在线 | 夜夜操夜夜干 | 国产美女免费视频 | av黄免费看 | 天天艹天天 | 成片视频在线观看 | 男女靠逼app | 正在播放国产91 | 91最新地址永久入口 | 免费福利片2019潦草影视午夜 | 久久精品中文字幕少妇 | 精品国产一区二区三区在线 | 国产精品丝袜久久久久久久不卡 | 高清久久久 | 91精品国产九九九久久久亚洲 | 色精品视频 | www.狠狠操.com| 中文字幕在线观看亚洲 | 毛片网站在线看 | 国产精品第二页 | 亚洲精品美女在线观看播放 | aⅴ精品av导航 | 欧美一级乱黄 | 国产一级片网站 | 中文字幕一区在线 | 片黄色毛片黄色毛片 | 在线国产视频一区 | 99国产精品视频免费观看一公开 | 国产精品va在线观看入 | www.久艹| 在线观看日韩国产 | 日韩毛片一区 | 五月婷婷视频在线观看 | 国产成人久久77777精品 | 日韩影视在线观看 | 日b黄色片 | 国产中文字幕一区二区 | 天天拍夜夜拍 | 日韩在线免费播放 | 久久人人爽人人爽人人 | 91成品人影院 | 夜夜澡人模人人添人人看 | 精品 激情| 日本黄色黄网站 | 久久99精品久久久久久清纯直播 | 人人玩人人添人人澡97 | 一级片观看 | 久久亚洲视频 | 天天色天天搞 | 久久久精品 | 美女精品久久 | 天天天色综合a | 国产丝袜高跟 | 天天操天天爱天天爽 | 国产中文在线观看 | 免费视频三区 | 色a网 | 夜色资源站wwwcom | 亚洲成年人免费网站 | 91亚洲精品国偷拍自产在线观看 | 色九九影院 | 久草青青在线观看 | 久久久久久久久久免费 | 国产精品综合久久久久 | 在线观看完整版免费 | 久久精品国产免费看久久精品 | 亚洲 欧美 国产 va在线影院 | 一区二区三区四区五区六区 | 欧美日韩色婷婷 | 日本精品中文字幕 | 久久久久综合精品福利啪啪 | 黄色三级网站 | 久久黄色免费视频 | 麻豆视频免费 | 国内精品视频免费 | 国产免费不卡 | 蜜臀av免费一区二区三区 | 亚州国产视频 | 久二影院| 国产精品密入口果冻 | 日韩高清在线观看 | 伊人网av | a极黄色片| 欧美成年网站 | 中文字幕黄色av | 精品91视频 | 91人人揉日日捏人人看 | 成人在线播放免费观看 | 国产不卡精品视频 | 久久国产精品一国产精品 | 欧美乱码精品一区二区 | 久久在线免费观看视频 | 亚州欧美视频 | 五月天中文字幕mv在线 | 日韩毛片一区 | 国产手机免费视频 | 亚洲五月婷婷 | 国产女人免费看a级丨片 | 国产在线精品国自产拍影院 | 午夜精品一二区 | 天天干天天拍天天操 | 91看片淫黄大片在线播放 | 中文字幕精品一区久久久久 | 在线观看成人毛片 | 国产明星视频三级a三级点| 国产成人免费在线观看 | 欧美精品少妇xxxxx喷水 | 五月婷婷色综合 | 中国黄色一级大片 | 国产精品久久久久高潮 | 亚洲精品系列 | 免费看一级黄色大全 | 超碰97.com| 欧美a级在线播放 | 日韩av看片 | 久草在线资源网 | 日韩精品综合在线 | 亚洲精品免费在线播放 | 香蕉视频亚洲 | 国产精品一区二区av日韩在线 | 国产 日韩 在线 亚洲 字幕 中文 | 久久久久久国产精品免费 | 综合精品久久久 | 天天操人人干 | 人人要人人澡人人爽人人dvd | www.综合网.com | 黄色中文字幕在线 | 亚洲国产日韩精品 | 国产精品一区在线 | 日韩肉感妇bbwbbwbbw | 色多多在线观看 | 在线视频app | 成人影片在线免费观看 | 欧美一区中文字幕 | 亚洲精品动漫成人3d无尽在线 | 国产精品高潮呻吟久久av无 | 中文字幕视频观看 | 久久99精品国产一区二区三区 | 久久ww| 正在播放国产一区 | 一区二区伦理电影 | 亚洲国产中文字幕在线观看 | 91黄色小视频 | 色狠狠一区二区 | 西西www4444大胆在线 | 欧美三级高清 | 国产精品久久电影网 | 国产精品综合av一区二区国产馆 | 中文亚洲欧美日韩 | 中文字幕第一 | 激情丁香| 成人午夜电影网站 | 国产九九九九九 | av中文在线 | 国产日韩视频在线播放 | 天天射天天干天天插 | 一区二区三区在线免费观看 | 在线日本v二区不卡 | 日本久久久久久久久 | 免费视频久久久 | 欧美小视频在线观看 | 国产精品美女www爽爽爽视频 | 97理论片 | 亚洲资源在线 | av黄色免费在线观看 | 在线成人观看 | 欧美精品一区二区蜜臀亚洲 | 波多野结衣一区二区三区中文字幕 | 国产福利精品在线观看 | 欧美老人xxxx18| 中文字幕 国产视频 | 五月婷婷黄色 | 特级毛片在线观看 | 日本99精品 | 久久久国产精品一区二区中文 | 欧美极品在线播放 | 国产亚洲人成网站在线观看 | 国产剧在线观看片 | 69精品视频 | 99色 | 奇米777777 | 色婷婷狠狠五月综合天色拍 | 国产成人精品久久久久蜜臀 | 精品久久久久久久久久 | 一区二区三区精品在线 | 69国产成人综合久久精品欧美 | 黄色大片免费网站 | 美女黄色网在线播放 | 欧美一二区在线 | 国产又粗又猛又黄 | 欧美日韩中文国产一区发布 | 一区二区精品 | 超碰99在线 | 91av久久 | 中文字幕在线看视频国产 | 成人在线视频一区 | 一区二区三区电影大全 | 六月婷婷网 | 国产成人精品国内自产拍免费看 | 成人av一区二区兰花在线播放 | 欧美日韩xxxxx | 草免费视频 | 粉嫩一二三区 | 456免费视频 | 日韩av线观看 | 免费 在线 中文 日本 | 天天操福利视频 | 手机看片1042 | 激情久久伊人 | 9ⅰ精品久久久久久久久中文字幕 | 在线免费观看的av | 久久香蕉一区 | 日韩欧美在线不卡 | 精品久久一区 | 中文高清av | 91亚洲激情 | 色婷婷精品大在线视频 | 8x成人免费视频 | 久草在线视频新 | 国产在线999| 久久久久久久久久久网站 | 久久亚洲日本 | 国产 日韩 欧美 在线 | av在线日韩 | 一区二区三区中文字幕在线 | 九九热视频在线播放 | 91视频免费播放 | 韩国三级av在线 | 久久久久色 | 最新中文字幕在线观看视频 | 久久国产高清视频 | 国产精品女人久久久久久 | 中文字幕乱码在线播放 | 日本一区二区三区免费看 | 操操综合网 | 中文字幕在线看视频国产 | 国产高清精 | 日本精品一区二区三区在线播放视频 | 精品一二三四在线 | 久久免费在线观看 | 亚洲精品综合久久 | 婷婷在线色 | 国内免费的中文字幕 | 亚洲视频axxx | www.99久久.com | 青青色影院 | 亚洲乱码中文字幕综合 | 亚洲国产小视频在线观看 | 青草视频在线播放 | 欧美日韩亚洲在线 | 蜜臀av.com | 日韩有码网站 | 狠狠撸电影 | 婷婷网在线 | 国产人成一区二区三区影院 | 亚洲精品久久视频 | 欧美日韩在线观看一区二区 | 久久久久99精品成人片三人毛片 | 久久久久久蜜桃一区二区 | 91欧美国产 | 日韩精品一区二区三区在线播放 | 久久爱导航 | 又黄又爽又色无遮挡免费 | 欧美日韩中 | 大胆欧美gogo免费视频一二区 | 三级黄免费看 | 日韩av在线免费看 | 欧美极品xxxx| 午夜av一区 | 久久狠狠干 | 天天综合网入口 | 夜夜夜夜夜夜操 | 夜夜躁日日躁狠狠久久88av | 久久精品日产第一区二区三区乱码 | 狠狠操欧美 | 爱情影院aqdy鲁丝片二区 | 97碰碰精品嫩模在线播放 | 黄色成人av| 欧美一二三专区 | 日本在线中文在线 | 最新中文字幕 | 国产精品av免费在线观看 | 亚洲国产精品成人女人久久 | 久久久亚洲精华液 | 国产剧情一区在线 | 中国一级特黄毛片大片久久 | 亚洲国产精品成人精品 | 超碰在线1 | 天天鲁天天干天天射 | 成年人视频免费在线 | 欧美性护士 | 日韩欧美一区二区三区免费观看 | 日韩国产精品久久久久久亚洲 | av丝袜制服 | 成人av一区二区在线观看 | 亚洲精品视频国产 | 91成人网在线观看 | 国产中文 | 久久久久久久久久久久久影院 | 国产精品自产拍在线观看桃花 | 中文字幕亚洲五码 | 国产精品v欧美精品 | 黄免费在线观看 | 欧美一区在线观看视频 | 天天干天天射天天操 | 99精品福利视频 | 色婷婷在线视频 | 久久人91精品久久久久久不卡 | 免费观看www7722午夜电影 | 亚洲精品一区中文字幕乱码 | 成人小视频在线观看免费 | 国产无遮挡猛进猛出免费软件 | 免费色视频网站 | 伊人五月综合 | japanese黑人亚洲人4k | 日韩午夜精品福利 | 欧美日韩性视频在线 | 亚洲成人第一区 | 在线免费试看 | 日日干天天操 | 久草精品电影 | 久久综合久久综合这里只有精品 | 久久精品国产精品亚洲 | 尤物97国产精品久久精品国产 | 久久国产精品99久久久久久老狼 | 免费精品视频在线观看 | 亚洲精品www久久久久久 | 亚洲国产免费看 | 久草色在线观看 | 国产精品成人aaaaa网站 | 天天色成人网 | 日韩美av在线 | 一级a毛片高清视频 | 黄色三级免费看 | 亚洲国产激情 | 黄色一级大片在线免费看产 | 夜夜爽www| 日韩欧美视频 | 成人综合日日夜夜 | 亚洲精选在线 | 天天射综合 | 亚洲成人精品 | 久久在线精品 | 五月天中文字幕mv在线 | 欧美日韩不卡在线视频 | 国产一区二区免费在线观看 | 亚州av免费 | 九九热在线精品 | 91亚洲国产成人久久精品网站 | 国产伦精品一区二区三区高清 | 青青射 | 久久久综合精品 | 亚洲国产精品一区二区尤物区 | 91在线一区二区 | 中文字幕网址 | a黄色片在线观看 | 国产精品自产拍 | 欧美一区二区在线看 | 99色网站| 91av美女| 国产精品黑丝在线观看 | 天天操天天干天天爽 | 蜜臀久久99精品久久久久久网站 | 中文字幕中文字幕中文字幕 | www.久草视频 | 九草在线观看 | 不卡国产在线 | 五月婷婷在线视频观看 | 免费看的国产视频网站 | 最近日本韩国中文字幕 | 爱射综合 | 欧美在线观看视频 | 久久99久久99精品免视看婷婷 | 久久精品4| 日韩在线视频精品 | 国产视频精品久久 | 国产成人一区二区啪在线观看 | 免费看色视频 | 久久精品导航 | 日韩中文字幕在线不卡 | 狠狠躁日日躁夜夜躁av | 三上悠亚一区二区在线观看 | 五月婷婷天堂 | 久久久久久视频 | 国产资源免费在线观看 | 中文字幕色站 | 日韩精品免费一区二区三区 | 国模视频一区二区 | 中文在线www| 中文字幕在线视频精品 | 久久国语露脸国产精品电影 | 国产一级视频在线 | 在线视频观看成人 | 久久国产精品小视频 | 热re99久久精品国产66热 | 91人人干| 麻豆视频免费在线观看 | 免费视频97 | 亚洲成人av在线播放 | 91视视频在线直接观看在线看网页在线看 | 国产视频一区精品 | 国产精品一区久久久久 | 精品亚洲视频在线观看 | 999久久久欧美日韩黑人 | 久久久毛片 | 超碰在线天天 | 天天干,天天操,天天射 | 日日日干 | 天堂av免费看 | 九九视频热 | 中文字幕在线观看免费高清完整版 | 日韩精品一区电影 | 久久精品2 | 久久人91精品久久久久久不卡 | 成人国产电影在线观看 | 在线观看免费高清视频大全追剧 | av在线免费网 | 色婷婷狠狠五月综合天色拍 | 久久理论电影网 | 亚洲五月花 | 国产三级视频 | 欧美精品久久久久久久免费 | 狠狠操在线 | 久久久精品国产免费观看同学 | 国产精品久久久久久久久久 | 午夜黄色大片 | www.久久99| 激情丁香久久 | 国产精品一区二区av麻豆 | 尤物一区二区三区 | 日韩性色| 成人亚洲欧美 | 亚洲欧洲av| 99精品视频在线免费观看 | 又黄又爽又色无遮挡免费 | 99久久精品一区二区成人 | a级国产乱理论片在线观看 特级毛片在线观看 | 麻豆免费视频网站 | 综合网中文字幕 | 国产91在线观看 | 亚洲午夜精品电影 | 91欧美精品 | 三级av网站 | 最近日韩中文字幕中文 | 韩国av免费在线观看 | 91高清免费在线观看 | 国产精品久久一区二区三区不卡 | 国产成人99av超碰超爽 | 久久亚洲欧美日韩精品专区 | 97超碰在线久草超碰在线观看 | av青草| 欧美日韩一区二区三区在线免费观看 | 麻豆一区在线观看 | 国产在线观看地址 | 亚洲一级二级三级 | 色停停五月天 | 91免费高清视频 | av网在线观看 | 狠狠操影视 | 丁香六月婷 | 色资源二区在线视频 | 四虎永久网站 | 九九国产视频 | 91成人国产| 国产免费久久 | 天堂在线一区二区三区 | 免费亚洲视频 | 久久超碰99 | 久久午夜精品视频 | 五月天色婷婷丁香 | 狠狠操狠狠干天天操 | 国产日韩在线一区 | 97久久精品午夜一区二区 | 天堂入口网站 | 黄色av成人在线观看 | 中文字幕在线影院 | 国产欧美最新羞羞视频在线观看 | 欧美日韩不卡一区二区三区 | 欧美精彩视频在线观看 | 国产丝袜美腿在线 | 91精品一区二区三区蜜臀 | 欧美一二三区播放 | 中文字幕一区二区三区在线观看 | 国产91九色蝌蚪 | 国产亚洲亚洲 | 久久综合综合久久综合 | 国产精品视频地址 | 国产精品视频你懂的 | 色婷婷综合久久久久中文字幕1 | 成人免费观看大片 | 97av.com| 亚洲国产精品成人女人久久 | av一级片在线观看 | 日韩夜夜爽 | 久久高清国产 | 草 免费视频 | 干干日日 | 久久99国产精品免费 | 91人人澡人人爽人人精品 | 亚洲精品久久久久58 | 欧美福利久久 | 久久精品这里都是精品 | 国产精品一区二区三区四区在线观看 | 四虎最新入口 | 丁香六月久久综合狠狠色 | 西西444www| 国产91国语对白在线 | 国产自制av | 欧美性成人 | 国产亚洲精品电影 | 亚洲一区美女视频在线观看免费 | 激情综合狠狠 | 激情综合亚洲 | av动图| 亚洲一区欧美激情 | 五月婷婷操 | 超级碰碰碰免费视频 | 国产麻豆视频 | 91精品国产91久久久久 | 福利一区在线 | 丝袜美女视频网站 | 国产精品地址 | 黄色软件在线观看视频 | 天天操夜夜叫 | 夜夜骑天天操 | 亚洲精品在线二区 | 96av在线视频 | 亚洲一区视频在线播放 | 国产精品免费观看网站 | 天堂av在线网址 | 日韩国产欧美在线视频 | 欧美精品在线一区二区 | 成人在线中文字幕 | japanesexxx乱女另类 | 日本久热 | av不卡免费在线观看 | 天天干天天拍天天操 | 日日干夜夜草 | 亚洲精品在线一区二区三区 | 超碰97国产在线 | 精品久久久影院 | 最近免费观看的电影完整版 | 亚洲电影网站 | 国产精品美女久久久免费 | 国内视频一区二区 | 欧美激情视频一二三区 | 波多野结衣在线视频免费观看 | 欧美乱熟臀69xxxxxx | 久久久这里有精品 | 99国产在线观看 | 亚洲一区二区三区miaa149 | 国产网站av | 欧美aa在线| 最新中文字幕在线播放 | 精品国产成人av在线免 | 欧美一区影院 | 成人日批视频 | av资源免费在线观看 | 天天色天天射天天综合网 | 日日夜夜91| 91香蕉视频色版 | 国产美女在线精品免费观看 | 亚洲午夜小视频 | 亚洲一区二区高潮无套美女 | 欧美成亚洲 | www欧美日韩 | 日韩三级在线 | 99国产精品一区 | 精品国产一区二区三区久久久久久 | 精品福利片| 午夜在线免费观看视频 | 久久五月情影视 | 午夜视频免费播放 | 综合久久久 | 天操夜夜操| 日韩电影在线观看一区二区三区 | 黄色av电影在线观看 | 久久av观看 | 国产粉嫩在线观看 | 深爱综合网 | 欧美国产日韩激情 | 中文字幕日韩国产 | 午夜国产福利在线 | 成人在线网站观看 | 在线免费观看的av | 国内精品久久久久久久 | 成人中文字幕在线 | 国产美女精品人人做人人爽 | 免费观看一级成人毛片 | 欧美精品久久久久久久久久 | 国产91成人在在线播放 | 亚洲精品在线免费 | 麻豆国产精品视频 | 亚洲激情在线播放 | 亚洲精区二区三区四区麻豆 | 中文字幕精品久久 | 西西44人体做爰大胆视频 | 麻豆91精品视频 | 麻豆91网站 | 欧美一级久久 | 五月天激情开心 | 91热精品| 精品国产1区2区 | 日韩欧美视频一区二区 | 国产手机在线观看视频 | 特级黄录像视频 | 免费在线观看日韩视频 | 国产精品美女久久久久久久久久久 | 在线观看第一页 | 日韩午夜小视频 | 91九色在线播放 | 色九九在线 | 日韩av中文字幕在线免费观看 | 日韩久久精品一区二区 | 一级欧美黄 | 天天干视频在线 | 最近日本中文字幕 | 天天色天天射天天操 | 超级碰碰碰免费视频 | 狠狠色伊人亚洲综合网站野外 | 最新日韩在线观看 | 九九久久影院 | 欧美国产日韩一区二区 | www日韩高清| 激情六月婷婷久久 | 国产精品va在线播放 | 久久免费视频在线观看30 | 永久免费的av电影 | 国产精品男女啪啪 | 欧美色图30p| 色综合网 | 精品福利av | 国产精品久久久av | 精品国产自在精品国产精野外直播 | 正在播放日韩 | 激情综合网五月 | 国产日韩视频在线观看 | 精品一二三区视频 | 日韩欧美一二三 | 亚洲成人午夜av | 免费看黄色小说的网站 | 日韩色区| 久久久久久久久久久久久国产精品 | 在线观看免费成人 | 97人人模人人爽人人喊中文字 | 国产伦理精品一区二区 | 国产精品免费在线观看视频 | 欧美亚洲久久 | 久久99国产精品久久99 | 天天噜天天色 | 国产xxxxx在线观看 | 欧美一级片免费在线观看 | 在线天堂日本 | 成人一级免费电影 | 五月天久久久久久 | 国产免费中文字幕 | 国产免费区 | 人人玩人人爽 | 午夜视频久久久 | 91激情视频在线观看 | 97看片网 | 成 人 免费 黄 色 视频 | 国产精品亚洲综合久久 | 福利av影院 | 成片视频在线观看 | 日韩91在线 | 夜夜爽夜夜操 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 日韩欧美在线一区 | 天天操天天是 | 亚洲国产中文在线观看 | 国产高清专区 | 久草综合视频 | 日韩亚洲国产中文字幕 | 免费毛片aaaaaa| 亚洲 精品在线视频 | 久久精品国产成人精品 | 91男人影院 | 麻豆果冻剧传媒在线播放 | 精品国产亚洲一区二区麻豆 | 亚洲视频每日更新 | 国产视频久久 | 日韩av网址在线 | 97超级碰碰| 99视频免费在线观看 | 一区二区精 | 国产大陆亚洲精品国产 | 国色天香第二季 | 日韩免费视频在线观看 | 麻豆 videos| 激情五月***国产精品 | 一区二区视频欧美 | 日韩三级免费 | 中文字幕 国产精品 | 日本特黄一级片 | 久久综合九色欧美综合狠狠 | 成在人线av| 草久视频在线 | 在线观看黄色小视频 | 国产精品一区二区美女视频免费看 | 成年人免费看片 | 欧美极品少妇xbxb性爽爽视频 | 久久久久电影网站 | 久久精品免费观看 | 日韩成人免费在线 | 福利视频区 | 亚洲欧美综合精品久久成人 | 天天干夜夜 | 国产综合久久 | 成人小视频在线观看免费 | 国产精品精品国产婷婷这里av | 狠狠狠狠狠狠狠干 | 久久国产影院 | 免费在线观看日韩欧美 | 超碰日韩 | 欧美不卡视频在线 | 日韩视频中文字幕 | 麻豆久久一区二区 | 狠狠狠干 | 久久久99国产精品免费 | 久久精品日产第一区二区三区乱码 | 午夜视频在线网站 | 国产成人精品久久亚洲高清不卡 | 香蕉97视频观看在线观看 | 欧美在线不卡一区 | 亚洲免费在线看 | 精品在线观看免费 | 天天操天 | 久久综合久久八八 | 天堂网一区 | 国产精品久久久久久五月尺 | 中文字幕999 | a级片网站 | 免费日韩一级片 | 日韩日韩日韩日韩 | 久久亚洲私人国产精品 | 天天射天天干天天爽 | 欧美最爽乱淫视频播放 | 91精彩视频在线观看 |