简洁自增ID实现方案
admin
2024-03-18 03:22:22

简介

从数据库性能角度考虑,我们经常需要数字型的自增主键,有时候我们并不想用像MySQL自带的自增,因为从1开始很数据位数不一样,对有点强迫症的人来说,不是很友好。

另外,别人也容易根据这种从1开始的自增id分析出业务数据信息。

有很多全局唯一ID的解决方案,例如snowflake等。很多时候,其实用不上,很多业务就是单机业务,完全不需要分布式。

很多时候,其实用13位时间戳完全够了,但是13位时间戳最多支持到1千的并发,感觉心里有有点不踏实。

有没有简介一点的折中方案呢?
当然,有。

单机ID自增实现

import java.util.concurrent.atomic.AtomicLong;public class IdGenertor {/*** 序列位数,建议不小于4位* 相对id生成来说{@link System#currentTimeMillis()}是耗时操作* 当为4时意味着每毫秒最多15个,每秒1万5千个*/private short sequenceBit;/*** 序列最大值*/private long maxSequence;/*** 序列最大值哨兵*/private long sentinel;/*** 自增序列*/private AtomicLong sequence;/*** 当前毫秒,13位数,41位时间戳*/private long currentMill;public static IdGenertor build(short sequenceBit){return new IdGenertor(sequenceBit);}public static IdGenertor build(int sequenceBit){return new IdGenertor((short) sequenceBit);}private IdGenertor(short sequenceBit) {if(sequenceBit > 22){throw new RuntimeException("序列不能大于22位");}this.sequenceBit = sequenceBit;maxSequence = -1L ^ (-1L << sequenceBit);sentinel = maxSequence + 1;currentMill = System.currentTimeMillis();sequence = new AtomicLong(0);}public long getId(){long up = sequence.compareAndExchange(sentinel, 0);if(up == sentinel){long current = System.currentTimeMillis();// 避免序列重置时,时间戳还没有改变造成的重复while (current == currentMill){current = System.currentTimeMillis();}currentMill = current;}return currentMill << sequenceBit | sequence.getAndIncrement();}
}

思路

思路非常简单,long 8字节,64位,13位数时间戳占41位,1位符号位,所以自增序列最多22位。

把时间戳和自增序列拼接起来就可以作为自增id了。

自增序列的位数可以设置,例如设置4位,就意味着每毫秒最多可以生成15个id,也就是每秒1万5000个,对于绝大多数场景来说都够了。

如果设置22位,每毫秒可以生成四百多万个id,这个完全没有必要,我在单机上测试,单线程情况下,当位数为22位是,每毫秒生成的id大概在9万左右,机器性能只能生成这么多,所以用不上四百多万。

再说每毫秒9万,每秒就是9000万,哪有那么大的并发量。

注意

在多线程下,性能会明显下降,和单线程比,性能大概下降了10倍,每毫秒大概只能生成9千。
可以简单做个测试:

@Test
public void multiGet() throws InterruptedException {ExecutorService service = Executors.newFixedThreadPool(4);IdGenertor idGenertor = IdGenertor.build(22);long s = System.currentTimeMillis();int n = 10000000;for (int i = 0; i < 4; i++) {service.execute(() -> {for (int j = 0; j < n; j++) {idGenertor.getId();}});}service.shutdown();while (!service.awaitTermination(100, TimeUnit.MILLISECONDS)) {}long time = System.currentTimeMillis() - s;System.out.println((double) n / time);
}

为看更严谨一点可以将测试用CountDownLatch改造一下。

序列位数选择

建议不小于4bit,小于4bit,可以考虑直接使用13位数时间戳。

通常来说4位基本就够绝大多数场景,并且和时间戳的关联也更紧密一些,当跨毫秒的时候,中间的差值也更小一些,自增更均匀。

下面是不同bit生成的id示例:

0bit:1670145499690
1bit:3340290999392
2bit:6680581998784
3bit:13361163997568
4bit:26722327995136
5bit:53444655990272
6bit:106889311980544
7bit:213778623961088
8bit:427557247922176
9bit:855114495844352
10bit:1710228991688704
11bit:3420457983377408
12bit:6840915966754816
13bit:13681831933509632
14bit:27363663867019264
15bit:54727327734038528
16bit:109454655468077056
17bit:218909310936154112
18bit:437818621872308224
19bit:875637243744616448
20bit:1751274487489232896
21bit:3502548974978465792
22bit:7005097949956931584

相关内容

热门资讯

冰雪春天|“冰雪热”持续升温,...   近日,一段展现无锡梅园景区内6600平方米冰雪世界的预告视频,在社交平台上引发热议。这个冬天,“...
123亿收编彪马!安踏要与耐克... 拿下彪马,如何驭马是关键。作者 | 方璐 于婞编辑丨于婞来源 | 野马财经即将进入农历马年,安踏体育...
天翼视联完成首轮增资引战,含沐... 新京报贝壳财经讯(记者韦英姿)1月27日,新京报贝壳财经记者获悉,近日,中电信天翼视联科技股份有限公...
华大基因2025年成本管控见成... 1月27日晚间,华大基因发布2025年度业绩预告。业绩预告显示,公司2025年营业收入预计为36亿元...