1. 分布式數(shù)據(jù)庫的CAP原理
- Consistency:強一致性
- Availability:可用性
- Partitition tolerance:分區(qū)容錯性
- 只能三選二:
CA:傳統(tǒng)關(guān)系型數(shù)據(jù)庫
AP:大型網(wǎng)站
CP: Redis、Mongodb
2. BASE
- Basically Available基本可用
- Soft state 軟狀態(tài)
- Eventually consistent 最終一致性
3. Redis
??Remote dictionary server(遠程字典服務(wù)器)是一個高性能的(key/value)分布式內(nèi)存數(shù)據(jù)庫,基于內(nèi)存運行,并支持持久化的NoSQL數(shù)據(jù)庫。具有如下特點:
- redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟時可以再次加載進行使用;
- redis不僅支持key/value類型的數(shù)據(jù),還提供list,set,zset,String等數(shù)據(jù)結(jié)構(gòu)的存儲;
- redis支持?jǐn)?shù)據(jù)的備份,即master-salve模式的數(shù)據(jù)備份。
4. Redis五大數(shù)據(jù)類型及應(yīng)用場景
- String 最多512M,以一種純字符串作為value的形式存在的。value可以存儲json格式、數(shù)值型等。
- string使用場景一般是存儲簡單的鍵值類型。比如用戶信息,登錄信息,配置信息等。
- string的incr/decr操作,即自減/自增操作。調(diào)用它是原子性的,無論調(diào)用多少次,都一一計算成功。例如需要增減庫存的操作。
- List 底層是一個鏈表,在redis中,插入list中的值,只需要找到list的key即可,而不需要像hash一樣插入兩層的key。list是一種有序的、可重復(fù)的集合。
- list可以使用左推、左拉、右推、右拉的方式。所以你可以使用list作為集合存儲,比如存儲某寶商鋪里面的所有商品。
- 也可以用作輕量級別的隊列來使用。左推左拉、右推右拉。需要注意的是盡管redis可以使用推拉的隊列模式,但是一定要注意場景。因為redis的隊列是一種輕量級別的,沒有隊列重試、隊列重放機制。消費完隊列消息在redis代表已經(jīng)刪除了。
- Hash String類型的field和value的映射表,hash特別適合適用于存儲對象。在redis中,hash因為是一個集合,所以有兩層。第一層是key:hash集合value,第二層是hashkey:string value。所以判斷是否采用hash的時候可以參照有兩層key的設(shè)計來做參考。并且注意的是,設(shè)置過期時間只能在第一層的key上面設(shè)置。
- 使用hash,一般是有那種需要兩層key的應(yīng)用場景,也可以是‘刪除一個key可以刪除所有內(nèi)容’的場景。例如一個商品有很多規(guī)格,規(guī)格里面有不同的值。
- 如果需要刪除商品時,可以一次性刪除‘商品id’的key,則商品里面的所有規(guī)格也會刪除,而不需要找到對應(yīng)的規(guī)格再做處理。如果查找商品id與規(guī)格id1的商品時,則通過兩個key查找即可。
- 或者查找所有商品的規(guī)格,查找商品id即可。
- 需要注意的是,經(jīng)過測試,在性能上來說一般hash里面的第二層key,不要超過200個為佳。盡管hash里面的key-value能達到500多MB的存儲容量。
- Set 是一種無序的,不能重復(fù)的集合。并且在redis中,只有一個key。
- 如保存一些標(biāo)簽的名字。標(biāo)簽的名字不可以重復(fù),順序是可以無序的。
- ZSet(Sorted Set:有序集合) 每個元素都會關(guān)聯(lián)一個double類型的分?jǐn)?shù),分?jǐn)?shù)允許重復(fù)
- 排行榜
5. Redis String的實現(xiàn)
??Redis雖然是用C語言寫的,但卻沒有直接用C語言的字符串,而是自己實現(xiàn)了一套字符串。目的就是為了提升速度,提升性能。Redis構(gòu)建了一個叫做簡單動態(tài)字符串(Simple Dynamic String),簡稱SDS
struct sdshdr{
// 記錄已使用長度
int len;
// 記錄空閑未使用的長度
int free;
// 字符數(shù)組
char[] buf;
};
Redis的字符串也會遵守C語言的字符串的實現(xiàn)規(guī)則,即最后一個字符為空字符。然而這個空字符不會被計算在len里頭。
- Redis動態(tài)擴展步驟:
- 計算出大小是否足夠
- 開辟空間至滿足所需大小
- 開辟與已使用大小len相同長度的空閑free空間(如果len < 1M),開辟1M長度的空閑free空間(如果len >= 1M)
- Redis字符串的性能優(yōu)勢
- 快速獲取字符串長度:直接返回len
- 避免緩沖區(qū)溢出:每次追加字符串時都會檢查空間是否夠用
- 降低空間分配次數(shù)提升內(nèi)存使用效率:(1)空間預(yù)分配;(2)惰性空間回收
6. Redis持久化
- RDB(redis database)在指定時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤,也就是snapshot快照,恢復(fù)時是將快照文件直接讀到內(nèi)存里。Redis會單獨創(chuàng)建(fork:復(fù)制一個與當(dāng)前進程一樣的進程)一個子進程來進行持久化,會先將數(shù)據(jù)寫入到臨時文件,待持久化過程結(jié)束,再替換上次持久化好的文件(dump.rdb)。主進程不進行IO操作。如果需要進行大規(guī)模數(shù)據(jù)的恢復(fù),且對數(shù)據(jù)完整性不敏感,那么RDB比AOF更高效。缺點就是最后一次持久化的數(shù)據(jù)可能丟失。
- 默認(rèn):1分鐘改了一萬次,5分鐘改了10次,15分鐘改了一次
- AOF以日志的形式來記錄每個寫操作,將redis執(zhí)行過的所有寫的指令記錄下來(讀操作不記錄),只需追加文件當(dāng)不可以改寫文件,redis重啟的話就根據(jù)日志文件的內(nèi)容將寫指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。
- 同步策略:always一直同步、everysec每秒同步、no不同步
- AOF的優(yōu)點:
- 備份機制更穩(wěn)健,丟失數(shù)據(jù)概率低
- 可讀的日志文本,可以處理誤操作
- AOF的缺點:
- 比RDB占用更多的磁盤空間
- 恢復(fù)備份速度慢
- 每次讀寫同步的話有一定的性能壓力
- 存在個別的bug,造成不能恢復(fù)
- AOF重寫機制:當(dāng)aof文件的大小超過所設(shè)定的閾值時,redis就會啟動AOF文件的內(nèi)容壓縮,只保留可以恢復(fù)數(shù)據(jù)的最小指令集,可以使用命令bgrewriteaof,fork出一條新進程來將文件重寫,redis會記錄上次重寫時的AOF大小,默認(rèn)配置是當(dāng)AOF文件大小是上次rewrite后大小的1倍且文件大于64M時觸發(fā)。
7. Redis的事務(wù)
- 定義:可以一次執(zhí)行多個命令,部分支持事務(wù)
- 命令:MULTI開啟事務(wù)、EXEC執(zhí)行事務(wù)、DISCARD放棄事務(wù)、WATCH監(jiān)視一個或多個key,如果在事務(wù)執(zhí)行之前這個key被其他命令所改動,那么事務(wù)將被打斷、UNWACTH一旦執(zhí)行了EXEC之前加的所有的監(jiān)控鎖都會被取消。
- 特性:
- 單獨的隔離操作:事務(wù)中所有的命令都會序列化、按順序地執(zhí)行。事務(wù)執(zhí)行過程中,不會被其他客戶端發(fā)送過來的命令請求所打斷。
- 沒有隔離級別的概念
- 不保證原子性:只要有一條命令執(zhí)行失敗,其他的命令仍然會執(zhí)行,不支持回滾
8. LUA腳本
- 定義:LUA是一種小巧的腳本語言,可以很容易地被C/C++調(diào)用,也可以調(diào)用C/C++函數(shù),一個完整的LUA解釋器不超過200k,適合作為嵌入式腳本語言。
- 在redis中的優(yōu)勢
- 將復(fù)雜或者多步的redis操作,寫為一個腳本,一次性提交給redis執(zhí)行,減少反復(fù)連接redis的次數(shù),提升性能。
- LUA腳本類似redis事務(wù),有一定的原子性,不會被其他命令插隊
9. Redis內(nèi)存淘汰策略
- 這八種大體上可以分為4種,lru、lfu、random、ttl。
1) volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰。
2) volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集中挑選將要過期的數(shù)據(jù)淘汰。
3) volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集中任意選擇數(shù)據(jù)淘汰。
4) volatile-lfu:從已設(shè)置過期時間的數(shù)據(jù)集挑選使用頻率最低的數(shù)據(jù)淘汰。
5) allkeys-lru:從數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰
6) allkeys-lfu:從數(shù)據(jù)集中挑選使用頻率最低的數(shù)據(jù)淘汰。
7) allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
8) no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù),這也是默認(rèn)策略。意思是當(dāng)內(nèi)存不足以容納新入數(shù)據(jù)時,新寫入操作就會報錯,請求可以繼續(xù)進行,線上任務(wù)也不能持續(xù)進行,采用no-enviction策略可以保證數(shù)據(jù)不被丟失。
10. 秒殺常見問題
- 連接超時
- 使用連接池
- 超賣問題
- 使用事務(wù)
- 庫存遺留
- 使用LUA腳本
11. Redis發(fā)布訂閱
- SUBSCRIBE c1 c2 c3
- PUBLISH c2 hello-redis
12. Redis主從復(fù)制
- 配從不配主:slaveof 主庫IP 主庫端口,每次與master斷開之后,都需要重新連接,除非配置redis.conf文件。
- 配置文件細節(jié)操作:
- 拷貝多個redis.conf文件
- 開啟daemonize yes
- Pid文件名字
- 指定端口
- Log文件名字
- Dump.rdb名字
- 常用招式
- 一主二仆
- Info replication:查看信息
-
SLAVEOF 127.0.0.1 6379:配置從庫
image.png
13. 哨兵模式
- 定義:反客為主(slaveof no one)自動化,能夠監(jiān)控主機是否故障,如果故障根據(jù)投票數(shù)自動將從庫轉(zhuǎn)為主庫
- 使用步驟:
- 調(diào)整結(jié)構(gòu),6379帶著80、81
- 自定義的/myredis目錄下新建sentinel.conf文件
- 配置哨兵,填寫內(nèi)容:Sentinel monitor host637(被監(jiān)控數(shù)據(jù)庫名字(自己起名字))127.0.0.1 6379 1(多于1票則設(shè)為主機)
- 啟動哨兵:Redis-sentinel /myredis/sentinel.conf
14. Java使用redis
- 連接:Jedis jedis = new Jedis(“127.0.0.1”,6379);
- 插入:jedis.set(“k1”,”v1”);
- 事務(wù):
Transaction transaction = jedis.multi();
transaction.set(“k2”,”v2”);
transaction.set(“k3”,”v3”);
transaction.exec();
- 加鎖:
public class TestTransaction {
public boolean transMethod() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
int balance;// 可用余額
int debt;// 欠額
int amtToSubtract = 10;// 實刷額度
jedis.watch("balance");
//jedis.set("balance","5");//此句不該出現(xiàn)。模擬其他程序已經(jīng)修改了該條目
balance = Integer.parseInt(jedis.get("balance"));
if (balance < amtToSubtract) {
jedis.unwatch();
System.out.println("modify");
return false;
} else {
System.out.println("***********transaction");
Transaction transaction = jedis.multi();
transaction.decrBy("balance", amtToSubtract);
transaction.incrBy("debt", amtToSubtract);
transaction.exec();
balance = Integer.parseInt(jedis.get("balance"));
debt = Integer.parseInt(jedis.get("debt"));
System.out.println("*******" + balance);
System.out.println("*******" + debt);
return true;
}
}
/**
* 通俗點講,watch命令就是標(biāo)記一個鍵,如果標(biāo)記了一個鍵, 在提交事務(wù)前如果該鍵被別人修改過,那事務(wù)就會失敗,這種情況通??梢栽诔绦蛑兄匦略賴L試一次。
* 首先標(biāo)記了鍵balance,然后檢查余額是否足夠,不足就取消標(biāo)記,并不做扣減; 足夠的話,就啟動事務(wù)進行更新操作,
* 如果在此期間鍵balance被其它人修改, 那在提交事務(wù)(執(zhí)行exec)時就會報錯, 程序中通常可以捕獲這類錯誤再重新執(zhí)行一次,直到成功。
*/
public static void main(String[] args) {
TestTransaction test = new TestTransaction();
boolean retValue = test.transMethod();
System.out.println("main retValue-------: " + retValue);
}
}
- 主從復(fù)制
- 配置從庫:Jedis jedis_s = new Jedis(“127.0.0.1”,6380);
- Jedis_s.slaveof(“127.0.0.1”,6379);
- JedisPool
JedisPoolConfig poolConfig = new JedisPoolConfig( );
poolConfig.setMaxActive ( 1000);
poolconfig.setMaxIdle ( 32);
poolconfig. setMaxwait (100*1000);poolconfig.setTestOnBorrow(true);
jedisPool = new JedisPool(poolConfig, "127.0.0.1",6379);
- maxActive:控制一個pool可分配多少個jedis實例,通過pool.getResource()來獲取;如果賦值為-1,則表示不限制;如果pool已經(jīng)分配了maxActive個jedis實例,則此時pool的狀態(tài)為exhausted。
- maxIdle:控制一個pool最多有多少個狀態(tài)為idle(空閑)的jedis實例;
- whenExhaustedAction:表示當(dāng)pool中的jedis實例都被allocated完時,pool要采取的操作:默認(rèn)有三種。
- WHEN_EXHAUSTED_FAIL -->表示無jedis實例時,直接拋出NoSuchElementException;
- WHEN_EXHAUSTED_BLOCK -->則表示阻塞住,或者達到maxWait時拋出JedisConnectionException;
- WHEN_EXHAUSTED_GRoW -->則表示新建一個jedis實例,也就說設(shè)置的maxActive無用;
- maxWait:表示當(dāng)borrow一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋JedisConnectionException;
- testOnBorrow:獲得一個jedis實例的時候是否檢查連接可用性(ping());如果為true,則得到的jedis實例均是可用的:
15. 解決session存儲問題
- 方案一:存在cookie里
- 不安全
- 網(wǎng)絡(luò)負(fù)擔(dān)效率低
- 方案二:存在文件服務(wù)器或數(shù)據(jù)庫里
- 大量的IO效率問題
- 方案三:session復(fù)制
- Session數(shù)據(jù)冗余
- 節(jié)點越多浪費越大
- 方案四:緩存數(shù)據(jù)庫
- 完全存在內(nèi)存中,速度快數(shù)據(jù)結(jié)構(gòu)簡單
16. 單線程+多路IO復(fù)用
??多路復(fù)用是指用一個線程來檢查多個文件描述符(socket)的就緒狀態(tài),比如調(diào)用select、poll、epoll函數(shù)進行監(jiān)視,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。得到就緒狀態(tài)后進行真正的操作可以在同一個線程里執(zhí)行,也可以啟動線程執(zhí)行(比如使用線程池)。
??多路I/O復(fù)用模型是利用 select、poll、epoll 可以同時監(jiān)察多個流的 I/O 事件的能力,在空閑的時候,會把當(dāng)前線程阻塞掉,當(dāng)有一個或多個流有 I/O 事件時,就從阻塞態(tài)中喚醒,于是程序就會輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作。這里“多路”指的是多個網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個線程。
- Select:每一個請求都進行詢問,最多1024個
- Poll:每一個請求都進行詢問不限制數(shù)量
- Epoll:監(jiān)視請求時為每個請求設(shè)置標(biāo)識符,不需要一一詢問
17. Select、poll、epoll
- select的幾大缺點:
1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大
2)同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
3)select支持的文件描述符數(shù)量太小了,默認(rèn)是1024 - 為什么epoll比select和poll更高效?
1)減少了用戶態(tài)和內(nèi)核態(tài)之間文件描述符的拷貝
2)減少了對就緒文件描述符的遍歷
3)select和poll只支持LT模式,而epoll支持高效的ET模式,并且epoll還支持EPOLLONESHOT事件。 - 無論哪種情況下,epoll都比select和poll高效嗎?
- epoll適用于連接較多,活動數(shù)量較少的情況。
1)epoll為了實現(xiàn)返回就緒的文件描述符,維護了一個紅黑樹和好多個等待隊列,內(nèi)核開銷很大。如果此時監(jiān)聽了很少的文件描述符,底層的開銷會得不償失;
2)epoll中注冊了回調(diào)函數(shù),當(dāng)有事件發(fā)生時,服務(wù)器設(shè)備驅(qū)動調(diào)用回調(diào)函數(shù)將就緒的fd掛在rdllist上,如果有很多的活動,同一時間需要調(diào)用的回調(diào)函數(shù)數(shù)量太多,服務(wù)器壓力太大。 - select和poll適用于連接較少的情況。
1)當(dāng)select和poll上監(jiān)聽的fd數(shù)量較少,內(nèi)核通知用戶現(xiàn)在有就緒事件發(fā)生,應(yīng)用程序判斷當(dāng)前是哪個fd就緒所消耗的時間復(fù)雜度就會大大減小。
- epoll適用于連接較多,活動數(shù)量較少的情況。
18. REDIS緩存穿透,緩存擊穿,緩存雪崩原因+解決方案
- 緩存穿透:key對應(yīng)的數(shù)據(jù)在數(shù)據(jù)庫和緩存并不存在,每次針對此key的請求從緩存獲取不到,請求都會到數(shù)據(jù)庫,從而可能壓垮數(shù)據(jù)庫。比如用一個不存在的用戶id獲取用戶信息,不論緩存還是數(shù)據(jù)庫都沒有,若黑客利用此漏洞進行攻擊可能壓垮數(shù)據(jù)庫。
- 解決方案:
1)最常見的則是采用布隆過濾器,在寫入數(shù)據(jù)庫時,將數(shù)據(jù)哈希到一個足夠大的bitmap中,一個一定不存在的數(shù)據(jù)會被這個bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。
2)簡單粗暴的方法:如果一個查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個空結(jié)果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
- 解決方案:
- 緩存擊穿:是指一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當(dāng)這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個屏障上鑿開了一個洞。
- 解決方案
1)使用互斥鎖:就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當(dāng)操作返回成功時,再進行l(wèi)oad db的操作并回設(shè)緩存;否則,就重試整個get緩存的方法。
2)設(shè)置永不過期
- 解決方案
- 緩存雪崩:當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給后端系統(tǒng)(比如DB)帶來很大壓力。
- 大量數(shù)據(jù)同時過期解決方案
1)用加鎖或者隊列的方式保證來保證不會有大量的線程對數(shù)據(jù)庫一次性進行讀寫,從而避免失效時大量的并發(fā)請求落到底層存儲系統(tǒng)上。加鎖排隊只是為了減輕數(shù)據(jù)庫的壓力,并沒有提高系統(tǒng)吞吐量。
2)將緩存失效時間分散開,比如我們可以在原有的失效時間基礎(chǔ)上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件。
3)雙key策略,主key設(shè)置過期時間,備key設(shè)置永久,主key過期時,返回備key內(nèi)容
4)后臺緩存更新,定時更新、消息隊列通知更新 -
服務(wù)器宕機解決方案
1)服務(wù)熔斷:在分布式系統(tǒng)中,我們往往需要依賴下游服務(wù),不管是內(nèi)部系統(tǒng)還是第三方服務(wù),如果下游出現(xiàn)問題,我們不在盲目地去請求,在一個周期內(nèi)失敗達到一定次數(shù),不在請求,及時失敗。過一段時間,在逐步放開請求,這樣既能防止不斷的調(diào)用,使下游服務(wù)更壞,保護了下游方,還能降低自己的執(zhí)行成本,快速的響應(yīng),減少延遲,增加吞吐量。
2)服務(wù)降級:降級就是為了解決資源不足和訪問量增加的矛盾,在有限的資源情況下,為了能抗住大量的請求,就需要對系統(tǒng)做出一些犧牲,有點“棄卒保帥”的意思。放棄一些功能,保證整個系統(tǒng)能平穩(wěn)運行。比如:搶購可以占時限流評論,將流量讓給秒殺業(yè)務(wù)
3)請求限流:通過對并發(fā)訪問進行限速。最簡單的方式,把多余的請求直接拒絕掉,可以根據(jù)一定的用戶規(guī)則進行拒絕策略
4)構(gòu)建redis高可靠集群
image.png
- 大量數(shù)據(jù)同時過期解決方案
19. 布隆過濾器
??布隆過濾器由「初始值都為 0 的位圖數(shù)組」和「 N 個哈希函數(shù)」兩部分組成。當(dāng)我們在寫入數(shù)據(jù)庫數(shù)據(jù)時,在布隆過濾器里做個標(biāo)記,這樣下次查詢數(shù)據(jù)是否在數(shù)據(jù)庫時,只需要查詢布隆過濾器,如果查詢到數(shù)據(jù)沒有被標(biāo)記,說明不在數(shù)據(jù)庫中。
布隆過濾器會通過 3 個操作完成標(biāo)記:
- 第一步,使用 N 個哈希函數(shù)分別對數(shù)據(jù)做哈希計算,得到 N 個哈希值;
- 第二步,將第一步得到的 N 個哈希值對位圖數(shù)組的長度取模,得到每個哈希值在位圖數(shù)組的對應(yīng)位置。
-
第三步,將每個哈希值在位圖數(shù)組的對應(yīng)位置的值設(shè)置為 1;
舉個例子,假設(shè)有一個位圖數(shù)組長度為 8,哈希函數(shù) 3 個的布隆過濾器。
image.png
??在數(shù)據(jù)庫寫入數(shù)據(jù) x 后,把數(shù)據(jù) x 標(biāo)記在布隆過濾器時,數(shù)據(jù) x 會被 3 個哈希函數(shù)分別計算出 3 個哈希值,然后在對這 3 個哈希值對 8 取模,假設(shè)取模的結(jié)果為 1、4、6,然后把位圖數(shù)組的第 1、4、6 位置的值設(shè)置為 1。當(dāng)應(yīng)用要查詢數(shù)據(jù) x 是否數(shù)據(jù)庫時,通過布隆過濾器只要查到位圖數(shù)組的第 1、4、6 位置的值是否全為 1,只要有一個為 0,就認(rèn)為數(shù)據(jù) x 不在數(shù)據(jù)庫中。
??布隆過濾器由于是基于哈希函數(shù)實現(xiàn)查找的,高效查找的同時存在哈希沖突的可能性,比如數(shù)據(jù) x 和數(shù)據(jù) y 可能都落在第 1、4、6 位置,而事實上,可能數(shù)據(jù)庫中并不存在數(shù)據(jù) y,存在誤判的情況。
??所以,查詢布隆過濾器說數(shù)據(jù)存在,并不一定證明數(shù)據(jù)庫中存在這個數(shù)據(jù),但是查詢到數(shù)據(jù)不存在,數(shù)據(jù)庫中一定就不存在這個數(shù)據(jù)。
20. 為什么要用redis而不用map做緩存?
- Redis 可以用幾十 G 內(nèi)存來做緩存,Map 不行,一般 JVM 也就分幾個 G 數(shù)據(jù)就夠大了
- Redis 的緩存可以持久化,Map 是內(nèi)存對象,程序一重啟數(shù)據(jù)就沒了
- Redis 可以實現(xiàn)分布式的緩存,Map 只能存在創(chuàng)建它的程序里
- Redis 可以處理每秒百萬級的并發(fā),是專業(yè)的緩存服務(wù),Map 只是一個普通的對象
- Redis 緩存有過期機制,Map 本身無此功能
- Redis 有豐富的 API,Map 就簡單太多了
21. 如何保持緩存和數(shù)據(jù)庫的一致性?
- 淘汰緩存還是更新緩存?
選擇淘汰緩存,原因:數(shù)據(jù)可能為簡單數(shù)據(jù),也可能為較復(fù)雜的數(shù)據(jù),復(fù)雜數(shù)據(jù)進行緩存的更新操作,成本較高,因此一般推薦淘汰緩存 - 先淘汰緩存還是先更新數(shù)據(jù)庫?
選擇先淘汰緩存,再更新數(shù)據(jù)庫,原因:假如先更新數(shù)據(jù)庫,再淘汰緩存,如果緩存淘汰失敗,那么后面的請求都會得到臟數(shù)據(jù),直至緩存過期。假如先淘汰緩存再更新數(shù)據(jù)庫,如果數(shù)據(jù)庫更新失敗,只會產(chǎn)生一次緩存miss,相比較而言,后者對業(yè)務(wù)影響更小一點。 - 延時雙刪策略:解決數(shù)據(jù)庫讀寫分離
public void write(String key,Object data){
redisUtils.del(key);
db.update(data);
Thread.Sleep(100);
redisUtils.del(key);
}
22. Redis分布式鎖的實現(xiàn)
- 加鎖:使用setnx key value命令,如果key不存在,設(shè)置value(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶端持有鎖了),則設(shè)置失敗(加鎖失敗)。
- 解鎖:使用del命令,通過刪除鍵值釋放鎖。釋放鎖之后,其他客戶端可以通過setnx命令進行加鎖。
23. Redis集群
- Redis集群解決內(nèi)存壓力,實現(xiàn)了對redis的水平擴容,即啟動N個redis節(jié)點,將整個數(shù)據(jù)庫分布在N個節(jié)點中,每個節(jié)點存儲數(shù)據(jù)的1/N。
- Redis集群通過分區(qū)來提供一定程度的可用性,即使集群中有一部分節(jié)點失效或者無法進行通訊,集群也可以基礎(chǔ)處理命令請求。
- Redis部署模型
-
模式一:單實例
image.png -
模式二:一主一從
image.png -
模式三:一主多從
image.png -
模式四:多主多從
image.png -
模式五:集群
image.png
- Redis緩存預(yù)熱
- 緩存預(yù)熱的思路
(1) 提前給redis中嵌入部分?jǐn)?shù)據(jù),再提供服務(wù),肯定不可能將所有數(shù)據(jù)都寫入redis,因為數(shù)據(jù)量太大了,第一耗費的時間太長了,第二redis根本就容納不下所有的數(shù)據(jù)
(2) 需要更具當(dāng)天的具體訪問情況,試試統(tǒng)計出頻率較高的熱數(shù)據(jù)
(3) 然后將訪問頻率較高的熱數(shù)據(jù)寫入到redis,肯定是熱數(shù)據(jù)也比較多,我們也得多個服務(wù)并行的讀取數(shù)據(jù)去寫,并行的分布式的緩存預(yù)熱
(4) 然后將嵌入的熱數(shù)據(jù)的redis對外提供服務(wù),這樣就不至于冷啟動,直接讓數(shù)據(jù)庫奔潰了 - 具體的實時方案:
(1) nginx+lua將訪問量上報到kafka中要統(tǒng)計出來當(dāng)前最新的實時的熱數(shù)據(jù)是哪些,我們就得將商品詳情頁訪問的請求對應(yīng)的流量,日志,實時上報到kafka中,
(2) storm從kafka中消費數(shù)據(jù),實時統(tǒng)計出每個商品的訪問次數(shù),訪問次數(shù)基于LRU內(nèi)存數(shù)據(jù)結(jié)構(gòu)的存儲方案。
a) 優(yōu)先用內(nèi)存中的一個LRUMap去存放,性能高,而且沒有外部依賴。否則的話,依賴redis,我們就是要防止reids掛掉數(shù)據(jù)丟失的情況,就不合適了;用mysql,扛不住高并發(fā)讀寫;用hbase,hadoop生態(tài)系統(tǒng),維護麻煩,太重了,其實我們只要統(tǒng)計出一段時間訪問最頻繁的商品,然后對它們進行訪問計數(shù),同時維護出一個前N個訪問最多的商品list即可。計算好每個task大致要存放的商品訪問次數(shù)的數(shù)量,計算出大小,然后構(gòu)建一個LURMap,apache commons collections有開源的實現(xiàn),設(shè)定好map的最大大小,就會自動根據(jù)LRU算法去剔除多余的數(shù)據(jù),保證內(nèi)存使用限制,即使有部分?jǐn)?shù)據(jù)被干掉了,然后下次來重新開始技術(shù),也沒什么關(guān)系,因為如果他被LRU算法干掉,那么它就不是熱數(shù)據(jù),說明最近一段時間很少訪問,
(3) 每個storm task啟動的時候,基于zk分布式鎖,將自己的id寫入zk的一個節(jié)點中
(4) 每個storm task負(fù)責(zé)完成自己這里的熱數(shù)據(jù)的統(tǒng)計,比如每次計數(shù)過后,維護一個錢1000個商品的list,每次計算完都更新這個list
(5) 寫一個后臺線程,每個一段時間,比如一分鐘,將排名錢1000的熱數(shù)據(jù)list,同步到zk中







