1. 程式人生 > 實用技巧 >Redis小結2

Redis小結2

一.Redis概念

NoSQL

Not Only SQL,泛指非關係型資料庫

Memcache:這是一個和Redis非常相似的資料庫,但是它的資料型別沒有Redis豐富。Memcache由LiveJournal的Brad Fitzpatrick開發,作為一套分散式的快取記憶體系統,被許多網站使用以提升網站的訪問速度,對於一些大型的、需要頻繁訪問資料庫的網站訪問速度的提升效果十分顯著。

Apache Cassandra:(社群內一般簡稱為C*)這是一套開源分散式NoSQL資料庫系統。它最初由Facebook開發,用於儲存收件箱等簡單格式資料,集Google BigTable的資料模型與Amazon Dynamo的完全分散式架構於一身。Facebook於2008將 Cassandra 開源,由於其良好的可擴充套件性和效能,被 Apple、Comcast、Instagram、Spotify、eBay、Rackspace、Netflix等知名網站所採用,成為了一種流行的分散式結構化資料儲存方案。

MongoDB:是一個基於分散式檔案儲存、面向文件的NoSQL資料庫,由C++編寫,旨在為WEB應用提供可擴充套件的高效能資料儲存解決方案。MongoDB是一個介於關係資料庫和非關係資料庫之間的產品,是非關係資料庫當中功能最豐富,最像關係型資料庫的,它支援的資料結構非常鬆散,是一種類似json的BSON格式。

redis

Remote Dictionary Server,遠端字典伺服器
redis是一個開源的、使用C語言編寫的、支援網路互動的、可基於記憶體也可持久化的Key-Value資料庫(非關係性資料庫)

二.Redis優點

  • 速度快,因為資料存在記憶體中,類似於HashMap,HashMap的優勢就是查詢和操作的時間複雜度都是O(1)
  • 支援豐富資料型別,支援string,list,set,sorted set,hash
  • 支援事務,操作都是原子性,所謂的原子性就是對資料的更改要麼全部執行,要麼全部不執行
  • 豐富的特性:可用於快取,訊息,按key設定過期時間,過期後將會自動刪除

三.Redis資料型別

1.字串(string)

常用命令

set key value
get key
exists  key   //key是否存在

redis會將字串型別轉換成數值;

由於INCR等指令本身就具有原子操作的特性,所以我們完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令來實現原子計數的效果

SDS結構

//簡單動態字串(Simple Dynamic String)
struct sdshdr{
    int len; //buf已使用的長度
    int free; //buf未使用的長度
    char buf[]; //儲存字串
};
//buf陣列長度 = free + len + 1  (其中1表示字串結尾空字元'\0')

C字串對比

C字串 簡單動態字串SDS
獲取字串長度複雜度O(N) 獲取字串長度複雜度O(1)
API不安全,存在緩衝區溢位 API安全,不會造成緩衝區溢位
修改字串存在多次記憶體分配 修改字串做多需要執行N次記憶體重分配
只能儲存文字資料 可以儲存文字或者二進位制資料(二進位制安全)
可以使用所有<string.h>庫函式 只能使用一部分<string.h>庫函式

2.雜湊(hash)

常用命令

hset hashKey  key1 value1 key2 value2
hget hashkey  key1
hgetall hashKey 

3.集合(set)

常用命令

sadd mySet value  //向集合新增元素
smembers mySet  //列出集合mySet中的所有元素
scard mySet    //返回集合中元素數量
sismember mySet value   //檢視value是否在集合mySet中
srem mySet value    //從集合mySet中刪除value
sunion mySet1 mySet2 //合併多個set,返回合併後的元素列表
del mySet

4.列表(list)

常用命令

lpush list  value  //在list左側(開頭)插入元素
rpush list  value  //在list右側(末尾)插入元素
lpop list //刪除並返回列表第一個元素
rpop  list //刪除並返回列表最後一個元素
llen  list
lrange myList 0 3  //列出mylist中從編號0到編號3的元素
lrange myList 0 -1   //列出mylist中從編號0到最後一個元素
del myList   

其他

Redis列表是簡單的字串列表,按照插入順序排序,頭部是左邊,尾部是右邊

底層實現上就是連結串列,不是陣列

5.有序集合(sort set)

常用命令

zadd zset1 key1 value1 //key作為value的編號來用於排序
zcard zset1   //統計zset1下key的個數
zrank zset1 value2   //檢視value2在zset1中排名位置
zrange zset1 0 2 withscores   //檢視0到2的所有值和分數按照排名
zrange zset1 0 -1   //只檢視zset中元素

其他

  • key不要太長,儘量不要超過1024位元組,這不僅消耗記憶體,而且會降低查詢的效率;

  • 有序集合底層使用了 壓縮連結串列和跳躍表:

其中跳躍表基於有序單鏈表,在連結串列的基礎上,每個結點不只包含一個指標,還可能包含多個指向後繼結點的指標,這樣就可以跳過一些不必要的結點,從而加快查詢、刪除等操作。如下圖就是一個普通跳躍表(和redis跳躍表不完全一致):

四.redis過期策略

1.定期刪除

redis是每隔100ms隨機抽取一些key來檢查和刪除的

2.惰性刪除

在你獲取某個key的時候,redis會檢查是否過期,過期則刪除並不返回結果

3.記憶體淘汰

當redis記憶體佔用過多時,進行記憶體淘汰

allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key(這個是最常用的)(Least Recently Used,最近最久未使用)

五.Redis常見問題

1.擊穿

概念

在Redis獲取某一key時,由於key不存在,而必須向DB發起一次請求的行為

原因

第一次訪問; 惡意訪問不存在的key; key過期

規避

伺服器啟動時,提前寫入;

規範key的命名,通過中介軟體攔截;

對某些高頻訪問的key,設定合理的TTL或永不過期

2.雪崩:

概念

Redis快取層由於某種原因宕機後,所有的請求會湧向儲存層,短時間內高併發請求可能導致儲存層掛機

規避

使用Redis叢集;

限流;

六.Redis協議

Redis客戶端通訊協議:RESP(Redis Serialization Protocol),其特點是:

  • 簡單
  • 解析速度快
  • 可讀性好

Redis叢集內部通訊協議:RECP(Redis Cluster Protocol ) ,其特點是:

  • 每一個node兩個tcp 連線
  • 一個負責client-server通訊(P: 6379)
  • 一個負責node之間通訊(P: 10000 + 6379)

七.Redis面試題

1.什麼是快取雪崩?解決方法?

  • redis掛了,請求全部從記憶體轉為走資料庫
  • 快取中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機

解決:

  • 快取資料的過期時間錯開,防止同一時間大量資料過期現象發生
  • 如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同搞得快取資料庫中
  • 設定熱點資料永不過期或更長合理過期時間

2.什麼是快取穿透/擊穿?如何解決?

快取穿透:

大量快取中不存在的請求key訪問直接落到資料庫,一般是惡意攻擊

快取擊穿:

熱點key在請求高峰失效,瞬間大量請求落到資料庫

解決:

①可以使用布隆過濾器(BloomFilter)或者壓縮filter攔截過濾不合法的請求

②查詢為空的結果也寫到快取中去(但過期時間短一點)

3.快取與資料庫雙寫一致

讀操作先去找快取,有則直接返回;若沒有就查詢資料庫,將該結果寫到快取中並返回給請求

  • 先刪除快取,再更新資料庫

    在高併發下表現不如意,在原子性被破壞時表現優異

  • 先更新資料庫,再刪除快取

    在高併發下表現優異,在原子性被破壞時表現不如意

快取同步的原理:如果後臺資料庫中內容修改了就需要將redis中的key進行刪除,下次訪問的時候,redis中沒有該資料,則從DB進行查詢,再次更新到redis中

4.布隆過濾器

判斷一個元素是否存在一個集合中

布隆過濾器的原理是,當一個元素被加入集合時,通過K個Hash函式將這個元素對映成一個一維的bool型的陣列中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。

優點:新增,查詢速度足夠快,記憶體小,程式碼簡單

缺點: 有一定誤判率且隨資料增加而增加; 不支援刪除

大白話布隆過濾器

https://www.cnblogs.com/CodeBear/p/10911177.html

5.Redis持久化

持久化就是把記憶體的資料寫到磁碟中去,防止服務宕機了記憶體資料丟失

6.Redis是單程序單執行緒的

redis利用佇列技術將併發訪問變為序列訪問,消除了傳統資料庫序列控制的開銷

八.參考連結

硬核!15張圖解Redis為什麼這麼快

https://www.cnblogs.com/caoyier/p/13896319.html

linux安裝redis一,超詳細說明與圖解!!

https://blog.csdn.net/qq_30764991/article/details/81564652

Redis中的跳躍表

https://blog.csdn.net/universe_ant/article/details/51134020

Redis原始碼解析:05跳躍表

https://blog.csdn.net/gqtcgq/article/details/50613896

布隆過濾器

一.作用:

判斷一個元素是否存在一個集合中

二.基本原理通俗:

當一個元素被加入集合時,通過K個Hash函式將這個元素對映成一個一維的布林型的陣列中的K個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。

三.演算法原理詳細:

  1. n個要新增的元素
  2. k個hash函式
  3. m位的空的bitArray
  4. 新增一個元素key時,用k個hash函式計算出k個雜湊值,並把bitArray對應的位元位置為1
  5. 判斷一個元素key是否存在時,用k個hash函式計算出k個雜湊值,並查詢bitArray中對應的位元位;如果至少有一位不為1,則一定不在集合中;如果全部都為1,則認為在集合中(存在誤判)

四.優缺點

優點:

新增,查詢速度足夠快,記憶體小,程式碼簡單

缺點:

有一定誤判率且隨資料增加而增加; 不支援刪除

五.簡單程式碼實現

package scala02

import scala.collection.mutable.ArrayBuffer
import scala.math.abs
import scala.util.hashing.MurmurHash3

 object BloomFilter {

   private val BYTE_SIZE: Int = 8
   private var m: Int = _   //m 為要存的位元陣列長度
   private var k: Int = _       //k 為雜湊函式的個數
   private var bitmapCharArray: Array[Char] = _  
   private var seedArray: Array[Int] = _


   /**
     * 生成空的bitArray
     * @param m
     * @return
     */
  private def generateEmptyBitmap(m:Int): Array[Char] ={
    val charNum = (m.toDouble/BYTE_SIZE).ceil.toInt //ceil 不小於該浮點數的最小整數, (2.1).ceil則為3.0
    val charArrayBuffer = ArrayBuffer.empty[Char]
    val char=0x00.toChar
    for (elem <- 0 until charNum ) {//0 until len  或者 0 to len-1
      charArrayBuffer.append(char)
    }
    charArrayBuffer.toArray

  }

   /**
     * 判斷字串是否可能存在於過濾器中
     *
     * @param str
     * @return
     */
   def exists(str: String): Boolean = {
     var flag = true
     var s = 0
     while (s < k) {
       val pos = hash(str, seedArray(s))
       if (!getBit(pos)) {
         flag = false
         s = k
       }
       s = s + 1
     }
     flag
   }


   /**
     * 將字串新增到過濾器中
     *
     * @param str
     */
   def put(str: String) = {
     seedArray.foreach(seed => {
       val pos = hash(str, seed)
       setBit(pos)
     })
   }

   /**
     * 將bitmap的第pos個bit置為1
     *
     * @param pos
     */
   private def setBit(pos: Int): Unit = {
     val charPos = getCharPos(pos)
     val char = bitmapCharArray(charPos)
     val bitPos = pos - charPos * BYTE_SIZE
     val byte = char.toByte
     val mask = 0x01 << bitPos
     val or = byte | mask
     bitmapCharArray(charPos) = or.toChar
   }
   


   /**
     * 基於MurmurHash3演算法計算字串的hash值
     *
     * @param str
     * @param seed hash種子
     * @return 取值範圍 0 ~ m-1
     */
   private def hash(str: String, seed: Int): Int = {
     abs(MurmurHash3.stringHash(str, seed)) % m
   }

   /**
     * 讀取bitmap的第pos個bit
     *
     * @param pos
     */
   private def getBit(pos: Int): Boolean = {
     val charPos = getCharPos(pos)
     val char = bitmapCharArray(charPos)
     val bitPos = pos - charPos * BYTE_SIZE
     val byte = char.toByte
     val mask = 0x01 << bitPos
     val and = byte & mask
     if (0 == and) false else true
   }

   /**
     * 獲取第pos個bit對應的char的位置(從0開始編號)
     *
     * @param pos
     * @return 0 ~ m/BYTE_SIZE-1
     */
   private def getCharPos(pos: Int): Int = {
     (pos.toDouble / BYTE_SIZE).toInt
   }

   /**
     * 判斷n是否為質數
     *
     * @param n
     * @return
     */
   private def isPrime(n: Int) = {
     var flag = true
     for (i <- 2 to n - 1) {
       if (n % i == 0) flag = false
     }
     flag
   }
   

}

六.參考連結

布隆過濾器原理及數學推導

https://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html