欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis精確去重計數(shù)方法(咆哮位圖)

 更新時間:2019年06月04日 10:34:06   作者:老錢  
這篇文章主要給大家介紹了關(guān)于Redis精確去重計數(shù)方法(咆哮位圖)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Redis具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

前言

如果要統(tǒng)計一篇文章的閱讀量,可以直接使用 Redis 的 incr 指令來完成。如果要求閱讀量必須按用戶去重,那就可以使用 set 來記錄閱讀了這篇文章的所有用戶 id,獲取 set 集合的長度就是去重閱讀量。但是如果爆款文章閱讀量太大,set 會浪費太多存儲空間。這時候我們就要使用 Redis 提供的 HyperLogLog 數(shù)據(jù)結(jié)構(gòu)來代替 set,它只會占用最多 12k 的存儲空間就可以完成海量的去重統(tǒng)計。但是它犧牲了準確度,它是模糊計數(shù),誤差率約為 0.81%。

那么有沒有一種不怎么浪費空間的精確計數(shù)方法呢?我們首先想到的就是位圖,可以使用位圖的一個位來表示一個用戶id。如果一個用戶id是32字節(jié),那么使用位圖就只需要占用 1/256 的空間就可以完成精確計數(shù)。但是如何將用戶id映射到位圖的位置呢?如果用戶id是連續(xù)的整數(shù)這很好辦,但是通常用戶系統(tǒng)的用戶id并不是整數(shù),而是字符串或者是有一定隨機性的大整數(shù)。

我們可以強行給每個用戶id賦予一個整數(shù)序列,然后將用戶id和整數(shù)的對應(yīng)關(guān)系存在redis中。

$next_user_id = incr user_id_seq
set user_id_xxx $next_user_id
$next_user_id = incr user_id_seq
set user_id_yyy $next_user_id
$next_user_id = incr user_id_seq
set user_id_zzz $next_user_id

這里你也許會提出疑問,你說是為了節(jié)省空間,這里存儲用戶id和整數(shù)的映射關(guān)系就不浪費空間了么?這個問題提的很好,但是同時我們也要看到這個映射關(guān)系是可以復用的,它可以統(tǒng)計所有文章的閱讀量,還可以統(tǒng)計簽到用戶的日活、月活,還可以用在很多其它的需要用戶去重的統(tǒng)計場合中。所謂「功在當代,利在千秋」就是這個意思。

有了這個映射關(guān)系,我們就很容易構(gòu)造出每一篇文章的閱讀打點位圖,來一個用戶,就將相應(yīng)位圖中相應(yīng)的位置為一。如果位從0變成1,那么就可以給閱讀數(shù)加1。這樣就可以很方便的獲得文章的閱讀數(shù)。

而且我們還可以動態(tài)計算閱讀了兩篇文章的公共用戶量有多少?將兩個位圖做一下 AND 計算,然后統(tǒng)計位圖中位 1 的個數(shù)。同樣,還可以有 OR 計算、XOR 計算等等都是可行的。

問題又來了!Redis 的位圖是密集位圖,什么意思呢?如果有一個很大的位圖,它只有最后一個位是 1,其它都是零,這個位圖還是會占用全部的內(nèi)存空間,這就不是一般的浪費了。你可以想象大部分文章的閱讀量都不大,但是它們的占用空間卻是很接近的,和哪些爆款文章占據(jù)的內(nèi)存差不多。

看來這個方案行不通,我們需要想想其它方案!這時咆哮位圖(RoaringBitmap)來了。

它將整個大位圖進行了分塊,如果整個塊都是零,那么這整個塊就不用存了。但是如果位1比較分散,每個塊里面都有1,雖然單個塊里的1很少,這樣只進行分塊還是不夠的,那該怎么辦呢?我們再想想,對于單個塊,是不是可以繼續(xù)優(yōu)化?如果單個塊內(nèi)部位 1 個數(shù)量很少,我們可以只存儲所有位1的塊內(nèi)偏移量(整數(shù)),也就是存一個整數(shù)列表,那么塊內(nèi)的存儲也可以降下來。這就是單個塊位圖的稀疏存儲形式 —— 存儲偏移量整數(shù)列表。只有單塊內(nèi)的位1超過了一個閾值,才會一次性將稀疏存儲轉(zhuǎn)換為密集存儲。

咆哮位圖除了可以大幅節(jié)約空間之外,還會降低 AND、OR 等位運算的計算效率。以前需要計算整個位圖,現(xiàn)在只需要計算部分塊。如果塊內(nèi)非常稀疏,那么只需要對這些小整數(shù)列表進行集合的 AND、OR 運算,如是計算量還能繼續(xù)減輕。

這里既不是用空間換時間,也沒有用時間換空間,而是用邏輯的復雜度同時換取了空間和時間。

咆哮位圖的位長最大為 2^32,對應(yīng)的空間為 512M(普通位圖),位偏移被分割成高 16 位和低 16 位,高 16 位表示塊偏移,低16位表示塊內(nèi)位置,單個塊可以表達 64k 的位長,也就是 8K 字節(jié)。最多會有64k個塊。現(xiàn)代處理器的 L1 緩存普遍要大于 8K,這樣可以保證單個塊都可以全部放入 L1 Cache,可以顯著提升性能。

如果單個塊所有的位全是零,那么它就不需要存儲。具體某個塊是否存在也可以是用位圖來表達,當塊很少時,用整數(shù)列表表示,當塊多了就可以轉(zhuǎn)換成普通位圖。整數(shù)列表占用的空間少,它還有類似于 ArrayList 的動態(tài)擴容機制避免反復擴容復制數(shù)組內(nèi)容。當列表中的數(shù)字超出4096個時,會立即轉(zhuǎn)變成普通位圖。

用來表達塊是否存在的數(shù)據(jù)結(jié)構(gòu)和表達單個塊數(shù)據(jù)的結(jié)構(gòu)可以是同一個,因為塊是否存在本質(zhì)上也是 0 和 1,就是普通的位標志。

但是 Redis 并沒有原生支持咆哮位圖這個數(shù)據(jù)結(jié)構(gòu)?。课覀冊撊绾问褂媚??

Redis 確實沒有原生的,但是咆哮位圖的 Redis Module 有。

github.com/aviggiano/r

這個項目的 star 數(shù)量并不是很多,我們來看看它的官方性能對比

OP TIME/OP (us) ST.DEV. (us)
R.SETBIT 31.89 28.49
SETBIT 29.98 29.23
R.GETBIT 29.90 14.60
GETBIT 28.63 14.58
R.BITCOUNT 32.13 0.10
BITCOUNT 192.38 0.96
R.BITPOS 70.27 0.14
BITPOS 87.70 0.62
R.BITOP NOT 156.66 3.15
BITOP NOT 364.46 5.62
R.BITOP AND 81.56 0.48
BITOP AND 492.97 8.32
R.BITOP OR 107.03 2.44
BITOP OR 461.68 8.42
R.BITOP XOR 69.07 2.82
BITOP XOR 440.75 7.90

很明顯這里對比的是稀疏位圖,只有稀疏位圖才可以呈現(xiàn)出這樣好看的數(shù)字。如果是密集位圖,咆哮位圖的性能肯定要稍弱于普通位圖,但是通常也不會弱太多。

下面我們來觀察一下源代碼看看它的內(nèi)部結(jié)構(gòu)是怎樣的

// 單個塊
typedef struct roaring_array_s {
 int32_t size;
 int32_t allocation_size;
 void **containers; // 指向整數(shù)數(shù)組或者普通位圖
 uint16_t *keys;
 uint8_t *typecodes;
 uint8_t flags;
} roaring_array_t;

// 所有塊
typedef struct roaring_bitmap_s {
 roaring_array_t high_low_container;
} roaring_bitmap_t;

很明顯可以看到塊存在與否和塊內(nèi)數(shù)據(jù)都是使用同樣的數(shù)據(jù)結(jié)構(gòu)表達的,它們都是 roaring_bitmap_t。這個結(jié)構(gòu)里面有多種編碼形式,類型使用 typecodes 字段來表示。

#define BITSET_CONTAINER_TYPE_CODE 1
#define ARRAY_CONTAINER_TYPE_CODE 2
#define RUN_CONTAINER_TYPE_CODE 3
#define SHARED_CONTAINER_TYPE_CODE 4

看到這里的類型定義,我們發(fā)現(xiàn)它不止前面提到的普通位圖和數(shù)組列表兩種形式,還有 RUN 和 SHARED 這兩種類型。RUN 形式是位圖的壓縮形式,比如連續(xù)的幾個位 101,102,103,104,105,106,107,108,109 表示成 RUN 后就是 101,8(1 后面是 8 個自增的整數(shù)),這樣在空間上就可以明顯壓縮不少。在正常情況下咆哮位圖內(nèi)部沒有 RUN 類型的塊。只有顯示調(diào)用了咆哮位圖的優(yōu)化 API 才會轉(zhuǎn)換成 RUN 格式,這個 API 是 roaring_bitmap_run_optimize。

而 SHARED 類型用于在多個咆哮位圖之間共享塊,它還提供了寫復制功能。當這個塊被修改時將會復制出新的一份。
咆哮位圖的計算邏輯還有更多的細節(jié),我們后面有空再繼續(xù)介紹。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • 編譯安裝redisd的方法示例詳解

    編譯安裝redisd的方法示例詳解

    這篇文章主要介紹了編譯安裝redisd的方法示例詳解,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • 關(guān)于redis狀態(tài)監(jiān)控和性能調(diào)優(yōu)詳解

    關(guān)于redis狀態(tài)監(jiān)控和性能調(diào)優(yōu)詳解

    Redis是一種高級key-value數(shù)據(jù)庫。它跟memcached類似,不過數(shù)據(jù)可以持久化,而且支持的數(shù)據(jù)類型很豐富。有字符串,鏈表、哈希、集合和有序集合5種。下面這篇文章主要給大家介紹了關(guān)于redis狀態(tài)監(jiān)控和性能調(diào)優(yōu)的相關(guān)資料,需要的朋友可以參考下。
    2017-09-09
  • redis使用watch秒殺搶購實現(xiàn)思路

    redis使用watch秒殺搶購實現(xiàn)思路

    這篇文章主要為大家詳細介紹了redis使用watch秒殺搶購的實現(xiàn)思路,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • Redis的使用模式之計數(shù)器模式實例

    Redis的使用模式之計數(shù)器模式實例

    這篇文章主要介紹了Redis的使用模式之計數(shù)器模式實例,本文講解了匯總計數(shù)器、按時間匯總的計數(shù)器、速度控制、使用 Hash 數(shù)據(jù)類型維護大量計數(shù)器等內(nèi)容,需要的朋友可以參考下
    2015-03-03
  • Redis鎖的過期時間小于業(yè)務(wù)的執(zhí)行時間如何續(xù)期

    Redis鎖的過期時間小于業(yè)務(wù)的執(zhí)行時間如何續(xù)期

    本文主要介紹了Redis鎖的過期時間小于業(yè)務(wù)的執(zhí)行時間如何續(xù)期,Redisson它能給Redis分布式鎖實現(xiàn)過期時間自動續(xù)期,具有一定的參考價值,感興趣的可以了解一下
    2024-05-05
  • redis分布式Jedis類型轉(zhuǎn)換的異常深入研究

    redis分布式Jedis類型轉(zhuǎn)換的異常深入研究

    這篇文章主要介紹了redis分布式Jedis類型轉(zhuǎn)換的異常深入研究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • Redis Template使用詳解示例教程

    Redis Template使用詳解示例教程

    RedisTemplate的底層通過RedisConnectionFactory對多種Redis驅(qū)動進行集成,上層通過RedisOperations提供豐富的API,并結(jié)合Spring基于泛型的bean注入,為開發(fā)提供了極大的便利,這篇文章主要介紹了Redis Template使用詳解示例教程,需要的朋友可以參考下
    2023-11-11
  • Redis的持久化方案詳解

    Redis的持久化方案詳解

    在本篇文章里小編給大家整理的是關(guān)于Redis的持久化方案詳解,有興趣的朋友們可以參考下。
    2020-03-03
  • Redis 出現(xiàn)錯誤1067的解決辦法

    Redis 出現(xiàn)錯誤1067的解決辦法

    這篇文章主要介紹了Redis 出現(xiàn)錯誤1067的解決辦法的相關(guān)資料,Redis 錯誤1067:進程意外終止,Redis不能啟動,Redis啟動不了,需要的朋友可以參考下
    2017-07-07
  • 解鎖redis鎖的正確姿勢

    解鎖redis鎖的正確姿勢

    這篇文章主要為大家詳細介紹了解鎖redis鎖的正確姿勢,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03

最新評論