探討下如何更好的使用緩存 —— Redis緩存的特殊用法以及與本地緩存一起構建多級緩存的實現

来源:https://www.cnblogs.com/softwarearch/archive/2023/01/17/16937368.html
-Advertisement-
Play Games

本篇文章,我們就一起聊一聊如何來更好的使用緩存,探尋下如何降低緩存交互過程的性能損耗、如何壓縮緩存的存儲空間占用、如何保證多個操作命令原子性等問題的解決策略,讓緩存在項目中可以發揮出更佳的效果。 ...


大家好,又見面了。


本文是筆者作為掘金技術社區簽約作者的身份輸出的緩存專欄系列內容,將會通過系列專題,講清楚緩存的方方面面。如果感興趣,歡迎關註以獲取後續更新。


通過前面的文章,我們一起剖析了Guava CacheCaffeineEhcache本地緩存框架的原理與使用場景,也一同領略了以Redis為代表的集中式緩存在分散式高併發場景下無可替代的價值。

現在的很多大型高併發系統都是採用的分散式部署方式,而作為高併發系統的基石,緩存是不可或缺的重要環節。項目中使用緩存的目的是為了提升整體的運算處理效率、降低對外的IO請求,而集中式緩存是獨立於進程之外部署的遠端服務,需要基於網路IO的方式交互。如果一個業務邏輯中涉及到非常頻繁的緩存操作,勢必會導致引入大量的網路IO交互,造成過大的性能損耗、加劇緩存伺服器的壓力。另外,對於現在互聯網系統的海量用戶數據,如何壓縮緩存數據占用容量,也是需要面臨的一個問題。

本篇文章,我們就一起聊一聊如何來更好的使用緩存,探尋下如何降低緩存交互過程的性能損耗、如何壓縮緩存的存儲空間占用、如何保證多個操作命令原子性等問題的解決策略,讓緩存在項目中可以發揮出更佳的效果。

通過BitMap降低Reids存儲容量壓力

在一些互聯網類的項目中,經常會有一些簽到相關功能。如果使用Redis來緩存用戶的簽到信息,我們一般而言會怎麼存儲呢?常見的會有下麵2種思路:

  1. 使用Set類型,每天生層1個Set,然後將簽到用戶添加到對應的Set中;
  2. 還是使用Set類型,每個用戶一個Set,然後將簽到的日期添加到Set中。

對於海量用戶的系統而言,按照上述的策略,那麼每天僅簽到信息這一項,就可能會有上千萬的記錄,一年累積下來的數據量更大 —— 這對Redis的存儲而言是筆不小的開銷。對於簽到這種簡單場景,只有簽到和沒簽到兩種情況,也即0/1的場景,我們也可以通過BitMap來進行存儲以大大降低記憶體占用。

BitMap(點陣圖)可以理解為一個bit數組,對應bit位可以存放0或者1,最終這個bit數組被轉換為一個字元串的形式存儲在Redis中。比如簽到這個場景,我們可以每天設定一個key,然後存儲的時候,我們可以將數字格式的userId表示在BitMap中具體的位置信息,而BitMap中此位置對應的bit值為1則表示該用戶已簽到。

Redis其實也提供了對BitMap存儲的支持。前面我們提過Redis支持String、Set、List、ZSet、Hash等數據結構,而BitMap能力的支持,其實是對String數據結構的一種擴展,使用String數據類型來支持BitMap的能力實現。比如下麵的代碼邏輯:

public void userSignIn(long userId) {
    String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    String redisKey = "UserSginIn_" + today;
    Boolean hasSigned = stringRedisTemplate.opsForValue().getBit(redisKey, userId);
    if (Boolean.TRUE.equals(hasSigned)) {
        System.out.println("今日已簽過到!");
    } else {
        stringRedisTemplate.opsForValue().setBit("TodayUserSign", userId, true);
        System.out.println("簽到成功!");
    }
}

對於Redis而言,每天就只有一條key-value數據。下麵對比下使用BitMap與使用普通key-value模式的數據占用情況對比。模擬構造10億用戶數據量進行壓測統計,結果如下:

  • BitMap格式: 150M
  • key-value格式: 41G

可以看出,在存儲容量占用方面,BitMap完勝。

關於pipeline管道批處理與multi事務原子性

使用Pipeline降低與Reids的IO交互頻率

在很多的業務場景中,我們可能會涉及到同時去執行好多條redis命令的操作,比如系統啟動的時候需要將DB中存量的數據全部載入到Redis中重建緩存的時候。如果業務流程需要頻繁的與Redis交互並提交命令,可能會導致在網路IO交互層面消耗太大,導致整體的性能降低。

這種情況下,可以使用pipeline將各個具體的請求分批次提交到Redis伺服器進行處理。

private void redisPipelineInsert() {
    stringRedisTemplate.executePipelined(new SessionCallback() {
        @Override
        public Object execute(RedisOperations operations) throws DataAccessException {
            try {
                // 具體的redis操作,多條操作都在此處理,最後會一起提交到Redis遠端去執行
            } catch (Exception e) {
                log.error("failed to execute pipelined...", e);
            }
            return null;
        }
    });
}

使用pipeline的方式,可以減少客戶端與redis服務端之間的網路交互頻次,但是pipeline也只是負責將原本需要多次網路交互的請求封裝一起提交到redis上,在redis層面其執行命令的時候依舊是逐個去執行,並不會保證這一批次的所有請求一定是連貫被執行,其中可能會被插入其餘的執行請求。

也就是說,pipeline的操作是不具備原子性的。

使用multi實現請求的事務

前面介紹pipeline的時候強調了其僅僅只是將多個命令打包一起提交給了伺服器,然後伺服器依舊是等同於逐個提交上來的策略進行處理,無法保證原子性。對於一些需要保證多個操作命令原子性的場景下,可以使用multi來實現。

當客戶端請求執行了multi命令之後,也即開啟了事務,服務端會將這個客戶端記錄為一個特殊的狀態,之後這個客戶端發送到伺服器上的命令,都會被臨時緩存起來而不會執行。只有當收到此客戶端發送exec命令的時候,redis才會將緩存的所有命令一起逐條的執行並且保證這一批命令被按照發送的順序執行、執行期間不會被其他命令插入打斷。

代碼示例如下:

private void redisMulti() {
    stringRedisTemplate.multi();
    stringRedisTemplate.opsForValue().set("key1", "value1");
    stringRedisTemplate.opsForValue().set("key2", "value2");
    stringRedisTemplate.exec();
}

需要註意的一點是,redis的事務與關係型資料庫中的事務是兩個不同概念,Redis的事務不支持回滾,只能算是Redis中的一種特殊標記,可以將這個事務範圍內的請求以指定的順序執行,中間不會被插入其餘的請求,可以保證多個命令執行的原子性。

pipeline與multi區別

從上面分別對pipelinemulti的介紹,可以看出兩者在定位與功能分工上的差異點:

  • pipeline是客戶端行為,只是負責將客戶端的多個請求一次性打包傳遞到伺服器端,服務端依舊是按照和單條請求一樣的處理,批量傳遞到服務端的請求之間可能會插入別的客戶端的請求操作,所以它是無法保證原子性的,側重點在於其可以提升客戶端的效率(降低頻繁的網路交互損耗)

  • multi是服務端行為,通過開啟事務緩存,保證客戶端在事務期間提交的請求可以被一起集中執行。它的側重點是保證多條請求的原子性,執行期間不會被插入其餘客戶端的請求,但是由於開啟事務以及命令緩存等額外的操作,其對性能略微有一些影響。

多級緩存機制

本地+遠端的二級緩存機制

在涉及與集中式緩存之間頻繁交互的時候,通過前面介紹的pipeline方式可以適當的降低與服務端之間網路交互的頻次,但是很多情況下,依舊會產生大量的網路交互,對於一些追求極致性能的系統而言,可能依舊無法滿足訴求。

回想下此前文章中花費大量篇幅介紹的本地緩存,本地緩存在分散式場景下容易造成數據不一致的問題,但是其最大特點就是快,因為數據都存儲在進程內。所以可以將本地緩存作為集中式緩存的一個補充策略,對於一些需要高頻讀取且不會經常變更的數據,緩存到本地進行使用。

常見的本地+遠端二級緩存有兩種存在形式。

  • 獨立劃分,各司其職

這種情況,將緩存數據分為了2種類型,一種是不常變更的數據,比如系統配置信息等,這種數據直接系統啟動的時候從DB中載入並緩存到進程記憶體中,然後業務運行過程中需要使用時候直接從記憶體讀取。而對於其他可能會經常變更的業務層面的數據,則緩存到Redis中。

  • 混合存儲,多級緩存

這種情況可以搭配Caffeine或者Ehcache等本地緩存框架一起實現。首先去本地緩存中執行查詢,如果查詢到則返回,查詢不到則去Redis中嘗試獲取。如果Redis中也獲取不到,則可以考慮去DB中進行回源兜底操作,然後將回源的結果存儲到Redis以及本地緩存中。這種情況下需要註意下如果數據發生變更的時候,需要刪除本地緩存,以確保下一次請求的時候,可以再次去Redis拉取最新的數據。

本地+遠端的二級緩存機制有著多方面的優點:

  • 主要操作都在本地進行,可以充分的享受到本地緩存的速度優勢

  • 大部分操作都在本地進行,充分降低了客戶端與遠端集中式緩存伺服器之間的IO交互,也降低了帶寬占用

  • 通過本地緩存層,抵擋了大部分的業務請求,對集中式緩存伺服器端進行減壓,大大降低服務端的壓力

  • 提升了業務的可靠性,本地緩存實際上也是一種額外的副本備份,極端情況下,及時集中式緩存的服務端宕機,因為本地還有緩存數據,所以業務節點依舊可以對外提供正常服務。

二級緩存的應用身影

其實,在C-S架構的系統裡面,多級緩存的概念使用的也非常的頻繁。經常Clinet端會緩存運行時需要的業務數據,然後採用定期更新或者事件觸發的方式從服務端更新本地的數據。而Server端負責存儲所有的數據,並保證數據更新的時候可以提供給客戶端進行更新獲取。

一個典型的例子,就是分散式系統中的配置中心或者是服務註冊管理中心。比如SpringCloud家族的Eureka,或者是Alibaba開源的Nacos。它們都有採用客戶端本地緩存+服務端數據統一存儲的方式,來保證整體的處理效率,降低客戶端對於Server端的實時交互依賴。

看一下Nacos的交互示意:

從圖中可以表直觀的看到,Client將業務數據緩存到各自本地,這樣業務邏輯進行處理的時候就可以直接從本地緩存中查詢到相關的業務節點映射信息,而Server端只需要負責在數據有變更的事後推送到Client端更新到本地緩存中即可,避免了Server端去承載業務請求的流量壓力。整體的可靠性也得到了保證,避免了Server端異常對業務正常處理造成影響。

小結回顧

好啦,到這裡呢,《深入理解緩存原理與實戰設計》系列專欄的內容就暫告一段落咯。本專欄圍繞緩存這個巨集大命題進行展開闡述,從緩存各種核心要素、到本地緩存的規範與標準介紹,從手寫本地緩存框架、到各種優秀本地緩存框架的上手與剖析,從本地緩存到集中式緩存再到最後的多級緩存的構建,一步步全方位、系統性地做了介紹。希望通過本專欄的介紹,可以讓大家對緩存有個更加深刻的理解,可以更好的在項目中去使用緩存,讓緩存真正的成為我們項目中性能提升的神兵利器

看到這裡,不知道各位小伙伴們對緩存的理解與使用,是否有了新的認識了呢?你覺得緩存還有哪些好的使用場景呢?歡迎評論區一起交流下,期待和各位小伙伴們一起切磋、共同成長。

我是悟道,聊技術、又不僅僅聊技術~

如果覺得有用,請點贊 + 關註讓我感受到您的支持。也可以關註下我的公眾號【架構悟道】,獲取更及時的更新。

期待與你一起探討,一起成長為更好的自己。

本文來自博客園,作者:架構悟道,歡迎關註公眾號[架構悟道]持續獲取更多乾貨,轉載請註明原文鏈接:https://www.cnblogs.com/softwarearch/p/16937368.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2023-01-17 一、Spring管理druid步驟 (1)導入jar包 <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <arti ...
  • 抽獎程式 ''' 抽獎程式 使用時可以修改嘉賓名單,然後單機‘開始’和‘停止’按鈕 來控制界面上名單的滾動實現抽獎功能,涉及的模塊主要 有多線程 ''' import itertools import random import threading import time import tkinte ...
  • 摘要:本文主要講解圖像局部直方圖均衡化和自動色彩均衡化處理。這些演算法可以廣泛應用於圖像增強、圖像去噪、圖像去霧等領域。 本文分享自華為雲社區《[Python從零到壹] 五十四.圖像增強及運算篇之局部直方圖均衡化和自動色彩均衡化處理》,作者: eastmount。 一.局部直方圖均衡化 前文通過調用O ...
  • 簡介 限流顧名思義是對流量大小進行限制,防止請求數量超過系統的負載能力,導致系統崩潰,起到保護作用。 現實生活中限流也隨處可見,節假日出門旅行的人數會劇增,對於旅游景點來說往往會不堪重負,如果不進行人數控制,對整個景點的壓力會非常大,游客的體驗也會非常差,還容易出現安全事故等危險。 同樣的在一線城市 ...
  • 轉載:https://blog.csdn.net/tslx1020/article/details/128250777 1、spawn - 冷啟動 frida-trace -U -f com.apple.ExampleCode -m “+[NSURL URLWithString:]" 2、attac ...
  • 伺服器信息 在阿裡雲買了個搶占式的伺服器,地區為華南廣州,系統為Ubuntu 20.04,8核16GB。 安裝Docker 命令如下: $ apt-get update -y $ apt-get upgrade -y $ apt-get install -y docker.io 安裝成功後,檢查一下 ...
  • 2023-01-14 一、Spring底層IOC實現 1、IOC:將對象的控制器反轉給Spring 2、BeanFactory與ApplicationContext (1)BeanFactory:IOC容器的基本實現,是Spring內部的使用介面,是面向Spring本身的,不是提供給開發人員使用的。 ...
  • 牛牛剛剛出生,嗷嗷待哺,一開始他只能學說簡單的數字,你跟他說一個整數,他立刻就能學會。輸入一個整數,輸出這個整數。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...