如何利用 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());简单粗暴,清晰明了,哈哈。
所以我们大致的格式应该是这样子的:

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 的方式好太多。
至此,一个简单的签到统计功能就已经实现了,大家可以根据自己的需求扩展,不当的地方欢迎大家指正,哈哈。
本作品采用《CC 协议》,转载必须注明作者和本文链接
留言中好多同学都会对使用 $redis->keys ('login:201905*') 存有疑义,所以就对这个指令做了更进一步的了解,具体请戳:《Redis 中 Keys 与 Scan 的使用》
 
           finecho 的个人博客
 finecho 的个人博客
         
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
                     
                     
             
         
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
         
             
             
           
           关于 LearnKu
                关于 LearnKu
               
                     
                     
                     粤公网安备 44030502004330号
 粤公网安备 44030502004330号 
 
推荐文章: