设计签到 用redis 和 MySQL

打印 上一主题 下一主题

主题 1029|帖子 1029|积分 3087

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
方案 1:使用 Redis BitMap(保举,服从高)

适用场景:高并发、数据量大、快速查询
设计思绪



  • 使用 Redis BitMap 存储用户的签到记录,天天占用 1 bit。
  • 比方:sign:{userId}:{yearMonth},表示某个用户在某月的签到数据。
  • 通过 BITFIELD 或 BITCOUNT 盘算一连签到天数
Redis存储

  1. # 用户 ID 1001 在 2025 年 3 月 1 日签到
  2. SETBIT sign:1001:202503 1 1  
  3. # 用户 ID 1001 在 2025 年 3 月 2 日签到
  4. SETBIT sign:1001:202503 2 1  
  5. # 统计 2025 年 3 月的签到天数
  6. BITCOUNT sign:1001:202503
复制代码
Java 实现

  1. public class SignService {
  2.     @Autowired
  3.     private StringRedisTemplate redisTemplate;
  4.     private static final String SIGN_KEY = "sign:%d:%s"; // sign:{userId}:{yearMonth}
  5.     // 签到
  6.     public void sign(Long userId, int day) {
  7.         String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
  8.         redisTemplate.opsForValue().setBit(key, day, true);
  9.     }
  10.     // 获取当月签到次数
  11.     public long getSignCount(Long userId) {
  12.         String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
  13.         return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
  14.     }
  15.     // 计算连续签到天数
  16.     public int getContinuousSignDays(Long userId) {
  17.         String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
  18.         BitSet bitSet = BitSet.valueOf(redisTemplate.opsForValue().get(key).getBytes());
  19.         int count = 0;
  20.         for (int i = bitSet.length() - 1; i >= 0; i--) {
  21.             if (!bitSet.get(i)) break;
  22.             count++;
  23.         }
  24.         return count;
  25.     }
  26.     private String getCurrentYearMonth() {
  27.         return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
  28.     }
  29. }
复制代码
优缺点

● 空间占用小:每个用户每月仅需 4 字节(31 天)。✔️
● 查询快:Redis BITCOUNT 盘算签到天数 O(1) 时间复杂度。✔️
● 适用于高并发:Redis 处置惩罚速度快。✔️
● 仅支持 按月存储,跨月签到数据需要额外处置惩罚。❌
● Redis 重启后数据可能丢失,需要持久化。❌
方案 2:使用 MySQL 记录签到天数

适用场景:用户量适中,需要持久化数据。
表设计

  1. CREATE TABLE user_sign (
  2.     id BIGINT PRIMARY KEY AUTO_INCREMENT,
  3. user_id BIGINT NOT NULL,
  4. sign_date DATE NOT NULL,
  5. continuous_days INT DEFAULT 1,
  6. UNIQUE (user_id, sign_date)
  7. );
复制代码
java 逻辑

  1. public void sign(Long userId) {
  2.     LocalDate today = LocalDate.now();
  3.     LocalDate yesterday = today.minusDays(1);
  4.     // 查询昨天是否签到
  5.     Optional<UserSign> lastSign = signRepository.findByUserIdAndSignDate(userId, yesterday);
  6.     int continuousDays = lastSign.map(s -> s.getContinuousDays() + 1).orElse(1);
  7.     // 插入签到数据
  8.     UserSign sign = new UserSign(userId, today, continuousDays);
  9.     signRepository.save(sign);
  10. }
复制代码
优缺点

● 数据持久化,不会丢失。✔️
● 可以支持 跨月签到 盘算。✔️
● 查询签到天数需要数据库查询,性能较低。❌
● 高并发场景 需要加缓存优化。❌
方案 3:使用 Redis + MySQL 结合

适用场景:既要高并发,又要持久化。
● Redis 作为缓存,存储当月签到数据,快速盘算签到天数。
● MySQL 作为持久化,定期同步签到数据,防止数据丢失。
  1. public void sign(Long userId) {
  2.     // 先更新 Redis
  3.     redisService.sign(userId);
  4.     // 异步任务写入数据库
  5.     executorService.submit(() -> signRepository.save(new UserSign(userId, LocalDate.now())));
  6. }
复制代码
● 结合了 Redis 高性能 和 MySQL 持久化 的优势。
● 常规查询用 Redis,数据持久化用 MySQL,适合生产环境。
奖励触发逻辑

无论哪种方案,奖励发放逻辑如下:
  1. public void checkAndReward(Long userId) {
  2.     int continuousDays = signService.getContinuousSignDays(userId);
  3.     if (continuousDays == 7) {
  4.         rewardService.giveReward(userId, "7天连续签到奖励");
  5.     } else if (continuousDays == 30) {
  6.         rewardService.giveReward(userId, "30天连续签到大奖");
  7.     }
  8. }
复制代码
● 签到天数达到 7/30 天,主动发放奖励。
● 奖励可以存入 Kafka / MQ 举行异步发放。

● 高并发场景(如 1000w 用户):✅ 方案 1(Redis BitMap)。
● 需要持久化(但并发一般):✅ 方案 2(MySQL)。
● 既要高并发又要持久化:✅ 方案 3(Redis + MySQL 结合)。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表