RedisBloom-Cuckoo-Filter源码剖析

CuckooFilter结构体

typedef uint8_t CuckooFingerprint;//8位置指纹
typedef uint64_t CuckooHash;//64为的哈希值
typedef uint8_t CuckooBucket[1];//定义了一个CuckooBucket类型,他是一个具有1个元素的uint8_t数组类型,每个桶都是1个字节,这个==uint8_t*,
typedef uint8_t MyCuckooBucket;

CuckooFilter(相当于一个链表每个节点元素都是一个CuckooFilter过滤器(定义为SubCF))

typedef struct {
    //numbucket*bucketsize=最多能添加的元素个数
    uint64_t numBuckets;//表示哈希表中桶的个数,这个是一个基数,相当于最低的位置的节点的,后面节点可能会更多
    uint64_t numItems;//哈希表中元素的个数
    uint64_t numDeletes;//删除元素的个数,当删除的元素达到一定量的时候,需要进行一个元素的压缩,将槽位迁移,提高有效性
    uint16_t numFilters;//节点的个数
    uint16_t bucketSize;//每个桶里面能容纳的元素个数
    uint16_t maxIterations;//在插入元素的时候,最多能迭代的次数,超过这个次数就插入失败
    //我们可以平衡内存和性能,确保Cuckoo Filter具有足够的存储空间来存储数据,并且尽可能地减少内存浪费。
    uint16_t expansion;//在哈希表需要扩容的时候,需要扩容的比例因子,newbucket=numbucket*(expansion^nfilter),保证按比例增大
    SubCF *filters;//链表,每一个元素是一个cuckoo filter
} CuckooFilter;

SubCF(每一个cuckoofilter子节点)

typedef struct {
    uint64_t numBuckets : 56;//哈希表中的桶个数
    uint64_t bucketSize : 8;//每个哈希表能容纳的元素个数
    MyCuckooBucket *data;//一个数组,每个元素是一个uint8 a[];//相当于这样的一个数组,每个哈希数组中存储最大不超过8位的指纹,存储的元素个数就是numbucket*bucketsize
} SubCF;

Cuckookey(每一个value都有两个索引位置和一个8位指纹)

typedef struct {
    uint64_t i1;
    uint64_t i2;
    CuckooFingerprint fp;
} CuckooKey;

请添加图片描述

插入

CF.ADD key value

//伪代码
hash=gethash(value)
insert(hash)
{
	Cuckookey pararm=getpararm(hash);//根据这个hash获得对应的指纹和槽位
	for i=CF.numfilter-1;i>=0;i--// 从最后一个节点开始
		if(在当前节点的某个槽位上找到了元素)
			把pararm->fp插入到该槽位上
			return ok

	//没有找到,就需要进行驱逐替换
	count=0;
	bucketidx=pararm->h1%numbucket;//获得该节点的其中一个bucketidx
	while(count++<maxevit)//如果超过了最大的驱逐数,就直接返回
		将当前节点插入到该bucketidx的两个槽位的其中一个槽位上
		获得被驱逐元素的另一个bucketidx位置
		如果当前bucketidx有空位可以插入就插入,结束循环 return ok
		该bucketidx的位置都被占了,就从当前的bucketidx中选一个进行替换,循环
		
	//都无法插入,就需要进行替换策略
	count=0
	while count++<maxevit
	{
		//随机挑选一个桶进行替换,调整该cuckoo filter元素分布情况
	}
	//当前没有元素
	grow();//增加一个新的cuckoo filter节点	

	return insert//增加完节点后继续递归执行插入的逻辑


}

元素替换的逻辑
请添加图片描述

完整代码


static CuckooInsertStatus Filter_KOInsert(CuckooFilter *filter, SubCF *curFilter,
                                          const LookupParams *params) {
    uint16_t maxIterations = filter->maxIterations;
    uint32_t numBuckets = curFilter->numBuckets;
    uint16_t bucketSize = filter->bucketSize;
    CuckooFingerprint fp = params->fp;//获得指纹,现在要插入元素的指纹

    uint16_t counter = 0;//执行替换的次数
    uint32_t victimIx = 0;//被替换的指纹索引
    uint32_t ii = params->h1 % numBuckets;//ii计算索引位

    while (counter++ < maxIterations) {
        uint8_t *bucket = &curFilter->data[ii * bucketSize];//操纵真正的桶的元素
        swapFPs(bucket + victimIx, &fp);//通过交换操作,就把fp插入进去了,而不需要进行赋值,
        //较换之后,将原来这个位置的值放到
        ii = getAltHash(fp, ii) % numBuckets;//计算另一个哈希位置
        // Insert the new item in potentially the same bucket
        uint8_t *empty = Bucket_FindAvailable(&curFilter->data[ii * bucketSize], bucketSize);//
        if (empty) {//如果当前元素有空槽位
            // printf("Found slot. Bucket[%lu], Pos=%lu\n", ii, empty - curFilter[ii]);
            // printf("Old FP Value: %d\n", *empty);
            // printf("Setting FP: %p\n", empty);
            *empty = fp;//把这个fp插入到他另一个哈希位置
            return CuckooInsert_Inserted;
        }
        //没有空位置的话,我们就强制在这个桶里面选择一个元素进行交换,victix+1这样就保证不会相同
        victimIx = (victimIx + 1) % bucketSize;//ictimtx表示当前元素被添加到桶中的位置,如果刚才这个位置的第一个位置大概率已经被交换过了,+1,保证了下次要交换的时候
    }

    // If we weren't able to insert, we roll back and try to insert new element in new filter,无法插入,就需要执行替换策略,将当前桶的第
    counter = 0;
    //随机挑选一个桶进行替换,防止过多的元素插入到同一个桶里面
    while (counter++ < maxIterations) {
        victimIx = (victimIx + bucketSize - 1) % bucketSize;
        ii = getAltHash(fp, ii) % numBuckets;
        uint8_t *bucket = &curFilter->data[ii * bucketSize];
        swapFPs(bucket + victimIx, &fp);//随机挑选一个桶来进行替换
    }

    return CuckooInsert_NoSpace;
}


static CuckooInsertStatus CuckooFilter_InsertFP(CuckooFilter *filter, const LookupParams *params) {
    for (uint16_t ii = filter->numFilters; ii > 0; --ii) {//从最后一个节点开始往前找
        uint8_t *slot = Filter_FindAvailable(&filter->filters[ii - 1], params);//计算可用的槽位
        if (slot) {
            *slot = params->fp;//把对应的fp插入进去
            filter->numItems++;//该元素的元素个数增加
            return CuckooInsert_Inserted;//有空槽位,就直接返回了
        }
    }
    //找了所有节点都没有位置
    // No space. Time to evict!,执行驱逐操作
    CuckooInsertStatus status =
        Filter_KOInsert(filter, &filter->filters[filter->numFilters - 1], params);//从最后一个链节点开始操作
    if (status == CuckooInsert_Inserted) {//插入成功的状态,
        filter->numItems++;//怎加当前元素的个数
        return CuckooInsert_Inserted;
    }
    //插入失败,不存在空余槽位
    if (filter->expansion == 0) {
        return CuckooInsert_NoSpace;//expansion=0说明不允许继续插入
    }
    //当前节点已经满了不允许继续插入了,同时允许我们进行扩容,我们就需要进行扩容,到新的节点上面操作
    if (CuckooFilter_Grow(filter) != 0) {
        return CuckooInsert_MemAllocFailed;
    }

    // Try to insert the filter again
    return CuckooFilter_InsertFP(filter, params);//再执行一次
}

//hash就是需要插入value处理过的hash值
CuckooInsertStatus CuckooFilter_Insert(CuckooFilter *filter, CuckooHash hash) {
    LookupParams params;
    getLookupParams(hash, &params);//根据该key对应的哈希值,生成他对应的两个哈希值,以及指纹
    return CuckooFilter_InsertFP(filter, &params);
}

删除

hash=gethash(value)

delete(hash)
	Cuckookey=getparam(hash)
	for i=CF.numfilter-1;i>=0;i--
		if 发现该元素存在
			将该槽位的元素置空
			CF.numdelete--;
			if CF.numdelete 超过了总容量的一定比例
				compact()进行压缩,槽位迁移
				
//删除过滤器中的元素
int CuckooFilter_Delete(CuckooFilter *filter, CuckooHash hash) {
    LookupParams params;
    getLookupParams(hash, &params);
    for (uint16_t ii = filter->numFilters; ii > 0; --ii) {
    	//在当前的节点上进行删除这个元素
        if (Filter_Delete(&filter->filters[ii - 1], &params)) {//删除成功
            filter->numItems--;//元素总个数减少
            filter->numDeletes++;//删除的个数增加
            //如果删除元素超过了元素总数的10%,就需要进行合并,因为有很多空间被闲置了
            if (filter->numFilters > 1 && filter->numDeletes > (double)filter->numItems * 0.10) {
                CuckooFilter_Compact(filter, false);
            }
            return 1;
        }
    }
    return 0;
}

压缩

请添加图片描述

#define RELOC_EMPTY 0
#define RELOC_OK 1
#define RELOC_FAIL -1

/**
 * Attempt to move a slot from one bucket to another filter
 */
static int relocateSlot(CuckooFilter *cf, CuckooBucket bucket, uint16_t filterIx, uint64_t bucketIx,
                        uint16_t slotIx) {
    LookupParams params = {0};//生成一个新的param插入到前面的节点中
    if ((params.fp = bucket[slotIx]) == CUCKOO_NULLFP) {//zhesdd 
        // Nothing in this slot.
        return RELOC_EMPTY;//为空的话,这个槽位就不需要进行迁移
    }

    // Because We try to insert in sub filter with less or equal number of
    // buckets, our current fingerprint is sufficient
    //把当前槽位的元素平行迁移到前一个节点
    params.h1 = bucketIx;
    params.h2 = getAltHash(params.fp, bucketIx);

    // Look at all the prior filters and attempt to find a home
    for (uint16_t ii = 0; ii < filterIx; ++ii) {
        uint8_t *slot = Filter_FindAvailable(&cf->filters[ii], &params);//在每一个节点上看,是否有这两个位置是空闲的,那么就插入上去
        if (slot) {
            *slot = params.fp;//把之前的元素进行设置
            bucket[slotIx] = CUCKOO_NULLFP;//把之前的槽设置成空
            return RELOC_OK;//设置完当前槽就迁移完成了
        }
    }
    return RELOC_FAIL;//前面节点已经没有任何过滤器可以进行使用插入改slot了,那么就返回fatal
}

/**
 * Attempt to strip a single filter moving it down a slot
 */
//将当前节点的有用词槽位迁移到前面的节点
static uint64_t CuckooFilter_CompactSingle(CuckooFilter *cf, uint16_t filterIx) {
    SubCF *currentFilter = &cf->filters[filterIx];//获得当前的节点
    MyCuckooBucket *filter = currentFilter->data;//获得对应的哈希表
    int rv = RELOC_OK;
    //这个是在访问最后一个节点
    for (uint64_t bucketIx = 0; bucketIx < currentFilter->numBuckets; ++bucketIx) {//遍历当前哈希表中存储的所有元素
        for (uint16_t slotIx = 0; slotIx < currentFilter->bucketSize; ++slotIx) {
            int status = relocateSlot(cf, &filter[bucketIx * currentFilter->bucketSize], filterIx,
                                      bucketIx, slotIx);
            if (status == RELOC_FAIL) {//改节点的元素,往前迁移都被插满了,就无法再进行插入了
                rv = RELOC_FAIL;
            }
        }
    }
    // we free a filter only if it the latest one
    // 如果该节点全部都迁移完了,就需要将最后一个节点进行释放
    if (rv == RELOC_OK && filterIx == cf->numFilters - 1) {
        CUCKOO_FREE(filter);
        cf->numFilters--;
    }
    return rv;
}

/**
 * Attempt to move elements to older filters. If latest filter is emptied, it is freed.
 * `bool` determines whether to continue iteration on other filters once a filter cannot
 * be freed and therefore following filter cannot be freed either.
 */
//重新组织元素,减少内存空间
void CuckooFilter_Compact(CuckooFilter *cf, bool cont) {
    for (uint64_t ii = cf->numFilters; ii > 1; --ii) {//遍历所有的过滤器,将其中的元素重刑分配到更老的filter中,如何成功,当前的就需要被释放掉,
        if (CuckooFilter_CompactSingle(cf, ii - 1) == RELOC_FAIL && !cont) {//一直持续,直到没有槽位可以操作为止
            // if compacting failed, stop as lower filters cannot be freed.
            break;
        }
    }
    cf->numDeletes = 0;//把这个值设置成0
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevin~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值