天天看點

MySQL資料庫和Redis緩存一緻性的更新政策

作者:哪吒程式設計

大家好,我是哪吒。

上一篇分享了Redis bigkeys指令會阻塞嗎?怎麼解決?,完成了Redis避免使用大Key的學習和了解。

一、更新政策

1、如果Redis中有資料,需要和資料庫中的值相同。

2、如果Redis中無資料,資料庫中的最新值要對Redis進行同步更新。

二、讀寫緩存

1、同步直寫政策

寫入資料庫也同步寫Redis緩存,緩存和資料庫中的資料一緻;對于讀寫緩存來說,要保證緩存和資料庫中的資料一緻,就要保證同步直寫政策。

2、異步緩寫政策

某些業務運作中,MySQL資料更新之後,允許在一定時間後再進行Redis資料同步,比如物流系統。

當出現異常情況時,不得不将失敗的動作重新修補,需要借助rabbitmq或kafka進行重寫。

三、雙檢加鎖政策

多個線程同時去查詢資料庫的這條資料,那麼我們可以在第一個查詢資料的請求上使用一個 互斥鎖來鎖住它。

其他的線程走到這一步拿不到鎖就等着,等第一個線程查詢到了資料,然後做緩存。

後面的線程進來發現已經有緩存了,就直接走緩存。

public String get(String key){
    // 從Redis緩存中讀取
    String value = redisTemplate.get(key);

    if(value != null){
        return value;
    }

    synchronized (RedisTest.class){
        // 重新嘗試從Redis緩存中讀取
        value = redisTemplate.get(key);
        if(value != null){
            return value;
        }

        // 從MySQL資料庫中查詢
        value = studentDao.get(key);
        // 寫入Redis緩存
        redisTemplate.setnx(key,value,time);
        return value;
    }
}           

四、資料庫和緩存一緻性的更新政策

1、先更新資料庫,再更新Redis

按照常理出牌的話,應該都是如此吧?那麼,這種情況下,會有啥問題呢?

如果更新資料庫成功後,更新Redis之前異常了,會出現什麼情況呢?

資料庫與Redis内緩存資料不一緻。

2、先更新緩存,再更新資料庫

多線程情況下,會有問題。

比如

  1. 線程1更新redis = 200;
  2. 線程2更新redis = 100;
  3. 線程2更新MySQL = 100;
  4. 線程1更新MySQL = 200;

結果呢,Redis=100、MySQL=200;我擦!

3、先删除緩存,再更新資料庫

  1. 線程1删除了Redis的緩存資料,然後去更新MySQL資料庫;
  2. 還沒等MySQL更新完畢,線程2殺來,讀取緩存資料;
  3. 但是,此時MySQL資料庫還沒更新,線程2讀取了MySQL中的舊值,然後線程2,還會将舊值寫入Redis作為資料緩存;
  4. 線程1更新完MySQL資料後,發現Redis中已經有資料了,之前都删過了,那我就不更新了;

完蛋了。。

延時雙删

延時雙删可以解決上面的問題,隻要sleep的時間大于線程2讀取資料再寫入緩存的時間就可以了,也就是線程1的二次清緩存操作要線上程2寫入緩存之後,這樣才能保證Redis緩存中的資料是最新的。

/**
 * 延時雙删
 * @autor 哪吒程式設計
 */
public void deleteRedisData(Student stu){
    // 删除Redis中的緩存資料
    jedis.del(stu);

    // 更新MySQL資料庫資料
    studentDao.update(stu);

    // 休息兩秒
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 删除Redis中的緩存資料
    jedis.del(stu);
}           

延遲雙删最大的問題就是sleep,在效率為王的今天,sleep能不用還是不用為好。

4、先更新資料庫,再删除緩存

  1. 線程1先更新資料庫,再删除Redis緩存;
  2. 線程2線上程1删除Redis緩存之前發起請求,得到了未删除的Redis緩存;
  3. 線程1此時才删除Redis緩存資料;

問題還是有,這翻來覆去的,沒完沒了了。

這種情況如何解決呢?

引入消息中間件解決戰鬥,再一次詳細的複盤一下。

  1. 更新資料庫;
  2. 資料庫将操作資訊寫入binlog日志;
  3. 訂閱程式提取出key和資料;
  4. 嘗試删除緩存操作,發現删除失敗;
  5. 将這些資料資訊發送到消息中間件中;
  6. 從消息中間件中擷取該資料,重新操作;

5、總結

哪吒推薦使用第四種方式,先更新資料庫,再删除緩存。

方式①和方式②缺點太過明顯,不考慮; 方式③中的sleep,總是讓人頭疼; 方式④是一個比較全面的方案,但是增加了學習成本、維護成本,因為增加了消息中間件。

五、MySQL主從複制工作原理

MySQL資料庫和Redis緩存一緻性的更新政策

1、當 master 主伺服器上的資料發生改變時,則将其改變寫入二進制事件日志檔案中;

2、salve 從伺服器會在一定時間間隔内對 master 主伺服器上的二進制日志進行探測,探測其是否發生過改變,

如果探測到 master 主伺服器的二進制事件日志發生了改變,則開始一個 I/O Thread 請求 master 二進制事件日志;

3、同時 master 主伺服器為每個 I/O Thread 啟動一個dump Thread,用于向其發送二進制事件日志;

4、slave 從伺服器将接收到的二進制事件日志儲存至自己本地的中繼日志檔案中;

5、salve 從伺服器将啟動 SQL Thread 從中繼日志中讀取二進制日志,在本地重放,使得其資料和主伺服器保持一緻;

6、最後 I/O Thread 和 SQL Thread 将進入睡眠狀态,等待下一次被喚醒;