用Redis实现签到功能
一、场景
在很多时候我们会遇到用户签到的场景,每天用户进入应用时,需要获取用户当天的签到状态,如果没签到,用户可以进行签到,并且得到相关的奖励。我们可能需要每天的签到情况,必要的时候可能还需要统计一下每天用户签到人数。
我们用Redis的Set数据结构可以轻松实现这个功能——以日期为key,以用户ID(对应着数据库的primary id)组成的集合为value,每当需要查询某个用户的签到状态时,只需要使用命令SISMEMBER key member就可以轻易得到想要的结果;用户签到时,使用命令SADD key member把用户ID添加到相应的日期中;统计某天用户的签到人数,可以用命令SCARD key。
以上的做法操作简便,易于理解,但是本篇要介绍的是另一种做法,使用Redis的位操作(bitmap)。
我们都知道数据在机器上存储的最小单元是位(bit),1位可以存储0和1两种状态。这里的场景需要存储正是签到和未签到两种状态,因此一个用户只需要占用1位,也就是用位操作比用集合操作要省很多空间,下面先说一下位操作的方式,最后会给出两种方式的内存占用对比。
Redis提供了一组位操作相关的指令,这里我们关注下面三个:
- BITCOUNT key [start end]
返回key的开始位置start到结束位置end之间位值为1的数量,如果key不存在,返回0;如果不指定start和end,返回整个key的位值为1的数量。
- GETBIT key offset
返回key的第offset位的位值。
- SETBIT key offset value
把key的第offset位的值设置为value,value只能是0或1。
说明一下Redis的位操作的偏移量(offset)是从0开始算起的,而且最左边那位是第0位,这与数值的二进制有点不同(数值的二进制最右边那位是第0位)。
二、解决方案
有了以上两组操作之后,再回到我们的场景,这里假定我们有500w注册用户,日活又主动签到的用户只有30w,新用户的活跃度更高。如果使用redis的Set的操作,那么我们每天需要存储的数据就是这30w用户的id,一般来说,新注册的用户的活跃度会比旧用户的活跃度要高,为了方便测试,我们假定每天活跃的用户就是id最大的30w用户。下面是两种方案的具体操作:
2.1、使用Set存储数据
先准备30w条redis指令并且写到一个data.txt文件中,格式如下:
SADD sign_in_20200113 4700001
SADD sign_in_20200113 4700002
SADD sign_in_20200113 4700003
SADD sign_in_20200113 4700004
SADD sign_in_20200113 4700005
...
然后通过redis的管道命令来把数据写到redis:
cat data.txt | redis-cli --pipe
完成后可以看一下数据是否成功写到redis中:
127.0.0.1:6379> scard sign_in_20200113
(integer) 300000
指定的key已经有30w个用户签到,同时用info命令查看一下这时redis的占用内存:
# Memory
used_memory:21604936
used_memory_human:20.60M
占用的内存大概是20M。
然后我们需要查询一个用户的签到状态和用户签到都非常方便。
2.2、使用bitmap存储数据
接下来我们再用bitmap进行操作,同样我们准备好相关的redis指令,如下:
SETBIT sign_in_20200113 4700001 1
SETBIT sign_in_20200113 4700002 1
SETBIT sign_in_20200113 4700003 1
SETBIT sign_in_20200113 4700004 1
SETBIT sign_in_20200113 4700005 1
...
完成后我们可以通过bitcount命令查看一下签到人数:
127.0.0.1:6379> bitcount sign_in_20200113
(integer) 300000
这时再看一下占用内存的情况:
# Memory
used_memory:2088200
used_memory_human:1.99M
只占了大约2M,和使用Set的方式相差了10倍!
三、方案对比
- 使用Set的方式所占用的内存只与数量相关,和存储哪些id无关
- 使用bitmap的方式所占用的内存与数量没有绝对的关系,而是与最高位有关。比如假设id为500w的用户签到了,那么从1号用户到4999999号用户不管是否签到,所占的内存都是500w个bit,这也是bitmap的最坏情况,假如上述场景是1号用户到30w号用户签到,那么使用的内存就只是30w个bit,大约只占了940K,比最坏情况还要省一半的空间。
- 使用bitmap存储,最大的offset是2^32-1,也就是一个bitmap格式的key最大可以存储512M的数据。
- 使用bitmap存储的时候,有可能一开始是id较小的用户签到了,后面会有id较大的用户签到,这种情况下key的长度需要动态扩展,这需要花费一定的时间。在MBP2010上给offset为232-1分配512M的内存大约需要300ms,给offset为230-1分配128M的内存大约需要80ms,offset为228-1分配32M需要约30ms,offset为226-1分配8M大约需要8ms。当然,如果分配了可以容纳高位的空间后,使用低位时就不需要再扩容,比如一开始就通过setbit设置了第500w位的值,后面再使用offset小于500w的位都可以直接使用。
- 如果需要另外存储,可以每天用定时任务把数据写在需要的地方,比如MySQL。
四、适用场景
redis的bitmap操作虽然优点明显,但局限性也是显而易见的。因为它使用1bit来存储数据,所以只适用存储只有两个状态的数据,比如用户签到,资源(视频、文章、商品)的已读或未读状态。
关于redis的bitmap更多用法,可以参考官方文档。
用Redis实现签到功能的更多相关文章
- Redis位图实现用户签到功能
场景需求 适用场景如签到送积分.签到领取奖励等,大致需求如下: 签到1天送1积分,连续签到2天送2积分,3天送3积分,3天以上均送3积分等. 如果连续签到中断,则重置计数,每月初重置计数. 当月签到满 ...
- 基于Redis位图实现用户签到功能
场景需求 适用场景如签到送积分.签到领取奖励等,大致需求如下: 签到1天送1积分,连续签到2天送2积分,3天送3积分,3天以上均送3积分等. 如果连续签到中断,则重置计数,每月初重置计数. 当月签到满 ...
- Redis实战篇(二)基于Bitmap实现用户签到功能
很多应用上都有用户签到的功能,尤其是配合积分系统一起使用.现在有以下需求: 签到1天得1积分,连续签到2天得2积分,3天得3积分,3天以上均得3积分等. 如果连续签到中断,则重置计数,每月重置计数. ...
- 使用 UICollectionView 实现日历签到功能
概述 在 App 中,日历通常与签到功能结合使用.是提高用户活跃度的一种方式,同时,签到数据中蕴含了丰富的极其有价值的信息.下面我们就来看看如何在 App 中实现日历签到功能. 效果图 ..... 思 ...
- Redis多机功能介绍
Redis多机功能目的:以单台Redis服务器过渡到多台Redis服务器 Redis单机在生产环境中存在的问题 1.内存容量不足 Redis使用内存来存书数据库中的数据,但是对于一台机器来说,硬件的内 ...
- (三)开始在OJ上添加签到功能
在了解完OJ文件下的各个文件夹的主要作用后,我们开始往里面添加东西(其实只要知道各文件夹是干什么的后,添加东西也变得非常简单了) 一 在数据库中添加对应功能的字段. 我们这个学期才刚开数据库这门课,所 ...
- Redis的各项功能解决了哪些问题?
先看一下Redis是一个什么东西.官方简介解释到:Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统,你可以把它作为数据库,缓存和消息中间件来使用.同时支持string ...
- Redis的事务功能详解
Redis的事务功能详解 MULTI.EXEC.DISCARD和WATCH命令是Redis事务功能的基础.Redis事务允许在一次单独的步骤中执行一组命令,并且可以保证如下两个重要事项: >Re ...
- Redis实现排行榜功能(实战)
需求前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次.1.展示前一百名列表.2.展示个人排名(如:张三,您当前的排名106579). ...
随机推荐
- UVA10943简单递推
题意: 给你两个数字n,k,意思是用k个不大于n的数字组合(相加和)为n一共有多少种方法? 思路: 比较简单的递推题目,d[i][j]表示用了i个数字的和为j一共有多少种情况,则 ...
- PHP Tips
开启x_debug,使用var_dump()的显示效果会更好,同时错误也很更详细.
- 【python】Leetcode每日一题-二叉搜索迭代器
[python]Leetcode每日一题-二叉搜索迭代器 [题目描述] 实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器: BSTIterator(T ...
- apache-tomcat-7.0.92
链接:https://pan.baidu.com/s/1wnTSjTknYfaeDV_pakrC9g 提取码:see7
- Pytest自动化测试-简易入门教程(02)
Pytest框架简介 Pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:1.简单灵活,容易上手,支持参数化2.能够支持简单的单元测试和复杂的功能测试,3.还可以用来做sel ...
- springboot 项目中css js 等静态资源无法访问的问题
目录 问题场景 问题分析 问题解决 问题场景 今天在开发一个springboot 项目的时候突然发现 css js 等静态资源竟然都报404找不到,折腾了好久终于把问题都解决了,决定写篇博客,纪录总结 ...
- Java并发编程(二)如何保证线程同时/交替执行
第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜.本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法. ...
- C++将数值转换为string
std::to_string string to_string (int val); string to_string (long val); string to_string (long long ...
- [并发编程 - socketserver模块实现并发、[进程查看父子进程pid、僵尸进程、孤儿进程、守护进程、互斥锁、队列、生产者消费者模型]
[并发编程 - socketserver模块实现并发.[进程查看父子进程pid.僵尸进程.孤儿进程.守护进程.互斥锁.队列.生产者消费者模型] socketserver模块实现并发 基于tcp的套接字 ...
- 高阶函数 / abs方法
abs()求绝对值,填括号里面