## 贴子楼层号

---

#### 问题

* 和分库分表的全局计数器区别的是，它要连续的且自增，所以不方便预分配区间的形式来削峰
* 要保证原子性。不能出现重复的楼层


#### 实现


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);
        }
    }
```





