应用场景复现:
某服务商有数千万用户,要有计划的给用户手机发送营销短信。
- 服务商的用户是在不断变化的,每次发送的用户是从总量中按指定条件的用户画像进行筛选的
- 一个用户一个月内只允许收到一条营销短信
- 每次发送营销短信前要能较精确的统计出可发送到的用户数量
- 运营提供的手机号清单禁止发送
采用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