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

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

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

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

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

基本命令

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

误判率

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

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

默认的error_rate是0.01,initial_size是100。

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

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

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的数据

开始搭建

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

配置文件

主库的配置文件

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

从库的配置文件

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

启动两个mysql容器

启动主库容器

3.启动主库容器(挂载外部目录,端口映射成33306,密码设置为root123456)
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

启动从库容器

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

创建用户并授权

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

主从配置

6.0配置详解
/*
change master to
master_host='MySQL主服务器IP地址',
master_port=端口号,
master_user='之前在MySQL主服务器上面创建的用户名',
master_password='之前创建的密码',
master_log_file='MySQL主服务器状态中的二进制文件名'(上条命令中有),
master_log_pos='MySQL主服务器状态中的position值';
*/
6.1连接从库,配置连接主库
docker exec -ti mysql-slave /bin/bash
mysql
6.2输入命令
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;
6.3启用从库
start slave;
6.4查看从库状态
show slave status\G;
这两个是yes表示配成功(可能要等待一会查看)
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

测试

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

Django实现读写分离

0.上面的主从搭建好
1.在setting中配置
DATABASES = {
# 主库
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 't1',
'USER': 'hkw',
'PASSWORD': 'root123456',
'HOST': '10.0.0.10',
'PORT': 7777,
},
# 从库
'mysql_slave': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 't1',
'USER': 'hkw',
'PASSWORD': 'root123456',
'HOST': '10.0.0.10',
'PORT': 7778,
},
}
2.手动指定使用的主库还是从库(默认不写就是default)
写库(主)
res=models.Book.objects.using('default').create(name='mysql_test', age=22)
读库(从)
res=models.Book.objects.using('mysql_slave').all()
3.自动指定(写router和配置setting)
3.0写个类
class Router:
def db_for_read(self, model, **hints):
print('read', model, hints)
return 'mysql_slave'
def db_for_write(self, model, **hints):
# model, hints:model对象,表创建的实例对象
print('write', model, hints)
return 'default'
3.1在setting中注册(类的导入路径)
DATABASE_ROUTERS = ['extension.models.master_and_slave.Router', ]
4.以后只要是写操作,就会用主库default,只要是读操作自动去从库mysql_slave
5.更细粒度(分库分表,只有大数据量大并发的时候会用到)
class Router:
def db_for_read(self, model, **hints):
if model._meta.model_name == 'book':
return 'mysql_slave'
else:
return 'default'
def db_for_write(self, model, **hints):
return 'default' # Django migrate报错处理:
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.')
解决:
mysql> SET GLOBAL binlog_format = 'ROW';
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. 【转帖】一道面试题:JVM老年代空间担保机制

    面试问题 昨天面试的时候,面试官问的问题: 什么是老年代空间担保机制?担保的过程是什么? 老年代空间担保机制是谁给谁担保? 为什么要有老年代空间担保机制?或者说空间担保机制的目的是什么? 如果没有老年 ...

  2. [转帖]使用GCC编译器实测兆芯KX-U6780A的SPEC CPU2006成绩

      https://baijiahao.baidu.com/s?id=1722775453962904303 兆芯KX-U6780A是一款8核2.7GHz的使用x86/AMD64指令集(架构)的国产C ...

  3. Find 查找并且展示最近24小时内创建的文件信息

    1. 命令为: find /gscloud/tools/patchinstall/patchfiles/ -maxdepth 1 -mtime 1 |cut -c40- >/deploy/pat ...

  4. Redis labs 的安装

    Install and setup This guide shows how to install Redis Enterprise Software, which includes several ...

  5. CentOS8 的容器运行时解决中文乱码问题的一个思路

    首先说明一下 CentOS7和CentOS8关于locale语言文件的位置是不一样的. Docker pull centos 拉取下来的镜像 一般是不带中文语言包的. 简单方法是在 CentOS之后安 ...

  6. ChaosMesh 半离线安装过程

    说明 所谓半离线值得是公司拉不下镜像来 !-_-! 都是学习的官网资料https://chaos-mesh.org/docs 镜像获取 其实镜像获取很容易. 找一个能上外网的VPC就可以了. mkdi ...

  7. Ant Design Vue中Table对齐方式显示省略号

    Ant Design Vue中Table对齐方式显示省略号 <template> <!-- bordered 表示表格中的边框 pagination="false" ...

  8. 【代码分享】使用 terraform, 在 ZeroSSL 上申请托管在 cloudflare 上的域名对应的证书

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 接上一篇:<使用 terraform, 在 Let' ...

  9. 如何在proto3中用上golang对应的interface{}类型

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 首先,我希望所有golang中用于http请求响应的结构, ...

  10. 谈谈Java中的反射机制

    前言 在使用框架进行开发时,我们的开发速度大大提升.我们感叹于它的神奇之处,我们使用它的时候,也要知道其"灵魂".正所谓,无反射,不框架,框架的灵魂就是反射. 另外,我们在ecli ...