㈠ 雪花算法生成id重复的坑
它的时间判断参数是一个成员变量,生命周期跟着当前类走。而调用的方法并不是个单例模式,所以每次新建一个对象,其内部判定的时间判断参数都是独立存在的,这样的话在并行程序的过程中,是有可能生成相同的id的。原吵岁本怀疑是否是使用了java8的stream的原因。然而发现,人家默认就是串行流,要使用并行流是需要而外加方法的,所以和这个没有关系。
解决方法,写一个IdentifierGeneratorutil,既然DefaultIdentifierGenerator的Sequence不是单例,那么我们就在外层做操作,把调用到的IdentifierGenerator变成单例。IdWorker这个类是嫌碰携MyBatisPlus雪花算法的实现,直接调用其方法获取,它内部是单例实现的。ps(若没有特殊需求,用官方提供的就好了)。雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。最高位是符号位,始终为0,不可用。41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。10位的机器标识,10位的长度最多支持部署1024个节点。12位的计数序列号,序列号即一系列的自增id,可以芹伏支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
㈡ 2022年雪花算法的最大与最小值
最高1位固定值0。
雪花算法,SnowFlake算法,是Twitter开源的分布式id生成算法。
其核心思想并闹游就是:使用一弯则个64bit的long型的数字作为全局唯一id。最高1位固定值绝销0,因为生成的id是正整数,如果是1就是负数了。
㈢ 雪花算法与Mysql自增的优缺点
雪花算法与Mysql自增的优缺点分别是:
雪花算法优点是:
1、不会重复。
2、有序,不会造成空间浪费和胡乱插入影响性能。
3、生成很快特别是比UUid快得多。
4、相比UUid更小。
缺点是:时间回拨造成错乱。
Mysql自增的优点是:
1、存储空间小。
2、插入和查询性能高。
缺点是:
1、int的范围可能不够大。
2、当要做数据迁移的时候,会很麻烦,主键容易冲突。
3、id自增,自身的业务增长情况很容易被别人掌握。
4、自增在高并发的情况下性能不好。
生成id的代码是:
自增和UUid差异的原因是:mysql数据库一般我们会采用支持事务的Innodb,在Innodb中,采用的是B+数索引。Innodb的存储结构,是聚簇索引。对于聚簇索引顺序主键和随机主键的对效率的影响很大。
自增是顺序主键存储,查找和插入都很方便(插入会按顺序插到前一个的后面),但UUid是无序的,通过计算获得的hashcode也会是无序的(是按照hashcode选择存储位置)。
所以对于他的查找效率很低,而且因为他是无序的,他的插入有可能会插到前面的数据中,会造成很多其他的操作,很影响性能或者很多存储空间因为没有顺序的存储而被空缺浪费。
㈣ 如何保证数据库集群中id的唯一性,假设每秒钟并发20万次
用雪花算法的工具类,1秒内可以生成26万不重复的值,数据库的主键不要自增,手动设置
packageentity;
importjava.lang.management.ManagementFactory;
importjava.net.InetAddress;
importjava.net.NetworkInterface;
/**
*<p>名称:IdWorker.java</p>
*<p>描述:分布式自增长ID</p>
*<pre>
*Twitter的SnowflakeJAVA实现方案
*</pre>
*核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
*1||0------00000---00000---000000000000
*在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
*然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
*然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
*这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
*并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
*<p>
*64位ID(42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
*
*@authorPolim
*/
publicclassIdWorker{
//时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
privatefinalstaticlongtwepoch=1288834974657L;
//机器标识位数
=5L;
//数据中心标识位数
=5L;
//机器ID最大值
=-1L^(-1L<<workerIdBits);
//数据中心ID最大值
=-1L^(-1L<<datacenterIdBits);
//毫秒内自增位
=12L;
//机器ID偏左移12位
=sequenceBits;
//数据中心ID左移17位
=sequenceBits+workerIdBits;
//时间毫秒左移22位
=sequenceBits+workerIdBits+datacenterIdBits;
=-1L^(-1L<<sequenceBits);
/*上次生产id时间戳*/
=-1L;
//0,并发控制
privatelongsequence=0L;
privatefinallongworkerId;
//数据标识id部分
privatefinallongdatacenterId;
publicIdWorker(){
this.datacenterId=getDatacenterId(maxDatacenterId);
this.workerId=getMaxWorkerId(datacenterId,maxWorkerId);
}
/**
*@paramworkerId
*工作机器ID
*@paramdatacenterId
*序列号
*/
publicIdWorker(longworkerId,longdatacenterId){
if(workerId>maxWorkerId||workerId<0){
(String.format("workerIdcan'tbegreaterthan%dorlessthan0",maxWorkerId));
}
if(datacenterId>maxDatacenterId||datacenterId<0){
(String.format("datacenterIdcan'tbegreaterthan%dorlessthan0",maxDatacenterId));
}
this.workerId=workerId;
this.datacenterId=datacenterId;
}
/**
*获取下一个ID
*
*@return
*/
publicsynchronizedlongnextId(){
longtimestamp=timeGen();
if(timestamp<lastTimestamp){
thrownewRuntimeException(String.format("Clockmovedbackwards.Refusingtogenerateidfor%dmilliseconds",lastTimestamp-timestamp));
}
if(lastTimestamp==timestamp){
//当前毫秒内,则+1
sequence=(sequence+1)&sequenceMask;
if(sequence==0){
//当前毫秒内计数满了,则等待下一秒
timestamp=tilNextMillis(lastTimestamp);
}
}else{
sequence=0L;
}
lastTimestamp=timestamp;
//ID偏移组合生成最终的ID,并返回ID
longnextId=((timestamp-twepoch)<<timestampLeftShift)
|(datacenterId<<datacenterIdShift)
|(workerId<<workerIdShift)|sequence;
returnnextId;
}
privatelongtilNextMillis(finallonglastTimestamp){
longtimestamp=this.timeGen();
while(timestamp<=lastTimestamp){
timestamp=this.timeGen();
}
returntimestamp;
}
privatelongtimeGen(){
returnSystem.currentTimeMillis();
}
/**
*<p>
*获取maxWorkerId
*</p>
*/
(longdatacenterId,longmaxWorkerId){
StringBuffermpid=newStringBuffer();
mpid.append(datacenterId);
Stringname=ManagementFactory.getRuntimeMXBean().getName();
if(!name.isEmpty()){
/*
*GETjvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
*MAC+PID的hashcode获取16个低位
*/
return(mpid.toString().hashCode()&0xffff)%(maxWorkerId+1);
}
/**
*<p>
*数据标识id部分
*</p>
*/
(longmaxDatacenterId){
longid=0L;
try{
InetAddressip=InetAddress.getLocalHost();
NetworkInterfacenetwork=NetworkInterface.getByInetAddress(ip);
if(network==null){
id=1L;
}else{
byte[]mac=network.getHardwareAddress();
id=((0x000000FF&(long)mac[mac.length-1])
|(0x0000FF00&(((long)mac[mac.length-2])<<8)))>>6;
id=id%(maxDatacenterId+1);
}
}catch(Exceptione){
System.out.println("getDatacenterId:"+e.getMessage());
}
returnid;
}
publicstaticvoidmain(String[]args){
//推特26万个不重复的ID
IdWorkeridWorker=newIdWorker(0,0);
for(inti=0;i<2600;i++){
System.out.println(idWorker.nextId());
}
}
}
㈤ 雪花算法之【线上订单号重复了一招搞定它!】
公司老的系统原先采用的时间戳生成订单号,导致了如下情形
打断一下:大家知道怎么查系统某项重复的数据吧
不得了,这样重复岂不是一单成功三方回调导致另一单也成功了。
多个服务差桐怎么保证生成的戚正订单号唯一呢?
先上code
以上是采用snowflake算法生成分布式唯一ID
41-bit的时间可以表示 (1L<<41)/(1000L360024*365)=69 年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。
这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示 2^12 个ID,理论上snowflake方案的QPS约为 409.6w/s ,这种分配方式虚仔坦可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。
这种方式的优缺点是:
优点:
缺点:
一般来说,采用这种方案就解决了。
还有诸如,mysql的 auto_increment策略,redis的INCR,zookeeper的单一节点修改版本号递增,以及zookeeper的持久顺序节点。