最近在做类似于探探的划卡功能,实在想不到他们是如何做到海量用户下的去重功能,目前我们的应用两日活50w,想到的只有放入redis布隆过滤器中,但是这样到后期很可能光查询去重,就要操作查询无数次redis,xdm给点意见吧[难过]
我猜测探探的划卡推荐的是先基于geohash对用户的关联用户群初始化的,假设用户A,他的地理信息是geohash的这一区域,这个区域在大数据库里假设有几千人,然后将这几千人和A的兴趣标签计算进行排序,就初始化了A的顺序人群卡片列表,这样就不存在去重问题了,A除非把这几千人或者几万人都划完,才可能再次启动算法,即找到这个geohash区域旁边格子的人,再生成一个顺序人群卡片列表
进行初始化
应用场景复现:
某服务商有数千万用户,要有计划的给用户手机发送营销短信。
服务商的用户是在不断变化的,每次发送的用户是从总量中按指定条件的用户画像进行筛选的
一个用户一个月内只允许收到一条营销短信
每次发送营销短信前要能较精确的统计出可发送到的用户数量
运营提供的手机号清单禁止发送
采用redis set数据类型程序业务设计:
每次待发送用户从库中导入redis set1
每天已发送的用户计入set2,set2按日期命名方便后续编程计算,如set20191010、set20191011
准备发送时将筛选的用户set1与最近30天的set2进行去重操作并写入set3
然后按set3的数据分页读取并发送,或者每次取固定条数发送并删除
采用redis bitmap数据类型程序业务设计:
方案与set数据类型类似,主要是bitmap整体存储空间更小,但需要考虑其性能和32位int范围可支持的数据量,非数字型的数据无法使用
redis里bitmap实际是一个不超过512M的字符串,因此会变成一个大key操作
假设当前站点有5000W用户,那么一天的数据大约为50000000 / 8 / 1024 / 1 024 = 6MB
步骤一 将原始数据写入 key1
FOR $i
SETBIT key1 $i 1
End
步骤二 将去重数据写入 key 2
FOR $i
SETBIT key2 $i 1
End
步骤三 key1 与 key2 去重得出 key3
BITOP NOT key3 key1
BITOP OR key4 key3 key2
BITOP NOT key5 key4
其中 key5就是我们需要的排重后的数据
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->select(0);
$sendCache = 'sendList';
$redis->setbit($sendCache, 10, 1);
$redis->setbit($sendCache, 11, 1);
$redis->setbit($sendCache, 12, 1);
$redis->setbit($sendCache, 102, 1);
$sendedCache = 'sendedList';
$redis->setbit($sendedCache, 11, 1);
$redis->setbit($sendedCache, 12, 1);
$redis->setbit($sendedCache, 13, 1);
$redis->setbit($sendedCache, 105, 1);
$sendList = $redis->get($sendCache);
$sendedList = $redis->get($sendedCache);
$sendList = unpack('C*', $sendList);
$sendedList = unpack('C*', $sendedList);
$count = 0;
$arr = [];
$n=0;
foreach($sendList as $key => $number) {
$sendedNumber = $sendedList[$key];
for($i = 0; $i < 8; $i++) {
if(($number >> $i & 1) == 1) {
if (($number >> $i & (~($sendedNumber >> $i))) == 1) {
$count++;
$arr[$n+7-$i] = 1;
}
}
}
$n += 8;
}
print_r($arr);
die;
实际验证是可行的,但是要注意 userId 的最大位数,尽量不要超过7位,不然会有很大的性能问题
下边两组数据测试数据,一个7位的userId,get 最大需要花费17s,一个8位的userId,get 最大需要花费170s
9999999 17.77s
99999999 170.57s