Redis分散式緩存系列(七)- ServiceStack破解、布隆過濾器、緩存雪崩、緩存穿透、緩存擊穿、緩存預熱

来源:https://www.cnblogs.com/xyh9039/archive/2023/03/23/17209010.html
-Advertisement-
Play Games

本系列將和大家分享Redis分散式緩存,本章主要簡單介紹下Redis中的布隆過濾器(Bloom Filter),以及如何破解ServiceStack和如何解決緩存雪崩、緩存穿透、緩存擊穿、緩存預熱問題。 ...


本系列將和大家分享Redis分散式緩存,本章主要簡單介紹下Redis中的布隆過濾器(Bloom Filter),以及如何破解ServiceStack和如何解決緩存雪崩、緩存穿透、緩存擊穿、緩存預熱問題。話不多說,下麵我們直接進入主題。

一、ServiceStack破解

首先我們先來看一下Demo的目錄結構,如下所示:

第一種方式:我們通過 NuGet 安裝 ServiceStack 相關的程式包。

然後在 MyRedis 控制台項目中運行如下測試代碼:

/// <summary>
/// 模擬拋出LicenseException異常
/// </summary>
public static void ThrowLicenseException()
{
    //模擬1小時內超過6000次請求
    Parallel.For(0, 10000, (i) =>
    {
        using (RedisStringService service = new RedisStringService())
        {
            service.Set("TianYa" + i, i);
            service.IncrementValue("TianYa" + i);
            Console.WriteLine(i);
        }
    });
}
using System;
using MyRedis.Scene;

namespace MyRedis
{
    /// <summary>
    /// Redis:Remote Dictionary Server 遠程字典伺服器
    /// 基於記憶體管理(數據存在記憶體),實現了5種數據結構(分別應對各種具體需求),單線程模型的應用程式(單進程單線程),對外提供插入--查詢--固化--集群功能。
    /// 正是因為基於記憶體管理所以速度快,可以用來提升性能。但是不能當資料庫,不能作為數據的最終依據。
    /// 單線程多進程的模式來提供集群服務。
    /// 單線程最大的好處就是原子性操作,就是要麼都成功,要麼都失敗,不會出現中間狀態。Redis每個命令都是原子性(因為單線程),不用考慮併發,不會出現中間狀態。(線程安全)
    /// Redis就是為開發而生,會為各種開發需求提供對應的解決方案。
    /// Redis只是為了提升性能,不做數據標準。任何的數據固化都是由資料庫完成的,Redis不能代替資料庫。
    /// Redis實現的5種數據結構:String、Hashtable、Set、ZSet和List。
    /// </summary>
    internal class Program
    {
        static void Main(string[] args)
        {
            //ServiceStackTest.ShowString();
            //OverSellFailedTest.Show();
            //OverSellTest.Show();
            //ServiceStackTest.ShowHash();
            //ServiceStackTest.ShowSet();
            //FriendManager.Show();
            //ServiceStackTest.ShowZSet();
            //RankManager.Show();
            //ServiceStackTest.ShowList();
            //ServiceStackTest.ShowPublishAndSubscribe();
            ServiceStackTest.ThrowLicenseException();
            Console.ReadKey();
        }
    }
}

運行結果如下所示:

可以發現此時會拋出異常,提示每小時只能進行6000次Redis請求。

第二種方式:使用破解好的 ServiceStack 相關的動態鏈接庫。

首先我們先從 GitHub 上下載最新版本的 ServiceStack 源碼,如下所示:

下載完成後打開對應的解決方案,如下所示:

打開後找到 LicenseUtils.cs 類: 

接著找到 LicenseUtils.cs 類中的 ApprovedUsage(...) 方法,修改代碼如下所示:

保存成功後重新生成下 ServiceStack.Text 和 ServiceStack.Redis 這兩個類庫:

重新生成完成後打開 ServiceStack.Redis 類庫的 bin/Debug 目錄,如下所示:

由於Demo中 TianYaSharpCore.ServiceStack 類庫的目標框架為 .NET Standard 2.1 ,故此處要使用 netstandard2.1 文件夾中的動態鏈接庫:

將破解好的這四個動態鏈接庫,複製到Demo的解決方案根目錄下新建的 Lib 文件夾中,如下所示:

然後將Demo中引用 NuGet 的 ServiceStack 相關程式包卸載掉,改成引用本地 Lib 文件夾中的那四個破解好的動態鏈接庫,如下所示:

此外,還需要從 NuGet 上添加 Microsoft.Bcl.Asynclnterfaces 程式包,如下所示:

最後我們重新生成下Demo的解決方案,再次運行 MyRedis 控制台項目,運行結果如下所示:

可以發現,此時不會再拋出異常了,說明我們破解成功了。

二、布隆過濾器

1、場景介紹

1)大數據去重,比如新聞推薦場景,新聞有很多,要不要在app/網站給用戶顯示,看過的顯然不需要顯示,大量的新聞數據面前如何快速去重?

2)註冊用戶,用戶名是否可用?

3)爬蟲程式中大數據量url去重。

4)垃圾郵件/垃圾簡訊去重。

5)用來防止緩存穿透。

2、引子

正式開始介紹布隆過濾器之前,我們先用常規的手段去解決數據去重的問題。

例子1:我們做爬蟲程式,會抓取很多的url地址,我們怎麼知道某個url地址是否爬取過呢?

使用set集合去重,如果數據量大了,有很多的url需要存儲,那麼set集合需要把所有的用過的url地址存下來。假設有1億個url地址,每個url地址有64個字元,那麼存儲這些數據就需要64*8*1億個比特位(這還不考慮本身數據結構需要占用的記憶體),這樣算下來我們就大概需要6個G的記憶體,記憶體占用幾乎是不可忍受的,更別說大型互聯網公司,數據量更大。也就是說用set集合來做大數據判重幾乎不可取。

例子2:使用資料庫判斷某個用戶名是否註冊過?

我們採用MySQL這種帶持久化的資料庫,雖然解決了記憶體問題,但是我們需要跟MySQL伺服器建立連接,MySQL伺服器需要從硬碟讀取判斷,這個時長也是不可忍受的...

總結來說:傳統方式在做大數據去重,要麼有時長的問題,要麼有記憶體占用的問題。那麼我們有沒有一種記憶體占用不那麼大,時長可以忍受的方式呢?布隆過濾器閃亮登場了!

3、布隆過濾器的介紹

布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進位向量和一系列隨機映射函數。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都比一般的演算法要好的多,缺點是有一定的誤判率和刪除困難。

4、過濾器原理

從上面的場景中,可以看出我們的需求很簡單隻是要判斷一下,某條數據是否存在,而不關心這條數據的具體內容,也就是說返回一個bool值即可。而我們電腦中二進位天然的滿足了這個特性,0代表false,1代表true。我們可以在記憶體中申請一段bit數組。(又稱bitmap翻譯為點陣圖)

我們先按照簡單的來,我們判斷的是int數值是否存在,比方說1、3、6、7、13已經存在,那我們可以把對應位置的bit位,置為1,表示存在。如圖: 

這時候如果我們需要判斷3是否存在的話,判斷該位置的bit位是否為1即可。這樣的話比我們聲明一個int類型的數組,挨個存儲,然後判斷,省去了極大的空間,int值占用4個byte位也就是32個bit位,而採用這種bit數組來做,一下子省去了32倍的記憶體空間。

現在我們知道了,int的儲存和判斷方案,我們回歸實際場景,比如判斷一個url是否使用過。一個url是一個字元串,它不是一個int值,如何確定位置呢? 此時hash函數就派上用場了,我們可以將字元串hash出一個int值

如圖所示:

當然上圖給出的是理想情況,而實際上我們很難找到一個足夠好hash函數,使得到的hash值在非常小的範圍內,通常一個好的hash函數算出的hash值足夠隨機且均勻,也就是說會隨機的散列到0-integer.max_value的範圍內,這是一個很大的值。

比方說www.baidu.com在java語言自帶的hash函數得出的值為270263191,如果我們只有1000萬個字元串需要判斷,但是隨便一個字元串的hash值有可能會大於1000萬這個數值,所以聲明1000萬大小的bit位是不夠用的,我們需要給出int最大值的bit位,而這會浪費大量的空間,與布隆過濾器的目標相悖。

這時候我們的取餘法,就可以派上用場了,我們把得出的hash值進行對有限bit位空間(bit數組的長度)進行取餘,得出位置。如圖:

上圖我們可以看出,使用取餘法,我們可以做到在有限的空間上存儲數據,達到了壓縮空間的目的,但是大家可以發現這裡會有一個衝突,有可能取餘後我們的不同數據落到同一個位置,甚至我們的hash函數都有可能發生衝突,也就是不同的字元串算出的hash值是一樣的。而且hash衝突幾乎是不可避免的,這也就導致我們的點陣圖上表示存在是有可能誤判的。也就是布隆過濾器告訴你那個位置有數據了但有可能是別人放進去的,你並沒有放進去。這是不可避免的,但是我們可以通過增加hash函數個數的方法來降低衝突率。如圖:

本例中採用3個不同的hash函數將同一個數據分別映射到點陣圖的三個位置上,在判存的時候,只有當映射的這3個位置的bit值都為1時才表示該數據已存在,否則表示該數據不存在。這樣可以在一定程度上降低誤判率。

5、總結

布隆過濾器採用了點陣圖數據結構,大大減少了記憶體占用,採用hash函數將數據映射到點陣圖上,但是Hash函數本身就有衝突,取模節省空間也會導致衝突率的上升。解決的辦法主要就是增加hash函數的個數和點陣圖大小的上限(即加大bit數組的長度)。

至於要使用多少個hash函數合適以及點陣圖的bit位多少合適沒有非常嚴謹的數學證明,我們這裡就不展開了。

市面上有一些現成的工具包已經給我們封裝好布隆過濾器:比如google的guava包就實現了布隆過濾器。又比如redis4.0後通過插件方式提供了布隆過濾器的實現等等。。。

謹記:布隆過濾器有可能會存在誤判的情況,也就是說,不存在的一定不存在,而存在的不一定會存在。

三、緩存雪崩

概念:緩存雪崩是指當緩存中有大量的key在同一時刻過期,或者Redis直接宕機了,導致大量的查詢請求全部到達資料庫,造成資料庫查詢壓力驟增,甚至直接掛掉。

解決方案:

針對第一種大量key同時過期的情況,解決起來比較簡單,只需要將每個key的過期時間打散即可,使它們的失效點儘可能均勻分佈。例如:可以給過期時間加個過期因數(其實就是個隨機數,比如:在1s~1000s 之間隨機取一個),然後我們的實際過期時間=設置的過期時間+過期因數。

針對第二種redis發生故障的情況,部署redis時可以使用redis的幾種高可用方案部署(主從複製模式、哨兵模式、集群模式)。

除了上面兩種解決方式,還可以使用其他策略,比如設置key永不過期、加分散式鎖等。

四、緩存穿透

概念:緩存穿透是指查詢一個緩存中和資料庫中都不存在的數據,導致每次查詢這條數據都會透過緩存,直接查庫,最後返回空。當用戶使用這種不存在的數據瘋狂發起查詢請求的時候,對資料庫造成的壓力就非常大,甚至可能直接掛掉。

可能原因:惡意破壞。

解決方案:

解決緩存穿透的方法一般有兩種,第一種是緩存空對象,第二種是使用布隆過濾器。

第一種方法比較好理解,就是當資料庫中查不到數據的時候,我緩存一個空對象,然後給這個空對象的緩存設置一個過期時間,這樣下次再查詢該數據的時候,就可以直接從緩存中拿到,從而達到了減小資料庫壓力的目的。但這種解決方式有兩個缺點:(1)需要緩存層提供更多的記憶體空間來緩存這些空對象,當這種空對象很多的時候,就會浪費更多的記憶體;(2)會導致緩存層和存儲層的數據不一致,即使在緩存空對象時給它設置了一個很短的過期時間,那也會導致這一段時間內的數據不一致問題。

第二種方案是使用布隆過濾器,這是比較推薦的方法。布隆過濾器可以用於檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都比一般的演算法要好的多,缺點是有一定的誤判率和刪除困難。在初始化布隆過濾器時,需要將所有的key保存到布隆過濾器中,布隆過濾器就相當於一個位於客戶端與緩存層中間的攔截器一樣,負責判斷key是否在集合中存在。如下圖:

五、緩存擊穿

概念:緩存擊穿是指當緩存中某個熱點數據過期了,在該熱點數據重新載入緩存之前,有大量的查詢請求穿過緩存,直接查詢資料庫。這種情況會導致資料庫壓力瞬間驟增,造成大量請求阻塞,甚至直接掛掉。

解決方案:

解決緩存擊穿的方法也有兩種,第一種是設置key永不過期;第二種是使用分散式鎖,保證同一時刻只能有一個查詢請求重新載入熱點數據到緩存中,這樣,其他的線程只需等待該線程運行完畢,即可重新從Redis中獲取數據。

第一種方式比較簡單,在設置熱點key的時候,不給key設置過期時間即可。不過還有另外一種方式也可以達到讓key不過期的目的,就是給key設置過期時間的時候,也要同時的在後臺啟一個定時任務去定時地更新這個緩存。

第二種方法使用加鎖的方式,鎖的對象就是key,這樣,當大量查詢同一個key的請求併發進來時,只能有一個請求獲取到鎖,獲取到鎖的線程查詢資料庫,然後將結果放入到緩存中,接著釋放鎖,此時,其他處於鎖等待的請求即可繼續執行,由於此時緩存中已經有了數據,所以可以直接從緩存中獲取到數據返回,並不會查詢資料庫。

六、緩存預熱

概念: 當系統上線時,緩存內還沒有數據,如果直接提供給用戶使用,每個請求都會穿過緩存去訪問底層資料庫,如果併發量大的話,很有可能在上線當天就會宕機,這種情況就叫“系統冷啟動”。而緩存預熱就是指系統上線後,提前將相關的緩存數據直接載入到緩存系統中。避免在用戶請求的時候,先查詢資料庫,然後再將數據緩存的問題,用戶直接查詢事先被預熱的緩存數據。通常來講,系統中一些經常使用的數據,一些高併發熱點數據,可以先預熱到緩存中;對於一些請求比較耗時的數據,也可以事先預熱到緩存中。

解決方案:

1、直接寫個緩存刷新頁面,上線時手工操作下;

2、數據量不大,可以在項目啟動的時候自動進行載入;

3、定時刷新緩存,緩存可能會過期,寫一個程式,時不時把數據預熱一下,往Redis裡面寫數據。

 

部分內容參考文獻:https://baijiahao.baidu.com/s?id=1730541502423010481&wfr=spider&for=pc

Demo源碼:

鏈接:https://pan.baidu.com/s/1RreI5HzAg-tW4WvWstm8ig 
提取碼:cm75

此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/17209010.html

版權聲明:如有雷同純屬巧合,如有侵權請及時聯繫本人修改,謝謝!!!


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

-Advertisement-
Play Games
更多相關文章
  • 最近工作中經常使用Teamcenter、NX集成開發的情況,因此在這裡記錄UF_UGMGR函數的使用。使用UF_UGMGR相關函數需要有Teamcenter使用經驗,理解Teamcenter中文件夾、偽文件夾、零組件、零組件版本、數據集、關係、表單、命名引用等對象。 相關的可以看幫助中 Teamce ...
  • 本篇文章將深入介紹 Yarn 三種調度器。Yarn 本身作為資源管理和調度服務,其中的資源調度模塊更是重中之重。下麵將介紹 Yarn 中實現的調度器功能,以及內部執行邏輯。 ...
  • 本文實現一個Echo TCP Server 完整代碼:https://github.com/csgopher/go-redis interface/tcp/Handler.go type Handler interface { Handle(ctx context.Context, conn net ...
  • 繼上一個博客中生成 簽章圖片後,今日遇到一個問題, 本地不管如何改代碼,都會將簽名文字顯示出來。 但是.... ....... 一旦部署在linux系統後,一直列印不出來,,糾結的呀。。 完全沒想到, 原來是linux系統裡面不相容本地的字體,也就是沒有那麼多中文字體,除非安裝。 可以驚醒安裝字體: ...
  • 在java打演算法題的時候,Scanner類、Sout的速度太慢,所以要用PrintWriter和BufferReader&StreamTokenizer類來進行快速輸入。代碼如下: import java.io.*; public class Main { public static PrintWr ...
  • 記憶體重疊是指在記憶體中存在兩個或多個區域,它們的地址範圍有交叉部分。在 C++ 中,記憶體重疊可能會導致程式出現不可預期的行為,因此我們需要瞭解它的原因和如何避免。 記憶體重疊的原因 記憶體重疊的主要原因是指針的使用。當我們使用指針訪問記憶體時,如果指針指向的記憶體區域與另一個區域有交叉部分,就會產生記憶體重疊。 ...
  • Mybatis高級特性能夠幫助我們更加靈活地操作資料庫,包括動態SQL、緩存機制、插件機制、自定義類型轉換等。學習這些特性可以讓我們更好地利用Mybatis,提高數據操作的效率和質量。 未來的道路由自己抉擇,事業的高度由自己決定。 動態SQL 動態SQL中,Mybatis提供了多種標簽來幫助我們構建 ...
  • 一、問題引入 在學習棧的過程中,教材有一個案例:利用棧結果解析括弧的匹配問題。括弧問題:[({}{})],說明 [] 、() 、{} 稱為一對。 號碼位置對應的括弧之間進行匹配,結果:0-7、 1-6、 2-3、 4-5 二、過程記錄 💡 基於順序棧實現 利用棧的特性:先進後出 ,對括弧進行匹配輸 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...