Redis 中的统计数据最终要写回到数据库中,这样才能保证数据的一致性。

思考?哪些数据需要同步?,是否一次性将所有数据同步到MySQL?

  • 当数据没有变动时,这样的数据无需更新

  • 当 Redis 数据量过大时,全部查出来做同步操作对性能有影响

解决方案:

  • 使用 Redis 的 ZSet,ZSet 可以设置分数,根据分数范围查询数据
  • 当文章被浏览,置顶等操作时,将该文章添加到ZSet中,同时增加分数
  • 使用定时任务,每次从 ZSet 中查询给定分数范围的数据,将其同步到数据库中。
  • 同步完成删除该范围的数据,即清除 ZSet 中已同步的数据

统计文章分数

打开模块:trip-article-server,找到:StrategyServiceImpl,修改statDataIncr方法,增加统计文章分数方法

RedisService 中加入该方法,当文章不存在时会新增一个

/**
* 针对 zset 成员进行增加分数
*
* @param prefix 前缀
* @param increment 增加的值
* @param member 成员
*/
public void zsetIncrement(KeyPrefix prefix, double increment, Object member, String... suffix) {
redisTemplate.opsForZSet().incrementScore(prefix.fullKey(suffix), member, increment);
}
StrategyServiceImpl
/**
* hashKey 对应的 value 自增
* @param hashKey hash key
* @param sid 攻略id
*/
private void statDataIncr(String hashKey, Long sid) {
redisService.hashIncrement(StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, hashKey, 1, sid + "");
redisService.zsetIncrement(StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP, 1, sid + "");
}

数据同步定时任务

打开模块:trip-data-server,在com.swx.data.job包下创建 StrategyStatDataPersistenceJob

StrategyStatDataPersistenceJob
/**
* 攻略统计数据持久化
*/
@Slf4j
@Component
public class StrategyStatDataPersistenceJob {

private final RedisService redisService;
private final StrategyService strategyService;

public StrategyStatDataPersistenceJob(RedisService redisService, StrategyService strategyService) {
this.redisService = redisService;
this.strategyService = strategyService;
}

/**
* 每10分钟执行一次
*/
@Scheduled(cron = "0 */10 * * * *")
public void task() {
log.info("[攻略数据持久化] ---------------- 持久化数据开始 ----------------");
// 根据分数范围获取指定的成员
Set<Integer> list = redisService.zsetRerange(StrategyRedisKeyPrefix.STRATEGIES_STAT_COUNT_RANK_ZSET, 0, Integer.MAX_VALUE);
if (list != null && !list.isEmpty()) {
// 根据成员id,拼接key 取出统计数据
List<Strategy> updateList = new ArrayList<>();
for (Integer id : list) {
Map<String, Object> map = redisService.getCacheMap(StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP.fullKey(id + ""));
// 将数据封装为攻略对象,将对象存入待更新的集合
Strategy strategy = new Strategy();
strategy.setViewnum((Integer) map.get("viewnum"));
strategy.setReplynum((Integer) map.get("replynum"));
strategy.setFavornum((Integer) map.get("favornum"));
strategy.setSharenum((Integer) map.get("sharenum"));
strategy.setThumbsupnum((Integer) map.get("thumbsupnum"));
strategy.setId(id.longValue());
updateList.add(strategy);
}
// 批量更新到数据库
strategyService.updateBatchById(updateList);
// 删除已经更新过的成员
redisService.zsetRemoveRange(StrategyRedisKeyPrefix.STRATEGIES_STAT_COUNT_RANK_ZSET, 0, Integer.MAX_VALUE);
log.info("[攻略数据持久化] 持久化数量:{}", list.size());
}
log.info("[攻略数据持久化] ---------------- 持久化数据结束 ----------------");
}
}

这里感觉有点问题,如果在同步时,有用户更新了文章的分数,那么按照范围删除数据不导致不同步吧?