java架構之路-(Redis專題)聊聊大廠那些redis


  上幾次說了redis的主從,哨兵,集群配置,但是內部的選舉一直沒說,先來簡單說一下選舉吧。

集群選舉

  redis cluster節點間采取gossip協議進行通信,也就是說,在每一個節點間,無論主節點還是從節點,他們之間都是存在相互通信的。例如你的redis端口號是6379,那么你的gossip協議端口號就是16379。

  gossip協議包含多種消息,包括ping,pong,meet,fail等等。

  ping:每個節點都會頻繁給其他節點發送ping,其中包含自己的狀態還有自己維護的集群元數據,互相通過ping交換元 數據;

  pong: 返回ping和meet,包含自己的狀態和其他信息,也可以用於信息廣播和更新;

  fail: 某個節點判斷另一個節點fail之后,就發送fail給其他節點,通知其他節點,指定的節點宕機了。

  meet:某個節點發送meet給新加入的節點,讓新節點加入集群中,然后新節點就會開始與其他節點進行通信,不需要 發送形成網絡的所需的所有CLUSTER MEET命令。發送CLUSTER MEET消息以便每個節點能夠達到其他每個節點只需通 過一條已知的節點鏈就夠了。由於在心跳包中會交換gossip信息,將會創建節點間缺失的鏈接。

  當我們的master節點和其slave節點中斷,或和其它節點中斷時,也就是連接超過了我們設置的cluster‐node‐timeout的值,這時就會認為我們的當前的master是不可用的,需要選舉了,這時將自己記錄的集群currentEpoch加1,並廣播FAILOVER_AUTH_REQUEST信息到所有節點上(包括其他主從的從節點),其它主節點收到FAILOVER_AUTH_REQUEST信息會給與一個FAILOVER_AUTH_ACK反饋,其它從節點不會有任何反應,當我們的slave收到ACK反饋達到半數以上時,會當選當前選舉內的master節點,其它slave節點不在進行選舉,作為該新master的slave節點。廣播Pong消息通知其他集群節點。

  流程大概就是這樣的,還有可能每個正在選舉的slave節點收到的ACK反饋是一樣的,這時再次觸發一次選舉,currentEpoch再加1,流程和上面一樣。這里要注意的是,並不是每個slave都在同一時刻向外發送FAILOVER_AUTH_REQUEST信息的,一般數據較新的節點會先發,數據的新舊由SLAVE_RANK來判斷,SLAVE_RANK越小,代表數據越新。


 Redis的優化與實際場景

緩存穿透

  我們在一般大型的互聯網項目查詢到的數據,都是查詢的緩存內的數據,也就是我們的redis內的數據,但是第一次查詢或者說根本不存在的數據,會穿過緩存到達我們的數據庫去查詢,如果大量這樣的請求過來,我們的數據庫是扛不住的。這就是我們常說的緩存穿透。處理思路很簡單,只要是請求過來的,沒有結果。存入緩存設置超時時間,再返回。設置時間是為了保證現在沒用到,現在沒緩存結果,不代表永遠沒有緩存結果。

@GetMapping(value = "/")
public String getIndex(String goods_id){
    //優先從緩存去拿
    String goods = stringRedisTemplate.opsForValue().get(goods_id);
    if (goods == null){
        //如果拿不到去數據庫拿
        goods = goodsService.getGoodsById(goods_id);
        //存入緩存,設置超時時間
        stringRedisTemplate.opsForValue().set(goods_id,goods,300);
    }
    return goods;
}

  如果是黑客來了,一直拿不同的緩存來請求我們的項目,這樣的思路是不可取的,我們可以使用布隆過濾器來實現阻止緩存擊穿問題。

緩存預熱

  雙11要來了,每次雙11的0點,會有大批的商品進行交易,如果這些商品不是存在緩存內的,超高的並發(都不用雙11,平時的秒殺就夠受的),大量的線程會涌入數據庫,給數據庫造成超大的壓力,我們這時應該提前將這些要秒殺的商品,提前存入到redis當中去,防止大批量的請求直接沖進數據庫。這就是我們提到的緩存預熱。

緩存失效

  剛才我們的說了預熱,但是我還是需要設置超時時間的時間的,不設置超時時間的話,你的數據庫更新了,而我們的緩存還是我們的最開始的數據,造成數據的不一致。假設我們在預熱的時候將大量的商品設置為300秒超時的時間,開始秒殺....過了300秒了。還是有一定的並發量,這時所有的緩存都失效了,還是會有大量的請求進入到我們的數據庫的,所以說我在設置緩存預熱時,不要設置同一個時間結束。會造成大量的緩存在同一時間失效,給我們的后台服務造成巨大壓力。

緩存雪崩

  有很多項目還是在停留在使用redis單機的狀態,如果說redis不在對我們的項目服務了,大量的請求會涌入我們的數據訪問層,造成我們的數據庫壓力超大,甚至數據庫宕機,從導致整個服務的不可用狀態。或者說我們的並發量遠遠超過我們的redis吞吐量。也會早成redis的擁塞,其它線程請求redis超時,早成redis假死現象,造成我們的redis雪崩。這時我們應該盡力采用高可用的緩存層架構,比如哨兵,比如集群架構,對於並發量超大的情況我們可以使用限流的方式來控制。

熱點緩存重建

  如果說,我們的設置了一個緩存,失效時間為300毫秒,但在失效那一刻,還是高並發的狀態,我們的服務器壓力還是巨大的,這些高並發的請求進入我們的數據庫,后果可想而知,所以我們要在這個熱點key的重建過程中,避免大量的請求進入我們的數據庫。我們可以這樣來做,嘗試加一把簡單的鎖。

@GetMapping(value = "/")
public String getIndex(String goods_id) throws InterruptedException {
    //優先從緩存去拿
    String goods = stringRedisTemplate.opsForValue().get(goods_id);
    if (goods == null){
        //如果拿不到去數據庫拿
        //設置只有一個請求可以進入數據庫,其余的線程自旋等待
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock" + goods_id, goods_id, Duration.ofMinutes(3));
        if(aBoolean){
            goods = goodsService.getGoodsById(goods_id);
            //存入緩存,設置超時時間
            stringRedisTemplate.opsForValue().set(goods_id,goods,300);
        }else{
            //自旋等待50毫秒
            Thread.sleep(50);
            //再次調用該方法,嘗試獲取數據
            getIndex(goods_id);
        }
    }
    return goods;
}

一些Redis的使用建議

  1.建議key設置為服務名:表名或者模塊名:表名作為key,便於后期的查找和使用。

  2.保證能識別語義的前提下,盡力設置key要簡潔,不要過長。

  3.不要在key中設置特殊字符,比如空格、換行等字符。

  4.redis中不要設置過大的值,一個字符串最大限制512M,但建議一般是要超過10kb大小,list,set,hash,zset不建議超過5000個元素,視情況而定。

  5.不要使用keys命令,建議使用scan命令進行替換。

  6.建議多使用原生命令,管道等操作盡力減少使用,推薦使用mget,mset這樣的命令。

這些優化其實都是圍繞着我們Redis的特性,單線程來說的,如果說我們存了一個bigKey或者是一次性塞入了超多的命令,很可能阻塞后面的命令,造成我們的redis假死現象,也會造成我們的網絡擁塞,占有了更多的帶寬。

Redis的清除策略

  1.被動刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key

  2.主動刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以Redis會定期主動淘汰一批已過期的key

  3.當前已用內存超過maxmemory限定時,觸發主動刪除策略。

  在redis啟動前,我們就配置了,最大的內存使用maxmemory,當前已用內存超過maxmemory限定時,會觸發主動清理策略。

  默認策略是volatile-lru,即超過最大內存后,在過期鍵中使用lru算法進行key的剔除,保證不過 期數據不被刪除,但是可能會出現OOM問題。

  其他策略如下:

allkeys-lru:根據LRU算法刪除鍵,不管數據有沒有設置超時屬性,直到騰出足夠空間 為止。

allkeys-random:隨機刪除所有鍵,直到騰出足夠空間為止。

volatile-random: 隨機刪除過期鍵,直到騰出足夠空間為止。

volatile-ttl:根據鍵值對象的ttl屬性,刪除最近將要過期數據。如果沒有,回退到

noeviction策略。 noeviction:不會剔除任何數據,拒絕所有寫入操作並返回客戶端錯誤信息"(error)OOM command not allowed when used memory",此時Redis只響應讀操作。

  注意:如果沒有配置我們的maxmemory屬性,當我們的內存寫滿以后,不會觸發任何清除策略,會直接將我們的數據存放在磁盤上,極具降低我們的redis性能。

總結:

  redis差不多就說這么多了,再深入的c語言代碼,我也不懂了,我們大概簡單使用,基礎的搭建主從,哨兵,集群,java鏈接redis,redis的優化這幾個角度來講解我們的redis,后面我會弄一篇redis的面試題,也是圍繞這些來講解的,還是那句話,真正懂得了內部的原理,什么樣的面試題都不在話下了...

 

最進弄了一個公眾號,小菜技術,歡迎大家的加入


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM