Redis解决雪花算法dataId和workId的自动选择问题

文章目录

  • 前言
  • 算法 1 代码
  • 算法 2 代码

前言

雪花算法是分布式系统中常用的唯一 id 生成算法,

一般创建雪花算法的时候,依赖dataCenterIdworkerId

这两个值的取值都为 0~31 之间的整型。

对于一个雪花算法的 id 生成器,需要设置这两个参数值。

如果在单实例中,这两个值随便怎么设置都是没问题的,

但是对于应用集群部署的时候,为了保证高可用,肯定需要多实例部署。

这个时候,就需要保证一个应用多个实例的这两个参数值是不同的。

一种办法是在启动应用实例的时候,人为设置不同值。

另一种思路,就是在程序启动的时候,自动进行计算,选举出一组合适的参数。

网上已经有很多方案了,甚至已经封装成了 jar 可以很方便的进行使用。

但是基本上都是基于 ZooKeeper 的。

我这里实现了一个基于 Redis 的实现方式

算法 1 代码

    public static SnowIdDto calculateDataIdAndWorkId(RedisTemplate redisTemplate, String appName) {
     
        String key = RedisPrefix.SNOW + appName;
        Object o = redisTemplate.opsForValue().get(key);
        if (o == null) {
     
//            初始化
            SnowIdDto snowIdDto = new SnowIdDto(System.currentTimeMillis(), 0, 0);
            List<SnowIdDto> list = new ArrayList<>(1);
            list.add(snowIdDto);
            redisTemplate.opsForValue().set(key, list);
            return snowIdDto;
        } else {
     
            List<SnowIdDto> list = (List<SnowIdDto>) o;

//           需要自己先排序,保证list中的数据,根据时间戳从小到大排布
            Collections.sort(list);

//            节点数据还没用完
            if (list.size() < DATA_SIZE * DATA_SIZE) {
     
                //计算下一个节点

//                当前最后一个节点
                SnowIdDto snowIdDto = list.get(list.size() - 1);

//                优先变更工作节点
                SnowIdDto nextNode = null;
                if (snowIdDto.getWorkerId() < DATA_SIZE - 1) {
     
                    nextNode = new SnowIdDto(System.currentTimeMillis(), snowIdDto.getDataCenterId(), snowIdDto.getWorkerId() + 1);

                } else {
     
                    nextNode = new SnowIdDto(System.currentTimeMillis(), snowIdDto.getDataCenterId() + 1, 0);
                }
                list.add(nextNode);
                redisTemplate.opsForValue().set(key, list);
                return nextNode;
            } else {
     
                //计算出目前时间戳最小的那个,返回,更新时间戳
                SnowIdDto snowIdDto = list.get(0);
                snowIdDto.setTimestamp(System.currentTimeMillis());
                Collections.sort(list);
                redisTemplate.opsForValue().set(key, list);
                return snowIdDto;
            }
        }
    }

  • SnowIdDto
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SnowIdDto implements Serializable, Comparable<SnowIdDto> {
     

    /**
     * 注册时的时间戳
     */
    private Long timestamp;

    /**
     * 数据中心节点  0~31
     */
    private Integer dataCenterId;
    /**
     * 工作节点 0~31
     */
    private Integer workerId;

    @Override
    public int compareTo(SnowIdDto o) {
     
        long ex = this.timestamp - o.getTimestamp();
        return ex > 0 ? 1 : -1;
    }
}

算法 2 代码

    private static final Integer DATA_SIZE = 32;
    private static final String[] RADIX_STR = new String[]{
     "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v"};
    private static Map<String, Integer> RADIX_MAP = new LinkedHashMap<>();

    static {
     
        for (int i = 0; i < DATA_SIZE; i++) {
     
            RADIX_MAP.put(RADIX_STR[i], i);
        }
    }
//    测试时用3
//    private static final Integer DATA_SIZE = 3;

    /**
     * 计算雪花算法参数的新算法
     *
     * @param redisTemplate
     * @param appName
     * @return
     */
    public static SnowIdDto calculateDataIdAndWorkId2(RedisTemplate redisTemplate, String appName) {
     
        String key = RedisPrefix.SNOW + appName;
        RedisAtomicLong redisAtomicLong = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        long andIncrement = redisAtomicLong.getAndIncrement();
//        result在0~1023之间
        long result = andIncrement % (DATA_SIZE * DATA_SIZE);
        //将result转化为32进制数,个位为worlId,十位为dataId
        String strResult = Integer.toString(Math.toIntExact(result), DATA_SIZE);
        String strResult2 = StringUtils.leftPad(strResult, 2, "0");
        String substring1 = strResult2.substring(0, 1);
        String substring2 = strResult2.substring(1, 2);
        Integer dataId = RADIX_MAP.get(substring1);
        Integer workId = RADIX_MAP.get(substring2);
        SnowIdDto snowIdDto = new SnowIdDto(System.currentTimeMillis(), dataId, workId);
        return snowIdDto;
    }

算法 2 就简单多了

你可能感兴趣的:(java,Spring)