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,則被檢元素很可能在。這就是布隆過濾器的基本思想。
三.演算法原理詳細:
- n個要新增的元素
- k個hash函式
- m位的空的bitArray
- 新增一個元素key時,用k個hash函式計算出k個雜湊值,並把bitArray對應的位元位置為1
- 判斷一個元素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