From 498f5a2e266629370cd4b390d3e807307e3770be Mon Sep 17 00:00:00 2001 From: sado Date: Fri, 13 Oct 2023 16:59:53 +0800 Subject: [PATCH] bloom filter --- kit/store/redis/redis.go | 4 +- pkg/bloom/README.md | 142 +++++++++++++++++++++++++++++++ pkg/bloom/bloom.go | 27 ++++++ pkg/bloom/image-bloom.png | Bin 0 -> 55464 bytes pkg/bloom/redis_provider.go | 117 +++++++++++++++++++++++++ pkg/bloom/redis_provider_test.go | 75 ++++++++++++++++ 6 files changed, 363 insertions(+), 2 deletions(-) create mode 100644 pkg/bloom/README.md create mode 100644 pkg/bloom/bloom.go create mode 100644 pkg/bloom/image-bloom.png create mode 100644 pkg/bloom/redis_provider.go create mode 100644 pkg/bloom/redis_provider_test.go diff --git a/kit/store/redis/redis.go b/kit/store/redis/redis.go index 53afd70..3fd5cd4 100644 --- a/kit/store/redis/redis.go +++ b/kit/store/redis/redis.go @@ -4,10 +4,10 @@ import ( "context" "fmt" - rdsV8 "github.com/go-redis/redis/v8" - "github.com/sado0823/go-kitx/errorx" "github.com/sado0823/go-kitx/kit/breaker" + + rdsV8 "github.com/go-redis/redis/v8" ) const ( diff --git a/pkg/bloom/README.md b/pkg/bloom/README.md new file mode 100644 index 0000000..5ac15ef --- /dev/null +++ b/pkg/bloom/README.md @@ -0,0 +1,142 @@ +# go-bloom +a go bloom filter , base on different implement like redis ... + + + +# 项目地址: + +https://github.com/sado0823/go-kitx + + + +# what? + +```js +上一篇在提到缓存击穿的时候, 有一种解决办法就是布隆过滤器 + + +布隆过滤器(英語:Bloom Filter)是1970年由布隆提出的。 它实际上是一个很长的二进制向量和一系列随机映射函数。 布隆过滤器可以用于检索一个元素是否在一个集合中。 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难 + +``` + + + +# why? + +```js +布隆过滤器: 可以判断某元素在不在集合里面,因为存在一定的误判和删除复杂问题 +``` + +一般的使用场景是: + +* 防止缓存击穿(防止恶意攻击) +* 垃圾邮箱过滤 +* cache digests (缓存索引) +* 模型检测器 +* 判断是否存在某行数据,用以减少对磁盘访问,提高服务的访问性能 + + + +# how? + +## 基本思想 + +通过多个`hash`方法, 进行多次hash操作, 使其值位于`bit`不同位上, 检测该`bit`上的数据是否为`1`, 从而判断是否存在 + +![image-20210912175241849](./image-bloom.png) + + + +## 源码分析 + +`interface: bloom.go` + +```go +// 过滤器的核心实现, 通过interface的方式, 可以支持多种实现 +// 目前实现了基于redis bit数据类型的过滤器 +Provider interface { + Add(data []byte) error + Exists(data []byte) (bool, error) +} + +// Filter is a bloom filter +Filter struct { + + // todo counter + total int64 + hit int64 + miss int64 + + provider Provider +} +``` + + + +`redis实现: internal/redis/redis_bit.go` + +```js +// 实现Provider接口的两个方法 + +// Add implement Provider interface +func (r *Provider) Add(data []byte) error { + location := r.getBitLocation(data) + return r.set(location) +} + +// Exists implement Provider interface +func (r *Provider) Exists(data []byte) (bool, error) { + location := r.getBitLocation(data) + return r.check(location) +} + +// 核心方法 +// 通过14次hash, 每次hash都在数据最后追加一个byte(index), 最后进行取模, 分布在map里面的每个区间 +// 检查是否存在时, 对每个bit位进行判断, 如果有一个等于0, 则数据不存在 +// getBitLocation return data hash to bit location +func (r *Provider) getBitLocation(data []byte) []uint { + l := make([]uint, maps) + for i := 0; i < maps; i++ { + hashV := r.hash(append(data, byte(i))) + l[i] = uint(hashV % uint64(maps)) + } + return l +} +``` + + + +`todo` + +```js +1) 可以实现统计数据, 比如总量, 命中率, 丢失率等 + +2) 实现其它bloom过滤器provider(目前只有基于redis bit) +``` + + + +# example + +```go +func test() { + filter := NewRedis("127.0.0.1:6379", "test-bloom", 1024) + + _ = filter.Add([]byte("a")) + _ = filter.Add([]byte("b)) + + _, _ = filter.Exists([]byte("a)) + _, _ = filter.Exists([]byte("p)) +} + +``` + + + + + +# references + +1.https://github.com/tal-tech/go-zero + +2.http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html diff --git a/pkg/bloom/bloom.go b/pkg/bloom/bloom.go new file mode 100644 index 0000000..24ec225 --- /dev/null +++ b/pkg/bloom/bloom.go @@ -0,0 +1,27 @@ +package bloom + +import ( + "context" +) + +type ( + // Filter is a bloom filter + Filter struct { + + // todo counter + //total int64 + //hit int64 + //miss int64 + + Provider + } + + Provider interface { + Add(ctx context.Context, data []byte) error + Exists(ctx context.Context, data []byte) (bool, error) + } +) + +func NewWithProvider(provider Provider) *Filter { + return &Filter{Provider: provider} +} diff --git a/pkg/bloom/image-bloom.png b/pkg/bloom/image-bloom.png new file mode 100644 index 0000000000000000000000000000000000000000..442cc10df751a5b4809d4dd346640f50ea4929f1 GIT binary patch literal 55464 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 literal 0 HcmV?d00001 diff --git a/pkg/bloom/redis_provider.go b/pkg/bloom/redis_provider.go new file mode 100644 index 0000000..e5faace --- /dev/null +++ b/pkg/bloom/redis_provider.go @@ -0,0 +1,117 @@ +package bloom + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/sado0823/go-kitx/kit/store/redis" + "github.com/spaolacci/murmur3" +) + +const ( + // for detail, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html + maps = 14 + setScript = ` +for _, offset in ipairs(ARGV) do + redis.call("setbit", KEYS[1], offset, 1) +end +` + checkScript = ` +for _, offset in ipairs(ARGV) do + if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then + return false + end +end +return true +` +) + +var ErrTooLargeOffset = errors.New("too large offset") + +type rdsProvider struct { + store *redis.Redis + key string + bits uint +} + +func NewRedisProvider(addr string, key string, bits uint) Provider { + return &rdsProvider{store: redis.New(addr), key: key, bits: bits} +} + +// Add implement Provider interface +func (r *rdsProvider) Add(ctx context.Context, data []byte) error { + location := r.getBitLocation(data) + return r.set(ctx, location) +} + +// Exists implement Provider interface +func (r *rdsProvider) Exists(ctx context.Context, data []byte) (bool, error) { + location := r.getBitLocation(data) + return r.check(ctx, location) +} + +// getBitLocation return data hash to bit location +func (r *rdsProvider) getBitLocation(data []byte) []uint { + l := make([]uint, maps) + for i := 0; i < maps; i++ { + hashV := r.hash(append(data, byte(i))) + l[i] = uint(hashV % uint64(maps)) + } + return l +} + +// set those offsets into bloom filter +func (r *rdsProvider) set(ctx context.Context, offsets []uint) error { + args, err := r.buildOffsetArgs(offsets) + if err != nil { + return err + } + + _, err = r.store.Eval(ctx, setScript, []string{r.key}, args) + if errors.Is(err, redis.Nil) { + return nil + } + + return err +} + +// check if those offsets are in bloom filter +func (r *rdsProvider) check(ctx context.Context, offsets []uint) (bool, error) { + args, err := r.buildOffsetArgs(offsets) + if err != nil { + return false, err + } + + eval, err := r.store.Eval(ctx, checkScript, []string{r.key}, args) + if errors.Is(err, redis.Nil) { + return false, nil + } else if err != nil { + return false, err + } + + return fmt.Sprintf("%v", eval) == "1", nil +} + +// buildOffsetArgs set []uint offset to []string that can use in redis +// and check if offset is larger than r.bits +func (r *rdsProvider) buildOffsetArgs(offsets []uint) ([]string, error) { + var args []string + + for _, offset := range offsets { + if offset >= r.bits { + return nil, ErrTooLargeOffset + } + + args = append(args, strconv.FormatUint(uint64(offset), 10)) + + } + + return args, nil +} + +// hash returns the hash value of data. +func (r *rdsProvider) hash(data []byte) uint64 { + return murmur3.Sum64(data) +} diff --git a/pkg/bloom/redis_provider_test.go b/pkg/bloom/redis_provider_test.go new file mode 100644 index 0000000..a1ebbe1 --- /dev/null +++ b/pkg/bloom/redis_provider_test.go @@ -0,0 +1,75 @@ +package bloom + +import ( + "context" + "testing" + "time" + + "github.com/sado0823/go-kitx/kit/store/redis" + + "github.com/alicebob/miniredis/v2" + "github.com/stretchr/testify/assert" +) + +// createRedis returns an in process redis.Redis. +func createRedis() (addr string, clean func(), err error) { + mr, err := miniredis.Run() + if err != nil { + return "", nil, err + } + + return mr.Addr(), func() { + ch := make(chan struct{}) + go func() { + mr.Close() + close(ch) + }() + select { + case <-ch: + case <-time.After(time.Second): + } + }, nil +} + +func TestRedisBitSet_New_Set_Test(t *testing.T) { + addr, clean, err := createRedis() + assert.Nil(t, err) + defer clean() + ctx := context.Background() + + bitSet := &rdsProvider{store: redis.New(addr), key: "test_key", bits: 1024} + isSetBefore, err := bitSet.check(ctx, []uint{0}) + if err != nil { + t.Fatal(err) + } + if isSetBefore { + t.Fatal("Bit should not be set") + } + err = bitSet.set(ctx, []uint{512}) + if err != nil { + t.Fatal(err) + } + isSetAfter, err := bitSet.check(ctx, []uint{512}) + if err != nil { + t.Fatal(err) + } + if !isSetAfter { + t.Fatal("Bit should be set") + } + +} + +func TestRedisBitSet_Add(t *testing.T) { + addr, clean, err := createRedis() + assert.Nil(t, err) + defer clean() + + ctx := context.Background() + + filter := &rdsProvider{store: redis.New(addr), key: "test_key", bits: 1024} + assert.Nil(t, filter.Add(ctx, []byte("hello"))) + assert.Nil(t, filter.Add(ctx, []byte("world"))) + ok, err := filter.Exists(ctx, []byte("hello")) + assert.Nil(t, err) + assert.True(t, ok) +}