• 我爱java系列---【分布式ID生成解决方案:UUID、Redis生成id、snowflake】


    1. 分布式ID生成解决方案

    为什么要生成唯一id:
    因为商品id在全局需要唯一, 如果使用数据库自增主键, 那么数据库可以保证单个数据库中这个id是唯一的,
    但是如果是数据库集群情况, 不能保证, id全局唯一.

    1.1 分布式ID生成解决方案

    1.1.1 UUID

    常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

    优点:

    1)简单,代码方便。

    2)生成ID性能非常好,基本不会有性能问题。

    3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。

     缺点:

    1)没有排序,无法保证趋势递增。

    2)UUID往往是使用字符串存储,查询的效率比较低。

    3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。

    4)传输数据量大

    5)不可读。

    1.1.2 Redis生成ID

    当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。

    优点: redis是内存操作, 速度快, 生成的是数字, 可读性好, 并且可以按照生成的时间先后排序。
    缺点: 如果整个系统没有用到redis技术, 那么这里使用redis会增加系统的技术复杂度.
        应用服务器到redis服务器获取唯一id, 增加网络io。

    1.1.3 开源算法snowflake

     snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0

    这个算法是Twitter公司研发的, 一共有64bit组成, 前1bit是校验位, 后41位是当前时间毫秒数构成的时间戳

    , 再往后10bit是配置的服务器编号, 再往后12bit是随机数。

    优点: 生成的是数字, 可读性好, 并且可以按照时间排序, 基本上不会重复, 不需要访问其他服务器就能生成,
          不会占用网络io。
    缺点: 因为前41bit是按照系统当前时间毫秒数来生成的, 所以在服务器集群中需要校对各个服务器时间, 必须一致
        如果各个服务器系统时间不准, 会造成生成出来的唯一id是无序的。

    1.2 snowflake快速入门

    1.2.1 快速入门

    (1)新建工程,将工具类IdWorker.java拷贝到工程中。

    import java.lang.management.ManagementFactory;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    
    /**
     * <p>名称:IdWorker.java</p>
     * <p>描述:分布式自增长ID</p>
     * <pre>
     *     Twitter的 Snowflake JAVA实现方案
     * </pre>
     * 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
     * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
     * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
     * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
     * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
     * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
     * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
     * <p>
     * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
     *
     * @author Polim
     */
    public class IdWorker {
        // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
        private final static long twepoch = 1288834974657L;
        // 机器标识位数
        private final static long workerIdBits = 5L;
        // 数据中心标识位数
        private final static long datacenterIdBits = 5L;
        // 机器ID最大值
        private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
        // 数据中心ID最大值
        private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
        // 毫秒内自增位
        private final static long sequenceBits = 12L;
        // 机器ID偏左移12位
        private final static long workerIdShift = sequenceBits;
        // 数据中心ID左移17位
        private final static long datacenterIdShift = sequenceBits + workerIdBits;
        // 时间毫秒左移22位
        private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    
        private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
        /* 上次生产id时间戳 */
        private static long lastTimestamp = -1L;
        // 0,并发控制
        private long sequence = 0L;
    
        private final long workerId;
        // 数据标识id部分
        private final long datacenterId;
    
        public IdWorker(){
            this.datacenterId = getDatacenterId(maxDatacenterId);
            this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
        }
        /**
         * @param workerId
         *            工作机器ID
         * @param datacenterId
         *            序列号
         */
        public IdWorker(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;
        }
        /**
         * 获取下一个ID
         *
         * @return
         */
        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) {
                // 当前毫秒内,则+1
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    // 当前毫秒内计数满了,则等待下一秒
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }
            lastTimestamp = timestamp;
            // ID偏移组合生成最终的ID,并返回ID
            long nextId = ((timestamp - twepoch) << timestampLeftShift)
                    | (datacenterId << datacenterIdShift)
                    | (workerId << workerIdShift) | sequence;
    
            return nextId;
        }
    
        private long tilNextMillis(final long lastTimestamp) {
            long timestamp = this.timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = this.timeGen();
            }
            return timestamp;
        }
    
        private long timeGen() {
            return System.currentTimeMillis();
        }
    
        /**
         * <p>
         * 获取 maxWorkerId
         * </p>
         */
        protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
            StringBuffer mpid = new StringBuffer();
            mpid.append(datacenterId);
            String name = ManagementFactory.getRuntimeMXBean().getName();
            if (!name.isEmpty()) {
                /*
                 * GET jvmPid
                 */
                mpid.append(name.split("@")[0]);
            }
            /*
             * MAC + PID 的 hashcode 获取16个低位
             */
            return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
        }
    
        /**
         * <p>
         * 数据标识id部分
         * </p>
         */
        protected static long getDatacenterId(long maxDatacenterId) {
            long id = 0L;
            try {
                InetAddress ip = InetAddress.getLocalHost();
                NetworkInterface network = NetworkInterface.getByInetAddress(ip);
                if (network == null) {
                    id = 1L;
                } else {
                    byte[] mac = network.getHardwareAddress();
                    id = ((0x000000FF & (long) mac[mac.length - 1])
                            | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            } catch (Exception e) {
                System.out.println(" getDatacenterId: " + e.getMessage());
            }
            return id;
        }
    
    
        public static void main(String[] args) {
    
            IdWorker idWorker=new IdWorker(0,0);
    
            for(int i=0;i<10000;i++){
                long nextId = idWorker.nextId();
                System.out.println(nextId);
            }
        }
    
    }

    (2)编写代码

    IdWorker idWorker=new IdWorker(1,1);
    
            for(int i=0;i<10000;i++){
                long id = idWorker.nextId();
                System.out.println(id);
            }

    配置分布式ID生成器

    (1)IdWorker.java拷贝到highgou_common工程com.highgou.util包中

    (2)highgou_service_goods的application.yml添加配置

    #工作机器号(给部署同样服务的机器编号,服务所在的机器号)
    workerId: 0
    #生成唯一id使用的随机数种子,类似于Math.Random(10)中的10 datacenterId: 0

    (3)修改GoodsApplication,增加代码

     @Value("${workerId}")
        private Integer workerId;
    
        @Value("${datacenterId}")
        private Integer datacenterId;
    
        @Bean
        public IdWorker idWorker(){
            return new IdWorker(workerId,datacenterId);
        }

    (4)在任意Controller类中注入IdWorker,测试是否能够生成ID

    愿你走出半生,归来仍是少年!
  • 相关阅读:
    未能加载文件或程序集“file:///C:Program Files (x86)SAP BusinessObjectsCrystal Reports for .NET Framework 4.0
    iwms后台出现从客户端(ctl00$cphMain$logo="<img src="pic/logo.g...")中检测到有潜在危险的 Request.Form 值。错误解决方法
    Socket层实现系列 — accept()的实现(二)
    Socket层实现系列 — accept()的实现(一)
    Socket层实现系列 — getsockname()和getpeername()的实现
    洛谷1279 字串距离
    poj 2411
    poj 2411
    noip提高组 2010 关押罪犯 (洛谷1525)
    二分图匹配 (匈牙利算法) 洛谷3386
  • 原文地址:https://www.cnblogs.com/hujunwei/p/11894915.html
Copyright © 2020-2023  润新知