常用的分布式ID生成策略
2017-02-24 00:00
309 查看
分布式的唯一ID是一个系统设计过程中经常遇到的问题。生成分布式ID的策略有多种,应用场景也不尽相同。下面简单介绍一下一些常用的分布式ID生成策略:
数据库自增ID
UUID
Redis
Snowflake
优点:
简单,使用数据库现成的实现。
ID唯一,不会重复。
ID递增,有序增长,可设置步长。
缺点:
存在单点写入问题。通常在数据库主-从架构的情况下,如果主库挂了,切换到从库的话,很有可能生成重复的ID,导致数据错乱。
扩展性差并存在自增上限。
优化:
在写多库的情况下,可通过设置不同步长避免冲突。如A库(1, 3, 5, 7) 、B库(2, 4, 6, 8)
可以通过集中式的ID生成。如利用一张表记录当前ID的最大值(如:10),每次集中取一批ID(如:11,12,13,14,15,16),当这批ID用完后,更新最大ID为16,再取下一批。
优点:
全球唯一,扩展性强,理论上不会出现重复。
性能好,本地可以生成,不需要远程交互。
缺点:
通常是字符串存储,ID较长,作为主键建立索引查询时性能差。
ID随机生成,没有连续性。
优化:
将uuid转成int64。这样既减少空间也能优化查询效率。
优点:
本地生成, 扩展性好,性能优异。
生成的ID简单、有序。
缺点:
使用时间进行计算,不同机器的时钟可能会造成生成的ID并不一定严格有序。
改进:
可以根据实际的需求对算法进行调整。
下面简单介绍一下Snowflake的基本原理。
snowflake使用64bit的存储空间,正好是一个长整数的存储空间。
时间前缀:
使用秒数记录。需要设置一个起始时间点(如:2016-01-01 00:00:00)。那么生成ID时,将当前的秒数-起始时间的秒数。年T = (1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139.46,表示这个算法可以支持使用139.46年。
机房标识:
可以用于表示机房编号之类。按(1L << 5) = 32计算,可以支持32个机房。
主机标识:
可以用于表示主机编号之类。按(1L << 5) = 32计算,可以支持32个主机。综合机房标识和主机标识,一共可以支持使用1024台主机。
顺序号:
按(1L << 12) = 4096计算,每秒可以生成4096个ID。再综合机房、主机、顺序号,1024*4096约400万,即每秒可以生成400万个不重复的ID。
参考:https://github.com/twitter/snowflake
注意:可以根据实际情况对snowflake的不同组成位数进行调整。
如实际上机器只需要10台的话,那机房和主机标识加起来4位即可。
如当机器少,但是要支持每秒生成的ID更多时,也可以调整顺序号的位数。
参考中的代码使用scala编写,下面是java版本的翻译:
数据库自增ID
UUID
Redis
Snowflake
(一)数据库自增ID
通过定义数据库的自增ID进行实现。优点:
简单,使用数据库现成的实现。
ID唯一,不会重复。
ID递增,有序增长,可设置步长。
缺点:
存在单点写入问题。通常在数据库主-从架构的情况下,如果主库挂了,切换到从库的话,很有可能生成重复的ID,导致数据错乱。
扩展性差并存在自增上限。
优化:
在写多库的情况下,可通过设置不同步长避免冲突。如A库(1, 3, 5, 7) 、B库(2, 4, 6, 8)
可以通过集中式的ID生成。如利用一张表记录当前ID的最大值(如:10),每次集中取一批ID(如:11,12,13,14,15,16),当这批ID用完后,更新最大ID为16,再取下一批。
(二)UUID
通过本地代码或者数据库生成。优点:
全球唯一,扩展性强,理论上不会出现重复。
性能好,本地可以生成,不需要远程交互。
缺点:
通常是字符串存储,ID较长,作为主键建立索引查询时性能差。
ID随机生成,没有连续性。
优化:
将uuid转成int64。这样既减少空间也能优化查询效率。
private static BigInteger getBigIntegerFromUuid() { UUID id = UUID.randomUUID(); ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(id.getMostSignificantBits()); bb.putLong(id.getLeastSignificantBits()); return new BigInteger(1, bb.array()); }
(三)Redis
通过redis的incrby或者lua方式进行生成ID。(四)Snowflake
Snowflake算法源自Twitter,主要应用于解决分布式的场景下的唯一ID。优点:
本地生成, 扩展性好,性能优异。
生成的ID简单、有序。
缺点:
使用时间进行计算,不同机器的时钟可能会造成生成的ID并不一定严格有序。
改进:
可以根据实际的需求对算法进行调整。
下面简单介绍一下Snowflake的基本原理。
snowflake使用64bit的存储空间,正好是一个长整数的存储空间。
时间前缀(42) + 机房标识(5) + 主机标识(5) + 顺序号(12)
时间前缀:
使用秒数记录。需要设置一个起始时间点(如:2016-01-01 00:00:00)。那么生成ID时,将当前的秒数-起始时间的秒数。年T = (1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139.46,表示这个算法可以支持使用139.46年。
机房标识:
可以用于表示机房编号之类。按(1L << 5) = 32计算,可以支持32个机房。
主机标识:
可以用于表示主机编号之类。按(1L << 5) = 32计算,可以支持32个主机。综合机房标识和主机标识,一共可以支持使用1024台主机。
顺序号:
按(1L << 12) = 4096计算,每秒可以生成4096个ID。再综合机房、主机、顺序号,1024*4096约400万,即每秒可以生成400万个不重复的ID。
参考:https://github.com/twitter/snowflake
注意:可以根据实际情况对snowflake的不同组成位数进行调整。
如实际上机器只需要10台的话,那机房和主机标识加起来4位即可。
如当机器少,但是要支持每秒生成的ID更多时,也可以调整顺序号的位数。
参考中的代码使用scala编写,下面是java版本的翻译:
package com.zheng.coderepo.snowflake; /** * Created by zhangchaozheng on 17-2-24. */ public class IdGen { 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 << sequenceBits); private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public IdGen(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > 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; } 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(); } }
相关文章推荐
- 分布式 ID 生成策略 —— 听云资深 Java 工程师
- 【Hibernate框架开发之四】Hibernate-Annotation常用的注解归总&&ID的生成策略&&联合主键
- 分布式ID生成策略(1)_snowflake算法
- 分布式ID生成策略
- 分布式高并发下全局ID生成策略
- Hibernate-Annotation常用的注解归总&&ID的生成策略&&联合主键
- 分布式ID生成策略(1)_snowflake算法
- 数据库分库分表(一)常见分布式主键ID生成策略
- 数据库分库分表常见分布式主键ID生成策略
- 分布式ID生成策略
- 分布式 ID 生成策略
- 【Hibernate框架开发之四】Hibernate-Annotation常用的注解归总&&ID的生成策略&&联合主键
- 分布式系统全局id生成策略
- 基于JPA的ID生成策略
- Hibernate学习(四)ID生成策略
- hibernate的ID生成策略(annotation方式@GeneratedValue)
- 常用的Hibernate主键生成策略
- hibernate的ID生成策略(annotation方式@GeneratedValue)
- Hibernate ID 生成策略
- Hibernate中id的table生成策略之联合主键