架構設計 | 緩存管理模式,監控和內存回收策略


本文源碼:GitHub·點這里 || GitEE·點這里

一、緩存設計

1、緩存的作用

在業務系統中,查詢時最容易出現性能問題的模塊,查詢面對的數據量大,篩選條件復雜,所以在系統架構中引入緩存層,則是非常必要的,用來緩存熱點數據,達到快速響應的目的。

緩存使用的基本原則:

  • 所有緩存數據,必須設置過期時間;
  • 核心業務流程不通過緩存層;
  • 緩存層移除,不影響現有流程;
  • 系統各個端首頁數據不實時查詢;
  • 報表數據不實時查詢加載;
  • 歸檔數據(定時統計的結果數據)不實時查詢;

這里是業務架構中常用的緩存策略,緩存通過犧牲強一致性來提高性能,所以並不是所有的業務都適合用緩存,實際考量都會針對具體的業務,比如用戶相關維度的數據修改頻率低,會使用緩存,但是用戶權限數據(比如:免費次數)會考慮實時校驗,緩存層使用的相對較少。

2、緩存設計模式

Cache-Aside模式

業務中最常用的緩存層設計模式,基本實現邏輯和相關概念如下:

  • 緩存命中:直接查詢緩存且命中,返回數據;
  • 緩存加載:查詢緩存未命中,從數據庫中查詢數據,獲取數據后並加載到緩存;
  • 緩存失效:數據更新寫到數據庫,操作成功后,讓緩存失效,查詢時候再重新加載;
  • 緩存穿透:查詢數據庫不存在的對象,也就不存在緩存層的命中;
  • 緩存擊穿:熱點key在失效的瞬間,高並發查詢這個key,擊穿緩存,直接請求數據庫;
  • 緩存雪崩:緩存Key大批量到過期時間,導致數據庫壓力過大;
  • 命中率:緩存設計的是否合理要看命中率,命中率高說明緩存有效抗住了大部分請求,命中率可以通過Redis監控信息計算,一般來說命中率在(70-80)%都算合理。
    並發問題

執行讀操作未命中緩存,然后查詢數據庫中取數據,數據已經查詢到還沒放入緩存,同時一個更新寫操作讓緩存失效,然后讀操作再把查詢到數據加載緩存,導致緩存的臟數據。

在遵守緩存使用原則下出現該情況概率非常低,可以通過復雜的Paxos協議保證一致性,一般情況是不考量該場景的處理,如果緩存管理過於復雜,會和緩存層核心理念相悖。

基本描述代碼:

@Service
public class KeyValueServiceImpl extends ServiceImpl<KeyValueMapper, KeyValueEntity> implements KeyValueService {

    @Resource
    private RedisService redisService ;

    @Override
    public KeyValueEntity select(Integer id) {
        // 查詢緩存
        String redisKey = RedisKeyUtil.getObectKey(id) ;
        String value = redisService.get(redisKey) ;
        if (!StringUtils.isEmpty(value) && !value.equals("null")){
            return JSON.parseObject(value,KeyValueEntity.class);
        }
        // 查詢庫
        KeyValueEntity keyValueEntity = this.getById(id) ;
        if (keyValueEntity != null){
            // 緩存寫入
            redisService.set(redisKey,JSON.toJSONString(keyValueEntity)) ;
        }
        // 返回值
        return keyValueEntity ;
    }

    @Override
    public boolean update(KeyValueEntity keyValueEntity) {
        // 更新數據
        boolean updateFlag = this.updateById(keyValueEntity) ;
        // 清除緩存
        if (updateFlag){
            redisService.delete(RedisKeyUtil.getObectKey(keyValueEntity.getId()));
        }
        return updateFlag ;
    }
}

Read-Throug模式

當應用系統向緩存系統請求數據時,如果緩存中並沒有對應的數據存在,緩存系統將向底層數據源的讀取數據。如果數據在緩存中存在,則直接返回緩存中存在的數據。把更新數據庫的操作由緩存層代勞了。

Write-Through模式

更新寫數據時,如果沒有命中緩存,則直接更新數據庫,如果命中了緩存,則先更新緩存,然后由緩存系統自行更新數據庫。

Write-Behind模式

應用系統對緩存中的數據進行更新時,只更新緩存,不更新數據庫,緩存系統會異步批量向底層數據源更新數據。

二、數據一致問題

業務開發模式中,會涉及到一個問題:如何最大限度保證數據庫和Redis緩存的數據一致性?

首先說明一下:數據庫和緩存強一致性同步成本太高,如果追求強一致,緩存層存在的價值就會很低,如上緩存模式一中幾乎可以解決大部分業務場景問題。

解決這個問題的方式很多:

方案一說明:

  • 數據庫更新寫入數據成功;
  • 准備一個先進先出模式的消息隊列;
  • 把更新的數據包裝為一個消息放入隊列;
  • 基於消息消費服務更新Redis緩存;

分析:消息隊列的穩定和可靠性,操作層面數據庫和緩存層解耦。

方案二說明:

  • 提供一個數據庫Binlog訂閱服務,並解析修改日志;
  • 服務獲取修改數據,並向Redis服務發送消息;
  • Redis數據進行修改,類似MySQL的主從同步機制;

分析:系統架構層面多出一個服務,且需要解析MySQL日志,操作難度較大,但流程上更為合理。

總結描述

分布式架構中,緩存層面的基本需求就是提高響應速度,不斷優化,追求數據庫和Redis緩存的數據快速一致性,從提供的各種方案中都可以看出,這也在增加緩存層面處理的復雜性,架構邏輯復雜,就容易導致程序錯誤,所以針對業務選擇合理的處理邏輯,這點很關鍵。

三、緩存監控

1、Redis服務監控

通過info命令查看Redis服務的參數信息,可以通過傳參查看指定分類配置。通過config..set設置具體配置參數。例如:

@Override
public Properties info(String var) {
    if (StringUtils.isEmpty(var)){
        return redisTemplate.getRequiredConnectionFactory().getConnection().info();
    }
    return redisTemplate.getRequiredConnectionFactory().getConnection().info(var);
}

傳參說明:

  • memory:內存消耗相關信息
  • server:有關Redis服務器的常規信息
  • clients:客戶端連接部分
  • stats:一般統計
  • cpu:CPU消耗統計信息

應用案例:

@RestController
public class MonitorController {

    @Resource
    private RedisService redisService ;

    private static final String[] monitorParam = new String[]{"memory","server","clients","stats","cpu"} ;

    @GetMapping("/monitor")
    public List<MonitorEntity> monitor (){
        List<MonitorEntity> monitorEntityList = new ArrayList<>() ;
        for (String param:monitorParam){
            Properties properties = redisService.info(param) ;
            MonitorEntity monitorEntity = new MonitorEntity () ;
            monitorEntity.setMonitorParam(param);
            monitorEntity.setProperties(properties);
            monitorEntityList.add(monitorEntity);
        }
        return monitorEntityList ;
    }

}

通過上述參數組合,把Redis相關配置參數打印出來,然后可視化輸出,儼然一副高端的感覺。

配置參數說明:

這里只對兩個參數說明一下,計算命中率的關鍵信息:

  • keyspace_misses:查找緩存Key失敗的次數;
  • keyspace_hits:查找緩存Key命中的次數;

公式:命中率=命中次數/(hits+misses)查找總次數。

2、LRU算法說明

Redis的數據是放在內存中的,所以速度快,自然也就受到內存大小的限制,如果內存使用超過配置,Redis有不同的回收處理策略。

內存模塊參數:maxmemory_policy

  • noenviction:不回收數據,查詢直接返回錯誤,但可以執行刪除;
  • allkeys-lru:從所有的數據中挑選最近最少使用的數據淘汰;
  • volatile-lru:已設置過期時間的數據中挑選最近最少使用的數據淘汰;
  • allkeys-random:從所有數據中任意選擇數據淘汰;
  • volatile-random:從已設置過期時間的數據中任意選擇數據淘汰;
  • volatile-ttl:從已設置過期時間的數據中挑選將要過期的數據淘汰;

大部分情況下,業務都是希望最熱點數據可以被緩存,所以相對使用allkeys-lru策略偏多。這里要根據業務模式特點衡量。

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推薦閱讀:《架構設計系列》,蘿卜青菜,各有所需

序號 標題
01 架構基礎:單服務.集群.分布式,基本區別和聯系
02 架構設計:分布式業務系統中,全局ID生成策略
03 架構設計:分布式系統調度,Zookeeper集群化管理
04 架構設計:接口冪等性原則,防重復提交Token管理


免責聲明!

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



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