探討下如何更好的使用緩存 —— 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
  • 前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 本文介紹基於Python語言中TensorFlow的Keras介面,實現深度神經網路回歸的方法。 1 寫在前面 前期一篇文章Python TensorFlow深度學習回歸代碼:DNNRegressor詳細介紹了基於TensorFlow tf.estimator介面的深度學習網路;而在TensorFl ...
  • 前段時間因業務需要完成了一個工作流組件的編碼工作。藉著這個機會跟大家分享一下整個創作過程,希望大家喜歡,組件暫且命名為"easyFlowable"。 接下來的文章我將從什麼是工作流、為什麼要自研這個工作流組件、架構設計三個維度跟大家來做個整體介紹。 ...
  • 1 簡介 我們之前使用了dapr的本地托管模式,但在生產中我們一般使用Kubernetes托管,本文介紹如何在GKE(GCP Kubernetes)安裝dapr。 相關文章: dapr本地托管的服務調用體驗與Java SDK的Spring Boot整合 dapr入門與本地托管模式嘗試 2 安裝GKE ...
  • 摘要:在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持預設即可。 本文分享自華為雲社區《為什麼需要對jvm進行優化,jvm運行參數之標準參數》,作者:共飲一杯無。 我們為什麼要對jvm做優化? 在本地開發環境中我們很少會遇到需要對jvm進行優化的需 ...
  • 背景 我們的業務共使用11台(阿裡雲)伺服器,使用SpringcloudAlibaba構建微服務集群,共計60個微服務,全部註冊在同一個Nacos集群 流量轉發路徑: nginx->spring-gateway->業務微服務 使用的版本如下: spring-boot.version:2.2.5.RE ...
  • 基於php+webuploader的大文件分片上傳,帶進度條,支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況)。文件上傳前先檢測該文件是否已上傳,如果已上傳提示“文件已存在”,如果未上傳則直接上傳。視頻上傳時會根據設定的參數(分片大小、分片數量)進行上傳,上傳過程中會在目標文件夾中生成一個臨 ...
  • 基於php大文件分片上傳至七牛雲,使用的是七牛雲js-sdk V2版本,引入js文件,配置簡單,可以暫停,暫停後支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況),可以配置分片大小和分片數量,官方文檔https://developer.qiniu.com/kodo/6889/javascrip ...