ID用处:
1. 订单编号,物流编号,身份证号 等需要展现给用户
2. 数据库主键,索引,以此来分分区,分表,分库
3. 消息ID
4. 动态帖子ID
要求:
1. 业务编号需要有一定的意义,人看起来不那么难受
N00001 √反例:3af516cd74ec41788cea2c700c62ed60
2. 数据库主键和索引,如果是Mysql需要:容易比较大小(数字), insert的ID需要自增(减少调整页的性能损耗,如果不理解可去看看Mysql索引的数据结构)
3. 分区,分表,分库:一般情况都是冷热数据分离,所以需要ID有时间元素。如果ID用于路由就跟业务有关联了,根据实际情况分析
4. 消息ID,只需要唯一标识就可以,但查重就需要在缓存中记录,如果有时间元素,就可以判断出消息是否重复发送,比如:当前消息处理到今天了,但突然发现一个昨天的消息,那么直接丢弃就行了。
5. 帖子列表获取是没法按照普通分页来操作的:
原因:新增速度远超过你刷新的速度,获取的第二页可能比第一页都早客户端帖子列表,只需要记录一个StartID和EndID即可,那么查询就是:> StartID ,limit 10,order **,这个字段最好是ID字段,否则排序数据库就受不了。
技术因素:
如果只有一个服务,多线程并发访问就可以用一个锁实现
分布式多实例服务,就有抢占的问题,锁必须独立在进程之外
如果分表是按照ID平均保存,那么就需要ID根据某种路由规则后值 应该是比较平均的,这类要求就需要按照实际情况看了
总结:(上面集中场景总结)
ID分为业务NUM 和 系统标识用的ID,业务ID一般都是产品给业务规则,在拼接递增序列即可,下面不在讨论
ID要求:全局唯一,时间增序
必须考虑分布式问题
方案:
- 数据库存储MaxNum字段
- 过程:访问,MaxNum使用,然后MaxNum+1 Insert
- 缺点:并发问题解决不了
- 优点:简单,不用引入其他组件
- 时间戳
- 过程:System.currentTimeMillis()=1658133306199
- 优点:直接用,不需要调用其他服务
- 缺点:超过1000并发就会重发
- 数据库自增字段使用
- 过程1:调用insert,并获取max(id);
- 优点:没啥优点,开发的时候简单点
- 缺点:会有并发问题,再怎么预置,都不能满足多服务并发请求问题;数据IO也是直接的瓶颈
- UUID
- 优点:服务之间不需要联系,就可以保证唯一
- 示例:3af516cd74ec41788cea2c700c62ed60
- 缺点:比对大小比较好性能;且没有顺
- redis incr 递增命令
- 用redis的单线程特性,incr递增并返回数据用于ID拼接
- 优点:性能有保障
- 缺点:只能是整数递增,其他ID数据需要拼接
- snowflake算法
- snowflake是twitter开源的分布式ID生成算法,其核心思想是:一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID,完全能满足业务的需求。
- 优点:根据一个方法生成;并发400W够用;递增;数值利于比较大小
- 缺点:微服务运行的机器时间可能不一致
- ID服务
- 任何一个ID生成工具都不可能生成符合全部场景的ID,这就需要自定义
- 分布式多实例服务是常态了,所以必须单独出来一个ID服务
snowflake代码
/** * 依据Twitter的分布式id生成算法而实现的唯一id生成器
* * SnowFlake的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * * 60 * 60 * 24 * 365) = 69
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
* 加起来刚好64位,为一个Long型。
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 * * @date: 2018-10-23 * @version: 1.0 */public class IdUtil {private final long twepoch = 1288834974657L;private final long workerIdBits = 5L;private final long datacenterIdBits = 5L;private final long maxWorkerId = -1L ^ (-1L << workerIdBits);private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);private final long sequenceBits = 12L;private final long workerIdShift = sequenceBits;private final long datacenterIdShift = sequenceBits + workerIdBits;private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;private final long sequenceMask = -1L ^ (-1L < maxWorkerId || workerId maxDatacenterId || dataCenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = dataCenterId;}/** * 获取唯一id编号, 长度为19位 * * @return 唯一id */public synchronized long nextId() {long timestamp = timeGen();if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", 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;}protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}protected long timeGen() {return System.currentTimeMillis();}public static void main(String[] args) {long shangyige = WktiesIdUtil.getUniqueId();for(int i = 0 ;i" + (zhege-shangyige));if(shangyige >= zhege){System.out.println(zhege);break;}shangyige = zhege;}}}
END