1. 程式人生 > >快取優化技術

快取優化技術

一、問題由來
首先說一下處理場景,圓通2017年全年營收199.82億,2017年全年業務完成量為50.64億件,這裡的每一件快遞都對應了1個快遞單號以及詳細的物流資訊,假如1個快遞單號就對應有10條物流資訊,那麼單單是這物流資訊就有500多億條,這個資料量是巨大的,那麼如果從這些海量的資料中查詢一條訂單資訊呢。”
這裡寫圖片描述
50億的訂單號大概是多少G的資料呢?單單訂單號
訂單號字串:889795292077396186—->18個位元組 * 10億 =90G
如果用Map去儲存的話就需要 90 * 2 = 180G //因為Map的有效儲存空間為50%

二、初步解決方案
這裡寫圖片描述
初步解決方案是在我們的應用與資料庫之間加上一個redis叢集,從原本的應用直接訪問資料庫去查詢資料,到現在的先在快取中查詢,要是在快取中查詢不到再去資料庫中查詢,這樣資料庫的吞吐量和併發量將至少提高100倍以上。我們可以把“仍未確定收貨”或者“某個時間截點之後(熱度高)”的訂單資訊儲存在redis叢集中,把那些時間久遠的訂單存放在資料庫中(因為那些時間久遠的物流資訊使用者一般很少會再做查詢),而redis的儲存是把資料存在記憶體中的,它的查詢是走記憶體的,查詢效率較高,查詢資料庫是要走io的,所以較慢。因此可以把“仍未確定收貨”這類查詢的可能性以及頻率較高的訂單資料存在redis中。當從redis中查詢不到再去資料庫中查詢。

對這種初步解決方案提出的思考(資料庫與快取之間的資訊同步這篇文章先不討論):假如有惡意使用者通過我們提供的訂單查詢介面,查詢一些根本就不存在的訂單號,因為訂單號是不存在的,所以在redis快取中自然是找不到,那麼最終還是到資料庫中去查詢(這就是所謂的快取穿透)。當惡意使用者通過執行緒或指令碼等方式頻繁去訪問我們的資料庫,而進行的查詢有是在這種海量資料中進行查詢,那麼無疑是會佔用大量的資料庫連線數,最終導致其他使用者訪問報錯,超過了資料庫的最大連線數。

初步解決方案的優化思路: 在redis之前加一層過濾,先在海量訂單號中快速判斷是否存在該訂單號,如果存在再去redis中查詢,如果redis中找不到就去資料庫中查詢

那麼怎樣快速判斷一個數據是否存在於海量資料中呢?

這裡寫圖片描述

在“布隆過濾器”的簡介中最後由一句話“它實際上是一個很長的二進位制向量和一系列隨機對映函式”,這句話有兩個關鍵詞“二進位制向量”和“一系列隨機函式”,大家先記住這兩個詞,以下將跟大家從原始碼的角度分析一下布隆過濾器的實現原理。
這裡寫圖片描述
這個圖的意思是,使用布隆過濾器,它會把你要存的資料(上圖已存在的三個物流單行),每個物流單號分別呼叫三個隨機對映函式,經過隨機對映函式中的演算法計算從而轉化成二進位制向量來儲存(把0改成1)。查詢資料存不存在該布隆過濾器中的原理是:同樣依次呼叫這幾個隨機對映函式,當呼叫到其中的某一個函式轉化成二進位制時,如果發現為0,則為不存在,不一定要把全部對映函式都執行完才能得出結論。

對布隆過濾器的思考:上圖的二進位制向量的長度位24,那麼當資料量不是3個物流單號,而是海量資料時,那麼二進位制向量上的豈不是全是1?這就是布隆過濾器存在誤判的根本原因。

<!--guava框架實現了布隆過濾器,注意的是18.0版本以上才提供了布隆過濾器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>18.0</version>
        </dependency>

這裡寫圖片描述

import java.nio.charset.Charset;

/**
 * @auther xiehuaxin
 * @create 2018-08-14 18:18
 * @todo
 */
public class Test {
    public static void main(String[] args) {
        int insertions = 1000000;
        //建立一個用於存放字串型別資料的布隆過濾器,初始化大小100w,誤判率是0.01
        BloomFilter bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),insertions,0.01);

        //往布隆過濾器中新增資料
        bloomFilter.put("data11111");

        //判斷布隆過濾器中是否存在字串“ABC”
        if(bloomFilter.mightContain("data11111")) {
            System.out.println("The bloomFilter is contain this data.");
        }else {
            System.out.println("The bloomFilter is not contain 'ABC'");
        }
    }
}

優化後的解決方案
布隆過濾器裡查詢不到的資料那肯定是不存在的,布隆過濾器存在的資料也不一定存在,因為它有誤判率
這裡寫圖片描述