Redis基于Bitmap实现用户签到功能
对于用户签到数据,如果直接采用数据库存储,当出现高并发访问时,对数据库压力会很大,例如双十一签到活动。这时候应该采用缓存,以减轻数据库的压力,Redis是高性能的内存数据库,适用于这样的场景。
如果采用String类型保存,当用户数量大时,内存开销就非常大。
如果采用集合类型保存,例如Set、Hash,查询用户某个范围的数据时,查询效率又不高。
Redis提供的数据类型BitMap(位图),每个bit位对应0和1两个状态。虽然内部还是采用String类型存储,但Redis提供了一些指令用于直接操作BitMap,可以把它看作一个bit数组,数组的下标就是偏移量。
它的优点是内存开销小,效率高且操作简单,很适合用于签到这类场景。缺点在于位计算和位表示数值的局限。如果要用位来做业务数据记录,就不要在意value的值。
Redis提供了以下几个指令用于操作BitMap:
SETBIT 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。 = 2.2.0
GETBIT 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。 = 2.2.0
BITOP 对一个或多个保存二进制位的字符串 key 进行位元操作。 = 2.6.0
BITFIELD BITFIELD 命令可以在一次调用中同时对多个位范围进行操作。 = 3.2.0
考虑到每月要重置连续签到次数,最简单的方式是按用户每月存一条签到数据。Key的格式为 u:sign:{uid}:{yyyMM},而Value则采用长度为4个字节的(32位)的BitMap(最大月份只有31天)。BitMap的每一位代表一天的签到,1表示已签,0表示未签。
例如 u:sign:1225:202101 表示ID=1225的用户在2021年1月的签到记录
# 用户1月6号签到
SETBIT u:sign:1225:202101 5 1 # 偏移量是从0开始,所以要把6减1
# 检查1月6号是否签到
GETBIT u:sign:1225:202101 5 # 偏移量是从0开始,所以要把6减1
# 统计1月份的签到次数
BITCOUNT u:sign:1225:202101
# 获取1月份前31天的签到数据
BITFIELD u:sign:1225:202101 get u31 0
# 获取1月份首次签到的日期
BITPOS u:sign:1225:202101 1 # 返回的首次签到的偏移量,加上1即为当月的某一天
示例代码
using StackExchange.Redis; using System; using System.Collections.Generic; using System.Linq; * 基于Redis Bitmap的用户签到功能实现类 * 实现功能: * 1. 用户签到 * 2. 检查用户是否签到 * 3. 获取当月签到次数 * 4. 获取当月连续签到次数 * 5. 获取当月首次签到日期 * 6. 获取当月签到情况 public class UserSignDemo private IDatabase _db; public UserSignDemo(IDatabase db) _db = db; * 用户签到 * @param uid 用户ID * @param date 日期 * @return 之前的签到状态 public bool DoSign(int uid, DateTime date) int offset = date.Day - 1; return _db.StringSetBit(BuildSignKey(uid, date), offset, true); * 检查用户是否签到 * @param uid 用户ID * @param date 日期 * @return 当前的签到状态 public bool CheckSign(int uid, DateTime date) int offset = date.Day - 1; return _db.StringGetBit(BuildSignKey(uid, date), offset); * 获取用户签到次数 * @param uid 用户ID * @param date 日期 * @return 当前的签到次数 public long GetSignCount(int uid, DateTime date) return _db.StringBitCount(BuildSignKey(uid, date)); * 获取当月连续签到次数 * @param uid 用户ID * @param date 日期 * @return 当月连续签到次数 public long GetContinuousSignCount(int uid, DateTime date) int signCount = 0; string type = $"u{date.Day}"; // 取1号到当天的签到状态 RedisResult result = _db.Execute("BITFIELD", (RedisKey)BuildSignKey(uid, date), "GET", type, 0); if (!result.IsNull) var list = (long[])result; if (list.Length 0) // 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况 long v = list[0]; for (int i = 0; i date.Day; i++) if (v 1 1 == v) // 低位为0且非当天说明连续签到中断了 if (i 0) break; else signCount += 1; v = 1; return signCount; * 获取当月首次签到日期 * @param uid 用户ID * @param date 日期 * @return 首次签到日期 public DateTime GetFirstSignDate(int uid, DateTime date) long pos = _db.StringBitPosition(BuildSignKey(uid, date), true); return pos 0 null : date.AddDays(date.Day - (int)(pos + 1)); * 获取当月签到情况 * @param uid 用户ID * @param date 日期 * @return Key为签到日期,Value为签到状态的Map public Dictionary string, bool GetSignInfo(int uid, DateTime date) Dictionary string, bool signMap = new Dictionary string, bool (date.Day); string type = $"u{GetDayOfMonth(date)}"; RedisResult result = _db.Execute("BITFIELD", (RedisKey)BuildSignKey(uid, date), "GET", type, 0); if (!result.IsNull) var list = (long[])result; if (list.Length 0) // 由低位到高位,为0表示未签,为1表示已签 long v = list[0]; for (int i = GetDayOfMonth(date); i i--) DateTime d = date.AddDays(i - date.Day); signMap.Add(FormatDate(d, "yyyy-MM-dd"), v 1 1 != v); v = 1; return signMap; private static string FormatDate(DateTime date) return FormatDate(date, "yyyyMM"); private static string FormatDate(DateTime date, string pattern) return date.ToString(pattern); * 构建签到Key * @param uid 用户ID * @param date 日期 * @return 签到Key private static string BuildSignKey(int uid, DateTime date) return $"u:sign:{uid}:{FormatDate(date)}"; * 获取月份天数 * @param date 日期 * @return 天数 private static int GetDayOfMonth(DateTime date) if (date.Month == 2) return 28; if (new int[] { 1, 3, 5, 7, 8, 10, 12 }.Contains(date.Month)) return 31; return 30; static void Main(string[] args) ConnectionMultiplexer connection = ConnectionMultiplexer.Connect("192.168.0.104:7001,password=123456"); UserSignDemo demo = new UserSignDemo(connection.GetDatabase()); DateTime today = DateTime.Now; int uid = 1225; { // doSign bool signed = demo.DoSign(uid, today); if (signed) Console.WriteLine("您已签到:" + FormatDate(today, "yyyy-MM-dd")); else Console.WriteLine("签到完成:" + FormatDate(today, "yyyy-MM-dd")); { // checkSign bool signed = demo.CheckSign(uid, today); if (signed) Console.WriteLine("您已签到:" + FormatDate(today, "yyyy-MM-dd")); else Console.WriteLine("尚未签到:" + FormatDate(today, "yyyy-MM-dd")); { // getSignCount long count = demo.GetSignCount(uid, today); Console.WriteLine("本月签到次数:" + count); { // getContinuousSignCount long count = demo.GetContinuousSignCount(uid, today); Console.WriteLine("连续签到次数:" + count); { // getFirstSignDate DateTime date = demo.GetFirstSignDate(uid, today); if (date.HasValue) Console.WriteLine("本月首次签到:" + FormatDate(date.Value, "yyyy-MM-dd")); else Console.WriteLine("本月首次签到:无"); { // getSignInfo Console.WriteLine("当月签到情况:"); Dictionary string, bool signInfo = new Dictionary string, bool (demo.GetSignInfo(uid, today)); foreach (var entry in signInfo) Console.WriteLine(entry.Key + ": " + (entry.Value "√" : "-"));
运行结果
位图优点是内存开销小,效率高且操作简单;缺点是位计算和位表示数值的局限。 位图适合二元状态的场景,例如用户签到、在线状态等场景。 String类型最大长度为512M。 注意SETBIT时的偏移量,当偏移量很大时,可能会有较大耗时。 位图不是绝对的好,有时可能更浪费空间。 如果位图很大,建议分拆键。如果要使用BITOP,建议读取到客户端再进行位计算。 参考资料
基于Redis位图实现用户签到功能
Redis 深度历险:核心原理与应用实践
Redis:Bitmap的setbit,getbit,bitcount,bitop等使用与应用场景
BITFIELD SET command is not working
到此这篇关于Redis基于Bitmap实现用户签到功能的文章就介绍到这了,更多相关Redis Bitmap用户签到内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
我想要获取技术服务或软件
服务范围:MySQL、ORACLE、SQLSERVER、MongoDB、PostgreSQL 、程序问题
服务方式:远程服务、电话支持、现场服务,沟通指定方式服务
技术标签:数据恢复、安装配置、数据迁移、集群容灾、异常处理、其它问题
本站部分文章参考或来源于网络,如有侵权请联系站长。
数据库远程运维 Redis基于Bitmap实现用户签到功能
相关文章
- 精巧设计:探索Redis之美(redis设计精妙)
- Redis:用于数据库的高性能缓存利器(redis是干什么用的)
- 机制Java使用Redis实现过期机制(redisjava过期)
- 缓存Java使用Redis实现超时缓存(redisjava过期)
- 内存使用查看Redis内存使用:管理你的缓存(redis查看)
- 如何在Redis中设置密码:简单明了的配置文件使用指南。(redis密码配置文件)
- 脚本支持的实时Redis数据同步(脚本同步redis)
- 深入实践Redis实现快速高效的数据库操作(深入实践redis实战)
- 使用sqoop实现Redis数据导入导出(sqoop支持redis)
- 高速查询Redis数据库多线程技术助力(多线程查询redis)
- 灵活搭建,实现更强大的Redis集群(redis集群支持命令)
- 分布与存储Redis集群数据分布与存储原理(redis集群原理 数据)
- 搭建Redis集群实现复制间的通信(redis集群之间复制)
- 利用Redis轻松实现集合快速调用(redis集合直接调用)
- 研究Redis集合的实现原理(redis集合实现原理)
- Redis限流技术Lua脚本实现(redis限流lua脚本)
- 使用Redis实现高效的性能限制(redis限定的)
- Redis护航有效防止网站抓取(redis 防抓取)
- Redis锁实现的事物机制(redis锁和事物)
- 研究Redis中的锁机制(redis里面的锁机制)
- 零售店的故事Redis的Zero返回(redis 返回0)
- 利用Redis管理过期访问的有效性(redis过期访问)
- 实现更安全的网站登录基于Redis的认证方式(redis 认证登录)
- Redis订阅实现高并发下数据同步(redis 订阅高并发)
- 机制Redis实现验证码过期机制的改进(redis缓存验证码过期)
- Redis利用远程机制实现批量数据删除(redis远程批量删除)