线上使用雪花算法生成id重复问题

这篇具有很好参考价值的文章主要介绍了线上使用雪花算法生成id重复问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

项目中使用的是hutool工具类库提供的雪花算法生成id方式,版本使用的是5.3.1

 		<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.1</version>
        </dependency>

雪花算法生成id方式提供了getSnowflake(workerId,datacenterId)获取单例的Snowflake对象,并对生成id的方法nextId()进行了synchronized加锁处理。

IdUtil

	public static Snowflake getSnowflake(long workerId, long datacenterId) {
		return Singleton.get(Snowflake.class, workerId, datacenterId);
	}

Snowflake

	public synchronized long nextId() {
		long timestamp = genTime();
		if (timestamp < lastTimestamp) {
			// 如果服务器时间有问题(时钟后退) 报错。
			throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
		}
		if (lastTimestamp == timestamp) {
			sequence = (sequence + 1) & sequenceMask;
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			sequence = 0L;
		}

		lastTimestamp = timestamp;

		return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence;
	}

项目中使用雪花算法
IdUtils

public class IdUtils {
    private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(1, 1);
    public static Long getNextId() {
        return SNOWFLAKE.nextId();
    }
}

举例controller
UserController

@Slf4j
@RestController
@RequestMapping("/id")
public class UserController {
    @Autowired
    private IUserService userService;


    @GetMapping("/next")
    public Long next() {
        Long id = IdUtils.getNextId();
        User user = new User().setId(id);
        boolean save = userService.save(user);
        if (save) {
            return id;
        }
        return 0L;
    }
}

线上环境报例如:BatchUpdateException: Duplicate entry ‘1531683498452185090’ for key ‘PRIMARY’ 插入主键冲突问题。

分析代码,定位到雪花算法生成id时出现了问题
首先排除时钟回退的情况,因为在5.3.1版本如果服务器时间有问题(时钟后退) 直接报错。

1单机

排除单机情况下出现id重复问题,SNOWFLAKE 是单例的,并且生成id的方法被synchronized修饰。

2集群环境下

需要手动设置dataCenterId 和 workerId值,不同机器相同时间戳要想保证生成的id不重复,那么dataCenterId 和workerId的组合必须是唯一的

private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(workerId , dataCenterId );

Mybatis-Plus v3.4.2 雪花算法实现类 Sequence,提供了两种构造方法:无参构造,自动生成 dataCenterId 和 workerId;有参构造,创建 Sequence 时明确指定标识位

Hutool v5.7.9 参照了 Mybatis-Plus dataCenterId 和 workerId 生成方案,提供了默认实现
一起看下 Sequence 的创建默认无参构造,如何生成 dataCenterId 和 workerId

public static long getDataCenterId(long maxDatacenterId) {
    long id = 1L;
    final byte[] mac = NetUtil.getLocalHardwareAddress();
    if (null != mac) {
        id = ((0x000000FF & (long) mac[mac.length - 2])
                | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
        id = id % (maxDatacenterId + 1);
    }
 
    return id;
}

入参 maxDatacenterId 是一个固定值,代表数据中心 ID 最大值,默认值 31

为什么最大值要是 31?因为 5bit 的二进制最大是 11111,对应十进制数值 31

获取 dataCenterId 时存在两种情况,一种是网络接口为空,默认取 1L;另一种不为空,通过 Mac 地址获取 dataCenterId

可以得知,dataCenterId 的取值与 Mac 地址有关

接下来再看看 workerId

public static long getWorkerId(long datacenterId, long maxWorkerId) {
    final StringBuilder mpid = new StringBuilder();
    mpid.append(datacenterId);
    try {
        mpid.append(RuntimeUtil.getPid());
    } catch (UtilException igonre) {
        //ignore
    }
    return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

入参 maxWorkderId 也是一个固定值,代表工作机器 ID 最大值,默认值 31;datacenterId 取自上述的 getDatacenterId 方法

name 变量值为 PID@IP,所以 name 需要根据 @ 分割并获取下标 0,得到 PID

通过 MAC + PID 的 hashcode 获取16个低位,进行运算,最终得到 workerId
分配标识位
Mybatis-Plus 标识位的获取依赖 Mac 地址和进程 PID,虽然能做到尽量不重复,但仍有小几率

当然了我们也可以自己实现生成workerId、datacenterId的策略
如下,但并未测试过

@Configuration
public class SnowFlakeIdConfig {

    @Bean
    public SnowFlakeIdUtil propertyConfigurer() {
        return new SnowFlakeIdUtil(getWorkId(), getDataCenterId(), 10);
    }


    /**
     * workId使用IP生成
     * @return workId
     */
    private static Long getWorkId() {
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            int[] ints = StringUtils.toCodePoints(hostAddress);
            int sums = 0;
            for (int b : ints) {
                sums = sums + b;
            }
            return (long) (sums % 32);
        }
        catch (UnknownHostException e) {
            // 失败就随机
            return RandomUtils.nextLong(0, 31);
        }
    }


    /**
     * dataCenterId使用hostName生成
     * @return dataCenterId
     */
    private static Long getDataCenterId() {
        try {
            String hostName = SystemUtils.getHostName();
            int[] ints = StringUtils.toCodePoints(hostName);
            int sums = 0;
            for (int i: ints) {
                sums = sums + i;
            }
            return (long) (sums % 32);
        }
        catch (Exception e) {
            // 失败就随机
            return RandomUtils.nextLong(0, 31);
        }
    }
}

很显然这些方法都依赖于获取ip 等信息,比如ip并非连续,甚至获取不到ip等信息时,还是有可能出现id重复问题

3docker容器

就比如在docker容器中,一般ip都是随机的,并且未经过设置还无法获得ip信息。
docker容器和宿主机环境是隔离的,但是可以在启动docker容器时将宿主机的主机名以环境变量的形式传入,代码在容器中获取该值即可。

这里采用另一种方法,我们可以手动设置workid生成规则,并存到redis中。
这里只设置了workId,保证workId和dataCenterId的组合不重复就可以。

workId的生成是系统每次启动,第一次获取Snowflake 对象时才会进行,

public class IdUtils {
    private static StringRedisTemplate stringRedisTemplate = ApplicationContextHolder.getBean(StringRedisTemplate.class);
    private static String SNOWFLAKE_WORKID = "snowflake:workid";
    private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(getWorkerId(SNOWFLAKE_WORKID), 1);


    public static Long getNextId() {
        return SNOWFLAKE.nextId();
    }


    /**
     * 容器环境生成workid 并redis缓存
     * @param key
     * @return
     */
    public static Long getWorkerId(String key) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_worker_id.lua")));
        redisScript.setResultType(Long.class);
        return stringRedisTemplate.execute(redisScript, Collections.singletonList(key));
    }
 }

ApplicationContext对象的获取 ,解决使用注解获取不到bean的问题

@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.applicationContext = applicationContext;
    }

    /**
     * 全局的applicationContext对象
     * @return applicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String beanName) {
        return (T) applicationContext.getBean(beanName);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

}

lua脚本 redis_worker_id.lua
workId初始为0 ,每次获取后+1,知道获取到31后重置为0
为什么上限是31呢,因为workId默认占5bit

local isExist = redis.call('exists', KEYS[1])
if isExist == 1
then
    local workerId = redis.call('get', KEYS[1])
    workerId = (workerId + 1) % 31
    redis.call('set', KEYS[1], workerId)
    return workerId
else
    redis.call('set', KEYS[1], 0)
    return 0
end

测试

使用nginx 端口8080

        location /api {  
            default_type  application/json;
            #internal;  
            keepalive_timeout   30s;  
            keepalive_requests  1000;  
            #支持keep-alive  
            proxy_http_version 1.1;  
            rewrite /api(/.*) $1 break;  
            proxy_pass_request_headers on;
            #more_clear_input_headers Accept-Encoding;  
            proxy_next_upstream error timeout;  
            #proxy_pass http://127.0.0.1:8081;
            proxy_pass http://backend;
        }
    }

    upstream backend {
        server 127.0.0.1:8081 max_fails=5 fail_timeout=10s weight=1;
        server 127.0.0.1:8082 max_fails=5 fail_timeout=10s weight=1;
    }  

代理访问8081、8082两个项目
将自己的项目端口号设置为8081,并复制Copy Configuration ,VM options设置

-Dserver.port=8082
这样启动nginx8080,项目8081、8082
然后使用Jmeter进行压测,比如1000个线程 循环10次进行插入数据库
访问路径
线上使用雪花算法生成id重复问题
不再出现id重复问题

参考
https://blog.csdn.net/weixin_36586120/article/details/118018414

https://www.cnblogs.com/hzzjj/p/15117771.html
https://blog.csdn.net/nickDaDa/article/details/89357667文章来源地址https://www.toymoban.com/news/detail-422534.html

到了这里,关于线上使用雪花算法生成id重复问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 雪花算法生成唯一数字id

    2024年02月02日
    浏览(47)
  • 分布式ID生成算法——雪花算法

    一、分布式ID ID可以唯一标识一条记录。 对于单体架构,我们可以使用自增ID来保证ID的唯一性。但是,在分布式系统中,简单的使用自增ID就会导致ID冲突。这也就引出了 分布式ID 问题。分布式ID也要求满足分布式系统的 高性能、高可用、高并发 的特点。 二、雪花算法 世界

    2024年02月06日
    浏览(47)
  • 分布式ID生成算法:雪花算法

    雪花算法(Snowflake)是一种分布式ID生成算法,可以生成唯一的、有序的、不重复的ID号,广泛应用于分布式系统中。其生成的ID号由64位二进制数组成,可以转换成16进制或10进制的字符串表示。 雪花算法的核心思想是将一个64位的二进制数分成四部分,分别表示时间戳、数据

    2024年02月15日
    浏览(37)
  • 分布式ID(2):雪花算法生成ID

    1 雪花算法简介 这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示: 41-bit的时间可以表示(1L

    2024年01月20日
    浏览(45)
  • 分布式—雪花算法生成ID

    由64个Bit(比特)位组成的long类型的数字 0 | 0000000000 0000000000 0000000000 000000000 | 00000 | 00000 | 000000000000 1个bit:符号位,始终为0。 41个bit:时间戳,精确到毫秒级别,可以使用69年。 10个bit:工作机器ID,可以部署在1024个节点上。 12个bit:序列号,每个节点每毫秒内最多可以生成

    2024年02月11日
    浏览(42)
  • 雪花算法ID生成器工具类

    可以通过配置bean添加到容器,注入使用

    2024年02月15日
    浏览(47)
  • 分布式唯一ID生成算法——雪花算法(SnowFlake)

    SnowFlake算法 据国家大气研究中心的查尔斯·奈特称,一般的雪花大约由10^19个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己漂亮独特的形状。 雪花算法表示生成的id如雪花般独一无二。 snowflake是

    2023年04月20日
    浏览(45)
  • 雪花算法生成分布式主键ID

    直接上代码,复制即可使用 在这个示例中,你可以通过 SnowflakeIdGenerator.init(dataCenterId, workerId); 初始化数据中心 ID 和工作 ID,然后通过 SnowflakeIdGenerator.generateId(); 静态方法生成 Snowflake ID 的字符串形式。

    2024年02月22日
    浏览(48)
  • 【Java笔记】分布式id生成-雪花算法

    随着业务的增长,有些表可能要占用很大的物理存储空间,为了解决该问题,后期使用数据库分片技术。将一个数据库进行拆分,通过数据库中间件连接。如果数据库中该表选用ID自增策略,则可能产生重复的ID,此时应该使用分布式ID生成策略来生成ID。 snowflake是Twitter开源的

    2024年02月11日
    浏览(43)
  • 【智能排班系统】雪花算法生成分布式ID

    在复杂而庞大的分布式系统中,确保数据实体的唯一标识性是一项至关重要的任务,生成全局唯一且有序的ID生成机制成为必不可少的环节。雪花算法(Snowflake Algorithm)正是为此目的而生,以其简洁的设计、高效的表现与良好的扩展性赢得了业界的广泛认可。 雪花算法最早由

    2024年04月10日
    浏览(83)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包