主頁 > 資料庫 > Redis 設計與實作 9:五大資料型別之集合

Redis 設計與實作 9:五大資料型別之集合

2021-01-06 07:25:15 資料庫

集合物件的編碼有兩種:intsethashtable

編碼一:intset

intset 的結構

整數集合 intset 是集合底層的實作之一,從名字就可以看出,這是專門為整數提供的集合型別,
其結構定義如下,在 intset.h

typedef struct intset {
    // 編碼方式
    uint32_t encoding;
    // 集合包含的元素數量
    uint32_t length;
    // 保存元素的陣列
    int8_t contents[];
} intset;
  • contents 中的元素,按照從小到大排序,并且不存在重復項,雖然元素定義是 int8_t 型別,但實際上,contents 存的元素型別取決于 encoding
  • encoding 有幾個型別,定義在 intset.c
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
encoding 型別 位元組
INTSET_ENC_INT16 int16_t 2
INTSET_ENC_INT32 int32_t 4
INTSET_ENC_INT64 int64_t 8

下圖展示了包含了 1、2、3 三個整數元素的集合結構:

常見操作原始碼分析

原始碼在 intset.c

1. 創建空集合

創建一個空的 intset,一開始的編碼是最小的 INTSET_ENC_INT16

intset *intsetNew(void) {
    intset *is = zmalloc(sizeof(intset));
    is->encoding = intrev32ifbe(INTSET_ENC_INT16);
    is->length = 0;
    return is;
}

2. 搜索

因為集合中的整數存的是有序的,所以查找是用二分查找,時間復雜度 \(O(nlogn)\)

uint8_t intsetFind(intset *is, int64_t value) {
    uint8_t valenc = _intsetValueEncoding(value);
    // 如果 value 的編碼大于集合的編碼,那肯定是不存在的
    // intsetSearch 是更底層的搜索,實作原始碼在下面,是個二分查找
    return valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,NULL);
}

// 集合搜索,是二分查找,
// 如果找到了,回傳1,并且把位置設定到 pos 變數中
// 如果找不到,回傳0,可以插入值的位置設定到 pos 變數中
static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
    int min = 0, max = intrev32ifbe(is->length)-1, mid = -1;
    int64_t cur = -1;

    // 陣列判空
    if (intrev32ifbe(is->length) == 0) {
        if (pos) *pos = 0;
        return 0;
    } else {
        // 看是否比最大的大或者比最小的小,這種情況也直接回傳不存在
        if (value > _intsetGet(is,max)) {
            if (pos) *pos = intrev32ifbe(is->length);
            return 0;
        } else if (value < _intsetGet(is,0)) {
            if (pos) *pos = 0;
            return 0;
        }
    }

    // 二分查找
    while(max >= min) {
        mid = ((unsigned int)min + (unsigned int)max) >> 1;
        cur = _intsetGet(is,mid);
        if (value > cur) {
            min = mid+1;
        } else if (value < cur) {
            max = mid-1;
        } else {
            break;
        }
    }

    if (value =https://www.cnblogs.com/chenchuxin/p/= cur) {
        if (pos) *pos = mid;
        return 1;
    } else {
        if (pos) *pos = min;
        return 0;
    }
}

3. 指定位置獲取

// 如果獲取得到,回傳1,找到的值設定進 value 變數
// 如果獲取不到,回傳 0
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
    if (pos < intrev32ifbe(is->length)) {
        *value = https://www.cnblogs.com/chenchuxin/p/_intsetGet(is,pos);
        return 1;
    }
    // 位置如果大于長度,肯定就獲取不到的
    return 0;
}
static int64_t _intsetGet(intset *is, int pos) {
    // 根據編碼獲取
    return _intsetGetEncoded(is,pos,intrev32ifbe(is->encoding));
}
static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
    int64_t v64;
   	// ...

    // 根據編碼的長度,從對應的位置后拷貝對應的位元組回傳
    if (enc == INTSET_ENC_INT64) {
        memcpy(&v64,((int64_t*)is->contents)+pos,sizeof(v64));
        memrev64ifbe(&v64);
        return v64;
    } else if (enc == INTSET_ENC_INT32) {
        // ...
        return v32;
    } else {
        // ...
    }
}

4. 插入

插入的步驟如下:

  1. 檢查如果插入的元素的編碼大于集合編碼,進行升級并插入
  2. 如果不需要升級,檢查元素是否存在,如果存在,則直接回傳
  3. 如果元素不存在,則擴容,在元素對應的位置插入值(它后面的元素則都往后挪)
intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
    // 插入的元素的編碼
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 1;

    // 如果插入的元素的編碼比當前集合的編碼大,需要進行升級
    if (valenc > intrev32ifbe(is->encoding)) {
        return intsetUpgradeAndAdd(is,value);
    } else {
        // 先查找看元素已存在,如果存在,則直接回傳
        if (intsetSearch(is,value,&pos)) {
            if (success) *success = 0;
            return is;
        }
		
		// 擴容
        is = intsetResize(is,intrev32ifbe(is->length)+1);
        // 將 pos 后的記憶體塊向后挪動一個位置,給新值騰空間
        if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);
    }

    // 把新值設定進 pos 位置上
    _intsetSet(is,pos,value);
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
    void *src, *dst;
    uint32_t bytes = intrev32ifbe(is->length)-from;
    uint32_t encoding = intrev32ifbe(is->encoding);

    if (encoding == INTSET_ENC_INT64) {
        src = https://www.cnblogs.com/chenchuxin/p/(int64_t*)is->contents+from;
        dst = (int64_t*)is->contents+to;
        bytes *= sizeof(int64_t);
    } else if (encoding == INTSET_ENC_INT32) {
        // ...
    } else {
        // ...
    }
    memmove(dst,src,bytes);
}

5. 升級

intset 插入元素的時候,會先檢測元素的長度,判斷元素應該屬于什么編碼(encoding),
如果當前元素的編碼,大于 intset 的編碼(整個集合最長的編碼),集合將進行升級后,才添加元素,

升級整數集合并添加新元素共分為 3 步進行:

  1. 根據新元素的編碼,擴展整數集合底層陣列的空間大小,并為新元素分配空間,
  2. 將底層陣列現有的所有元素都轉換成與新元素相同的型別,并將型別轉換后的元素放置到正確的位上,而且在放置元素的程序中,需要繼續維持底層陣列的有序性質不變,
  3. 將新元素添加到底層陣列里面,
// 升級并插入新值
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    // 當前編碼
    uint8_t curenc = intrev32ifbe(is->encoding);
    // 新的編碼
    uint8_t newenc = _intsetValueEncoding(value);
    // 當前元素個數
    int length = intrev32ifbe(is->length);
    // value 的編碼比其他的都大,那么這個 value 不是最大值就是最小值,
    // 如果是最大值就放在陣列最后,最小值就放在陣列最前面
    int prepend = value < 0 ? 1 : 0;

    // 設定 encoding 屬性為新編碼
    is->encoding = intrev32ifbe(newenc);
    // 根據新編碼給擴展集合需要的空間,實作原始碼在下面
    is = intsetResize(is,intrev32ifbe(is->length)+1);

    // 從尾到頭依次遍歷挪動原來的值,為什么不從頭到尾呢?因為陣列是同一個,從頭到尾會覆寫原來的值
    while(length--)
        // _intsetGetEncoded(is,length,curenc) 表示根據編碼和位置獲取值
        // prepend 為了確保如果 value 是最小的值,那么前面會留一個空位置
        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));

    if (prepend)
    	// 當 value 是最小值時,放在第一個空位
        _intsetSet(is,0,value);
    else
        // 當 value 是最大值,放在最后一個位置
        _intsetSet(is,intrev32ifbe(is->length),value);
    // 長度加 1
    is->length = intrev32ifbe(intrev32ifbe(is->length)+1);
    return is;
}

// 整數集合重新分配記憶體
static intset *intsetResize(intset *is, uint32_t len) {
    // 根據編碼算出集合需要的空間
    uint32_t size = len*intrev32ifbe(is->encoding);
    // 分配記憶體
    is = zrealloc(is,sizeof(intset)+size);
    return is;
}

6. 降級

并沒有降級

7. 洗掉

洗掉的步驟如下:

  1. 找到值的位置 pos
  2. pos 后面的元素向前挪,覆寫掉 pos 上的元素
  3. 縮容:長度減一
intset *intsetRemove(intset *is, int64_t value, int *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 0;

    // 查找值的位置
    if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
        uint32_t len = intrev32ifbe(is->length);
        if (success) *success = 1;
        // 把洗掉位置后面的元素都挪到前面來,直接覆寫掉 pos 的元素
        if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
        // 再縮容
        is = intsetResize(is,len-1);
        is->length = intrev32ifbe(len-1);
    }
    return is;
}

編碼二:hashtable

hashtable 編碼用的是字典 dict 作為底層實作,關于 dict,具體的前文 Redis 設計與實作 4:字典 dict 已經寫了,包括了 dict 基本操作的原始碼解讀,

下圖展示了包含 "a"、"b"、"c"、"d" 四個元素的集合結構:

編碼的轉換

當集合物件滿足以下兩個條件時,采用 intset 編碼:

  1. 所有元素都是整數
  2. 元素數量不超過512個(用通過 set-max-intset-entries 配置項配置)

不能同時滿足以上兩個條件,則采用 tablehash 編碼,

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/245142.html

標籤:NoSQL

上一篇:mysql主從備份說明(win系統)

下一篇:[20201231]RAC buffer states: XCUR, SCUR, PI,CR.txt

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more