分布式id黑科技完善_author lry-程序员宅基地

技术标签: 唯一id  分布式  

    分布式环境下唯一id确实算个问题,今天分享的算法自己项目已经跑了两年,效率高也没出现重复情况;

直接上干货:

核心两个类:

package com.longbei.appservice.common;

import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 高并发场景下System.currentTimeMillis()的性能问题的优化
 * <p><p>
 * System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我还没测试过,有人说是100倍左右)<p>
 * System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道<p>
 * 后台定时更新时钟,JVM退出时,线程自动回收<p>
 * 10亿:43410,206,210.72815533980582%<p>
 * 1亿:4699,29,162.0344827586207%<p>
 * 1000万:480,12,40.0%<p>
 * 100万:50,10,5.0%<p>
 * @author lry
 */
public class SystemClock {

    private final long period;
    private final AtomicLong now;

    private SystemClock(long period) {
        this.period = period;
        this.now = new AtomicLong(System.currentTimeMillis());
        scheduleClockUpdating();
    }

    private static class InstanceHolder {
        public static final SystemClock INSTANCE = new SystemClock(1);
    }

    private static SystemClock instance() {
        return InstanceHolder.INSTANCE;
    }

    private void scheduleClockUpdating() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "System Clock");
                thread.setDaemon(true);
                return thread;
            }
        });
        scheduler.scheduleAtFixedRate(new Runnable() {
            public void run() {
                now.set(System.currentTimeMillis());
            }
        }, period, period, TimeUnit.MILLISECONDS);
    }

    private long currentTimeMillis() {
        return now.get();
    }

    public static long now() {
        return instance().currentTimeMillis();
    }
    
   public static String nowDate() {
      return new Timestamp(instance().currentTimeMillis()).toString();
   }

}

package com.longbei.appservice.common;

/**
 * 分布式高效有序ID生产黑科技(sequence)
 * 
 * @author lry
 */
public class Sequence {

   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;

   /**
    * @param workerId 工作机器ID
    * @param datacenterId 序列号
    */
   public Sequence(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) {
         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 SystemClock.now();
   }

}

private static SFSequence sequ = new SFSequence(machineId%32,0);
**
 * 内存方式分配全局唯一ID
 * 
 * @author shuzc 总共四种方式,getNextId,getUniqueIdAsString,getNextId和数据库自增id.
 *         1.用户id用getNextId;如果分表按最后2 ~ 3位分即可
 *         2.订单id用getUniqueIdAsString,最好在这个串的前面添加某个前缀,如果想在中间添加,修改下代码;如果分表按最后2 ~
 *         3位分即可 3.商品id用getNextId,如果分表按最后2 ~ 3位分即可 4.进步id用getNextId,这个应该没有分表需求
 *         5.点赞记录的id,或者一些流水性质的,比如用户操作记录等,这些记录应该只有查询的场景,没有更新等场景;用数据库自增id,
 *         或者getUniqueIdAsLong,这种分表也不会利用主键的
 * @return
 */
@Service
@SuppressWarnings("hiding")
public class IdGenerateService  {
   private static int MAX_TRY_TIMES = 20;
   private static long machineId = calculateMachineId();
   private static final SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
   private static final SimpleDateFormat SHORT_DATE_TIME_FORMAT = new SimpleDateFormat("yyMMddHHmmss");
   private static final String MACHINE_ID_FORMAT = "%1d";
   private static final AtomicLong seq = new AtomicLong(0);

   private static long calculateMachineId() {
      try {
         for (NetworkInterface iface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
            for (InetAddress raddr : Collections.list(iface.getInetAddresses())) {
               if (raddr.isSiteLocalAddress() && !raddr.isLoopbackAddress() && !(raddr instanceof Inet6Address)) {
                  return getLocalMac(raddr);
               }
            }
         }
      } catch (SocketException e) {
         throw new IllegalStateException("fail to get host ip");
      }

      throw new IllegalStateException("fail to get host ip");
   }
   
   private static long getLocalMac(InetAddress ia) throws SocketException {
      byte[] mac = NetworkInterface.getByInetAddress(ia).getHardwareAddress();
      int temp = 0;
      StringBuffer sb = new StringBuffer("");
      for (int i = 0; i < mac.length; i++) {
         if (i != 0) {
            sb.append("-");
         }
         temp ^= (mac[i] & 0xff);
      }
      // 与时间异或,更有利于排除重复的可能
      return temp ^ (System.currentTimeMillis() % 100);
   }
   public Long getUniqueIdAsLong() {
      return sequ.nextId();
   }

   private static SFSequence sequ = new SFSequence(machineId%32,0);

for (InetAddress raddr : Collections. list(iface.getInetAddresses())) { if (raddr.isSiteLocalAddress() && !raddr.isLoopbackAddress() && !(raddr instanceof Inet6Address)) { return getLocalMac(raddr) ; } } } } catch (SocketException e) { throw new IllegalStateException( "fail to get host ip") ; } throw new IllegalStateException( "fail to get host ip") ;}
}


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/smkk12345/article/details/79380710

智能推荐

php中统一编码语句,在PHP中比较字符串之前,使编码统一-程序员宅基地

文章浏览阅读84次。我正在开发一项功能,要求我获取网页的内容,然后检查该页面中是否存在某些文本.这是一个反向链接检查工具.问题是-函数在大多数情况下都能完美运行,但是有时,当链接明显位于该页面时,它会将页面标记为没有链接.我已经将其跟踪到视觉上比较输出中的字符串的程度,并且它们匹配得很好,但是使用==运算符,php告诉我它们不匹配.意识到这可能是某种编码问题,所以我决定看看如果在它们上使用base64_encode(...

Linux学习之路(二)查看系统剩余空间_kali查看剩余空间-程序员宅基地

文章浏览阅读523次。Linux下查看系统剩余空间1.查看系统整体空间剩余情况在命令行中输入“df -h”可以查看系统的分配,已使用和可用情况。如下图:2.查看每个文件夹的占用情况在命令行中输入 “du -sh *”可以查看每个文件夹的大小。此举可以快速定位大文件所存在的位置。..._kali查看剩余空间

金融评测指标empyrical库详解Sortino、calmar、omega、sharpe、annual_return、max_drawdown-程序员宅基地

文章浏览阅读1.9w次,点赞14次,收藏99次。empyreal生平empyrical是常用金融风险和表现度量。被zipline和pyfolio采用。三者都是quantopian开发维护。Github 官方文档API参考sortino_ratioempyrical.sortino_ratio(returns, required_return=0, period='daily', annualization=None, ..._empyrical库

.NET高级工程师面试经历_高级.net高级高频面试-程序员宅基地

文章浏览阅读1w次,点赞3次,收藏20次。1.SQL Server数据库操作的原子性,出Select之外,Update、Insert、Delete的操作都是原子性的,不可拆分,执行的最小单位;可以用于充值交费中 ,如果多个请求进行更新同一条 数据时,直接使用update Table1 set money=money+100 这种方式就可以避免多个语句,更新一条记录导致的更新失败的问题(一般想法是,先查询当前的账户余额,然后进行更新,这..._高级.net高级高频面试

Consider defining a bean of type ‘com.csf.mapper.UserMapper‘ in your configuration_consider defining a bean of type 'mapper.usermappe-程序员宅基地

文章浏览阅读882次。SpringBoot整合mybatis时,启动报错:**Field userMapper in com.csf.controller.UserController required a bean of type ‘com.csf.mapper.UserMapper’ that could not be found.The injection point has the following annotations:- @org.springframework.beans.factory.annotati_consider defining a bean of type 'mapper.usermapper' in your configuration.

ORA-02062: distributed recovery 引起的 ORA-02019: connection description for remote database not found...-程序员宅基地

文章浏览阅读5.9k次。一个测试数据库隔三差五的报一个ORA-02019出来,查找alert文件,有一个详细的trace:*** 2007-10-12 21:47:55.083ERROR, tran=2.34.74876, sessio..._connection description for remote database not found

随便推点

java 小数乘法_集合复习教案-程序员宅基地

文章浏览阅读159次。集合jí hé[释义]①(动)许多分散的人或物聚在一起。民兵已经在村前~了。(作谓语)②(动)使集合。~各种材料;加以分析。(作谓语)[构成]动补式:集〈合[反义]分散、解散[同音]极核...仿写复习教案一、《语文课程标准》:根据语言情境的需要进行仿写、扩写、续写等,做到语言准确、连贯、流畅。其实仿写语句试题有一定的综合性,它往往涉及语法结构、表达方式、语意连贯、修辞运用、风格谐调等许多方面,也间..._java 小数乘法

v-if多个条件判断语句_v-else-if最多写几个-程序员宅基地

文章浏览阅读4.4w次。v-if多个条件判断语句还有条件则继续增加v-else-if// 我这里是三个条件,如果还有条件则继续增加 v-else-if <div v-if="item.enable === '已签到'"> <span class="badge badge-success">{{ item.enable }}</span> </div> <..._v-else-if最多写几个

linux 主从多进程编程,linux网络编程学习笔记之5 -并发机制与线程池-程序员宅基地

文章浏览阅读64次。linux网络编程学习笔记之五 -----并发机制与线程池进程线程分配方式简述下常见的进程和线程分配方式:(好吧,我只是举几个例子作为笔记。。。并发的水太深了,不敢妄谈。。。)1、进程线程预分配简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销。可以在请求到达前预先进行分配。2、进程线程延迟分配预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定..._while (1) { usleep(50000); }

vue拖拽列表vuedraggable_vue拖拽双列表-程序员宅基地

文章浏览阅读457次。效果:要点:安装npm install vuedraggable引入import draggable from 'vuedraggable'使用<vuedraggable class="wrapper" v-model="list"> <transition-group> <div v-for="item in list" :key..._vue拖拽双列表

idea快速创建serilizableuid_idea快速添加ser-程序员宅基地

文章浏览阅读784次。_idea快速添加ser

攻防世界-pwn pwn-100(ROP)_攻防世界pwn pwn-100-程序员宅基地

文章浏览阅读2.8k次,点赞4次,收藏7次。此题是LCTF 2016年的pwn100参考文章:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&ctid=1570x01 文件分析64位elf无stack无PIE 0x02 运行分析 看起来像一个无限循环,不断接收输入。 0x03 静态分析main:sub_40068E:..._攻防世界pwn pwn-100