Redis实现布隆过滤器

前言

布隆过滤器使用场景

比如有如下几个需求:

  1. 原本有10亿个号码,现在又来了10万个号码,要快速准确判断这10万个号码是否在10亿个号码库中?

  解决办法一:将10亿个号码存入数据库中,进行数据库查询,准确性有了,但是速度会比较慢。

  解决办法二:将10亿号码放入内存中,比如Redis缓存中,这里我们算一下占用内存大小:10亿*8字节=8GB,通过内存查询,准确性和速度都有了,但是大约8gb的内存空间,挺浪费内存空间的。

  1. 接触过爬虫的,应该有这么一个需求,需要爬虫的网站千千万万,对于一个新的网站url,我们如何判断这个url我们是否已经爬过了?

    解决办法还是上面的两种,很显然,都不太好。

  2. 同理还有垃圾邮箱的过滤。

    那么对于类似这种,大数据量集合,如何准确快速的判断某个数据是否在大数据量集合中,并且不占用内存,布隆过滤器应运而生了。

布隆过滤器简介

一种数据结构,是由一串很长的二进制向量组成,可以将其看成一个二进制数组。既然是二进制,那么里面存放的不是0,就是1,但是初始默认值都是0。

它是一种 space efficient 的概率型数据结构,用于判断一个元素是否在集合中。

当布隆过滤器说,某个数据存在时,这个数据可能不存在;当布隆过滤器说,某个数据不存在时,那么这个数据一定不存在。

大致数据结构图

布隆过滤器优缺点

  • 优点很明显,二进制组成的数组,占用内存极少(节省内存),并且插入和查询速度都足够快。

  • 随着数据的增加,误判率会增加;还有无法判断数据一定存在;另外还有一个重要缺点,无法删除数据。(布隆过滤器是不支持删除数据的,如果需要删除数据则需要重建缓存信息。)

  • 布隆过滤器使用多次hash计算,也会存在hash冲突情况。这会导致一个问题,当检测过滤器是否存在数据时,检测到存在,实际不一定存在。相同的检测到不存在,则缓存中一定不存在。

布隆过滤器原理

BloomFilter的算法是,首先分配一块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0。

加入元素时,采用 k 个相互独立的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1。

检测 key 是否存在,仍然用这 k 个 Hash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。

如下图所示

哈希函数会出现碰撞,所以布隆过滤器会存在误判。

这里的误判率是指,BloomFilter 判断某个 key 存在,但它实际不存在的概率,因为它存的是 key 的 Hash 值,而非 key 的值。

所以有概率存在这样的 key,它们内容不同,但多次 Hash 后的 Hash 值都相同。

对于 BloomFilter 判断不存在的 key ,则是 100% 不存在的,反证法,如果这个 key 存在,那它每次 Hash 后对应的 Hash 值位置肯定是 1,而不会是 0。布隆过滤器判断存在不一定真的存在。

为什么不允许删除元素呢?

删除意味着需要将对应的 k 个 bits 位置设置为 0,其中有可能是其他元素对应的位。

因此 remove 会引入 false negative,这是绝对不被允许的。

文档

https://redis.io/docs/stack/bloom/

https://github.com/RedisBloom/RedisBloom/

扩展

布谷鸟过滤器(为了解决布隆过滤器不能删除元素的问题)

操作

docker安装

docker详解:https://www.cnblogs.com/hkwJsxl/p/17164139.html

RedisBloom需要先进行安装,推荐使用Docker进行安装,简单方便(或使用直接编译安装)

  1. 1.拉取镜像
  2. docker pull redislabs/rebloom:latest
  3. 1.1或使用阿里云上的(速度会快点)
  4. docker pull registry.cn-hangzhou.aliyuncs.com/hankewei/hkwimage:redisbloom2.4.5
  5. docker tag registry.cn-hangzhou.aliyuncs.com/hankewei/hkwimage:redisbloom2.4.5 redislabs/rebloom:2.4.5
  6. docker rmi registry.cn-hangzhou.aliyuncs.com/hankewei/hkwimage:redisbloom2.4.5
  7. 2.运行
  8. docker run -di -p 6666:6379 --name redis-redisbloom redislabs/rebloom:2.4.5
  9. 3.进入
  10. docker exec -it redis-redisbloom /bin/bash
  11. 4.登录到Redis
  12. redis-cli
  13. # 查看Redis模块
  14. 127.0.0.1:6379> info Modules
  15. # Modules
  16. module:name=bf,ver=20206,api=1,filters=0,usedby=[],using=[],options=[]

第二种安装方式(编译安装)

当然也可以直接编译进行安装

  1. 1.git克隆
  2. git clone https://github.com/RedisBloom/RedisBloom.git
  3. 2.解压缩
  4. tar -zxf RedisBloom-2.4.5.tar
  5. 3.进入文件
  6. cd RedisBloom-2.4.5
  7. 4.编译,会生成一个rebloom.so文件
  8. make
  9. 5.安装集成
  10. 需要修改 redis.conf 文件,新增 loadmodule配置,并重启 Redis
  11. loadmodule /home/RedisBloom-2.4.5/redisbloom.so
  12. 如果是集群,则每个实例的配置文件都需要加入配置。
  13. 6.启动
  14. redis-server /home/redis/conf/redis.conf
  15. 如果没有更改配置文件,需要指定参数
  16. redis-server --loadmodule /home/RedisBloom-2.4.5/redisbloom.so
  17. redis-cli -h 127.0.0.1 -p 6379

此模块不仅仅实现了布隆过滤器,还实现了 CuckooFilter(布谷鸟过滤器),以及 TopK 功能。CuckooFilter 是在 BloomFilter 的基础上主要解决了BloomFilter不能删除的缺点。先来看看 BloomFilter,后面介绍一下 CuckooFilter

基本命令

  1. bf.add 添加元素到布隆过滤器
  2. bf.madd 添加多个元素到布隆过滤器,bf.add只能添加一个
  3. bf.exists 判断元素是否在布隆过滤器
  4. bf.mexists 判断多个元素是否在布隆过滤器
添加数据
  1. # 单个添加
  2. 127.0.0.1:6379> bf.add bfkey 1
  3. (integer) 1
  4. 127.0.0.1:6379> bf.add bfkey 2
  5. (integer) 1
  6. 127.0.0.1:6379> bf.add bfkey 3
  7. (integer) 1
  8. 127.0.0.1:6379> bf.add bfkey 3
  9. (integer) 0
  10. # 批量添加
  11. 127.0.0.1:6379> bf.madd bfkey 4 5 6
  12. 1) (integer) 1
  13. 2) (integer) 1
  14. 3) (integer) 1
  15. # 通过添加会发现,如果元素已经存在,则返回的是0值。
检测数据
  1. # 检测单个值
  2. 127.0.0.1:6379> bf.exists bfkey 1
  3. (integer) 1
  4. 127.0.0.1:6379> bf.exists bfkey 2
  5. (integer) 1
  6. 127.0.0.1:6379> bf.exists bfkey 10
  7. (integer) 0
  8. # 批量检测
  9. 127.0.0.1:6379> bf.mexists bfkey 1 2 3 10
  10. 1) (integer) 1
  11. 2) (integer) 1
  12. 3) (integer) 1
  13. 4) (integer) 0
  14. # 通过检测会发现,如果元素不存在,则返回的是0值。

误判率

布隆过滤器在第一次add的时候自动创建基于默认参数的过滤器,Redis还提供了自定义参数的布隆过滤器。

在add之前使用bf.reserve指令显式创建,其有3个参数,keyerror_rateinitial_size,错误率越低,需要的空间越大,error_rate表示预计错误率,initial_size参数表示预计放入的元素数量,当实际数量超过这个值时,误判率会上升,所以需要提前设置一个较大的数值来避免超出。

默认的error_rate是0.01,initial_size是100。

利用布隆过滤器减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。

  1. # 误判率测试
  2. import redis
  3. client = redis.Redis(host='10.0.0.10', port=6666)
  4. size = 100000
  5. count = 0
  6. client.execute_command("bf.reserve", "hkw", 0.001, size) # 如果没有这一行,误判率会高很多(error rate: 1.096%)
  7. for i in range(size):
  8. client.execute_command("bf.add", "hkw", "xxx%d" % i)
  9. result = client.execute_command("bf.exists", "hkw", "xxx%d" % (i + 1))
  10. if result == 1:
  11. print(i)
  12. count += 1
  13. print("size: {} , error rate: {}%".format(size, round(count / size * 100, 5)))
  14. """
  15. 结果:
  16. 85547
  17. 91103
  18. 93019
  19. size: 100000 , error rate: 0.003%
  20. """

MySQL主从搭建(Docker实现)

docker详解:https://www.cnblogs.com/hkwJsxl/p/17164139.html

主从同步的流程或原理

  1. master会将变动记录到二进制日志里面
  2. master有一个I/O线程将二进制日志发送到slave
  3. slave有一个I/O线程把master发送的二进制写入到relay日志里面
  4. slave有一个SQL线程,按照relay日志处理slave的数据

开始搭建

  1. 0.创建目录
  2. mkdir -p /home/mysql11/data/ /home/mysql11/conf /home/mysql11/logs/ /home/mysql22/data/ /home/mysql22/conf /home/mysql22/logs/

配置文件

主库的配置文件

  1. 1.主库的配置(主从server-id不能相同)
  2. vim /home/mysql11/conf/my.cnf
  3. [mysqld]
  4. # 主服务器唯一ID
  5. server-id=1
  6. # 启用二进制日志
  7. log-bin=mysql-bin
  8. # 设置需要复制的数据库,需要复制的主数据库名字(默认同步所有数据库)
  9. # binlog-do-db=testdb
  10. # 设置logbin格式
  11. binlog_format=STATEMENT

从库的配置文件

  1. 2.从库的配置
  2. vim /home/mysql22/conf/my.cnf
  3. [mysqld]
  4. #从服务器唯一ID
  5. server-id=2
  6. #日志
  7. log-bin=mysql-slave-bin
  8. #启用中继日志
  9. relay-log=mysql-relay

启动两个mysql容器

启动主库容器

  1. 3.启动主库容器(挂载外部目录,端口映射成33306,密码设置为root123456
  2. docker run -di -v /home/mysql11/data/:/var/lib/mysql -v /home/mysql11/conf:/etc/mysql/conf.d -v /home/mysql11/logs/:/var/log/mysql -p 7777:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD=root123456 mysql:mysql8.0.32

启动从库容器

  1. 4.启动从库容器(挂载外部目录,端口映射成33307,密码设置为root123456
  2. docker run -di -v /home/mysql22/data/:/var/lib/mysql -v /home/mysql22/conf:/etc/mysql/conf.d -v /home/mysql22/logs/:/var/log/mysql -p 7778:3306 --name mysql-slave -e MYSQL_ROOT_PASSWORD=root123456 mysql:mysql8.0.32

报错处理

  1. # 错误信息
  2. 1.
  3. 2023-03-04 13:48:56+00:00 [ERROR] [Entrypoint]: mysqld failed while attempting to check config
  4. command was: mysqld --privileged=true --verbose --help --log-bin-index=/tmp/tmp.Biw5OQ23f8
  5. mysqld: Can't read dir of '/etc/mysql/conf.d/' (OS errno 2 - No such file or directory)
  6. mysqld: [ERROR] Stopped processing the 'includedir' directive in file /etc/my.cnf at line 36.
  7. mysqld: [ERROR] Fatal error in defaults handling. Program aborted!
  8. 2.
  9. 2023-03-04T14:33:20.604801Z 1 [ERROR] [MY-012956] [InnoDB] Cannot allocate memory for the buffer pool
  10. 2023-03-04T14:33:20.628786Z 1 [ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error Generic error.
  11. 2023-03-04T14:33:20.637344Z 1 [ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
  12. 2023-03-04T14:33:20.669372Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
  13. 2023-03-04T14:33:20.670085Z 0 [ERROR] [MY-010119] [Server] Aborting
  14. # 注意点
  15. 1.mysql一直起不来,原因是不同的mysql版本可能目录结构不同,原先挂载的/home/mysql11/conf:/etc/mysql报错,后改为了/home/mysql11/conf:/etc/mysql/conf.d
  16. 2.log-bin=mysql-bin 这个配置不要随便更改
  17. 3.第二个报错解决:主要是Cannot allocate memory for the buffer pool
  18. 这是因为MySQL内存不足导致启动失败
  19. 查询内存的命令:free -h
  20. 解决方法:
  21. 增加swap交换空间解决问题:
  22. dd if=/dev/zero of=/swapfile bs=1M count=1024
  23. mkswap /swapfile
  24. swapon /swapfile
  25. 增加自动挂载:
  26. sudo vim /etc/fstab
  27. 在下面添加:
  28. /swapfile swap swap defaults 0 0
  29. 重启mysql问题解决

创建用户并授权

  1. 5.创建用户并授权
  2. 5.0进入主库中
  3. docker exec -ti mysql-master /bin/bash
  4. mysql -uroot -p
  5. root123456
  6. 5.1创建用户
  7. CREATE USER 'hkw'@'localhost' IDENTIFIED BY 'root123456';
  8. CREATE USER 'hkw'@'%' IDENTIFIED BY 'root123456';
  9. 5.2设定权限
  10. grant all privileges on *.* to 'hkw'@'localhost';
  11. grant all privileges on *.* to 'hkw'@'%';
  12. 5.3刷新权限
  13. flush privileges;
  14. 5.4查看主服务器状态,可以看到日志文件的名字,和现在处在哪个位置
  15. show master status;

主从配置

  1. 6.0配置详解
  2. /*
  3. change master to
  4. master_host='MySQL主服务器IP地址',
  5. master_port=端口号,
  6. master_user='之前在MySQL主服务器上面创建的用户名',
  7. master_password='之前创建的密码',
  8. master_log_file='MySQL主服务器状态中的二进制文件名'(上条命令中有),
  9. master_log_pos='MySQL主服务器状态中的position值';
  10. */
  11. 6.1连接从库,配置连接主库
  12. docker exec -ti mysql-slave /bin/bash
  13. mysql
  14. 6.2输入命令
  15. change master to master_host='10.0.0.10',master_port=7777,master_user='hkw',master_password='root123456',master_log_file='mysql-bin.000003',master_log_pos=0;
  16. 6.3启用从库
  17. start slave;
  18. 6.4查看从库状态
  19. show slave status\G;
  20. 这两个是yes表示配成功(可能要等待一会查看)
  21. Slave_IO_Running: Yes
  22. Slave_SQL_Running: Yes

测试

  1. 7.0测试(可有可无)
  2. navicat连一连
  3. 7.1在主库上创建数据库t1
  4. create database t1;
  5. use t1;
  6. 7.2创建表
  7. create table t1 (id int not null PRIMARY KEY AUTO_INCREMENT, name varchar(100)not null, age tinyint);
  8. 7.3插入数据
  9. insert t1 (id,name,age) values(1,'xxx',20),(2,'yyy',21),(3,'zzz',22);

Django实现读写分离

  1. 0.上面的主从搭建好
  2. 1.setting中配置
  3. DATABASES = {
  4. # 主库
  5. 'default': {
  6. 'ENGINE': 'django.db.backends.mysql',
  7. 'NAME': 't1',
  8. 'USER': 'hkw',
  9. 'PASSWORD': 'root123456',
  10. 'HOST': '10.0.0.10',
  11. 'PORT': 7777,
  12. },
  13. # 从库
  14. 'mysql_slave': {
  15. 'ENGINE': 'django.db.backends.mysql',
  16. 'NAME': 't1',
  17. 'USER': 'hkw',
  18. 'PASSWORD': 'root123456',
  19. 'HOST': '10.0.0.10',
  20. 'PORT': 7778,
  21. },
  22. }
  23. 2.手动指定使用的主库还是从库(默认不写就是default
  24. 写库(主)
  25. res=models.Book.objects.using('default').create(name='mysql_test', age=22)
  26. 读库(从)
  27. res=models.Book.objects.using('mysql_slave').all()
  28. 3.自动指定(写router和配置setting
  29. 3.0写个类
  30. class Router:
  31. def db_for_read(self, model, **hints):
  32. print('read', model, hints)
  33. return 'mysql_slave'
  34. def db_for_write(self, model, **hints):
  35. # model, hints:model对象,表创建的实例对象
  36. print('write', model, hints)
  37. return 'default'
  38. 3.1setting中注册(类的导入路径)
  39. DATABASE_ROUTERS = ['extension.models.master_and_slave.Router', ]
  40. 4.以后只要是写操作,就会用主库default,只要是读操作自动去从库mysql_slave
  41. 5.更细粒度(分库分表,只有大数据量大并发的时候会用到)
  42. class Router:
  43. def db_for_read(self, model, **hints):
  44. if model._meta.model_name == 'book':
  45. return 'mysql_slave'
  46. else:
  47. return 'default'
  48. def db_for_write(self, model, **hints):
  49. return 'default'
  50. # Django migrate报错处理:
  51. django.db.utils.OperationalError: (1665, 'Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.')
  52. 解决:
  53. mysql> SET GLOBAL binlog_format = 'ROW';
  54. mysql> show variables like 'binlog_format';

08-Redis系列之-Redis布隆过滤器,MySQL主从,Django读写分离的更多相关文章

  1. Mysql主从配置+读写分离

    Mysql主从配置+读写分离     MySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具.因此,我们首先要在系统中源码编译安装cmake工具. ...

  2. Mysql主从配置+读写分离(转)

       MySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具.因此,我们首先要在系统中源码编译安装cmake工具. 注:安装前须查看是否已经安装了 ...

  3. 使用docker 实现MySQL主从同步/读写分离

    1. 利用 docker 实现 mysql 主从同步 / 读写分离 为了保证数据的完整和安全,mysql 设计了主从同步,一个挂掉还可以用另个.最近重构论坛,想来改成主从吧.担心失误,就先拿 dock ...

  4. 聊聊Mysql主从同步读写分离配置实现

    Hi,各位热爱技术的小伙伴您们好,好久没有写点东西了,今天写点关于mysql主从同步配置的操作日志同大家一起分享.最近自己在全新搭建一个mysql主从同步读写分离数据库简单集群,我讲实际操作步骤整理分 ...

  5. Docker容器启动Mysql,Docker实现Mysql主从,读写分离

    Docker容器启动Mysql,Docker实现Mysql主从,读写分离 一.Docker文件编排 二.配置主从复制 2.1 配置master 2.2 配置slave 三.验证主从复制 3.1 mas ...

  6. docker-compose.yml样例(mysql主从+mycat读写分离)

    Docker-compose.yml文件示例 1.mysql主从复制的docker-compose.yml文件 # cat docker-compose.yml version: '2' # 这个ve ...

  7. mysql 主从同步-读写分离

    主从同步与读写分离测试 一.  实验环境(主从同步) Master                   centos 7.3              192.168.138.13 Slave     ...

  8. mysql主从同步--读写分离。

    1.mysql 安装参考 https://www.cnblogs.com/ttzzyy/p/9063737.html 2. 主mysql,从mysql 指定配置文件启动 mysqld --defaul ...

  9. 关系型数据库MySQL主从同步-读写分离

    1.环境准备 我的数据库版本是MySQL 5.6 MySQL主机至少两个实例,可以是多实例,可以是多台主机 关闭selinux,关闭防火墙等基础优化 2.安装 yum -y install make ...

  10. MySQL主从及读写分离配置

    <<MySQL主从又叫做Replication.AB复制.简单讲就是A和B两台机器做主从后,在A上写数据,B也会跟着写数据,两者数据实时同步>> MySQL主从是基于binlo ...

随机推荐

  1. [转帖]Chrome 109发布,最后一个支持Windows 7/8的版本

    https://www.163.com/dy/article/HQR3QQFD0511CUMI.html 出品 | OSC开源社区(ID:oschina2013) Google 在去年 12 月 1 ...

  2. nginx 进行目录浏览的简单配置

    1. 公司网络安全不让用vsftpd的匿名网络访问了, 没办法 只能够使用 nginx 通过http协议来处理. 2. 最简单的办法就是另外开一个nginx进程简单设置一下nginx的配置文件 wor ...

  3. ARM平台安装Docker的方法

    1. 找了一下有一个网站能够下载docker的arm的deb包可以使用 网址为: https://download.docker.com/linux/ubuntu/dists/xenial/pool/ ...

  4. zabbix监控进程和监控日志

    zabbix监控进程和监控日志 文章目录 zabbix监控进程和监控日志 一.自定义监控进程 1.新建脚本存放目录 2.修改zabbix_agentd.conf文件 3.zabbix server端进 ...

  5. date的命令使用.

    date命令的使用 1.直接用date命令显示日期时间 在命令行中输入date然后回车,显示结果"Wed Aug 7 08:58:07 CST 2019".这是系统根据设定的时区显 ...

  6. 【0基础学爬虫】爬虫基础之HTTP协议的基本原理介绍

    大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶.逆向相关文章,为实现从易到难全方位覆盖,特设[0基础学爬 ...

  7. 6.7 Windows驱动开发:内核枚举LoadImage映像回调

    在笔者之前的文章<内核特征码搜索函数封装>中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核LoadImage映像回调,在Win64环境下我们可以设置一个LoadI ...

  8. 8.9 RDTSC时钟检测反调试

    RDTSC时钟检测同样可实现反调试检测,使用时钟检测方法是利用rdtsc汇编指令,它返回至系统重新启动以来的时钟数,并且将其作为一个64位的值存入EDX:EAX寄存器中,通过运行两次rdstc指令,然 ...

  9. Vue3用户代码片段

    1.defineComponent语法 { "Print to console": { "prefix": "vue3", "bo ...

  10. 史上最大电池!小米智能家庭屏Pro 8图赏

    今天小米智能家庭屏 Pro 8正式开售,集智能家居中控,智能网关以及娱乐教育三大功能为一体,首发749元. 它是一款全新的智能生态产品中控屏,配备了7500mAh大容量电池以及通用性更好的USB Ty ...