- 和分库分表的全局计数器区别的是,它要连续的且自增,所以不方便预分配区间的形式来削峰
- 要保证原子性。不能出现重复的楼层
1.Manager层的调用入口
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中是否有楼层值
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这一特性可以避免多条评论并发创建,但都因为缓存无值经过初始化这一步带来的脏数据
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操作。该方法线程安全。
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);
}
}