全局唯一ID使用场景

分布式系统设计时,数据分片场景下,通常需要一个全局唯一id;

在消息系统中需要消息唯一ID标识来防止消息重复;

多系统打通需要一个全局唯一标识 (如集团各业务线面对不同用户,需要一个全局用户id)。

如何生成一个全局唯一id?或者说设计一个ID发号器呢?

常用如下几种方式:

1、UUID

Universally Unique Identifier 是自由软件基金会组织制定的唯一辨识ID生成标准,大多数系统已实现,如微软的GUID实现。

生成格式如:3d422567-f034-4ab4-b98f-a34fd263d0de

2、sequence表 

使用DB统一维护一张(N张)发号表, 使用主键自增值生成唯一ID。

生成格式如:1,2,3,4,5....(递增数字)

3、SnowFlake 雪花算法

Twitter实现的算法,使用时间戳+机器分配标识+自增序列组成64位数字ID。

生成格式如:1292755860950487050

具体实现方式

1、UUID实现方案

1.1 使用linux程式

$ /usr/bin/uuidgen
dce6813b-c989-45ba-9704-0289c3e7c8d3

1.2 代码实现

package main
import ("fmt""os/exec"
)func main() {out, err := exec.Command("uuidgen").Output()if err != nil {panic(err) //todo}fmt.Printf("%s", out)
}

1.3 google提供的实现方案

github.com/google/uuid

1.4 优点

性能高,本地生成,无依赖

1.5 缺点

生成格式太长,不适合做数据库主键id;

基于mac地址生成算法可能导致mac泄露

2、sequece表实现方案

2.1sequence表结构

id int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id'
stub varchar(10) NOT NULL DEFAULT '' COMMENT '存根'

2.2sql实现

插入并获取最新序号,这里也可以直接使用replace方式。

BEGIN;
insert into sequence (stub) value ('x');
select LAST_INSERT_ID();
COMMIT;

2.3服务部署

冗余服务部署,可部署多个库表,设置不同step,让每个sequence产生不同号码。

如部署2个服务节点,并行获取数据,两个表均设置step=2,一个start from 1 (获取1,3,5,7,9...),一个start from 2 (获取2,4,6,8,10...)

2.4优点

ID呈单调自增趋势,满足一些场景如搜索排序

方案成熟,使用及部署简单

2.5缺点

依赖DB,有单节点DB性能瓶颈

如果DB采用主从架构,主从切换时可能会重复发号

生成号码存在递增规律,如可推断出一天的新增订单量,两天在同一时间点分别下单,然后根据订单号相减

2.6优化方案

提前从数据库读取一段放到代理服务器内存中,可减少数据库IO操作,提高性能

3、SnowFlake 雪花算法实现方案

3.1 实现原理:

(图片来自网络)

1位最高位:符号位不使用(0表正数,1表负数)

41位时间戳:2^41-1个数字代表69年,所以设置发号起始时间最好为发号器首次运行时间

10位工作机器id:也会分为5位datacenterId和5位workerId

12位序列号:2^12-1个数字总共4095,同一毫秒同一机器节点可以并发产生4095个不同Id

3.2代码实现

package main
import ("fmt""errors""runtime""sync""time"
)//global var
var sequence int = 0
var lastTime int = -1
//every segment bit
var workerIdBits = 5
var datacenterIdBits = 5
var sequenceBits = 12
//every segment max number
var maxWorkerId int = -1 ^ (-1 << workerIdBits)
var maxDatacenterId int = -1 ^ (-1 << datacenterIdBits)
var maxSequence int = -1 ^ (-1 << sequenceBits)
//bit operation shift
var workerIdShift = sequenceBits
var datacenterShift = workerIdBits + sequenceBits
var timestampShift = datacenterIdBits + workerIdBits + sequenceBitstype Snowflake struct {datacenterId intworkerId     intepoch        intmt           *sync.Mutex
}func NewSnowflake(datacenterId int, workerId int, epoch int) (*Snowflake, error) {if datacenterId > maxDatacenterId || datacenterId < 0 {return nil, errors.New(fmt.Sprintf("datacenterId cant be greater than %d or less than 0", maxDatacenterId))}if workerId > maxWorkerId || workerId < 0 {return nil, errors.New(fmt.Sprintf("workerId cant be greater than %d or less than 0", maxWorkerId))}if epoch > getCurrentTime() {return nil, errors.New(fmt.Sprintf("epoch time cant be after now"))}sf := Snowflake{datacenterId, workerId, epoch, new(sync.Mutex)}return &sf, nil
}func (sf *Snowflake) getUniqueId() int {sf.mt.Lock()defer sf.mt.Unlock()//get current timecurrentTime := getCurrentTime()//compute sequenceif currentTime < lastTime { //occur clock back//panic or wait,wait is not the best way.can be optimized.currentTime = waitUntilNextTime(lastTime)sequence = 0} else if currentTime == lastTime { //at the same time(micro-second)sequence = (sequence + 1) & maxSequenceif sequence == 0 { //overflow max num,wait next timecurrentTime = waitUntilNextTime(lastTime)}} else if currentTime > lastTime { //next timesequence = 0lastTime = currentTime}//generate idreturn (currentTime-sf.epoch)<<timestampShift | sf.datacenterId<<datacenterShift |sf.workerId<<workerIdShift | sequence
}func waitUntilNextTime(lasttime int) int {currentTime := getCurrentTime()for currentTime <= lasttime {time.Sleep(1 * time.Second / 1000) //sleep micro secondcurrentTime = getCurrentTime()}return currentTime
}func getCurrentTime() int {return int(time.Now().UnixNano() / 1e6) //micro second
}func main() {runtime.GOMAXPROCS(runtime.NumCPU())datacenterId := 0workerId := 0epoch := 1596850974657s, err := NewSnowflake(datacenterId, workerId, epoch)if err != nil {panic(err)}var wg sync.WaitGroupfor i := 0; i < 1000000; i++ {wg.Add(1)go func() {fmt.Println(s.getUniqueId())wg.Done()}()}wg.Wait()
}

3.3 运行效果

$go run snowflake.go >y

$cat y|wc -l

1000000

$sort -nr y|uniq -c|head -2

1 1649336840618140

1 1649336840618139

并发获取100w个号码,没有重复。

3.4 服务部署

不同机器使用不同DatacenterId(数据中心集群id),WorkerId (机器节点id),最多可部署1024个节点。

3.5优点

使用时间在高位,ID呈现递增趋势,满足一些场景如搜索排序。

不依赖其他组件,易于部署维护。

3.6缺点

依赖机器时钟,因时钟回拨问题会导致发号重复或不可用,代码实现时采用循环等待下一时钟的方式,可能会有性能问题。

3.7优化方案

多节点部署时,可使用zookeeper做节点分布式协调 一致性管理,当出现时钟回拨可由zk来同步时间或摘除节点。

4.总结

UUID

sequence

snowflake

描述

集成在标准系统中可简单生成

使用DB自增id实现

根据时间+机器分配标识+自增序列生成

依赖

DB

优点

本地生成性能高

部署简单

生成ID单调递增

部署简单

生成ID单调递增

缺点

生成号码复杂,很多场景不利于使用

依赖DB有性能问题和重复发号问题

号码存在规律会泄露信息

依赖系统时钟,时钟回拨会造成重复发号问题

说明:

针对sequence表方式、snowflake方式的缺点,美团leaf给出了更详细的优化方案可以参考,这里就不过多引用,直接查看参考文档。

参考文档

meituan leaf:

https://tech.meituan.com/2017/04/21/mt-leaf.html

twitter snowflake:

https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala

- END -

关注公众号,每周分享给你一个进阶知识

常用的分布式唯一ID生成方案相关推荐

  1. 一线大厂的分布式唯一ID生成方案是什么样的?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户 ...

  2. 一起学习下一线大厂的分布式唯一ID生成方案!

    来源 | http://www.toutiao.com/i6682672464708764174 一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法 ...

  3. mysql 分布式 生成序号_分布式唯一ID生成方案

    唯一ID在业务系统中经常用到,例如数据库的唯一主键,那么唯一ID如何生成,我们这里介绍一些常见的实现方案. 字符串ID 如果采用字符串id,那么很简单,直接使用jdk自带的UUID,原始生成的是带中划 ...

  4. Java架构直通车——分布式唯一 ID生成方案

    文章目录 分布式ID的几种生成方案 UUID MySQL主键自增 数据库自增ID改进方案 雪花算法(SnowFlake) 雪花算法的优化 Redis自增id Zookeeper有序节点 最近要做区块链 ...

  5. 分布式唯一ID生成方案

    ​ 在应用程序中,经常需要全局唯一的ID作为数据库主键.如何生成全局唯一ID? ​ 首先,需要确定全局唯一ID是整型还是字符串?如果是字符串,那么现有的UUID就完全满足需求,不需要额外的工作.缺点是 ...

  6. Java分布式唯一ID生成方案——比UUID效率更高的生成id工具类

    package com.xinyartech.erp.core.util;import java.lang.management.ManagementFactory; import java.net. ...

  7. 分布式系统唯一ID生成方案浅析

    有情怀,有干货,微信搜索[荒古传说]关注这个不一样的程序员. 分布式系统唯一ID生成方案浅析 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.业务ID需要满足的要求如下 全局唯一性:不能出 ...

  8. 全局唯一ID生成方案

    2019独角兽企业重金招聘Python工程师标准>>> 全局唯一ID生成方案对比 - http://cenalulu.github.io/mysql/guid-generate/ 转 ...

  9. asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号

    本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...

最新文章

  1. 如何在LINUX里用su切换用户
  2. Android DialogFragment 遇到 java.lang.IllegalStateException: Fragment already added: 的解决方法
  3. Android Touch事件传递机制解析 (推荐)
  4. C malloc 用法
  5. 阿里云分布式容器平台即将全面启动公测
  6. Uva 10004(二分图的判定)
  7. 【MyBatis框架】Mybatis开发dao方法第一部分
  8. java uuid fasterxml_可笑!可悲!可叹!你竟然还不知道Java如何生成UUID?
  9. 华为网络技术培训笔记之常用网络工具(一)
  10. python socket客户端_python 使用socket模拟tcp客户端和tcp服务器端
  11. 编译原理考试知识点总结
  12. Linux安装Tab键补全功能
  13. 国内首款PCB资料分析软件,华秋DFM使用介绍
  14. 这12张数据治理内涵图,你看懂了吗
  15. 实现android wifi语音通话功能吗,Android下自写类似系统wifi管理功能的实现
  16. 【MATLAB】基于油猴脚本和MATLAB下载原创力文档
  17. 手写数字识别的小优化
  18. 从数据仓库到百万标签库,精细化数据管理,这么做就够了
  19. not enough arguments in call to oprot.Flush
  20. 彻底掌握 Javascript(十一)日期-曾亮-专题视频课程

热门文章

  1. Linux CPU占用率监控工具小结
  2. Apache和PHP结合、Apache默认虚拟主机
  3. VBS教程:函数-FormatPercent 函数
  4. 释放广域网潜能的“简单”之道
  5. 【转】Cache Buffer Chain 第二篇
  6. 【源码阅读】dbutil包中BasicRowProcessor内部类CaseInsensiti...
  7. 多域型SSL证书和通配型证书安装指南- iis 6.0 (windows 2003)
  8. 普通域用户设置共享文件夹
  9. docker搞个wordpress
  10. Gradle发布4.7版本,支持Java 10