刷题签到接口优化思路

背景

接口实现思路:

  1. 通过 userId 和当前年份从 Redis 中获取对应的 Bitmap
  2. 获取当前年份的总天数
  3. 循环天数拼接日期,根据日期去 Bitmap 中判断是否有签到记录,并记录到数组中
  4. 最后,将拼接好的、一年的签到记录返回给前端
@Override
public Map<LocalDate, Boolean> getUserSignInRecord(long userId, Integer year) {
    if (year == null) {
        LocalDate date = LocalDate.now();
        year = date.getYear();
    }
    String key = RedisConstant.getUserSignInRedisKey(year, userId);
    RBitSet signInBitSet = redissonClient.getBitSet(key);
    // LinkedHashMap 保证有序
    Map<LocalDate, Boolean> result = new LinkedHashMap<>();
    // 获取当前年份的总天数
    int totalDays = Year.of(year).length();
    // 依次获取每一天的签到状态
    for (int dayOfYear = 1; dayOfYear <= totalDays; dayOfYear++) {
        // 获取 key:当前日期
        LocalDate currentDate = LocalDate.ofYearDay(year, dayOfYear);
        // 获取 value:当天是否有刷题
        boolean hasRecord = signInBitSet.get(dayOfYear);
        // 将结果放入 map
        result.put(currentDate, hasRecord);
    }
    return result;
}

性能优化

思路一、判断每天是否刷题逻辑优化

循环内部需要判断当天是否有刷题,这种写法效率非常低,因为需要循环365次,每次循环都需要和 Redis 交互。
具体来说,signInBitSet 通过 Redisson 客户端与 Redis 交互的 RBitSet 对象,但是 RBitSet.get() 方法会触发一次 Redis 请求来获取对应位的值。

解决办法:在循环外缓存一下 Bitmap 中的数据

// 加载 BitSet 到内存中,避免后续读取时发送多次请求
BitSet bitSet = signInBitSet.asBitSet();

之后在循环内使用 bitSet.get即可:

// 获取 value:当天是否有刷题
boolean hasRecord = bitSet.get(dayOfYear);

这样一次连接就获取全部 Redis 的数据,之后再在缓存中找就快一些。

思路二、刷题记录返回值优化

我们最开始用一个 Map 存储记录,但是我们不需要获取完全组组装好的数据,这样传输的数据多、计算时间耗时、带宽占用多、效率低。我们只需要告诉用户哪天有刷题就行。

@Override
public List<Integer> getUserSignInRecord(long userId, Integer year) {
    if (year == null) {
        LocalDate date = LocalDate.now();
        year = date.getYear();
    }
    String key = RedisConstant.getUserSignInRedisKey(year, userId);
    RBitSet signInBitSet = redissonClient.getBitSet(key);
    // 加载 BitSet 到内存中,避免后续读取时发送多次请求
    BitSet bitSet = signInBitSet.asBitSet();
    // 统计签到的日期
    List<Integer> dayList = new ArrayList<>();
    // 获取当前年份的总天数
    int totalDays = Year.of(year).length();
    // 依次获取每一天的签到状态
    for (int dayOfYear = 1; dayOfYear <= totalDays; dayOfYear++) {
        // 获取 value:当天是否有刷题
        boolean hasRecord = bitSet.get(dayOfYear);
        if (hasRecord) {
          dayList.add(dayOfYear);
        }
    }
    return dayList;
}

将 Map 换为 List 数据结构,只存储刷题的记录。

思路三、计算优化

一般遇到 循环 要注意,因为循环需要消耗 CPU 计算资源。
在 java 的 BitSet 类中,可以使用 nextSetBit(int fromIndex) 和nextClearBit(int fromIndex) 方法来获取从指定索引开始的下一个 已设置(即为1)未设置(即为0)
使用 nextSetBit ,可以跳过无意义的循环检测,通过 位运算来获取值设置为 1 的位置。

@Override
public List<Integer> getUserSignInRecord(long userId, Integer year) {
    if (year == null) {
        LocalDate date = LocalDate.now();
        year = date.getYear();
    }
    String key = RedisConstant.getUserSignInRedisKey(year, userId);
    RBitSet signInBitSet = redissonClient.getBitSet(key);
    // 加载 BitSet 到内存中,避免后续读取时发送多次请求
    BitSet bitSet = signInBitSet.asBitSet();
    // 统计签到的日期
    List<Integer> dayList = new ArrayList<>();
    // 从索引 0 开始查找下一个被设置为 1 的位
    int index = bitSet.nextSetBit(0);
    while (index >= 0) {
        dayList.add(index);
        // 查找下一个被设置为 1 的位
        index = bitSet.nextSetBit(index + 1);
    }
    return dayList;
}

总结

接口性能优化思路:

  1. 减少网络请求或调用次数。
  2. 减少接口传输数据的体积
  3. 减少循环和计算
  4. 通过客户端计算减少服务的压力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值