• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

分布式ID生成规则及方案

互联网 diligentman 2个月前 (02-28) 16次浏览

在分布式架构的业务系统中,全局ID的需求广泛存在,比如:

  1. 电商业务中的交易订单ID
  2. 微服务调用的链路跟踪ID
  3. 分布式事务中的全局事务ID
  4. 消息队列中的全局消息ID

目录 [top]

分布式id的要求

  • 全局唯一:必须保证ID是全局性唯一的,基本要求。
  • 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈。
  • 高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性。
  • 好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单。
  • 趋势递增:最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求。
  • 安全性:如果id是连续的,可能导致恶意用户爬取数据。

分布式id的生成方式

  • 时间戳/业务标识+盐
  • UUID
  • 数据库自增ID
  • 数据库多主模式
  • 号段模式
  • Redis
  • zookeeper
  • ETCD
  • 雪花算法(SnowFlake)
  • 滴滴出品(TinyID)
  • 百度 (Uidgenerator)
  • 美团(Leaf)
  • 微信(seqsvr)

时间戳/业务标识+盐

该方式主要是在时间戳、业务标识符或者其他因素的场景下,加上随机/固定标识的属性来生成唯一标识。 代码举例:

public static  String getTaskCode(String bizPrefix, int length) {
		// 业务标识 + 时间戳 + 盐
		return bizPrefix + DateUtil.getDate2Simple(new Date()) + getRandom(length);
}

private static long getRandom(long n) {
		long min = 1, max = 9;
		for (int i = 1; i < n; i++) {
				min *= 10;
				max *= 10;
		}
		long rangeLong = (((long)(new Random().nextDouble() * (max - min)))) + min;
		return rangeLong;
}

上面getRandom就是“盐”,可以通过任意的方式来生成。 优点:方便快捷,带有一定的业务含义,长度可控 缺点:极端情况下有冲突的概率,依赖机器时钟,并发相对较低的情况。

UUID(非自增)

可以直接依赖于java的UUID来生成,具有全局唯一的特点:

public static String getTaskCode(String[] args) {
		return UUID.randomUUID().toString().replaceAll("-", "");
}

需要注意的是,UUID算法其实有五种分类:

  1. Time-based UUID 基于时间的UUID,通过计算当前时间戳、随机数和机器MAC地址得到。由于算法中使用了MAC地址,这个版本的UUID可以保证在全球范围内的唯一性。但与此同时,使用MAC地址会带来安全性问题,这就是这个版本UUID受到批评的地方。如果应用只是在局域网范围内使用,也可以使用退化的算法,以IP地址来替换MAC地址——JAVA的UUID往往是这样实现的。
  2. DCE security UUID DCE(Distributed Computing Environment)安全的UUID,和基于时间的UUID算法相同,但会把时间戳的前四位置换为POSIX的UID或GID。这个版本的UUID在实际中使用较少。
  3. Name-based UUID 基于名字的UUID:通过计算名字和名字空间的MD5散列值得到。这个版本的UUID保证了(1)相同名字空间中不同名字生成的UUID的唯一性;(2)不同名字空间中的UUID唯一性;(3)相同名字空间中相同名字的UUID重复生成是相同的。
  4. Randomly generated UUID 根据随机数,或者伪随机数生成UUID。这种UUID产生重复的概率是可以计算出来的,但随机数的东西就像是买彩票:你指望它发财是不可能的,但是狗屎运通常会在不经意中到来。
  5. 基于名字的UUID(SHA 1) 和版本3的UUID算法类似,只是散列值计算使用SHA 1算法。

优点:全球唯一,没有依赖,生成速度快。 缺点:没有业务含义。xx位长的字符串。存储性能差(例如落盘数据库,一般业务标识都会是索引,非有序的id可能导致频繁页分裂)。 另外,上述几种版本的UUID中,其中

  • 1/2 版本适合分布式计算环境下,具有高度唯一性。
  • 3/5 版本适合一定范围内名字唯一,且需要或可能会重复生成UUID的环境下。
  • 4版本不推荐使用(这刚好是java中UUID.randomUUID()的方式!)

数据库自增id&数据库多主模式&号段模式(保证自增)

实现简单,基于数据库主键自增来保证唯一性,确定是并发情况下性能相当糟糕,优化方式可以改为DB多实例业务分片模式,或者再加上基于号段的方式(一次性向DB申请例如1000个id加载到内存,然后在内存中分配。如果此时业务应用宕机,则再从数据库申请下一个号段的id)。

Redis(保证自增)

通过redis原子性的incr命令来实现,性能高。但是redis集群模式下需要注意几个问题:

  1. redis集群 + slave的模式,由于主从同步是异步的,这在某些情况下可能会发生重复id,可以修改源码为同步模式(可能会消耗性能)。
  2. redis的持久化技术可能需要使用AOF模式来竟可能防止重复id。
  3. 性能优化的另外一种途径就是参考上述的号段模式,批量申请id,同时将实例节点和id注册到zookeeper中,实现AP和CP的保证。同时为了防止id重复,可以先写zk再赋值业务,缺点是id可能会跳跃,但是一般都是可接受的(这点会有专门的博客详述分析。)

zookeeper(保证自增)

由于是CP的保证,能够实现数据一致性。通过注册持久节点 + 顺序节点的特性来生成自增的唯一ID

ETCD(保证自增)

zookeeper基本类似

—– 华丽的分界线,从下开始将是生产环境的首选方式,也是各个厂商的具体方案 —–

SnowFlake

基本上是其他厂商分布式id的基础,大部分的实现都是基于这个来思考或者封装的,只是其他厂商是针对某些场景做出的优化或者侧重点不同,比如百度的就是为了解决K8S环境下实例经常漂移的问题。 SnowFlake源码分析

Leaf

基本上是SnowFlake + 数据库号段的综合实现。 Leaf源码分析及优化

结束语

分布式id主要是为了解决分布式场景下唯一标识的问题,结合数据可能最终会落盘在MySQL之类的DB中,分布式id需要着重考虑两个部分:全局唯一 & 递增。再保证了上述两点的情况下,可以尽可能提高性能、稳定性、虚拟化等场景。因此,结合业务需要来做技术选型或者改造,才是技术服务业务的最佳保证。

展开阅读全文

redisjavasnowflakezookeeperleafetcd

© 著作权归作者所有

举报

打赏

0


0 收藏

微信
QQ
微博

分享

作者的其它热门文章

MQ的那些事儿——第一季
MooseFS官方文档翻译
PinPoint使用手册
ActiveMQ5.8 vs 5.15.4性能对比


程序员灯塔
转载请注明原文链接:分布式ID生成规则及方案
喜欢 (0)