java 位运算取8位_利用 Redis 位运算快速实现签到统计功能

上篇文章 已经对 Redis 基础命令进行了一个大致的学习,接下来我们就需要解决 Issue“增加用户活跃度统计” 啦!

其实当我看到这个 Issue 的时候,我的第一反应是利用 Mysql 来实现,创建一个签到表,记录用户 ID 和 签到时间,然后统计的时候从数据库中取出来然后聚合计算,完美,哈哈。

但是当看到要求说要用 Redis 位运算的时候,我就在想,为啥呢,仔细想了一哈,发现如果用 Mysql 来实现的话虽然简单粗暴,但是也有弊端,比如我们想要做一些复杂的功能就不是太方便了,或者说不是太高性能了,比如,今天是连续签到的第几天,在一定时间内连续签到了多少天。另外一方面,如果按100万用户量级来计算,一个用户每年可以产生 365条记录,100万用户的所有签到记录那就有点恐怖了,查询计算速度也会越来越慢。

所以毅然选择 Redis,下面给大家介绍一下究竟为啥选择它。

准备

大家知道 Redis 的字符串数据都是以二进制的形式存放的,所以说 Redis 的 Bit 操作非常适合处理这个场景,因为 Bit 的值为 0 或 1,用户是否打卡也可以用 0 或 1 来表示,我们把签到的天数对应到每个字节上,打卡了就是1,没打卡就是0,那么一个用户一年下来的记录就是 365 位的长度,100万用户一年只需要耗费大约 43 M 左右的存储空间就可以了,而且速度贼快,大伙可能会问,这个究竟是怎么计算来的,我们来看一下官方的解释:

在一台 2010MacBook Pro 上,offset 为2^32-1(分配512MB)需要~300ms,offset 为2^30-1(分配128MB)需要~80ms,offset 为2^28-1(分配32 MB)需要~30ms,offset 为2^26-1(分配8MB)需要8ms。

大概的空间占用计算公式是:( offset / 8 / 1024 / 1024 )MB

这里的 offset ,大家姑且当做用户 ID 来看,哈哈。

那么究竟如何去打卡呢,我们可以利用 setbit 命令来实现,setbit 的作用说的直白点就是:在你想要的位置操作字节值,比如说用户 3 在 3月13号 签到了,那么 setbit(20190313, 3 ,1) 就可以实现签到功能了,这里的 offset 就是3,同理,不同的用户不同的日期,改变对应的值就好了。

那么下面我们来实战一下:

实例

1. 实例化一个 redis 连接

$redis = app('redis.connection');

2. 如何去设计 key 呢?

$dayKey = 'login:'.\now()->format('Ymd'); // 输出类似:login:20190310

// 普通写法

$dayKey = 'login:'.\date('Ymd',\time());

简单粗暴,清晰明了,哈哈。

所以我们大致的格式应该是这样子的:

11f08131f5c08cb5eee521eb71dbf434.png

3. 签到

  • setbit - SETBIT KEY_NAME OFFSET (Time complexity: O(1))

对 key 所储存的字符串值,设置或清除指定偏移量上的位 bit

$redis->setbit($dayKey, $this->user->id, 1);

可以看到在存储方面不仅耗费内存少,快,而且操作还方便,就这么一句话就搞定了,我当初也以为会是很复杂的操作,哈哈。并且它还有非常低的灵活高效的统计计算成本。

4. 统计一周内的签到数据

  • bitop - BITOP operation destkey key [key ...]

对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上AND : 对一个或多个 key 求逻辑并OR : 对一个或多个 key 求逻辑或XOR : 对一个或多个 key 求逻辑异或NOT : 对给定 key 求逻辑非

$redis->bitop('AND', 'threeAnd', 'login:20190311', 'login:20190312', 'login:20190313');

echo "连续三天都签到的用户数量:" . $redis->bitCount('threeAnd');

$redis->bitop('OR', 'threeOr', 'login:20190311', 'login:20190312', 'login:20190313');

echo "三天中签到用户数量(有一天签也算签了):" . $redis->bitCount('threeOr');

$redis->bitop('AND', 'monthActivities'', $redis->keys('login:201903*'));

echo "连续一个月签到用户数量:" . $redis->bitCount('monthActivities');

echo "当前用户指定天数是否签到:" . $redis->getbit('login:20190311', $this->user->id);

.....

是不是特别方便快捷的统计查询,哈哈,

结束语

从上面的例子中大家可以看到不管在存储上面还是在统计计算上面,位运算都比 mysql 的方式好太多。

至此,一个简单的签到统计功能就已经实现了,大家可以根据自己的需求扩展,不当的地方欢迎大家指正,哈哈。

相关链接

  1.  一刻社区源码:https://github.com/overtrue/api.yike.io

  2.  reids 中 bitmap 的妙用:https://segmentfault.com/a/1190000008188655

你可能感兴趣的:(java,位运算取8位)