分布式全局唯一ID生成器(支持多种注册中心)的实现

分布式全局唯一ID生成器(支持多种注册中心)的实现在订单、支付的业务场景中,单号的生成规则与生成方式十分重要,实现有很多种,最简单的是基于mysql自增主键实现,方案优劣不多说,大家都清楚。我们今天实现一种分布式的、可扩展的并且在高并发场景能保证高性能的全局唯一ID生成方案(基于twitter的snowflake原理进行改编和扩展)。不多说直接上代码。packagecom.zxm.adapter;importorg.apache.z…

大家好,又见面了,我是你们的朋友全栈君。

在订单、支付的业务场景中,单号的生成规则与生成方式十分重要,实现有很多种,最简单的是基于mysql自增主键实现,方案优劣不多说,大家都清楚。我们今天实现一种分布式的、可扩展的并且在高并发场景能保证高性能的全局唯一ID生成方案(基于twitter的snowflake原理进行改编和扩展)。不多说直接上代码。

package com.zxm.adapter;

import org.apache.zookeeper.KeeperException;

/**
 * @Author zxm
 * @Description 注册适配器
 * @Date Create in 上午 9:45 2019/4/12 0012
 */
public interface RegistryAdapter {
    long getWorkerId() throws KeeperException, InterruptedException;
}
package com.zxm.adapter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.zookeeper.*;

import java.io.IOException;

/**
 * @Author zxm
 * @Description 基于zk的注册适配器
 * @Date Create in 上午 9:44 2019/4/12 0012
 */
@Slf4j
public class ZkRegistryAdapter implements RegistryAdapter {
    private static final int DEFAULT_SESSION_TIMEOUT = 3000;
    private static final String ROOT_NODE = "/idWorker";

    private static final int DEFAULT_MOD_VALUE = 1024;
    private ZooKeeper zkClient;

    public ZkRegistryAdapter(String connectString) throws Exception {
        this(connectString, DEFAULT_SESSION_TIMEOUT);
    }

    public ZkRegistryAdapter(String connectString, int sessionTimeOut) throws Exception {
        try {
            zkClient = new ZooKeeper(connectString, sessionTimeOut, watchedEvent -> log.info("path:{}, state:{}", watchedEvent.getPath(), watchedEvent.getState()));
            initRootNode(zkClient);
        } catch (IOException e) {
            log.error("zookeeper connect error,url:{},errorMsg:{}", connectString, e.getMessage());
            throw e;
        }
    }

    private void initRootNode(ZooKeeper zkClient) throws KeeperException, InterruptedException {
        if (zkClient.exists(ROOT_NODE, false) == null) {
            String path = zkClient.create(ROOT_NODE, "idWorker".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            if (StringUtils.isNotBlank(path)) {
                log.info("root node init success,path:{}", path);
            }
        }
    }

    @Override
    public long getWorkerId() throws KeeperException, InterruptedException {
        String path = zkClient.create(ROOT_NODE + "/_", "idWorker".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        if (StringUtils.isNotBlank(path)) {
            log.info("node create success,path:{}", path);
            return Long.valueOf(path.substring(ROOT_NODE.length() + 2, path.length())) % DEFAULT_MOD_VALUE;
        }
        return -1;
    }
}
package com.zxm.core;

import com.zxm.adapter.RegistryAdapter;
import lombok.extern.slf4j.Slf4j;

/**
 * Id_Worker<br>
 * Id_Worker的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 0000000000 - 0000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
 * 41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的机器位(最多支持1023台机器)<br>
 * 12位序列,毫秒内的计数,7位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 */
@Slf4j
public class IdWorker {

    /**
     * 开始时间截 (2018-01-01 00:00:00)
     */
    private static final long twepoch = 1514736000000L;

    /**
     * 机器id所占的位数
     */
    private static final long workerIdBits = 10L;

    /**
     * 序列在id中占的位数
     */
    private static final long sequenceBits = 12L;


    private static final long workerIdShift = sequenceBits;

    /**
     * 时间截向左移22位(12+10)
     */
    private static final long timestampLeftShift = sequenceBits + workerIdBits;

    /**
     * 生成序列的掩码,这里为4095
     */
    private static final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 工作机器ID(0~1023)
     */
    private static long workerId;

    /**
     * 毫秒内序列(0~4095)
     */
    private static long sequence = 0L;

    /**
     * 上次生成ID的时间截
     */
    private static long lastTimestamp = -1L;

    public IdWorker(RegistryAdapter registryAdapter) throws Exception {
        if (null == registryAdapter) {
            throw new Exception("registryAdapter init fail");
        }
        workerId = registryAdapter.getWorkerId();
        log.info("GLOBAL_WORkER_ID INIT:" + workerId);
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return id
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        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;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;
        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (workerId << workerIdShift)
                | exchangeSequence(sequence);
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected static long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected static long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 打乱自增序列
     *
     * @param sequence
     * @return
     * @since
     */
    protected static long exchangeSequence(long sequence) {
        String tmp = Long.toBinaryString(sequence | (1 << 12));
        StringBuffer sb = new StringBuffer(tmp.substring(1));
        long sqr = Long.parseLong(sb.reverse().toString(), 2) & sequenceMask;
        return sqr;
    }
}

测试生成结果:

package com.zxm;

import com.zxm.adapter.RegistryAdapter;
import com.zxm.adapter.ZkRegistryAdapter;
import com.zxm.core.IdWorker;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) throws Exception {
        RegistryAdapter registryAdapter = new ZkRegistryAdapter("10.10.4.17:2181", 3000);
        Thread.sleep(10 * 1000);
        IdWorker idWorker = new IdWorker(registryAdapter);

        for (int i = 0; i < 10; i++) {
            System.out.println(idWorker.nextId());
        }
    }
}

分布式全局唯一ID生成器(支持多种注册中心)的实现

在spring项目中使用时配置如下:

@Configuration  
 public class IdWorkerConfig {  
     @Bean
     public RegistryAdapter registryAdapter() throws Exception{
         return new ZkRegistryAdapter("10.10.4.4:2181", 3000);
     }
 
     @Bean
     public IdWorker idWorker() throws Exception{
         return new IdWorker(registryAdapter());
     }
 }  

测试用例:

public class IdWorkerTest{
    @Autowire
    private IdWorker idWorker;
    
    @Test
    public void test(){
        for (int i = 0; i < 10; i++) {
            System.out.println(idWorker.nextId());
        }
    }
}

执行结果同上。

项目地址:https://github.com/zhangxiaomin1993/id-worker

项目后续会逐渐完善,代码仅供大家学习参考!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/151708.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 武侠金曲「建议收藏」

    十首香港武侠金曲唱出无限中国意境

    2022年4月13日
    38
  • django 用户注册_云端注册用户怎么注册

    django 用户注册_云端注册用户怎么注册前言我们使用django创建用户可以使用注册接口的方式,也可以使用django自带的后台管理系统,这里就介绍使用后台管理系统创建用户admin后台管理系统在使用之前我们可以使用第三方的插件,来美

    2022年7月29日
    3
  • COLMO管线机隐藏安装_五金加工厂的设备清单

    COLMO管线机隐藏安装_五金加工厂的设备清单ActiveXControlReport-GeneratedbyActiveXManagerfrom4Developers. ComputerName:YUERGeneratedOn:Tuesday,November21,2006ControlCount:138ControlName:RefEditControlVersion:1.0Status:

    2022年10月15日
    0
  • PXE+Kickstart 高效批量网络装机

    PXE+Kickstart 高效批量网络装机文章目录1.PXE概述1.1PXE定义1.2PXE批量部署的优点1.3PXE搭建的前提条件1.4PXE架构示意图2.搭建PXE远程安装服务器2.1安装并启用TFTP服务2.2安装并启用DHCP服务2.3准备Linux内核、初始化镜像文件2.4准备PXE引导程序1.PXE概述1.1PXE定义  PXE(预启动执行环境,在操作系统之前运行)是由Intel公司开发的网络引导技术,工作在Client/Server模式,允许客户机通过网络从远

    2022年6月26日
    24
  • 如何用python画心形_用python制作音乐

    如何用python画心形_用python制作音乐用python绘制爱心的基本步骤如下:首先先下载安装好python程序。在我们自己的电脑上找到python的IDLE工具。2.然后打开IDLE,新建一个文件,命名为test1.py。3.接着我们就开始导入turtle库,然后编辑代码。importturtleimporttime#画心形圆弧defhart_arc():foriinrange(200):turtle.right(1)t…

    2022年9月7日
    1
  • Php公众号40029,微信开发之微信公众平台,网页授权及 40029 问题解决

    Php公众号40029,微信开发之微信公众平台,网页授权及 40029 问题解决本文将带你了解微信开发微信公众平台,网页授权及40029问题解决,希望本文对大家学微信有所帮助。1、跳转授权链接https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=…

    2022年4月29日
    41

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号