分库分表 Sharding-JDBC 分布式主键

点点圈 提交于 2020-02-28 07:01:28

在传统企业软件开发中,主键自动生成技术是基本需求,各个数据库对于该自增主键的需求提供了相应的支持,如MySQL的自增键。对于MySQL而言,分库分表之后,不同库、不同表生成全局唯一的主键是非常麻烦的事情。因为同一个逻辑表内的不同物理表之间的自增主键是无法互相感知的,这样会生成重复的主键。

目前有许多第三方解决方案可以完美解决这个问题,比如UUID等依靠特定算法自生成不重复键,或者通过引入主键生成服务(Redis或者ZooKeeper)等。

Sharding-JDBC提供了注解生成接口KeyGenerator。各个实现类通过实现generateKey()方法即可对外提供生成主键的功能。下面分析Sharding-JDBC分布式主键的使用。

DefaultKeyGenerator是Sharding-JDBC默认的主键生成器。该主键生成器采用Twitter Snowflake算法实现生成64位的Long型编号。国内很多大型互联网公司发号器服务基于该算法加部分改造实现。下面分析DefaultKeyGenerator产生的编号的组成。

DefaultKeyGenerator生成的64位Long型编号的组成如图所示。

64位Long型编号中各个部分的如表所示。

位数 含义 取值范围
1 符号位 0
41 时间戳 从2016-11-01 00:00:00 开始的毫秒数,支持约70年
10 工作进程编号 最大进程编号1024
12 序列号 每毫秒从0开始自增,每毫秒最多4096个编号,每秒最多4096000个编号

DefaultKeyGenerator部分代码如下:

package io.shardingjdbc.core.keygen;

import com.google.common.base.Preconditions;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * Default distributed primary key generator.
 * 
 * Use snowflake algorithm. Length is 64 bit.
 *
 * Call @{@code DefaultKeyGenerator.setWorkerId} to set.
 */
@Slf4j
public final class DefaultKeyGenerator implements KeyGenerator {
    
    // 2016-11-01 00:00:00 000 对应的毫秒数
    public static final long EPOCH;

    // 12位序列号
    private static final long SEQUENCE_BITS = 12L;

    // 10位工作进程号
    private static final long WORKER_ID_BITS = 10L;

    // 12位序列号自增量掩码(最大值)
    private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;

    // 工作进程ID左移比特数(位数)
    private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS;

    // 时间戳左移比特数(位数)
    private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS;

    // 工作进程ID最大值
    private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS;
    
    @Setter
    // 当前时间
    private static TimeService timeService = new TimeService();

    // 工作进程
    private static long workerId;
    
    // 初始化 EPOCH
    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2016, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }
    
    // 自增量
    private long sequence;
    
    // 最后生成编号时间戳,单位:毫秒
    private long lastTime;
    
    /**
     * 设置工作进程
     */
    public static void setWorkerId(final long workerId) {
        Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE);
        DefaultKeyGenerator.workerId = workerId;
    }
    
    /**
     * Generate key.
     * 创建id
     * 
     * @return key type is @{@link Long}.
     */
    @Override
    public synchronized Number generateKey() {
        long currentMillis = timeService.getCurrentMillis();
        Preconditions.checkState(lastTime <= currentMillis, 
            "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", 
            lastTime, currentMillis);
        
        if (lastTime == currentMillis) {
            if (0L == (sequence = ++sequence & SEQUENCE_MASK)) {
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }
        lastTime = currentMillis;
        if (log.isDebugEnabled()) {
            log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence);
        }
        return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence;
    }
    
    private long waitUntilNextTime(final long lastTime) {
        long time = timeService.getCurrentMillis();
        while (time <= lastTime) {
            time = timeService.getCurrentMillis();
        }
        return time;
    }
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!