经常使用的分布式ID生成策略

分布式的惟一ID是一个系统设计过程当中常常遇到的问题。生成分布式ID的策略有多种,应用场景也不尽相同。下面简单介绍一下一些经常使用的分布式ID生成策略:java

  1. 数据库自增ID
  2. UUID
  3. Redis
  4. Snowflake

(一)数据库自增ID

经过定义数据库的自增ID进行实现。git

优势:github

  1. 简单,使用数据库现成的实现。
  2. ID惟一,不会重复。
  3. ID递增,有序增加,可设置步长。

缺点:redis

  1. 存在单点写入问题。一般在数据库主-从架构的状况下,若是主库挂了,切换到从库的话,颇有可能生成重复的ID,致使数据错乱。
  2. 扩展性差并存在自增上限。

优化:算法

  1. 在写多库的状况下,可经过设置不一样步长避免冲突。如A库(1, 3, 5, 7) 、B库(2, 4, 6, 8)
  2. 能够经过集中式的ID生成。如利用一张表记录当前ID的最大值(如:10),每次集中取一批ID(如:11,12,13,14,15,16),当这批ID用完后,更新最大ID为16,再取下一批。

(二)UUID

经过本地代码或者数据库生成。数据库

优势:架构

  1. 全球惟一,扩展性强,理论上不会出现重复。
  2. 性能好,本地能够生成,不须要远程交互。

缺点:less

  1. 一般是字符串存储,ID较长,做为主键创建索引查询时性能差。
  2. ID随机生成,没有连续性。

优化:dom

  1. 将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。

优势:

  1. 本地生成, 扩展性好,性能优异。
  2. 生成的ID简单、有序。

缺点:

  1. 使用时间进行计算,不一样机器的时钟可能会形成生成的ID并不必定严格有序。

改进:

  1. 能够根据实际的需求对算法进行调整。

下面简单介绍一下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的不一样组成位数进行调整。

  1. 如实际上机器只须要10台的话,那机房和主机标识加起来4位便可。
  2. 如当机器少,可是要支持每秒生成的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();
    }
}
相关文章
相关标签/搜索