可实现的全局唯一有序ID生成策略
在博客园搜素全局唯一有序ID,罗列出来的文章大致讲述了以下几个问题,常见的生成全局唯一id的常见方法 :使用数据库自动增长序列实现 ; 使用UUID实现; 使用redis实现; 使用Twitter的snowflake算法实现;使用数据库+本地缓存实现。作为一个记录性质的博客,简单总结一下。
在实际的生产场景中,经常会出现如下的情况比方说订单号:D channelNo 流水号 样例PSDK1600000001, PSDK1600000002, PSDK1600000003... 这种具有业务意义的全局唯一id且有序自增。先来看一下使用比较多的Twitter的snowflake算法,snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。一个简单的实现如下:
/**
* Twitter的分布式自增ID雪花算法snowflake
**/
public class SnowFlake {
/**
* 起始的时间戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳 public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
} /**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
} if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
} lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
} private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
} private long getNewstmp() {
return System.currentTimeMillis();
} public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
System.out.println(snowFlake.nextId());
}
System.out.println(System.currentTimeMillis() - start);
}
}
算法中引入了时间因子,所以可以保证生成的id唯一且有序,但是满足不了业务字段+流水号有序自增的要求。如果在此基础上再配合使用数据库本地缓存自然也是可以实现的,不过复杂化了。上述代码执行两次结果如下:
385063405393940480
385063405393940481
385063405393940482
385063405393940483
385063405393940484
385063405393940485
385063405393940486
385063405393940487
385063405398134784
385063405398134785 385064572152844288
385064572152844289
385064572152844290
385064572152844291
385064572152844292
385064572152844293
385064572152844294
385064572152844295
385064572152844296
385064572152844297
简单的方法就是我们放弃自己造轮子的思想。mongodb中数据的基本单元称为document,在一个特定集合内部需要唯一的标识文档,因此mongdb中存储的文档都由一个‘_id’键,这个键的值可以是任意类型的ObjectId,要求不同的机器都能用全局唯一的同种方法方便的生成它。因此不能使用自增主键,ObjectId 底层也是借鉴了雪花算法,使用12字节的存储空间 |0|1|2|3|4|5|6 |7|8|9|10|11| |时间戳 |机器ID|PID|计数器| 前四个字节时间戳是从标准纪元开始的时间戳,单位为秒 。时间戳保证秒级唯一,机器ID保证设计时考虑分布式,避免时钟同步,PID保证同一台服务器运行多个mongod实例时的唯一性,最后的计数器保证同一秒内的唯一性。
mongo在spring boot中的引入和配置,此处不再介绍。
创建model类
package com.slowcity.admin.generate.dbmodel;
import java.io.Serializable;
public class BaseSequence implements Serializable{
private static final long serialVersionUID = 475722757687764546L;
private String id;
private String name;
private Long sequence;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getSequence() {
return sequence;
}
public void setSequence(Long sequence) {
this.sequence = sequence;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((sequence == null) ? 0 : sequence.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BaseSequence other = (BaseSequence) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (sequence == null) {
if (other.sequence != null)
return false;
} else if (!sequence.equals(other.sequence))
return false;
return true;
}
@Override
public String toString() {
return "BaseSequence [id=" + id + ", name=" + name + ", sequence=" + sequence + "]";
}
}
public class DigitalTaskSequence extends BaseSequence{
private static final long serialVersionUID = -7287622688931253780L;
}
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.stereotype.Component; @Component
@Document(collection = "dm_id_task")
public class DigitalTaskSequenceMG extends DigitalTaskSequence {
private static final long serialVersionUID = -425011291271386371L;
@Id
@Override
public String getId() {
return super.getId();
}
}
service
import java.util.List;
import com.slowcity.admin.generate.dbmodel.BaseSequence;
public interface SequenceGenericService {
public String generateId(Class<? extends BaseSequence> clazz);
List<BaseSequence> initAllId();
}
import java.util.ArrayList;
import java.util.List; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import com.slowcity.admin.generate.dbmodel.BaseSequence;
import com.slowcity.admin.generate.dbmodel.DigitalTaskSequenceMG;
import com.slowcity.admin.generate.repository.SequenceGenericRepository;
import com.slowcity.admin.generate.service.SequenceGenericService; @Service
@Transactional
public class SequenceGenericServiceImpl implements SequenceGenericService {
private static final Logger log = LoggerFactory.getLogger(SequenceGenericServiceImpl.class);
private SequenceGenericRepository sequenceGenericRepository; public SequenceGenericServiceImpl(SequenceGenericRepository sequenceGenericRepository) {
this.sequenceGenericRepository = sequenceGenericRepository;
} @Override
public String generateId(Class<? extends BaseSequence> clazz) {
String id = sequenceGenericRepository.generateId(clazz);
log.info("{} generate {}", clazz.getName(), id);
return id;
} @Override
public List<BaseSequence> initAllId() { List<BaseSequence> baseSequenceList = new ArrayList<>(),
baseSequenceResultList = new ArrayList<>();
DigitalTaskSequenceMG digitalTaskSequenceMG = new DigitalTaskSequenceMG();
digitalTaskSequenceMG.setName("sequence");
digitalTaskSequenceMG.setSequence(1210000000000000000L); //1210可以代表业务号 000000000000000代表自增流水号
baseSequenceList.add(digitalTaskSequenceMG); for (BaseSequence baseSequence:baseSequenceList) {
BaseSequence resultSequence = sequenceGenericRepository.initAllId(baseSequence);
if(resultSequence != null){
baseSequenceResultList.add(resultSequence);
}
}
return baseSequenceResultList;
} }
数据实现层
import com.slowcity.admin.generate.dbmodel.BaseSequence;
public interface SequenceGenericRepository {
public String generateId(Class<? extends BaseSequence> clazz);
BaseSequence initAllId(BaseSequence Sequence);
}
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component; import com.slowcity.admin.generate.dbmodel.BaseSequence;
import com.slowcity.admin.generate.repository.SequenceGenericRepository; @Component
public class SequenceMongoGenericRepository implements SequenceGenericRepository {
private Map<Class,Class<? extends BaseSequence>> baseSequenceMap;
private MongoTemplate mongoTemplate;
public SequenceMongoGenericRepository(List<BaseSequence> baseSequences, MongoTemplate mongoTemplate){
baseSequenceMap = baseSequences.stream()
.collect(Collectors.toMap(baseSequence -> baseSequence.getClass().getSuperclass(),
BaseSequence::getClass));
this.mongoTemplate = mongoTemplate;
} @Override
public String generateId(Class<? extends BaseSequence> clazz) {
Class<? extends BaseSequence> childClazz = baseSequenceMap.get(clazz);
if(childClazz != null) {
Query query = new Query(Criteria.where("name").is("sequence"));
Update update = new Update().inc("sequence", 1);
Object dbm = mongoTemplate.findAndModify(query, update, childClazz);
if(dbm != null) {
BaseSequence bs = (BaseSequence)dbm;
return String.valueOf(bs.getSequence());
}
}
return null;
} @Override
public BaseSequence initAllId(BaseSequence Sequence) { Query query = new Query(Criteria.where("name").is("sequence"));
Class clazz = Sequence.getClass();
List<? extends BaseSequence> list = mongoTemplate.find(query,clazz);
if(list.isEmpty()){
mongoTemplate.save(Sequence);
return Sequence;
}
return null;
}
}
controller
import java.util.List; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import com.slowcity.admin.generate.dbmodel.BaseSequence;
import com.slowcity.admin.generate.dbmodel.DigitalTaskSequence;
import com.slowcity.admin.generate.service.SequenceGenericService; /**
* id生成器
* @author moona
*
*/
@RestController
@RequestMapping("/generateId/task")
public class TaskGenerateIdController { @Autowired
private SequenceGenericService sequenceGenericService;
@RequestMapping(value = "/taskId", method = RequestMethod.GET)
public String generateTaskId() {
return sequenceGenericService.generateId(DigitalTaskSequence.class);
} @RequestMapping(value = "/init", method = RequestMethod.GET)
public List<BaseSequence> generateTaskIdinit() {
return sequenceGenericService.initAllId();
} }
执行初始化调用方法

对应数据库

开始测试生成id
第一次调用:

第2次调用

第10次调用

此时再查看数据库,序列已经到1210000000000000011 下次调用直接取值了。真正做到了了分布式满足业务的自增全局唯一索引。mongo底层是原子性的,所以也不会出现并发的问题。如果将id生成策略部署成单台机器服务,则可以满足不同服务不同业务的需求,真正做到可定制可扩展。尽可放心使用。

【end】
可实现的全局唯一有序ID生成策略的更多相关文章
- 高并发分布式环境中获取全局唯一ID[分布式数据库全局唯一主键生成]
需求说明 在过去单机系统中,生成唯一ID比较简单,可以使用MySQL的自增主键或者Oracle中的sequence, 在现在的大型高并发分布式系统中,以上策略就会有问题了,因为不同的数据库会部署到不同 ...
- 常见分布式全局唯一ID生成策略
全局唯一的 ID 几乎是所有系统都会遇到的刚需.这个 id 在搜索, 存储数据, 加快检索速度 等等很多方面都有着重要的意义.工业上有多种策略来获取这个全局唯一的id,针对常见的几种场景,我在这里进行 ...
- 分库分表的情况下生成全局唯一的ID
分库分表情况下 跨库的问题怎么解决? 分布式事务怎么解决? 查询结果集集合合并的问题? 全局唯一的id怎么解决? 一般要求:1.保证生成的ID全局唯一,不可重复 2.生成的后一个Id必须大于前一个Id ...
- 常见分布式唯一ID生成策略
方法一: 用数据库的 auto_increment 来生成 优点: 此方法使用数据库原有的功能,所以相对简单 能够保证唯一性 能够保证递增性 id 之间的步长是固定且可自定义的 缺点: 可用性难以保证 ...
- 分布式全局不重复ID生成算法
分布式全局不重复ID生成算法 算法全局id唯一id 在分布式系统中经常会使用到生成全局唯一不重复ID的情况.本篇博客介绍生成的一些方法. 常见的一些方式: 1.通过DB做全局自增操作 优点:简单.高 ...
- 全局唯一订单号生成方法(参考snowflake)
backgroud Snowflake is a network service for generating unique ID numbers at high scale with some si ...
- 高并发环境下全局id生成策略
解决方案: 基于Redis的全局id生成策略:(推荐此方法) 基于雪花算法的全局id生成: https://www.cnblogs.com/kobe-qi/p/8761690.html 基于zooke ...
- 融云技术分享:解密融云IM产品的聊天消息ID生成策略
本文来自融云技术团队原创分享,原文发布于“融云全球互联网通信云”公众号,原题<如何实现分布式场景下唯一 ID 生成?>,即时通讯网收录时有部分改动. 1.引言 对于IM应用来说,消息ID( ...
- 数据库主键ID生成策略
前言: 系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,下面介绍一些常见的ID生成策略. Sequence ID UUID GUID COMB Snowflake 最开始的自增ID为了实现分库 ...
随机推荐
- C++解决最基本的迷宫问题
问题描述:给定一个最基本的迷宫图,用一个数组表示,值0表示有路,1表示有障碍物,找一条,从矩阵的左上角,到右下角的最短路.求最短路,大家最先想到的可能是用BFS求,本文也是BFS求最短路的. 源代码如 ...
- 采用WPF技术,开发OFD电子文档阅读器
前言 OFD是国家标准版式文档格式,于2016年生效.OFD文档国家标准参见<电子文件存储与交换格式版式文档>.既然是国家标准,OFD随后肯定会首先在政务系统使用,并逐步推向社会各个方面. ...
- Bat批处理命令执行中文路径解决方法
用window自带的记事本新建一个bat,然后把命令复制进去,保存就OK
- String的优化 Stringbuffer和Stringbuilder
string 上次说到string是最好衍生出来的一种字符类型,实现原理是由char[].我们知道数组一旦创建时不可更改的,所以每一次进行字符串的拼接都是在new一个新的字符串进行添加,这样的话对内存 ...
- Mysql学习笔记整理之选用B+tree结构
为什么mysql不使用平衡二叉树? 数据处的深度决定着他的IO操作次数,IO操作耗时大 每一个磁盘块保存的数据量太小 B+Tree和B-Tree的区别? B+树几点关键字搜索采用闭合区间 B+树非叶节 ...
- [Scala]集合中List元素转Tuple元素的函数迭代写法
____ 本文链接: https://www.cnblogs.com/senwren/p/Scala-Lis-2-Tuple.html —— Scala没有提供相应写法, 但迭代写法仍然可以做到. 有 ...
- Java String 类解析
I.构造函数: public String() {} 默认构造函数 public String(String original) {} 使用原有字符串构造 public String(char va ...
- 多智能体系统(MAS)简介
1.背景 自然界中大量个体聚集时往往能够形成协调.有序,甚至令人感到震撼的运动场景,比如天空中集体翱翔的庞大的鸟群.海洋中成群游动的鱼群,陆地上合作捕猎的狼群.这些群体现象所表现出的分布.协调.自 ...
- Spring boot 梳理 - 模版引擎 -freemarker
开发环境中关闭缓存 spring: thymeleaf: cache: false freemarker: cache: false Spring boot 集成 freemarker <dep ...
- Fresco添加HTTP请求头
项目中用Fresco来管理图片由于服务器图片有不同的版本需要根据客户端的屏幕密度来选择不同的图片共享一份用OkHttp下载图片并添加HTTP头代码. public class OkHttpNetwor ...