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 pg

import (
	"fmt"
	_ "github.com/lib/pq"
	"github.com/ziyifast/log"
	"time"
	"xorm.io/xorm"
)

var Cli *xorm.Engine

const (
	host     = "localhost"
	port     = 5432
	user     = "postgres"
	password = "postgres"
	dbName   = "postgres"
)

var Engine *xorm.Engine

func 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 = engine
	log.Infof("connect postgresql success")
}
②blond_filter/redis/redis.go
package redis

import "github.com/go-redis/redis"

var (
	Client       *redis.Client
	PlayerPrefix = "player:"
)

func init() {
	Client = redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "", // no password set
		DB:       0,  // use default DB
	})
}
③blond_filter/model/player.go
package model

type 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 dao

import (
	"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 service

import (
	"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 redis
	player, err := util.PlayerCache.GetById(id)
	if err != nil {
		return nil, err
	}
	if player != nil {
		return player, nil
	}
	//query db and cache result
	p, 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 controller

import (
	"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 util

import (
	"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 main

import (
	"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 util

import (
	"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 hashCode
	key := fmt.Sprintf("%s%d", redis.PlayerPrefix, 1)
	hashCode := int(math.Abs(float64(getHashCode(key))))
	//calculate the offset
	offset := hashCode % base
	_, err := redis.Client.SetBit(key, int64(offset), 1).Result()
	if err != nil {
		panic(err)
	}
}

func getHashCode(str string) int {
	var hash int32 = 17
	for 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 % base
	res, 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 service

import (
	"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) {
		log.Infof("the player does not exist in the blond filter,return it!!! ")
		return nil, nil
	}

	//query redis
	player, err := util.PlayerCache.GetById(id)
	if err != nil {
		return nil, err
	}
	if player != nil {
		return player, nil
	}
	//query db and cache result
	p, 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 main

import (
	"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的数据。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGLM:CPU版本如何安装和部署使用

前段时间想自己部署一个ChatGLM来训练相关的物料当做chatgpt使用&#xff0c;但是奈何没有gpu机器&#xff0c;只能使用cpu服务器尝试使用看看效果 我部署的 Chinese-LangChain 这个项目&#xff0c;使用的是LLM&#xff08;ChatGLM&#xff09;embedding(GanymedeNil/text2vec…

【活动】2024年AI辅助研发:深度变革与无限潜力

作为一名前端程序员&#xff0c;深入探讨2024年AI在软件研发领域的应用趋势及其影响&#xff0c;我们可以看到一场引人注目的转型正在发生。AI辅助研发对于前端开发而言&#xff0c;不仅意味着效率的飞跃&#xff0c;更是在用户体验设计、代码编写、性能优化、项目管理等诸多方…

什么是Java内存模型

当问到 Java 内存模型的时候&#xff0c;一定要注意&#xff0c;Java 内存模型&#xff08;Java Memory Model&#xff0c;JMM&#xff09;它和 JVM 内存布局&#xff08;JVM 运行时数据区域&#xff09;是不一样的&#xff0c;它们是两个完全不同的概念。 1.为什么要有 Java …

Windows按文件类型指定默认应用程序方法,.py文件设置默认打开程序实例演示

有两种方法可以设置按文件类型指定默认应用。 一个是系统的设置&#xff0c;但是部分类型里面是没有的&#xff0c;这种就要通过注册表来添加。 如果没有的话&#xff0c;通过 winR 打开运行&#xff0c;然后输入 regedit 打开注册表&#xff0c;在 计算机\HKEY_CLASSES_ROO…

防御保护--IPSEC VPPN实验

实验拓扑图 实验背景&#xff1a;FW1和FW2是双机热备的状态。 实验要求&#xff1a;在FW5和FW3之间建立一条IPSEC通道&#xff0c;保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 IPSEC VPPN实验配置&#xff08;由于是双机热备状态&#xff0c;所以FW1和FW2只需要配置FW1…

实景三维逛景区,VR智慧景区打造云上旅游新体验

哈尔滨旅游的爆火&#xff0c;让其他地方的文旅景区宣传也纷纷发力。VR智慧景区将传统的旅游体验从线下拓展至线上&#xff0c;为游客带来不一样的旅行体验&#xff0c;人们可以提前在手机上沉浸式体验景区的真实环境&#xff0c;避免实地游玩踩雷&#xff0c;也为人们节省了旅…

COMSOL中使用自定义函数

目录 函数的用法 &#xff08;1&#xff09;解析函数 &#xff08;2&#xff09;插值函数 &#xff08;3&#xff09;分段函数 &#xff08;4&#xff09;高斯脉冲 &#xff08;5&#xff09;斜坡函数 &#xff08;6&#xff09;阶跃函数 &#xff08;7&#xff09;矩形…

JAVA实战开源项目:电子元器件管理系统(Vue+SpringBoot)

目录 一、摘要1.1 项目简介1.2 项目录屏 二、研究内容三、界面展示3.1 登录&注册&主页3.2 元器件单位模块3.3 元器件仓库模块3.4 元器件供应商模块3.5 元器件品类模块3.6 元器件明细模块3.7 元器件类型模块3.8 元器件采购模块3.9 元器件领用模块3.10 系统基础模块 四、…

BlackHole

BlackHole 文章目录 BlackHole一、关于 BlackHole功能描述 二、安装、卸载安装方式一&#xff1a;下载安装器方式二&#xff1a;使用 Homebrew 安装 卸载方式一&#xff1a;使用卸载器方式二&#xff1a;手动卸载 三、用户使用指南1、Logic Pro X2、GarageBand3、Reaper4、录制…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…

2024 批量下载公众号文章内容/阅读数/在看数/点赞数/留言数/粉丝数导出pdf文章备份(带留言):公众号爱在冰川近3000篇历史文章在线查看,找文章方便了

关于公众号文章批量下载&#xff0c;我之前写过很多文章&#xff1a; 视频更新版&#xff1a;批量下载公众号文章内容/话题/图片/封面/音频/视频&#xff0c;导出html&#xff0c;pdf&#xff0c;excel包含阅读数/点赞数/留言数 2021陶博士2006/caoz的梦呓/刘备我祖/六神读金…

自研cloud框架专题–web模块(三)

项目特点一:框架集成 1.引入核心依赖2.配置相关功能 二:功能介绍 1.swagger支持并提供swagger快速配置2.knife增强swagger支持3.全局请求参数校验(Validation)支持4.字段脱敏支持5.默认jackson序列化6.xss,cors支持7.访问日志支持8.全局异常处理,统一返回结果9.系统关键及常用信…

【Cesium for Supermap】S3MTiles图层box裁剪

效果图&#xff1a; 代码&#xff1a; let viewer new Cesium.Viewer(cesiumContainer);// 添加SuperMap iServer发布的S3M缓存服务let promise viewer.scene.addS3MTilesLayerByScp("http://www.supermapol.com/realspace/services/3D-BIMbuilding/rest/realspace/data…

操作系统体系结构(不是很重点)

目录 一. 大内核与微内核二. 分层结构, 模块化和外核2.1 分层结构2.2 模块化2.3 宏内核与微内核 三. 外核 \quad 一. 大内核与微内核 \quad 由于对系统资源进行管理的功能不会直接涉及硬件, 所以有的就没有把这个功能放在内核里面 注意: 考试的时候不要写变态, 要写CPU状态的转…

数据结构---复杂度(1)

1.时间复杂度 衡量算法的好坏&#xff0c;使用大写的o来表示时间复杂度&#xff0c;通俗的讲&#xff0c;就是一个算法执行的次数&#xff1b; 时间复杂度就是数学里面的函数表达式&#xff1b;本质上是一个函数&#xff1b; 下面举几个例子&#xff1a; (1)这里的执行次数是…

Redis 之七:穿透、击穿、雪崩

&#xff08;本内容部分来自知乎网等网络&#xff09; Redis 缓存的使用&#xff0c;极大的提升了应用程序的性能和效率&#xff0c;特别是数据查询方面。但同时&#xff0c;它也带来了一些问题。其中&#xff0c;最要害的问题&#xff0c;就是数据的一致性问题&#xff0c;从严…

java 数据结构二叉树

目录 树 树的概念 树的表示形式 二叉树 两种特殊的二叉树 二叉树的性质 二叉树的存储 二叉树的基本操作 二叉树的遍历 二叉树的基本操作 二叉树oj题 树 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次…

3D资产管理

3D 资产管理是指组织、跟踪、优化和分发 3D 模型和资产以用于游戏、电影、AR/VR 体验等各种应用的过程。 3D资产管理也称为3D内容管理。 随着游戏、电影、建筑、工程等行业中 3D 内容的增长&#xff0c;实施有效的资产管理工作流程对于提高生产力、减少错误、简化工作流程以及使…

前端实现单点登录

简单概括就是&#xff0c;一个系统登录&#xff0c;跳转多个系统&#xff0c;其他系统不需要再登录&#xff0c;直接进入页面 登录的系统 <template><div><div class"content"><div class"item" v-for"(item,index) in list&q…

【wine】winetricks部署一个windows xp 应用程序的基础运行环境

AI 的资料 我想基于wintricks的“安装windows dll 或组件”功能&#xff0c;安装一个基础的windows xp运行环境&#xff0c;应当安装那些项目&#xff1f; 为了基于winetricks创建一个基础的Windows XP运行环境&#xff0c;您应该考虑安装以下项目以提高兼容性&#xff1a; 核…