用户刷题记录日历——签到表功能实现
MySQL实现
在数据库中设计一张签到表,记录用户每次签到的日期及其他相关信息。然后通过时间范围查询得到用户的签到记录。
CREATE TABLE user_sign_in (id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 主键,自动递增userId BIGINT NOT NULL, -- 用户ID,关联用户表signDate DATE NOT NULL, -- 签到日期createdTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 记录创建时间UNIQUE KEY uq_user_date (userId, signDate) -- 用户ID和签到日期的唯一性约束
);
通过唯一索引,可以确保同一用户在同一天内只能签到一次。v7MVXjmkYSVF6bWEkbEtbK7rqOSiRVZrvgDMFuqdQNE=
通过下面的 SQL 即可查询用户的签到记录:
SELECT signDate FROM user_sign_in
WHERE userId = ? AND signDate BETWEEN ?AND ?;
分析:
优点:原理简单,容易实现,适用于用户量较小的系统。
缺点:
1、存储空间占用大
字段名 | 数据类型 | 估算空间 | 说明 |
---|---|---|---|
id | BIGINT | 8 字节 | 主键,自增 |
userId | BIGINT | 8 字节 | 用户ID |
signDate | DATE | 3 字节 | 日期:YYYY-MM-DD |
createdTime | TIMESTAMP | 4 字节 | 时间戳(支持秒级精度) |
如果存储 100 万个用户,365天的签到记录,需要 100 万× 365 × 40字节 = 14GB
随着用户量和数据量增大,对数据库的压力增大,直接查询数据库性能较差。除了单接口的响应会增加,可能整个系统都会被其拖垮。
基于缓存 Redis Set
可以利用内存缓存加速读写,常用的本地缓存是 Caffeine,分布式缓存是 Redis。
由于每个用户会有多个签到记录,很适合使用 Redis 的 Set 类型存储,每个用户对应一个键,Set 内的每个元素为签到的具体日期
Redis Key 的设计为:user:signins:{userId}
其中:
- user 是业务领域前缀
- signins 是具体操作或功能
- {userId} 表示每个用户,是动态值
如果 Redis 被多个项目公用,还可以在开头增加项目前缀区分,比如 interviewPal:user:signins:{userId}。
具体示例如下,可以使用 Redis 命令行工具添加值到集合中:
SADD user:signins:123 "2024-09-01"
SADD user:signins:123 "2024-09-02"
使用命令查找集合中的值:
SMEMBERS user:signins:123
优点:Set 数据结构天然支持去重,适合存储和检索打卡记录。
存储 100 万个用户 的 365 天 签到记录,使用 Redis 集合类型来存储每个用户的签到信息,每个用户需要大约 1880 字节(365 × 4字节)的空间,总共需要大约 1.88GB 的内存空间,相比数据库节约了 10 倍左右。
Bitmap 位图
Bitmap 位图,是一种使用位(bit)来表示数据的 紧凑 数据结构。每个位可以存储两个值:0 或 1,常用于表示某种状态或标志。因为每个位仅占用 1 位内存,Bitmap 在大规模存储二值数据(如布尔值)时,非常高效且节约空间。
核心思想:与其存储用户签到的具体日期,不如存储用户在今年的第 N 天是否签到
2024-01-01 => 1(第一天)
2024-01-03 => 3(第三天)
使用位图类型存储,每个用户对应一个键,Bitmap 的 每一位 来表示用户在 某一天 是否打卡。
把 int 优化成用 bit 存储,那么占用的空间可以优化 32 倍!假设原先占用的大小需要 32 G,那么改造后仅需 1 G
对于刷题签到记录场景,一个用户存储一年的数据仅需占用 46 字节,因为 46 * 8 = 368,能覆盖 365 天的记录。那一百万用户也才占用 43.8 MB,相比于 Redis Set 结构节约了 40 多倍存储空间!
Redis Key 的设计为:user:signins:{年份}:{userId}
设置某一个 bit 值的命令如下:
-- 表示用户在第 240 天打卡
SETBIT user:signins:2024:123 240 1
-- 表示用户在第 241 天打卡
SETBIT user:signins:2024:123 241 1
查询某一个 bit 值的命令:
GETBIT user:signins:2024:123 240