微服務化後快取怎麼做

方丈發表於2019-05-06

摘要

最近接手的程式碼中遇到幾個快取的問題,存在一些設計原則的問題,這裡總結一下,希望可以對你有幫助

問題

問題1: 店鋪資料的獲取,將使用者關注的資料放在店鋪資訊一起返回

對外提供的介面

List<Shop> getPageShop(final Query query,final Boolean cache);
複製程式碼

返回的店鋪資訊

public class Shop {

    public static final long DEFAULT_PRIORITY = 10L;

    /**
     * 唯一標識
     */
    private Long id;
   //省略了店鋪其他資訊
    /**
     * 使用者關注
     */
    private ShopAttention attention;
}
複製程式碼

當呼叫方設定cache為true時,因為有快取的存在,獲取不到使用者是否關注的資料。

問題2: 統計店鋪的被關注數導致的慢SQL,導致資料庫cpu飆高,影響到了整個應用

SQL

SELECT shop_id, count(user_Id) as attentionNumber
FROM shop_attention
WHERE shop_id IN
<foreach collection="shopIds" item="shopId" separator="," open="(" close=")">
    #{shopId}
</foreach>
GROUP BY shopId
複製程式碼

這兩種程式碼的寫法都是基於一個基準

不同的地方的快取策略不一樣,比如我更新的地方,查詢資料時不能快取,頁面展示的查詢的地方需要快取。 既然服務提供方不知道該不該快取,那就不管了,交給呼叫方去管理

這種假設本身沒什麼問題,但是忽略了另外一個原則,服務的內聚性。不應該被外部知道的就沒必要暴露給外部

無論是程式導向的C,還是物件導向的語言,都強調內聚性,也就是高內聚,低耦合。單體應用中應當遵循這個原則,微服務同樣遵循這個原則。但是在實際過程中,我們發現做到高內聚並不簡單。我們必須要時時刻刻審視方法/服務的邊界,只有確定好職責邊界,才能寫出高內聚的程式碼

問題分析

第一個問題,從快取的角度來看,是忽略了資料的更新頻繁性以及資料獲取的不同場景。

對於店鋪這樣一個大的聚合根,本身包含的資訊很多,有些資料可能會被頻繁更改的,有些則會很少更新的。那麼不同的修改頻率,是否快取/快取策略自然不同,使用同一個引數Boolean cache來控制顯然不妥

第二個問題,這種統計類的需求使用SQL統計是一種在資料量比較小的情況下的權宜之計,當資料規模增大後,必須要使用離線計算或者流式計算來解決。它本身是一個慢SQL,所以必須要控制號呼叫量,這種統計的資料量的時效性應該由服務方控制,不需要暴露給呼叫方。否則就會出現上述的問題,呼叫方並不清楚其中的邏輯,不走快取的話就會使得呼叫次數增加,QPS的增加會導致慢SQL打垮資料庫

解法

快取更新本身就是一個難解的問題,在微服務化後,多個服務就更加複雜了。涉及到跨服務的多級快取一致性的問題。

所以對大部分的業務,我們可以遵循這樣的原則來簡單有效處理。

  • 對資料的有效性比較敏感的呼叫都收斂到服務內部(領域內部應該更合適),不要暴露給呼叫方, 領域內部做資料的快取失效控制
  • 快取預計算(有些頁面的地方不希望首次開啟慢)的邏輯也應該放在領域內控制,不要暴露給呼叫方。 在領域內部控制在不同的地方使用不同的快取策略,比如更新資料的地方需要獲取及時的資料。比如商品的價格,和商品的所屬類目更新頻次不同,需要有不同的過期時間。
  • 跨服務呼叫為了減少rpc呼叫,可以再進行一層快取。因為這些呼叫可以接受過期的資料,再進行一層快取沒問題,expired time疊加也沒多大影響(expire time在這邊主要是影響快取的命中數)

以上述店鋪查詢問題改造為例

在這裡插入圖片描述
擴充套件:如果後續有case在跨服務的呼叫時,對資料的過期比較敏感,並且在呼叫方也做了快取,那就是跨服務的多級快取一致性的問題。那就需要服務方告知呼叫方快取何時失效,使用訊息佇列or其他方式來實現。

關注【方丈的寺院】,與方丈一起開始技術修行之路

在這裡插入圖片描述

參考

martin.kleppmann.com/2012/10/01/…

相關文章