问题
- 和分库分表的全局计数器区别的是,它要连续的且自增,所以不方便预分配区间的形式来削峰
- 要保证原子性。不能出现重复的楼层
实现
1.Manager层的调用入口
1 2 3 4 5 6 7
| private long getNextFloor(Long tid) { if (replyCacheManager.getMaxFloor(tid) == null) { long maxFloor = replyDao.getMaxFloor(tid); replyCacheManager.initFloor(tid, maxFloor); } return replyCacheManager.getNextFloor(tid); }
|
2.首先根据当前的key判断cache中是否有楼层值
1 2 3 4 5 6 7 8 9 10
| public Long getMaxFloor(Long tid) { String key = getKeyBbsFloorTid(tid); try { String value = bbsRedisClient.get(key); return StringUtils.isEmpty(value) ? null : Long.parseLong(value); } catch (RedisException e) { logger.error("[ReplyCacheManager.getMaxFloor] invoke error!", e); throw ExceptionUtils.newServiceException(ResultCode.PARAM_SERVICE_ERROR, e); } }
|
3.如果缓存没有值会进行初始化,从数据库表查询当前最大的楼层号,然后预热到缓存
StringCommands.setnx(String, String): 将字符串值value关联到key,如果key已存在则不做任何改变。
借助redis这一特性可以避免多条评论并发创建,但都因为缓存无值经过初始化这一步带来的脏数据
1 2 3 4 5 6 7 8 9
| public void initFloor(Long tid, Long floor) { String key = getKeyBbsFloorTid(tid); try { bbsRedisClient.setnx(key, String.valueOf(floor)); } catch (RedisException e) { logger.error("[ReplyCacheManager.initFloor] invoke error!", e); throw ExceptionUtils.newServiceException(ResultCode.PARAM_SERVICE_ERROR, e); } }
|
4.获取楼层号
StringCommands.incr(String):将key中储存的数字值加1,如果key不存在,以0为key的初始值,然后执行INCR操作。该方法线程安全。
1 2 3 4 5 6 7 8 9
| public long getNextFloor(Long tid) { String key = getKeyBbsFloorTid(tid); try { return bbsRedisClient.incr(key); } catch (RedisException e) { logger.error("[ReplyCacheManager.getNextFloor] invoke error!", e); throw ExceptionUtils.newServiceException(ResultCode.PARAM_SERVICE_ERROR, e); } }
|