分布式系统内集成雪花算法的实践

3 篇文章 0 订阅
1 篇文章 0 订阅
文章介绍了如何在分布式环境中使用雪花算法生成唯一标识,以避免UUID可能导致的重复问题。通过集成MybatisPlus并利用Redis的lua脚本和分布式锁机制,实现机器ID的自动分配和续期,确保ID的唯一性和服务的稳定性。
摘要由CSDN通过智能技术生成

分布式系统内集成雪花算法的实践


前言

之前业务系统一致使用UUID来作为生成唯一标识,由于是分布式业务并且是多机器多节点,所以可能会存在重复情况,之前的业务也只是用捕获入库失败异常来for循环三次去实现减少失败情况,但是小概率还是会存在失败,尽管非常小概率…所以对代码进行优化,使用雪花算法进行修改。


如下开始雪花算法的实现

一、什么是雪花算法?

雪花算法是64位的二进制组成,展示如下:
在这里插入图片描述
具体算法实现可以百度,不讲算法。我们能自定义的就只剩下机器id和服务id号。

二、实现

1.思路

一般在分布式环境下,以应用名+机器号存入redis,服务初始化时,参照Redission实现分布式锁一样,开一个线程做完看门狗来续期,用lua脚本获取一个唯一且有效的序列号,设置过期时间为30秒
获取序列化之后,开启一个守护线程,每20秒延长过期时间为30秒,机器id就一直占用,服务关闭,这个Id会自动释放


import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Classname SnowFlakeIDWorker
 * @Description 分布式雪花算法workerId维护
 * @Version 1.0.0
 * @Date 2023/2/17 10:47
 * @Created by WFX1024
 */
@Slf4j
@Component
public class SnowFlakeIDWorker {


    /**
     * key标识
     */
    private final String idWorker = "SnowFlakeWorkerId";

    /**
     * lua 脚本
     */
    private final String script =
            "redis.replicate_commands();local now = redis.call('TIME')[1]\n" +
                    "local idWordsKey = KEYS[1]\n" +
                    "local serviceName = KEYS[2]\n" +
                    "local sp = ':'\n" +
                    "for i = 0, 100 do\n" +
                    "    local serviceKey = idWordsKey..sp..serviceName..sp..i\n" +
                    "    if redis.call('SETNX', serviceKey, now) == 1 then\n" +
                    "        redis.call('Expire', serviceKey, 30)\n" +
                    "        return i;\n" +
                    "    end\n" +
                    "end\n" +
                    "return -1";


    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 获取机器标识号
     * @param serviceName 服务名称
     */
    public Long getWorkerIdNum(String serviceName) {
        // 实例化脚本对象
        DefaultRedisScript<Long> lua = new DefaultRedisScript<>();
        lua.setResultType(Long.class);
        lua.setScriptText(script);
        List<String> keys = Lists.newArrayList(idWorker, serviceName);
        // 获取序列号
        Long num = (Long)redisTemplate.execute(lua, keys, keys.size());
        String key = String.join(":", keys) + ":" + num;

        // -1 代表workerId用完了
        if (num < 0){
            log.error("目前Id已用完,num:{}",num);
            throw new RuntimeException("workerId超出最大数量");
        }
        // 自动续期
        this.autoExpire(key);
        return num;
    }

    /**
     * 自动续期
     */
    private void autoExpire(String key) {
        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
                new BasicThreadFactory.Builder().namingPattern("id_auto_expire-%d").daemon(true).build());
        executorService.scheduleAtFixedRate(() ->  {
            redisTemplate.expire(key, 30, TimeUnit.SECONDS);
            log.info("SnowFlakeIDWorker-自动续期id成功:{}", key);
        }, 0, 20, TimeUnit.SECONDS);
    }

}

2.使用集成MybatisPlus的雪花算法实现

代码如下(示例):


/**
 * @Classname MybatisPlusConfig
 * @Description mybatisplus配置
 * @Version 1.0.0
 * @Date 2023/2/17 10:47
 * @Created by WFX1024
 */
@EnableTransactionManagement
@Configuration
@MapperScan(basePackages = { "com.*.*.mapper"})
public class MybatisPlusConfig {


    /**
     * 分页插件,自动识别数据库类型 多租户,请参考官网【插件扩展】
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    @Autowired
    private SnowFlakeIDWorker snowFlakeIDWorker;
    /**
     * 初始化雪花ID
     */
    @PostConstruct
    public void initMybatisPlus(){

        long num = snowFlakeIDWorker.getWorkerIdNum("activity");
        // 获取前5位
        long workerId = num >> 5;
        // 获取后5位
        long dataCenterId = num & (~(-1L << 5L));
        // 自定义初始化雪花算法
        IdWorker.initSequence(workerId, dataCenterId);
    }

}

配置在初始化配置时候初始化雪花算法。


maven配置

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
</dependency>

总结

本次主要实现了使用Lua脚本+redis实现作为机器号存储,自动过期机制维护机器号,从而实现不用单独的分布式id服务进行分布式id生成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值