乐趣区

关于数据库:设计-基于-Redis-谈一谈缓存设计思想

王奇 参谋软件工程师

目前从事 PaaS 中间件服务(Redis/MongoDB/ELK 等)开发工作,对 NoSQL 数据库有深刻的钻研以及丰盛的二次开发教训,热衷对 NoSQL 数据库畛域内的最新技术动静的学习,可能把握行业技术发展趋势。

| 前言

前段时间跟共事一起聊到 Redis 的那些坑,不约而同感触很深。有的时候当业务规模未达到肯定瓶颈,很可能发现不了问题,例如上面的这段代码。

public static LotteryPeriod getCurrentAwardNumberPeriod(String gameId) {List<LotteryPeriod> periodList = getPeriodsByGameId(gameId);
    if (periodList == null) {return null;}
    Timestamp now = DateUtil.getCurrentTimestamp();
    LotteryPeriod lotteryPeriod = null;
    for (LotteryPeriod period : periodList) {if (period.getEndTime().before(now) && period.getAwardTime().before(now)) {if (LotteryAwardCache.getLotteryAward(gameId, period.getPeriod(), false) != null) {
             lotteryPeriod = period;
             break;
          }
        }
    }
    return lotteryPeriod;
}

这是一个对于期次类的一个业务,这么写乍眼看没什么问题,但因为业务类型减少,工夫一久代码量超过了 3000+,靠近 20 个getXXPeriod(String),Java 运行后加载了几万个 Period 实例。

| 改良思路

  • Timestamp –> Long
  • startTime, endTime….betDeadline 迭代比拟 –> Timeline
  • Cache

| Period 缓存设计思维

Period 实体类蕴含以上公有属性和构造方法,零碎所有无关期次的工夫计算均围绕 startTime、endTime、saleStartTime、saleEndTime、awardTime、openTime 这些属性。

如果将所有期次对象依照期次程序缓存起来,后续的计算也不不便。同时效率也是须要思考的,如果每次用户读取某些非凡需要的期次,例如获取以后销售期次,每次都须要循环解决并判断销售开始工夫、完结工夫与以后工夫的关系。最初,Timestamp 的比拟在 Redis 缓存外面不能间接计算,这样导致很多计算须要将数据传输到本地后再进行,计算的算法效率也不肯定有 Redis 高。

基于上述几点思考做以下设计:将每一个工夫维度依照 Redis 缓存的 SortedSet 构造保留,Timestamp 转化为 Long 作为 SortedSet 的 score 存储,member 则为 periodId;为了进步用户拜访效率,将每个特定需要的期次信息间接应用 Redis 缓存 String 构造保留,行将对象转化为字节数组保留;为保障这些数据时效性,设置正当的过期工夫并且定时刷新这些数据。

一级缓存

总计有五种时间轴(TimelineEnum 枚举类)—— START_TIME、END_TIME、SALE_START_TIME、SALE_END_TIME、OPEN_TIME。

每种时间轴应用 SortedSet 构造保留,初始化的时候载入最新肯定数量的期次,个别是将来 N 天以及过来 M 天,这里的数量次要保障追号性能和最近开奖号码性能显示失常即可。时间轴对应的工夫属性转化为 Long 后作为 SortedSet 的 score,periodId 则为 member,这样每条时间轴均依照对应工夫属性从小到大排序,获取满足小于或者大于某个工夫点的期次列表应用 Redis 命令即可不便获取。

零碎提供 periodRedisService.refreshTimeline(gameId) 依照指定彩种刷新所有的 Timeline,提供 periodRedisService.rebuildTimeline(gameId) 依照指定彩种重建所有的 Timeline,此办法会先删除所有 Timeline 的 Key 再刷新。

二级缓存

总计有以下零碎须要拜访对象(PeriodEnum 枚举类)—— CURRENT_PERIOD、CURRENT_PERIODS、TODAY_PERIODS、CURRENT_SALE_PERIOD、NEXT_SALE_PERIOD、LAST_10AWARD_PERIOD、FUTURE_3DAY_PERIOD、LAST_OPEN_PERIOD、RECENT_3PERIODS、LAST_100AWARD_PERIOD。

每种对象应用 String 构造保留,定时刷新这些曾经过期的缓存。零碎提供 periodRedisService.refreshPeriodInfo(gameId) 依照指定彩种刷新所有的这些缓存信息,提供 periodRedisService.refreshExpirePeriodInfo(gameId) 依照指定彩种刷新所有的 Key。

| 改善后

@Override
public List<GamePeriod> getCurrentPeriods(Long gameId) {String key = RedisConstant.getPeriodDetailKey(gameId, RedisConstant.CURRENT_PERIOD);
    List<GamePeriod> list = redisService.hessian2Get(key);
    if (GameCache.getGame(gameId).getGameType() != Game.GAME_TYPE_TRADITIONAL) {list = resetRedisTimeline(gameId, RedisConstant.CURRENT_PERIOD, list);
    }
    return (list == null || list.isEmpty()) ? null : list;
}
退出移动版