多项目正在使用的基于redis的分布式id生成器

1.分布式id实现方案

我们先看看常见的分布id解决方案以及各自特点的对比

        1.UUID 这种方案复杂度最低,但是会影响存储空间和性能

        2.利用单机数据库的自增主键,作为分布式ID的生成器,复杂度适中,ID长度较UUID更短,但是受到单机数据库性能的限制,并发量大的时候,该方案也不是最优方案。

        3.利用redis,zookeeper的特性来生成id,如:redis的自增命令,zookeeper的顺序节点,这种方案和单机数据库(mysql)性能相比,性能会有所提高,可以适当选用。

         4.雪花算法:一切问题如果能直接用算法解决,那是最合适的,利用雪花算法可以生成分布式Id,其底层原理就是通过某台机器在某一毫秒内对某一个数字自增,这种方案也能保证分布式架构中的系统id唯一,但是只能保证趋势递增。

2、基于redis的分布式高性能的redis分布式id生成器

        基本步骤:根据初始化workerid,并保存应用和ip地址信息,存入redis, 缓存2个小时。并定时更新redis的workerId的时间戳. 然后根据workid和雪花算法生成分布式id, 主要步骤如下:

1)初始化workerId

    /**
     * 生成workid
     */
    private long initAndGetWorkerId(@NonNull SnowflakeWorkerAutoService snowflakeWorkerAutoService) {
        long time = System.currentTimeMillis();
        log.info("[getWorkerIdAuto] 开始生成雪花算法workId");
        long workId = snowflakeWorkerAutoService.getWorkerId(moduleName, SnowflakeIdGenerator.MAX_WORKERID);
        log.info("[getWorkerIdAuto] 结束雪花算法workId:{},[生成雪花workId耗时]:{}", workId, System.currentTimeMillis() - time);
        // 定时更新workId
        snowflakeWorkerAutoService.timerUpdateModifiedTime(moduleName, workId);
        log.info("[getWorkerIdAuto] 创建定时任务workId:{}, moduleName:{}, hostAddress:{}", workId,
                moduleName, IpAddressHelper.getLocalIpAddress());
        log.info("[getWorkerIdAuto] 定时更新雪花算法workId:{}", workId);
        return workId;
    }
 protected long getWorkerId(String moduleName, long maxWorkId) {
        SnowflakeWorkerAutoEntity snowflakeWorkerAuto = new SnowflakeWorkerAutoEntity();
        snowflakeWorkerAuto.setModuleName(moduleName);
        snowflakeWorkerAuto.setHostAddress(IpAddressHelper.getLocalIpAddress());
        snowflakeWorkerAuto.setCreatedTime(LocalDateTime.now());
        snowflakeWorkerAuto.setModifiedTime(LocalDateTime.now());

        Long workerId = null;
        for (long tryWorkId = 1; tryWorkId <= maxWorkId; tryWorkId++) {
            snowflakeWorkerAuto.setId(tryWorkId);
            Boolean setSuccess = redisTemplate.opsForValue().setIfAbsent(
                    workIdKeyPre + tryWorkId, snowflakeWorkerAuto, 2, TimeUnit.HOURS);
            if (setSuccess != null && setSuccess) {
                workerId = tryWorkId;
                break;
            }
        }

        // workId若是null,表示workId被用完
        if (workerId == null) {
            throw new RuntimeException("ELE的雪花算法的workId已经用完,建议配置您业务特有的[ele.snowflake.redis-key-prefix]");
        }
        return workerId;
    }

  2)定时更新workerid

  /**
     * 定时更新修改时间
     *
     * @param moduleName 服务名称
     * @param workId     workId
     */
    public void timerUpdateModifiedTime(String moduleName, long workId) {
        if (this.executorService == null) {
            this.executorService = new ScheduledThreadPoolExecutor(1,
                    new BasicThreadFactory.Builder().namingPattern("snowflake-workid-thread-%d").daemon(true).build());
            SnowflakeWorkerAutoService snowflakeWorkerAutoService = this;
            Runnable timerTask = () -> {
                long startTime = System.currentTimeMillis();
                try {
                    snowflakeWorkerAutoService.updateModifiedTime(moduleName, workId);
                    log.info("[snowflake.updateModifiedTime] workId {} 进行续约, 耗时: {} ms", workId, System.currentTimeMillis() - startTime);
                } catch (Exception e) {
                    log.error(String.format("[snowflake.updateModifiedTime] workId %s 续约失败, 耗时: %s ms",
                            workId, System.currentTimeMillis() - startTime), e);
                }
            };
            this.executorService.scheduleAtFixedRate(timerTask, 5, 5, TimeUnit.MINUTES);
        }
    }    

    protected void updateModifiedTime(String moduleName, long workId) {
        SnowflakeWorkerAutoEntity snowflakeWorkerAuto = new SnowflakeWorkerAutoEntity();
        snowflakeWorkerAuto.setModuleName(moduleName);
        snowflakeWorkerAuto.setHostAddress(IpAddressHelper.getLocalIpAddress());
        snowflakeWorkerAuto.setCreatedTime(LocalDateTime.now());
        snowflakeWorkerAuto.setModifiedTime(LocalDateTime.now());

        SnowflakeWorkerAutoEntity entity = redisTemplate.opsForValue().get(workIdKeyPre + workId);
        if (entity == null) {
            log.error("redis雪花缓存的workId丢失: {}", workId);
        } else {
            if (!StringUtils.equals(snowflakeWorkerAuto.getHostAddress(), entity.getHostAddress())) {
                log.error("redis雪花缓存IP信息发生不可预知变动: {} -> {}", snowflakeWorkerAuto.getHostAddress(), entity.getHostAddress());
            }
            if (!StringUtils.equals(snowflakeWorkerAuto.getModuleName(), entity.getModuleName())) {
                log.error("redis雪花缓存module信息发生不可预知变动: {} -> {}", snowflakeWorkerAuto.getModuleName(), entity.getModuleName());
            }
        }

        redisTemplate.opsForValue().set(workIdKeyPre + workId, snowflakeWorkerAuto, 2, TimeUnit.HOURS);
    }

3)根据workerId和雪花算法生成分布式id

    /**
     * 下一个标识
     *
     * @return 下一个标识
     */
    @Override
    public synchronized long nextId() {
        // 获取当前时间戳
        long timestamp = getCurrentTimestamp();

        // 判断时间戳大小
        // 判断时间戳大小: 相等则递增, 归零则等待下一毫秒
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = getNextTimestamp();
            }
        }
        // 判断时间戳大小: 大于则设置顺序值
        else {
            // sequence = 0L;
            // 给同一毫秒时留出递增空间
            if (sequence < (SEQUENCE_MASK_QUARTER)) {
                sequence = (sequence + 1) & SEQUENCE_MASK;
            } else {
                sequence = 0L;
            }
        }

        // 设置上次时间戳
        lastTimestamp = timestamp;

        // 返回雪花标识
        return ((timestamp - EPOCH_TIMESTAMP) << TIMESTAMP_SHIFT) | (workerId << WORKERID_SHIFT) | sequence;
    }

本文只介绍了核心代码,完整代码已免费开放, 地址:https://download.csdn.net/download/wangyantao111/89829468

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值