首页 > 数据库 >08-Redis系列之-Redis布隆过滤器,MySQL主从,Django读写分离

08-Redis系列之-Redis布隆过滤器,MySQL主从,Django读写分离

时间:2023-03-05 20:48:35浏览次数:70  
标签:bf 08 Redis 布隆 Django master mysql 过滤器 home

Redis实现布隆过滤器

前言

布隆过滤器使用场景

比如有如下几个需求:

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

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

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

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

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

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

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

布隆过滤器简介

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

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

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

大致数据结构图

img

布隆过滤器优缺点

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

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

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

布隆过滤器原理

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

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

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

如下图所示

img

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

这里的误判率是指,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';

标签:bf,08,Redis,布隆,Django,master,mysql,过滤器,home
From: https://www.cnblogs.com/hkwJsxl/p/17181554.html

相关文章

  • 07-Redis系列之-双写一致性,缓存详解和优化点
    双写一致性redis和mysql数据同步方案先更新数据库,再更新缓存(一般不用)先删缓存,再更新数据库(在存数据的时候,请求来了,缓存不是最新的,一般也不用)先更新数据库,再删缓存(请求......
  • 05-Redis系列之-主从复制配置和优化,fork和aof两大阻塞
    主从复制原理一台主服务器配多台从服务器,主服务器宕机后,从服务器挑选一台顶上去。从服务器同步主服务器的数据,这个同步是单向的,并且从服务器不能设置值,否则会造成数据的......
  • 钩子函数,modelfrom组件,django中间件
    钩子函数,modelfrom组件,django中间件钩子函数需求:以上我们写的form组件中写一个注册功能,现在要求判断用户是否已经存在提供了两个自定义的校验方式; 局部钩子:校验单个字......
  • 01-Redis系列之-Redis介绍安装配置
    Redis初识Redis主要内容1redis介绍,特性,安装和配置,典型应用场景2单线程架构,5大数据类型操作,通用指令和高级API的使用3高级用法(慢查询,管道,shu发布订阅,bitmap位图,Hype......
  • 04-Redis系列之-持久化(RDB,AOF)
    持久化的作用什么是持久化redis的所有数据保存在内存中,对数据的更新将异步的保存到硬盘上持久化的实现方式快照:某时某刻数据的一个完整备份(mysql的Dump,redis的RDB)......
  • 03-Redis系列之-高级用法详解
    慢查询生命周期我们配置一个时间,如果查询时间超过了我们设置的时间,我们就认为这是一个慢查询.慢查询发生在第三阶段客户端超时不一定慢查询,但慢查询是客户端超时的一个......
  • 02-Redis系列之-架构和高级API的使用
    通用部分通用命令#1-keys#打印出所有keykeys*#打印出所有以n开头的keykeysn*#打印出所有以nam开头,第四个字母是a到z的范围keysnam[a-z]#四位长度,以n开......
  • Redis基础篇
    1、简单介绍一下Redis优点和缺点?优点:1、本质上是一个Key-Value类型的内存数据库,很像memcached2、整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据......
  • 多对多三种创建方式,django内置序列化组件(drf前身),批量操作数据,分页器思路,自定义
    多对多三种创建方式,django内置序列化组件(drf前身),批量操作数据,分页器思路,自定义分页器的使用,form组件多对多三种创建方式1.全自动创建 classBook(models.Model):......
  • springboot中redis的基本数据类型测试
    在springboot中依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artif......