Golang基于Redis bitmap实现布隆过滤器(完结版)

Golang基于Redis bitmap实现布隆过滤器(完结版)

为了防止黑客恶意刷接口(请求压根不存在的数据),目前通常有以下几种做法:

  1. 限制IP(限流)
  2. Redis缓存不存在的key
  3. 布隆过滤器挡在Redis前

完整代码地址:

https://github.com/ziyifast/ziyifast-code_instruction/tree/main/blond_filter

1 概念:

1.1 本质:超大bit数组

  • 原理:由一个初始值都为0的bit数组和多个hash函数构成(相当于多把锁才能打开一把钥匙,才能确认某个元素是否真的存在,提高布隆过滤器的准确率),用于快速判断集合中是否存在某个元素
  • 使用3步骤:初始化bitmap -> 添加元素到bitmap(占坑位) -> 判断是否存在
    -Hash冲突: 为了避免hash冲突,我们可以通过多个hash函数进行映射,比如:将player:1982分别通过多个hash函数映射到多个offset。在查询时,就需要判断是否映射的所有的offset都存在。(一个hash函数冲突概率可能很高,但是通过不同多个hash进行映射,大幅降低冲突概率)

在这里插入图片描述

注意📢:

  1. 是否存在:
    • 有,可能有;因为存在hash冲突,比如我添加的是王五在1号来上班了,但是王五和李四hash值一样,结果我查询李四时,发现hash定为的offset为1了,我就误以为李四也来上班了
    • 无,是肯定无。100%不存在
  2. 使用时,bit数组尽量大些,防止扩容。当实际元素超过初始化数量时,应重建布隆过滤器,重新分配一个size更大的过滤器,再将所有历史元素批量add
  3. 避免删除元素,防止误删(hash冲突:我原本想删李四的记录,结果把王五的也删除了,“连坐”)

1.2 应用场景:防止Redis缓存穿透(海量数据中判断某个元素是否存在)

  • 应用场景:加在数据库、Redis之前。
    • 在查询之前,先查布隆过滤器是否存在,如果不存在直接返回请求。如果存在,再查询Redis、数据库,看是否真的存在。防止因缓存穿透导致数据库被打挂掉。
    • 防止被人恶意刷接口

2 环境准备

2.1 安装docker

yum install -y yum-utils
yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo
yum install docker
systemctl start docker

2.2 搭建Postgres

docker run -d \
-p 5432:5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-v /Users/ziyi2/docker-home/pg:/var/lib/postgresql/data \
--name pg \
--restart always \
docker.io/postgres:9.6-alpine# -p port 映射端口,可以通过宿主机的端口访问到容器内的服务
# -d 是detach 保持程序后台运行的意思
# -e environment 设置环境变量
# -v volume 文件或者文件夹的挂载

2.3 搭建Redis

docker run -d \ 
--name redis \
-v /Users/ziyi2/docker-home/redis:/data \
-p 6379:6379 redis

3 代码实现

完整代码地址:

https://github.com/ziyifast/ziyifast-code_instruction/tree/main/blond_filter

3.1 方案

思路:

  1. 先搭建Iris+Postgres,然后再数据库前挡一层Redis
  2. 在Redis之前再加一层布隆过滤器。效果:
    请求 - 布隆过滤器 - Redis - Postgres

代码结构:
在这里插入图片描述

3.2 Iris+Redis+Postgres

注意:案例中部分代码不规范,主要起演示作用

①blond_filter/pg/pg.go
package pgimport ("fmt"_ "github.com/lib/pq""github.com/ziyifast/log""time""xorm.io/xorm"
)var Cli *xorm.Engineconst (host     = "localhost"port     = 5432user     = "postgres"password = "postgres"dbName   = "postgres"
)var Engine *xorm.Enginefunc init() {psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbName)engine, err := xorm.NewEngine("postgres", psqlInfo)if err != nil {log.Fatal(err)}engine.ShowSQL(true)engine.SetMaxIdleConns(10)engine.SetMaxOpenConns(20)engine.SetConnMaxLifetime(time.Minute * 10)engine.Cascade(true)if err = engine.Ping(); err != nil {log.Fatalf("%v", err)}Engine = enginelog.Infof("connect postgresql success")
}
②blond_filter/redis/redis.go
package redisimport "github.com/go-redis/redis"var (Client       *redis.ClientPlayerPrefix = "player:"
)func init() {Client = redis.NewClient(&redis.Options{Addr:     "127.0.0.1:6379",Password: "", // no password setDB:       0,  // use default DB})
}
③blond_filter/model/player.go
package modeltype Player struct {Id   int64  `xorm:"id" json:"id"`Name string `xorm:"name" json:"name"`Age  int    `xorm:"age" json:"age"`
}func (p *Player) TableName() string {return "player"
}
④blond_filter/dao/player_dao.go
package daoimport ("github.com/aobco/log""myTest/demo_home/blond_filter/model""myTest/demo_home/blond_filter/pg""time"
)type playerDao struct {
}var PlayerDao = new(playerDao)func (p *playerDao) InsertOne(player model.Player) (int64, error) {return pg.Engine.InsertOne(player)
}func (p *playerDao) GetById(id int64) (*model.Player, error) {log.Infof("query postgres,time:%v", time.Now().String())player := new(model.Player)get, err := pg.Engine.Where("id=?", id).Get(player)if err != nil {log.Errorf("%v", err)}if !get {return nil, nil}return player, nil
}
⑤blond_filter/service/player_service.go
package serviceimport ("github.com/ziyifast/log""myTest/demo_home/blond_filter/dao""myTest/demo_home/blond_filter/model""myTest/demo_home/blond_filter/util"
)type playerService struct {
}var PlayerService = new(playerService)func (s *playerService) FindById(id int64) (*model.Player, error) { query blond filter//if !util.CheckExist(id) {//	return nil, nil//}//query redisplayer, err := util.PlayerCache.GetById(id)if err != nil {return nil, err}if player != nil {return player, nil}//query db and cache resultp, err := dao.PlayerDao.GetById(id)if err != nil {log.Errorf("%v", err)return nil, err}if p != nil {err = util.PlayerCache.Put(p)if err != nil {log.Errorf("%v", err)}return p, nil}return p, nil
}
⑥blond_filter/controller/player_controller.go
package controllerimport ("encoding/json""github.com/kataras/iris/v12""github.com/kataras/iris/v12/mvc""myTest/demo_home/blond_filter/service""net/http""strconv"
)type PlayerController struct {Ctx iris.Context
}func (p *PlayerController) BeforeActivation(b mvc.BeforeActivation) {b.Handle("GET", "/find/{id}", "FindById")
}func (p *PlayerController) FindById() mvc.Result {defer p.Ctx.Next()pId := p.Ctx.Params().Get("id")id, err := strconv.ParseInt(pId, 10, 64)if err != nil {return mvc.Response{Code:        http.StatusBadRequest,Content:     []byte(err.Error()),ContentType: "application/json",}}player, err := service.PlayerService.FindById(id)if err != nil {return mvc.Response{Code:        http.StatusInternalServerError,Content:     []byte(err.Error()),ContentType: "application/json",}}marshal, err := json.Marshal(player)if err != nil {return mvc.Response{Code:        http.StatusInternalServerError,Content:     []byte(err.Error()),ContentType: "application/json",}}return mvc.Response{Code:        http.StatusOK,Content:     marshal,ContentType: "application/json",}
}
⑦blond_filter/util/player_cache.go

Redis缓存模块

package utilimport ("encoding/json""github.com/go-redis/redis""github.com/ziyifast/log""myTest/demo_home/blond_filter/model"redis2 "myTest/demo_home/blond_filter/redis""strconv""time"
)type playerCache struct {
}var (PlayerCache = new(playerCache)PlayerKey   = "player"
)func (c *playerCache) GetById(id int64) (*model.Player, error) {log.Infof("query redis,time:%v", time.Now().String())result, err := redis2.Client.HGet(PlayerKey, strconv.FormatInt(id, 10)).Result()if err != nil && err != redis.Nil {log.Errorf("%v", err)return nil, err}if result == "" {return nil, nil}p := new(model.Player)err = json.Unmarshal([]byte(result), p)if err != nil {log.Errorf("%v", err)return nil, err}return p, nil
}func (c *playerCache) Put(player *model.Player) error {marshal, err := json.Marshal(player)if err != nil {log.Errorf("%v", err)return err}_, err = redis2.Client.HSet(PlayerKey, strconv.FormatInt(player.Id, 10), string(marshal)).Result()if err != nil {log.Errorf("%v", err)return err}return nil
}
⑧blond_filter/main.go
package mainimport ("github.com/kataras/iris/v12""github.com/kataras/iris/v12/mvc""myTest/demo_home/blond_filter/controller"
)func main() {//pg.Engine.Sync(new(model.Player))app := iris.New()pMvc := mvc.New(app.Party("player"))pMvc.Handle(new(controller.PlayerController))//util.InitBlondFilter()app.Listen(":9999", nil)
}
演示

我们在请求到达之后,先去查询Redis,如果Redis没有则去查询Postgres,但如果此时有黑客恶意查询压根不合法的数据。就会导致在Redis一直查不到数据而不断请求Postgres。

  • 导致Postgres负载过高
  1. 请求不存在的用户
    在这里插入图片描述
  2. 查看
    在这里插入图片描述

3.3 添加布隆过滤器(通过Redis bitmap实现)

新增布隆过滤器,加在Redis之前。

  • 请求流程:请求 - 布隆过滤器 - Redis - 数据库
①blond_filter/util/check_blond_util.go

实现简易版hashCode。

  • 为了避免hash冲突,我们可以通过多个hash函数进行映射,比如:将player:1982分别通过多个hash函数映射到多个offset。在查询时,就需要判断是否映射的所有的offset都存在。(一个hash函数冲突概率可能很高,但是通过不同多个hash进行映射,大幅降低冲突概率)
package utilimport ("fmt""github.com/ziyifast/log""math""myTest/demo_home/blond_filter/redis"
)var base = 1 << 32// achieve blond filter
// 1. calculate the hash of key
// 2. preload the players data
func InitBlondFilter() {//get hashCodekey := fmt.Sprintf("%s%d", redis.PlayerPrefix, 1)hashCode := int(math.Abs(float64(getHashCode(key))))//calculate the offsetoffset := hashCode % base_, err := redis.Client.SetBit(key, int64(offset), 1).Result()if err != nil {panic(err)}
}func getHashCode(str string) int {var hash int32 = 17for i := 0; i < len(str); i++ {hash = hash*31 + int32(str[i])}return int(hash)
}func CheckExist(id int64) bool {key := fmt.Sprintf("%s%d", redis.PlayerPrefix, id)hashCode := int(math.Abs(float64(getHashCode(key))))offset := hashCode % baseres, err := redis.Client.GetBit(key, int64(offset)).Result()if err != nil {log.Errorf("%v", err)return false}log.Infof("%v", res)return res == 1
}
②blond_filter/service/player_service.go

在查询Redis之前,先去查询布隆过滤器是否有数据

package serviceimport ("github.com/ziyifast/log""myTest/demo_home/blond_filter/dao""myTest/demo_home/blond_filter/model""myTest/demo_home/blond_filter/util"
)type playerService struct {
}var PlayerService = new(playerService)func (s *playerService) FindById(id int64) (*model.Player, error) {// query blond filterif !util.CheckExist(id) {log.Infof("the player does not exist in the blond filter,return it!!! ")return nil, nil}//query redisplayer, err := util.PlayerCache.GetById(id)if err != nil {return nil, err}if player != nil {return player, nil}//query db and cache resultp, err := dao.PlayerDao.GetById(id)if err != nil {log.Errorf("%v", err)return nil, err}if p != nil {err = util.PlayerCache.Put(p)if err != nil {log.Errorf("%v", err)}return p, nil}return p, nil
}
③blond_filter/main.go
package mainimport ("github.com/kataras/iris/v12""github.com/kataras/iris/v12/mvc""myTest/demo_home/blond_filter/controller""myTest/demo_home/blond_filter/util"
)func main() {//pg.Engine.Sync(new(model.Player))app := iris.New()pMvc := mvc.New(app.Party("player"))pMvc.Handle(new(controller.PlayerController))util.InitBlondFilter()app.Listen(":9999", nil)
}
演示
  1. 请求不存在的用户
    在这里插入图片描述
  2. 查看:已经被布隆过滤器拦截,恶意请求不会打到Redis和Postgres
    在这里插入图片描述

如果查询存在的数据,当布隆过滤器中包含时,则会继续查询Redis和Postgres,查看数据是否真的存在。(因为存在Hash冲突,导致可能误判。)

  • 比如id=1982与id=28算出来的hash值一致,但其实只有28存在Redis。这时我们通过hash值查询1982,bitmap对应offset返回值为表示存在值,但其实这时Redis中只有28的数据。因此我们要继续向下查询看Redis和Postgres是否真的存在1982的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/730384.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对simplex算法的时间复杂度进行分析

对于simplex算法,如果每进行一次pivot变换,目标函数所得到的结果都会有可能出现增加的情况,所以得到的结论中,可以肯定它的值是一定不会出现减少的情况的,每次从目标函数中找到一个系数大于0的变量,然后再在约束条件中选取能够让它的增值最少的那个来继续进行pivot变换。…

linux kernel物理内存概述(五)

目录 概述 一、快速路径分配 1、get_page_from_freelist 2、rmqueue()函数 二、慢速路径分配 1、分配流程 三、direct_compact 概述 物理内存分配步骤 1、初始化,参数初始化 2、内存充足&#xff0c;快速分配 get_page_from_freelist 3、内存压力大&#xff0c;慢速…

类和对象-C++运算符重载

#include <iostream> #include <string> using namespace std;class Person { public:Person(int age){m_Agenew int (age);}~Person(){if(m_Age!NULL){delete m_Age;m_AgeNULL;}}//重载 赋值运算符Person& operator (Person &p){//编译器提供深拷贝//m_Ag…

嵌入式软件开发工程师如何提高C语言编码技能?

嵌入式软件开发工程师如何提高C语言编码技能&#xff1f; 在开始前我分享下我的经历&#xff0c;我刚入行时遇到一个好公司和师父&#xff0c;给了我机会&#xff0c;一年时间从3k薪资涨到18k的&#xff0c; 我师父给了一些 电气工程师学习方法和资料&#xff0c;让我不断提升…

vSphere 8考试认证题库 2024最新(VCP 8.0版本)

VMware VCP-DCV&#xff08;2V0-21.23&#xff09;认证考试题库&#xff0c;已全部更新&#xff0c;答案已经完成校对&#xff0c;完整题库请扫描上方二维码访问。正常考可以考到450分以上&#xff08;满分500分&#xff0c;300分通过&#xff09; An administrator is tasked …

echarts 模拟时间轴播放效果

使用echarts柱状图模拟时间轴播放控制。开始/暂停/快进/慢进/点选 代码可直接放echart官方示例执行 let start new Date(2024-01-01); let end new Date(2024-01-10); let diff end - start; let dotLen 200;let data [start, end]; option {color: [#3398DB],tooltip: …

论文解读:Rectifying the Shortcut Learning of Background for Few-Shot Learning

文章汇总 问题&动机&解决方法 图像背景是一种有害知识的来源&#xff0c;这是少数镜头学习模型容易吸收的(问题) 通过在训练和评估中提取图像中的前景目标而不需要任何额外的监督来解决这个问题(动机) 在训练和评估时将模型的注意力吸引到图像前景上(方法) 摘要 …

Day25:安全开发-PHP应用文件管理模块包含上传遍历写入删除下载安全

目录 PHP文件操作安全 文件包含 文件删除 文件编辑 文件下载 云产品OSS存储对象去存储文件(泄漏安全) 思维导图 PHP知识点 功能&#xff1a;新闻列表&#xff0c;会员中心&#xff0c;资源下载&#xff0c;留言版&#xff0c;后台模块&#xff0c;模版引用&#xff0c;框…

关于出国留学和考研比较----以本人双非跨考计算机为例

文章目录 中心论点国内就业现状勿让旧认知害了自己那出国留学真的一无是处了吗?1. 藤校仍旧是具有极高价值2. 时间成本低3. 研究生一定比单纯的本科找工作强!4. 很多人说出国读博好,可以无脑入,真是这样吗? 中心论点 如果在选择出国留学还是国内考研的最终核心诉求都是有更好…

向量的内积、长度、正交性

目录 向量的内积 向量的长度&#xff08;模&#xff09; 标准正交基 标准正交化 正交矩阵 向量的内积 向量的长度&#xff08;模&#xff09; 标准正交基 标准正交化 正交矩阵

JavaWeb——014SpringBoot原理(配置优先级、Bean管理、SpringBoot原理)

SpingBoot原理 目录 SpingBoot原理1. 配置优先级2. Bean管理2.1 获取Bean2.2 Bean作用域2.3 第三方Bean 3. SpringBoot原理3.1 起步依赖3.2 自动配置3.2.1 概述3.2.2 常见方案3.2.2.1 概述3.2.2.2 方案一3.2.2.3 方案二 3.2.3 原理分析3.2.3.1 源码跟踪3.2.3.2 Conditional 3.2…

超市小程序有哪些功能 怎么制作

超市小程序是非常有用的工具&#xff0c;可以帮助超市提升用户体验&#xff0c;提高销售额。下面我们来看一下超市小程序可以具备哪些功能&#xff0c;以及如何制作一个高效的超市小程序。 1. **商品展示与搜索功能**&#xff1a;用户可以浏览超市的商品信息&#xff0c;包括价…

同等学力申硕专业介绍——管理学硕士

同等学力申硕的专业很多。 目前有十三大门类&#xff0c;分别是医学、法学、管理学、工学、教育学、经济学、艺术学、文学、历史学、理学、哲学、农学、军事学等&#xff0c;每个大门类中都有很多的细分专业。 今天为大家介绍同等学力申硕专业——管理学。 专业介绍 管理学是…

【学习记录】PointLIO代码 update_iterated_dyn_share_modified 中函数指针的用法

最近在看PointLio的代码&#xff0c;有一部分看了半天没看懂&#xff0c;学习整理如下。 1、PointLio在迭代卡尔曼部分的代码 在esekfom.hpp中&#xff0c;有部分代码如下&#xff1a; void init_dyn_share_modified(processModel f_in, processMatrix1 f_x_in, measurement…

上班族真香副业:工资4500,靠steam游戏搬砖项目月入过w

steam游戏搬砖项目已经存在好多年了&#xff0c;这个项目比较冷门且能持续稳定盈利&#xff0c;是一个非常不错的项目。即使你没玩过steam游戏也没关系&#xff0c;这个steam游戏搬砖项目既不需要你会玩游戏&#xff0c;也不需要你懂英语。 steam游戏搬砖项目的盈利点在汇率差和…

Python的数据库编程基础知识

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;如果停止&#xff0c;就是低谷&#xf…

【代码随想录算法训练营Day29】 491.递增子序列;46.全排列;47.全排列 II

文章目录 ❇️Day 29 第七章 回溯算法 part05✴️今日内容❇️491.递增子序列自己的思路随想录思路自己的代码 ❇️46.全排列思路代码流程 ❇️47.全排列 II思路代码 ❇️Day 29 第七章 回溯算法 part05 ✴️今日内容 491.递增子序列46.全排列47.全排列 II ❇️491.递增子序…

【性能测试】Jmeter+InfluxDB+Grafana 搭建性能监控平台

一、背景 为什么要搭建性能监控平台&#xff1f; 在用 Jmeter 获取性能测试结果的时候&#xff0c;Jmeter自带的测试报告如下&#xff1a; 这个报告有几个很明显的缺点&#xff1a; 只能自己看&#xff0c;无法实时共享&#xff1b;报告信息的展示比较简陋单一&#xff0c;不…

在外包公司搞了2年,出来技术都没了...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了2年的的功能…

网络工程师笔记9

动态路由 RIP路由协议 配置简单 易于维护 适用于小型网络 周期性是30s发一次