Java面試題系列(十)——Redis

1. 分布式數(shù)據(jù)庫的CAP原理

  • Consistency:強(qiáng)一致性
  • Availability:可用性
  • Partitition tolerance:分區(qū)容錯(cuò)性
  • 只能三選二:
    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(遠(yuǎn)程字典服務(wù)器)是一個(gè)高性能的(key/value)分布式內(nèi)存數(shù)據(jù)庫,基于內(nèi)存運(yùn)行,并支持持久化的NoSQL數(shù)據(jù)庫。具有如下特點(diǎn):

  • redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟時(shí)可以再次加載進(jìn)行使用;
  • 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)用多少次,都一一計(jì)算成功。例如需要增減庫存的操作。
  • List 底層是一個(gè)鏈表,在redis中,插入list中的值,只需要找到list的key即可,而不需要像hash一樣插入兩層的key。list是一種有序的、可重復(fù)的集合。
    • list可以使用左推、左拉、右推、右拉的方式。所以你可以使用list作為集合存儲,比如存儲某寶商鋪里面的所有商品。
    • 也可以用作輕量級別的隊(duì)列來使用。左推左拉、右推右拉。需要注意的是盡管redis可以使用推拉的隊(duì)列模式,但是一定要注意場景。因?yàn)閞edis的隊(duì)列是一種輕量級別的,沒有隊(duì)列重試、隊(duì)列重放機(jī)制。消費(fèi)完隊(duì)列消息在redis代表已經(jīng)刪除了。
  • Hash String類型的field和value的映射表,hash特別適合適用于存儲對象。在redis中,hash因?yàn)槭且粋€(gè)集合,所以有兩層。第一層是key:hash集合value,第二層是hashkey:string value。所以判斷是否采用hash的時(shí)候可以參照有兩層key的設(shè)計(jì)來做參考。并且注意的是,設(shè)置過期時(shí)間只能在第一層的key上面設(shè)置。
    • 使用hash,一般是有那種需要兩層key的應(yīng)用場景,也可以是‘刪除一個(gè)key可以刪除所有內(nèi)容’的場景。例如一個(gè)商品有很多規(guī)格,規(guī)格里面有不同的值。
    • 如果需要?jiǎng)h除商品時(shí),可以一次性刪除‘商品id’的key,則商品里面的所有規(guī)格也會刪除,而不需要找到對應(yīng)的規(guī)格再做處理。如果查找商品id與規(guī)格id1的商品時(shí),則通過兩個(gè)key查找即可。
    • 或者查找所有商品的規(guī)格,查找商品id即可。
    • 需要注意的是,經(jīng)過測試,在性能上來說一般hash里面的第二層key,不要超過200個(gè)為佳。盡管hash里面的key-value能達(dá)到500多MB的存儲容量。
  • Set 是一種無序的,不能重復(fù)的集合。并且在redis中,只有一個(gè)key。
    • 如保存一些標(biāo)簽的名字。標(biāo)簽的名字不可以重復(fù),順序是可以無序的。
  • ZSet(Sorted Set:有序集合) 每個(gè)元素都會關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù),分?jǐn)?shù)允許重復(fù)
    • 排行榜

5. Redis String的實(shí)現(xiàn)

??Redis雖然是用C語言寫的,但卻沒有直接用C語言的字符串,而是自己實(shí)現(xiàn)了一套字符串。目的就是為了提升速度,提升性能。Redis構(gòu)建了一個(gè)叫做簡單動(dòng)態(tài)字符串(Simple Dynamic String),簡稱SDS

struct sdshdr{    
    //  記錄已使用長度    
    int len;    
    // 記錄空閑未使用的長度    
    int free;    
    // 字符數(shù)組    
    char[] buf;    
};  

Redis的字符串也會遵守C語言的字符串的實(shí)現(xiàn)規(guī)則,即最后一個(gè)字符為空字符。然而這個(gè)空字符不會被計(jì)算在len里頭。

  • Redis動(dòng)態(tài)擴(kuò)展步驟:
    • 計(jì)算出大小是否足夠
    • 開辟空間至滿足所需大小
    • 開辟與已使用大小len相同長度的空閑free空間(如果len < 1M),開辟1M長度的空閑free空間(如果len >= 1M)
  • Redis字符串的性能優(yōu)勢
    • 快速獲取字符串長度:直接返回len
    • 避免緩沖區(qū)溢出:每次追加字符串時(shí)都會檢查空間是否夠用
    • 降低空間分配次數(shù)提升內(nèi)存使用效率:(1)空間預(yù)分配;(2)惰性空間回收

6. Redis持久化

  • RDB(redis database)在指定時(shí)間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤,也就是snapshot快照,恢復(fù)時(shí)是將快照文件直接讀到內(nèi)存里。Redis會單獨(dú)創(chuàng)建(fork:復(fù)制一個(gè)與當(dāng)前進(jìn)程一樣的進(jìn)程)一個(gè)子進(jìn)程來進(jìn)行持久化,會先將數(shù)據(jù)寫入到臨時(shí)文件,待持久化過程結(jié)束,再替換上次持久化好的文件(dump.rdb)。主進(jìn)程不進(jìn)行IO操作。如果需要進(jìn)行大規(guī)模數(shù)據(jù)的恢復(fù),且對數(shù)據(jù)完整性不敏感,那么RDB比AOF更高效。缺點(diǎn)就是最后一次持久化的數(shù)據(jù)可能丟失。
    • 默認(rèn):1分鐘改了一萬次,5分鐘改了10次,15分鐘改了一次
  • AOF以日志的形式來記錄每個(gè)寫操作,將redis執(zhí)行過的所有寫的指令記錄下來(讀操作不記錄),只需追加文件當(dāng)不可以改寫文件,redis重啟的話就根據(jù)日志文件的內(nèi)容將寫指令從前到后執(zhí)行一次以完成數(shù)據(jù)的恢復(fù)工作。
    • 同步策略:always一直同步、everysec每秒同步、no不同步
    • AOF的優(yōu)點(diǎn):
      • 備份機(jī)制更穩(wěn)健,丟失數(shù)據(jù)概率低
      • 可讀的日志文本,可以處理誤操作
    • AOF的缺點(diǎn):
      • 比RDB占用更多的磁盤空間
      • 恢復(fù)備份速度慢
      • 每次讀寫同步的話有一定的性能壓力
      • 存在個(gè)別的bug,造成不能恢復(fù)
    • AOF重寫機(jī)制:當(dāng)aof文件的大小超過所設(shè)定的閾值時(shí),redis就會啟動(dòng)AOF文件的內(nèi)容壓縮,只保留可以恢復(fù)數(shù)據(jù)的最小指令集,可以使用命令bgrewriteaof,fork出一條新進(jìn)程來將文件重寫,redis會記錄上次重寫時(shí)的AOF大小,默認(rèn)配置是當(dāng)AOF文件大小是上次rewrite后大小的1倍且文件大于64M時(shí)觸發(fā)。

7. Redis的事務(wù)

  • 定義:可以一次執(zhí)行多個(gè)命令,部分支持事務(wù)
  • 命令:MULTI開啟事務(wù)、EXEC執(zhí)行事務(wù)、DISCARD放棄事務(wù)、WATCH監(jiān)視一個(gè)或多個(gè)key,如果在事務(wù)執(zhí)行之前這個(gè)key被其他命令所改動(dòng),那么事務(wù)將被打斷、UNWACTH一旦執(zhí)行了EXEC之前加的所有的監(jiān)控鎖都會被取消。
  • 特性:
    • 單獨(dú)的隔離操作:事務(wù)中所有的命令都會序列化、按順序地執(zhí)行。事務(wù)執(zhí)行過程中,不會被其他客戶端發(fā)送過來的命令請求所打斷。
    • 沒有隔離級別的概念
    • 不保證原子性:只要有一條命令執(zhí)行失敗,其他的命令仍然會執(zhí)行,不支持回滾

8. LUA腳本

  • 定義:LUA是一種小巧的腳本語言,可以很容易地被C/C++調(diào)用,也可以調(diào)用C/C++函數(shù),一個(gè)完整的LUA解釋器不超過200k,適合作為嵌入式腳本語言。
  • 在redis中的優(yōu)勢
    • 將復(fù)雜或者多步的redis操作,寫為一個(gè)腳本,一次性提交給redis執(zhí)行,減少反復(fù)連接redis的次數(shù),提升性能。
    • LUA腳本類似redis事務(wù),有一定的原子性,不會被其他命令插隊(duì)

9. Redis內(nèi)存淘汰策略

  • 這八種大體上可以分為4種,lru、lfu、random、ttl。
1)  volatile-lru:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰。
2)  volatile-ttl:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選將要過期的數(shù)據(jù)淘汰。
3)  volatile-random:從已設(shè)置過期時(shí)間的數(shù)據(jù)集中任意選擇數(shù)據(jù)淘汰。
4)  volatile-lfu:從已設(shè)置過期時(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ù)時(shí),新寫入操作就會報(bào)錯(cuò),請求可以繼續(xù)進(jìn)行,線上任務(wù)也不能持續(xù)進(jìn)行,采用no-enviction策略可以保證數(shù)據(jù)不被丟失。

10. 秒殺常見問題

  • 連接超時(shí)
    • 使用連接池
  • 超賣問題
    • 使用事務(wù)
  • 庫存遺留
    • 使用LUA腳本

11. Redis發(fā)布訂閱

  • SUBSCRIBE c1 c2 c3
  • PUBLISH c2 hello-redis

12. Redis主從復(fù)制

  • 配從不配主:slaveof 主庫IP 主庫端口,每次與master斷開之后,都需要重新連接,除非配置redis.conf文件。
  • 配置文件細(xì)節(jié)操作:
    • 拷貝多個(gè)redis.conf文件
    • 開啟daemonize yes
    • Pid文件名字
    • 指定端口
    • Log文件名字
    • Dump.rdb名字
  • 常用招式
    • 一主二仆
    • Info replication:查看信息
    • SLAVEOF 127.0.0.1 6379:配置從庫


      image.png

13. 哨兵模式

  • 定義:反客為主(slaveof no one)自動(dòng)化,能夠監(jiān)控主機(jī)是否故障,如果故障根據(jù)投票數(shù)自動(dòng)將從庫轉(zhuǎn)為主庫
  • 使用步驟:
    • 調(diào)整結(jié)構(gòu),6379帶著80、81
    • 自定義的/myredis目錄下新建sentinel.conf文件
    • 配置哨兵,填寫內(nèi)容:Sentinel monitor host637(被監(jiān)控?cái)?shù)據(jù)庫名字(自己起名字))127.0.0.1 6379 1(多于1票則設(shè)為主機(jī))
    • 啟動(dòng)哨兵: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;// 實(shí)刷額度

     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;
     }
  }

  /**
   * 通俗點(diǎn)講,watch命令就是標(biāo)記一個(gè)鍵,如果標(biāo)記了一個(gè)鍵, 在提交事務(wù)前如果該鍵被別人修改過,那事務(wù)就會失敗,這種情況通常可以在程序中重新再嘗試一次。
   * 首先標(biāo)記了鍵balance,然后檢查余額是否足夠,不足就取消標(biāo)記,并不做扣減; 足夠的話,就啟動(dòng)事務(wù)進(jìn)行更新操作,
   * 如果在此期間鍵balance被其它人修改, 那在提交事務(wù)(執(zhí)行exec)時(shí)就會報(bào)錯(cuò), 程序中通常可以捕獲這類錯(cuò)誤再重新執(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:控制一個(gè)pool可分配多少個(gè)jedis實(shí)例,通過pool.getResource()來獲取;如果賦值為-1,則表示不限制;如果pool已經(jīng)分配了maxActive個(gè)jedis實(shí)例,則此時(shí)pool的狀態(tài)為exhausted。
-   maxIdle:控制一個(gè)pool最多有多少個(gè)狀態(tài)為idle(空閑)的jedis實(shí)例;
-   whenExhaustedAction:表示當(dāng)pool中的jedis實(shí)例都被allocated完時(shí),pool要采取的操作:默認(rèn)有三種。
-   WHEN_EXHAUSTED_FAIL -->表示無jedis實(shí)例時(shí),直接拋出NoSuchElementException;
-   WHEN_EXHAUSTED_BLOCK -->則表示阻塞住,或者達(dá)到maxWait時(shí)拋出JedisConnectionException;
-   WHEN_EXHAUSTED_GRoW -->則表示新建一個(gè)jedis實(shí)例,也就說設(shè)置的maxActive無用;
-   maxWait:表示當(dāng)borrow一個(gè)jedis實(shí)例時(shí),最大的等待時(shí)間,如果超過等待時(shí)間,則直接拋JedisConnectionException;
-   testOnBorrow:獲得一個(gè)jedis實(shí)例的時(shí)候是否檢查連接可用性(ping());如果為true,則得到的jedis實(shí)例均是可用的:

15. 解決session存儲問題

  • 方案一:存在cookie里
    • 不安全
    • 網(wǎng)絡(luò)負(fù)擔(dān)效率低
  • 方案二:存在文件服務(wù)器或數(shù)據(jù)庫里
    • 大量的IO效率問題
  • 方案三:session復(fù)制
    • Session數(shù)據(jù)冗余
    • 節(jié)點(diǎn)越多浪費(fèi)越大
  • 方案四:緩存數(shù)據(jù)庫
    • 完全存在內(nèi)存中,速度快數(shù)據(jù)結(jié)構(gòu)簡單

16. 單線程+多路IO復(fù)用

??多路復(fù)用是指用一個(gè)線程來檢查多個(gè)文件描述符(socket)的就緒狀態(tài),比如調(diào)用select、poll、epoll函數(shù)進(jìn)行監(jiān)視,傳入多個(gè)文件描述符,如果有一個(gè)文件描述符就緒,則返回,否則阻塞直到超時(shí)。得到就緒狀態(tài)后進(jìn)行真正的操作可以在同一個(gè)線程里執(zhí)行,也可以啟動(dòng)線程執(zhí)行(比如使用線程池)。
??多路I/O復(fù)用模型是利用 select、poll、epoll 可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候,會把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有 I/O 事件時(shí),就從阻塞態(tài)中喚醒,于是程序就會輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流),并且只依次順序的處理就緒的流,這種做法就避免了大量的無用操作。這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接,“復(fù)用”指的是復(fù)用同一個(gè)線程。

  • Select:每一個(gè)請求都進(jìn)行詢問,最多1024個(gè)
  • Poll:每一個(gè)請求都進(jìn)行詢問不限制數(shù)量
  • Epoll:監(jiān)視請求時(shí)為每個(gè)請求設(shè)置標(biāo)識符,不需要一一詢問

17. Select、poll、epoll

  • select的幾大缺點(diǎn):
    1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開銷在fd很多時(shí)會很大
    2)同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個(gè)開銷在fd很多時(shí)也很大
    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適用于連接較多,活動(dòng)數(shù)量較少的情況。
      1)epoll為了實(shí)現(xiàn)返回就緒的文件描述符,維護(hù)了一個(gè)紅黑樹和好多個(gè)等待隊(duì)列,內(nèi)核開銷很大。如果此時(shí)監(jiān)聽了很少的文件描述符,底層的開銷會得不償失;
      2)epoll中注冊了回調(diào)函數(shù),當(dāng)有事件發(fā)生時(shí),服務(wù)器設(shè)備驅(qū)動(dòng)調(diào)用回調(diào)函數(shù)將就緒的fd掛在rdllist上,如果有很多的活動(dòng),同一時(shí)間需要調(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)前是哪個(gè)fd就緒所消耗的時(shí)間復(fù)雜度就會大大減小。

18. REDIS緩存穿透,緩存擊穿,緩存雪崩原因+解決方案

  • 緩存穿透:key對應(yīng)的數(shù)據(jù)在數(shù)據(jù)庫和緩存并不存在,每次針對此key的請求從緩存獲取不到,請求都會到數(shù)據(jù)庫,從而可能壓垮數(shù)據(jù)庫。比如用一個(gè)不存在的用戶id獲取用戶信息,不論緩存還是數(shù)據(jù)庫都沒有,若黑客利用此漏洞進(jìn)行攻擊可能壓垮數(shù)據(jù)庫。
    • 解決方案:
      1)最常見的則是采用布隆過濾器,在寫入數(shù)據(jù)庫時(shí),將數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會被這個(gè)bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。
      2)簡單粗暴的方法:如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會很短,最長不超過五分鐘。
  • 緩存擊穿:是指一個(gè)key非常熱點(diǎn),在不停的扛著大并發(fā),大并發(fā)集中對這一個(gè)點(diǎn)進(jìn)行訪問,當(dāng)這個(gè)key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,就像在一個(gè)屏障上鑿開了一個(gè)洞。
    • 解決方案
      1)使用互斥鎖:就是在緩存失效的時(shí)候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者M(jìn)emcache的ADD)去set一個(gè)mutex key,當(dāng)操作返回成功時(shí),再進(jìn)行l(wèi)oad db的操作并回設(shè)緩存;否則,就重試整個(gè)get緩存的方法。
      2)設(shè)置永不過期
  • 緩存雪崩:當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個(gè)時(shí)間段失效,這樣在失效的時(shí)候,也會給后端系統(tǒng)(比如DB)帶來很大壓力。
    • 大量數(shù)據(jù)同時(shí)過期解決方案
      1)用加鎖或者隊(duì)列的方式保證來保證不會有大量的線程對數(shù)據(jù)庫一次性進(jìn)行讀寫,從而避免失效時(shí)大量的并發(fā)請求落到底層存儲系統(tǒng)上。加鎖排隊(duì)只是為了減輕數(shù)據(jù)庫的壓力,并沒有提高系統(tǒng)吞吐量。
      2)將緩存失效時(shí)間分散開,比如我們可以在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會降低,就很難引發(fā)集體失效的事件。
      3)雙key策略,主key設(shè)置過期時(shí)間,備key設(shè)置永久,主key過期時(shí),返回備key內(nèi)容
      4)后臺緩存更新,定時(shí)更新、消息隊(duì)列通知更新
    • 服務(wù)器宕機(jī)解決方案
      1)服務(wù)熔斷:在分布式系統(tǒng)中,我們往往需要依賴下游服務(wù),不管是內(nèi)部系統(tǒng)還是第三方服務(wù),如果下游出現(xiàn)問題,我們不在盲目地去請求,在一個(gè)周期內(nèi)失敗達(dá)到一定次數(shù),不在請求,及時(shí)失敗。過一段時(shí)間,在逐步放開請求,這樣既能防止不斷的調(diào)用,使下游服務(wù)更壞,保護(hù)了下游方,還能降低自己的執(zhí)行成本,快速的響應(yīng),減少延遲,增加吞吐量。
      2)服務(wù)降級:降級就是為了解決資源不足和訪問量增加的矛盾,在有限的資源情況下,為了能抗住大量的請求,就需要對系統(tǒng)做出一些犧牲,有點(diǎn)“棄卒保帥”的意思。放棄一些功能,保證整個(gè)系統(tǒng)能平穩(wěn)運(yùn)行。比如:搶購可以占時(shí)限流評論,將流量讓給秒殺業(yè)務(wù)
      3)請求限流:通過對并發(fā)訪問進(jìn)行限速。最簡單的方式,把多余的請求直接拒絕掉,可以根據(jù)一定的用戶規(guī)則進(jìn)行拒絕策略
      4)構(gòu)建redis高可靠集群


      image.png

19. 布隆過濾器

??布隆過濾器由「初始值都為 0 的位圖數(shù)組」和「 N 個(gè)哈希函數(shù)」兩部分組成。當(dāng)我們在寫入數(shù)據(jù)庫數(shù)據(jù)時(shí),在布隆過濾器里做個(gè)標(biāo)記,這樣下次查詢數(shù)據(jù)是否在數(shù)據(jù)庫時(shí),只需要查詢布隆過濾器,如果查詢到數(shù)據(jù)沒有被標(biāo)記,說明不在數(shù)據(jù)庫中。
布隆過濾器會通過 3 個(gè)操作完成標(biāo)記:

  • 第一步,使用 N 個(gè)哈希函數(shù)分別對數(shù)據(jù)做哈希計(jì)算,得到 N 個(gè)哈希值;
  • 第二步,將第一步得到的 N 個(gè)哈希值對位圖數(shù)組的長度取模,得到每個(gè)哈希值在位圖數(shù)組的對應(yīng)位置。
  • 第三步,將每個(gè)哈希值在位圖數(shù)組的對應(yīng)位置的值設(shè)置為 1;
    舉個(gè)例子,假設(shè)有一個(gè)位圖數(shù)組長度為 8,哈希函數(shù) 3 個(gè)的布隆過濾器。


    image.png

??在數(shù)據(jù)庫寫入數(shù)據(jù) x 后,把數(shù)據(jù) x 標(biāo)記在布隆過濾器時(shí),數(shù)據(jù) x 會被 3 個(gè)哈希函數(shù)分別計(jì)算出 3 個(gè)哈希值,然后在對這 3 個(gè)哈希值對 8 取模,假設(shè)取模的結(jié)果為 1、4、6,然后把位圖數(shù)組的第 1、4、6 位置的值設(shè)置為 1。當(dāng)應(yīng)用要查詢數(shù)據(jù) x 是否數(shù)據(jù)庫時(shí),通過布隆過濾器只要查到位圖數(shù)組的第 1、4、6 位置的值是否全為 1,只要有一個(gè)為 0,就認(rèn)為數(shù)據(jù) x 不在數(shù)據(jù)庫中。
??布隆過濾器由于是基于哈希函數(shù)實(shí)現(xiàn)查找的,高效查找的同時(shí)存在哈希沖突的可能性,比如數(shù)據(jù) x 和數(shù)據(jù) y 可能都落在第 1、4、6 位置,而事實(shí)上,可能數(shù)據(jù)庫中并不存在數(shù)據(jù) y,存在誤判的情況。
??所以,查詢布隆過濾器說數(shù)據(jù)存在,并不一定證明數(shù)據(jù)庫中存在這個(gè)數(shù)據(jù),但是查詢到數(shù)據(jù)不存在,數(shù)據(jù)庫中一定就不存在這個(gè)數(shù)據(jù)。

20. 為什么要用redis而不用map做緩存?

  • Redis 可以用幾十 G 內(nèi)存來做緩存,Map 不行,一般 JVM 也就分幾個(gè) G 數(shù)據(jù)就夠大了
  • Redis 的緩存可以持久化,Map 是內(nèi)存對象,程序一重啟數(shù)據(jù)就沒了
  • Redis 可以實(shí)現(xiàn)分布式的緩存,Map 只能存在創(chuàng)建它的程序里
  • Redis 可以處理每秒百萬級的并發(fā),是專業(yè)的緩存服務(wù),Map 只是一個(gè)普通的對象
  • Redis 緩存有過期機(jī)制,Map 本身無此功能
  • Redis 有豐富的 API,Map 就簡單太多了

21. 如何保持緩存和數(shù)據(jù)庫的一致性?

  • 淘汰緩存還是更新緩存?
    選擇淘汰緩存,原因:數(shù)據(jù)可能為簡單數(shù)據(jù),也可能為較復(fù)雜的數(shù)據(jù),復(fù)雜數(shù)據(jù)進(jìn)行緩存的更新操作,成本較高,因此一般推薦淘汰緩存
  • 先淘汰緩存還是先更新數(shù)據(jù)庫?
    選擇先淘汰緩存,再更新數(shù)據(jù)庫,原因:假如先更新數(shù)據(jù)庫,再淘汰緩存,如果緩存淘汰失敗,那么后面的請求都會得到臟數(shù)據(jù),直至緩存過期。假如先淘汰緩存再更新數(shù)據(jù)庫,如果數(shù)據(jù)庫更新失敗,只會產(chǎn)生一次緩存miss,相比較而言,后者對業(yè)務(wù)影響更小一點(diǎn)。
  • 延時(shí)雙刪策略:解決數(shù)據(jù)庫讀寫分離
public void write(String key,Object data){
    redisUtils.del(key);
    db.update(data);
    Thread.Sleep(100);
    redisUtils.del(key);
}

22. Redis分布式鎖的實(shí)現(xiàn)

  • 加鎖:使用setnx key value命令,如果key不存在,設(shè)置value(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶端持有鎖了),則設(shè)置失敗(加鎖失敗)。
  • 解鎖:使用del命令,通過刪除鍵值釋放鎖。釋放鎖之后,其他客戶端可以通過setnx命令進(jìn)行加鎖。

23. Redis集群

  • Redis集群解決內(nèi)存壓力,實(shí)現(xiàn)了對redis的水平擴(kuò)容,即啟動(dòng)N個(gè)redis節(jié)點(diǎn),將整個(gè)數(shù)據(jù)庫分布在N個(gè)節(jié)點(diǎn)中,每個(gè)節(jié)點(diǎn)存儲數(shù)據(jù)的1/N。
  • Redis集群通過分區(qū)來提供一定程度的可用性,即使集群中有一部分節(jié)點(diǎn)失效或者無法進(jìn)行通訊,集群也可以基礎(chǔ)處理命令請求。
  1. Redis部署模型
  • 模式一:單實(shí)例


    image.png
  • 模式二:一主一從


    image.png
  • 模式三:一主多從


    image.png
  • 模式四:多主多從


    image.png
  • 模式五:集群


    image.png
  1. Redis緩存預(yù)熱
  • 緩存預(yù)熱的思路
    (1) 提前給redis中嵌入部分?jǐn)?shù)據(jù),再提供服務(wù),肯定不可能將所有數(shù)據(jù)都寫入redis,因?yàn)閿?shù)據(jù)量太大了,第一耗費(fèi)的時(shí)間太長了,第二redis根本就容納不下所有的數(shù)據(jù)
    (2) 需要更具當(dāng)天的具體訪問情況,試試統(tǒng)計(jì)出頻率較高的熱數(shù)據(jù)
    (3) 然后將訪問頻率較高的熱數(shù)據(jù)寫入到redis,肯定是熱數(shù)據(jù)也比較多,我們也得多個(gè)服務(wù)并行的讀取數(shù)據(jù)去寫,并行的分布式的緩存預(yù)熱
    (4) 然后將嵌入的熱數(shù)據(jù)的redis對外提供服務(wù),這樣就不至于冷啟動(dòng),直接讓數(shù)據(jù)庫奔潰了
  • 具體的實(shí)時(shí)方案:
    (1) nginx+lua將訪問量上報(bào)到kafka中要統(tǒng)計(jì)出來當(dāng)前最新的實(shí)時(shí)的熱數(shù)據(jù)是哪些,我們就得將商品詳情頁訪問的請求對應(yīng)的流量,日志,實(shí)時(shí)上報(bào)到kafka中,
    (2) storm從kafka中消費(fèi)數(shù)據(jù),實(shí)時(shí)統(tǒng)計(jì)出每個(gè)商品的訪問次數(shù),訪問次數(shù)基于LRU內(nèi)存數(shù)據(jù)結(jié)構(gòu)的存儲方案。
    a) 優(yōu)先用內(nèi)存中的一個(gè)LRUMap去存放,性能高,而且沒有外部依賴。否則的話,依賴redis,我們就是要防止reids掛掉數(shù)據(jù)丟失的情況,就不合適了;用mysql,扛不住高并發(fā)讀寫;用hbase,hadoop生態(tài)系統(tǒng),維護(hù)麻煩,太重了,其實(shí)我們只要統(tǒng)計(jì)出一段時(shí)間訪問最頻繁的商品,然后對它們進(jìn)行訪問計(jì)數(shù),同時(shí)維護(hù)出一個(gè)前N個(gè)訪問最多的商品list即可。計(jì)算好每個(gè)task大致要存放的商品訪問次數(shù)的數(shù)量,計(jì)算出大小,然后構(gòu)建一個(gè)LURMap,apache commons collections有開源的實(shí)現(xiàn),設(shè)定好map的最大大小,就會自動(dòng)根據(jù)LRU算法去剔除多余的數(shù)據(jù),保證內(nèi)存使用限制,即使有部分?jǐn)?shù)據(jù)被干掉了,然后下次來重新開始技術(shù),也沒什么關(guān)系,因?yàn)槿绻籐RU算法干掉,那么它就不是熱數(shù)據(jù),說明最近一段時(shí)間很少訪問,
    (3) 每個(gè)storm task啟動(dòng)的時(shí)候,基于zk分布式鎖,將自己的id寫入zk的一個(gè)節(jié)點(diǎn)中
    (4) 每個(gè)storm task負(fù)責(zé)完成自己這里的熱數(shù)據(jù)的統(tǒng)計(jì),比如每次計(jì)數(shù)過后,維護(hù)一個(gè)錢1000個(gè)商品的list,每次計(jì)算完都更新這個(gè)list
    (5) 寫一個(gè)后臺線程,每個(gè)一段時(shí)間,比如一分鐘,將排名錢1000的熱數(shù)據(jù)list,同步到zk中
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,427評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內(nèi)容