第四节:多种判重方案落地(基于DB、基于缓存、基于布隆过滤器)
一. 说明
1. 常见场景
A 注册的时候手机号判重 【本节以这个为例】
B 注册的时候昵称判重
C 商品详情页请求的时候,利用对商品id判重 (商品的数量是海量的)
2 常见方案
A 可以直接去数据中查
B 本地缓存 或 redis中 同步一份 (海量数据的时候会占用内存空间)
C 数据量非常大,且重复率不是很高,使用布隆过滤器
二. 布隆过滤器复习
1 用bloomfilter过滤器防重复?一定能防止吗?
不一定,通常使用bloom快速判重,然后需要结合DB来操作。
A 只能证明一定不存在,只要一个值为0则证明一定不存在。
B 3个值都是1,可能存在,因为这个1可能是其他key生成的,这个时候再去DB中查一下。
2 bloomFilter底层是什么结构?判重的原理是什么?
底层结构:是 二进制位(bit)组成的数组【bit array】,初始全为0 和 多个独立的hash函数。
判重的原理:先初始化一个值全为0的大二进制位数组。添加key时,通过3次hash计算并对数组的长度取模得到数据下标,将对应位置的值设为1。查找时,过程是一样的,若3个位置都为1,证明key可能存在;只要有一个位置不为1,key则一定不存在。
3 补充:
换算关系:位/比特(bit)<字节(byte)<千字节(kb)<兆字节(mb)<gb
1byte=8bit 1k=1024byte
### 创建布隆过滤器(容量 1000,错误率 0.1%)
BF.RESERVE mybloom 0.001 1000
### 添加单个元素(成功返回1)
BF.ADD mybloom apple
### 批量操作(成功返回1)
BF.MADD mybloom cherry date
### 检查单个存在性
BF.EXISTS mybloom apple # 返回 1(存在)
BF.EXISTS mybloom banana # 返回 0(不存在)
### 批量检查存在性
BF.MEXISTS mybloom apple cherry grape # 返回 1,1,0
### 删除布隆过滤器
DEL mybloom
三. 实操
1 基本用法
创建、添加、批量添加、判断是否存在、删除
public IActionResult BasicUse()
{
//这里只能使用CSRedisClient类,RedisHelper类中没有集成
//1 创建布隆过滤器(容量 1000,错误率 0.1%)
string key = "mybloom";
decimal errorRate = 0.001m;
long capacity = 1000;
bool res1 = redisClient.BfReserve(key, errorRate, capacity);
//2 添加单个元素(成功返回1)
bool res2 = redisClient.BfAdd(key, "apple");
//3 批量添加
bool[] res3 = redisClient.BfMAdd(key, ["cherry", "date"]);
//4 检查元素是否存在
bool res4 = redisClient.BfExists(key, "apple");
bool res5 = redisClient.BfExists(key, "banana");
//5. 批量检查存在性
bool[] res6 = redisClient.BfMExists(key, ["apple", "cherry", "grape"]);
//6. 删除布隆过滤器
//long res7 = redisClient.Del(key);
2 注册场景判重手机号
核心逻辑
(1) bloomfilter中判断手机号是否存在,如果不存在,则一定不存在,继续后续业务
(2) 判断存在,只能说明是可能存在的,需要DB层次精确判断一下
(3) 全部执行完毕,则将手机号插入bloomfilter
program代码
//初始化布隆过滤器--注册判重手机号(如果不存在)
csredis.BfReserve("userPhoneList", 0.001m, 1000000);
action代码
/// <summary>
/// 02-使用手机号注册用户--手机号判重
/// </summary>
/// <param name="phoneNumber">手机号</param>
/// <param name="verificationCode">验证码</param>
/// <param name="password">密码</param>
/// <returns></returns>
[HttpPost]
public IActionResult RegisterByPhone(string phoneNumber, string verificationCode, string password)
{
try
{
//1 验证码判断--假设正确
//2 手机号判重
string bloomPhoneKey = "userPhoneList";
//2.1 布隆过滤器中判重
bool phoneMayExist = redisClient.BfExists(bloomPhoneKey, phoneNumber);
if (phoneMayExist)
{
//2.2 表示bloom中判断可能存在,需要DB进行精确判重
//bool phoneExists = await _userRepository.CheckPhoneExistsAsync(phoneNumber); //模拟
bool phoneExists = true;
if (phoneExists)
{
return Json(new { status = "error", msg = "该手机号已被注册", data = "" });
}
}
//3 其它DB层面的业务--假设成功
//4 将手机号存入布隆过滤器
redisClient.BfAdd(bloomPhoneKey, phoneNumber);
return Json(new { status = "error", msg = "注册成功", data = "" });
}
catch (Exception)
{
return Json(new { status = "error", msg = "注册失败", data = "" });
}
}
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。