可实现的全局唯一有序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为了实现分库 ...
随机推荐
- 16 (OC)* UIAnimation和CoreAnimation
目录 一 Core Animation 二 核心动画 2.1 基础动画 2.2 关键帧动画 2.3 动画组 2.4 转场动画 2.5 逐帧动画 三 UIView动画封装 3.1 基础动画 3.2 弹簧 ...
- H5当弹出弹窗遮罩时如何阻止滚动穿透(使用css方式)
最近的一个H5活动中有一个是点击[分享]弹窗指引遮罩弹窗引导用户进行分享,但突然发现弹出弹窗的时候下层仍然可以进行滑动,这个问题是个H5经久不衰讨论的问题,重点是我这个页面在安卓系统上有明显的滑动闪烁 ...
- python 数据分析师
简介 越来越多的政府机关.企事业单位将选择拥有数据分析师资质的专业人士为他们的项目做出科学.合理的分析.以便正确决策:越来越多的风险投资机构把数据分析师所出具的数据分析报告作为其判断项目是否可行及是否 ...
- 【Django】ESRTful APi
如何利用rest framework搭建Django API框架! 环境:win10 python3.6 思路步骤: 创建一个可以序列化的类 去数据库取数据交给序列化的类处理 把序列化的数据返回前 ...
- springboot全局异常拦截源码解读
在springboot中我们可以通过注解@ControllerAdvice来声明一个异常拦截类,通过@ExceptionHandler获取拦截类抛出来的具体异常类,我们可以通过阅读源码并debug去解 ...
- MVC4 Jqgrid设计与实现
项目本来使用的是webgrid.后台弃用改成Jqgrid插件. 首先介绍一下webgrid的用法:webgrid是mvc中HtmlHelper自带的.首先创建viewmodel用于数据的绑定,然后在页 ...
- SpringBoot自定义异常,优雅解决业务逻辑中的错误
概要 你是不是在为业务逻辑中出现的异常弄的焦头烂额,常常在后台报错,前端却无法提示错误内容,导致用户体验极差?比如下单失败,前端只能提示下单失败,但是却不知道为什么失败,是库存不足,还是余额不足,亦或 ...
- 一道题考你对__autoreleasing和__block的理解
考虑下面的代码,有哪些问题,如何把他改成正确的形式? @interface TestObj : NSObject @end @implementation TestObj - (void)method ...
- zend studio 13.6 导入项目及其他设置
1. 先创建一个新的项目:file -> new -> project 2. 创建新项目之后,在左侧的项目目录上右键 -> import 到此导入项目, 完成! 设置编码utf-8 ...
- Inkscape 旋转并复制
画一个图形,点击图标. 然后图标中心有个十字叉, 然后把这个十字叉拖到你想要旋转的地方. 然后shift+ctrl+m打开变换菜单. 选择旋转选项卡,然后设置角度,点击应用.就可以旋转了,如果配合ct ...