服务id生成方案

I.问题

    在微服务系统中,系统的业务逻辑会分布在不一样的服务中,不一样的服务也能够起多个,那么如何标识每个服务,须要对每个服务起一个惟一id,该id须要具备惟一性,以及知足必定的顺序性,例如按照服务启动的顺序生成服务id号,若是每一个服务都使用了雪花算法,雪花算法中的节点id如何生成?实现方案有4种。java

II.方案

  1. 配置文件
    在配置文件中增长一个服务nodeId配置(简单粗暴,不推荐,万不得已才使用)node

    application:
      #别的服务配置其余nodeId
      nodeId: 1
  2. 使用随机数
    当须要生成惟一id,在必定返回内,生成随机数,例如在1-1024中生成随机数。算法

    return new Random().nextInt(1024) + 1;
  3. 获取当前服务所在机器的mac地址,对mac地址进行位运算(若是一个机器部署多个服务,就会有问题了)apache

    static int getNodeId() {
         InetAddress ip = InetAddress.getLocalHost();
         NetworkInterface network = NetworkInterface.getByInetAddress(ip);
         int id;
         if (network == null) {
             log.info("network is [{}]",network)
         } else {
             byte[] mac = network.getHardwareAddress();
             id = ((0x000000FF & (int) mac[mac.length - 1]) | (0x0000FF00 & (((int) mac[mac.length - 2]) << 8))) >> 6;
         }
         return id;
     }
  4. 使用zookeeper临时节点(推荐)
    首先创建一个zk持久节点,每个服务启动时在该节点下创建一个临时节点,若是服务中止了,临时节点也会中止。数组

    package com.xiayu.config;
    
      import lombok.Getter;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.curator.framework.CuratorFramework;
      import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
      import org.apache.curator.framework.recipes.locks.Locker;
      import org.apache.zookeeper.CreateMode;
      import org.apache.zookeeper.Watcher;
      import org.apache.zookeeper.data.Stat;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
    
      import java.lang.management.ManagementFactory;
      import java.net.InetAddress;
      import java.nio.charset.StandardCharsets;
      import java.util.List;
      import java.util.concurrent.TimeUnit;
      import java.util.stream.Collectors;
      import java.util.stream.IntStream;
    
      @Getter
      @Slf4j
      public class Node {
    
          static final Logger LOG = LoggerFactory.getLogger(Node.class);
    
          private final int nodeId; //生成的节点id
    
          private final static int MAX_NODE_NUM = 1024; //节点数目最大值
    
          final private String nonReentrantLockPath = "/application/lock/nonreentrant";
    
          final private String nodePath = "/application/nodes"; //节点目录
    
          static final private String fullNodePrefix = "/application/nodes/node"; //节点下的临时节点
    
          static final private  String nodePrefix = "node"; //临时节点前缀
    
          final private InterProcessSemaphoreMutex interProcessSemaphoreMutex;
    
          final private CuratorFramework client; //zk 客户端
    
          public Node(CuratorFramework client) throws Exception {
              this.client = client;
              interProcessSemaphoreMutex = new InterProcessSemaphoreMutex(client, "/application/lock/nonreentrant");
              this.nodeId = generateNodeIdId();
          }
    
          static byte[] getData() {//临时节点下的数据
              String ip = "0";
              try {
                  ip = InetAddress.getLocalHost().getHostAddress();
              } catch (Exception ex) {
              }
    
              return (ip + "," + ManagementFactory.getRuntimeMXBean().getName()).getBytes(StandardCharsets.UTF_8);
          }
    
          int generateNodeIdId() throws Exception {
              try (Locker locker = new Locker(interProcessSemaphoreMutex, 2, TimeUnit.MINUTES)) { //可重入锁
                  Stat exist = this.client.checkExists().forPath(nodePath); //服务节点目录
                  if (exist == null) { //服务节点目录不存在就建立
                      this.client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(nodePath);
                  }
                  //服务节点目录下的全部临时节点
                  List<String> nodes = this.client.getChildren().usingWatcher((Watcher) event -> LOG.info("Got event [{}]", event)).forPath(nodePath);
    
                  LOG.info("got temp nodes [{}]", nodes);
                  //找到服务节点中,数目最大的节点
                  List<Integer> existsIds = nodes.stream()
                          .filter(x -> x.startsWith(nodePrefix)) //过滤
                          .map(x -> x.substring(nodePrefix.length()))
                          //先截取掉 fullWorkerNodePrefix 获得后面的数字
                          //如/application/nodes/node45  获取45
                          .map(Integer::parseInt)
                          .collect(Collectors.toList()); //获取list
                  if (existsIds.size() >= MAX_NODE_NUM) { //若是数组数目已经大于最大值,那么服务将起不了了
                      throw new IllegalStateException("Max " + MAX_NODE_NUM + " nodeId reached, Cannot start new instance");
                  }
                  int max = existsIds.stream().max(Integer::compareTo).orElse(-1); //找到数组中的最大值
    
                  int nextId = max + 1; //在1到最大值加1中找到一个随机值,而且不能已经存在,这样作是由于临时节点随时可能消失
                  if (existsIds.size() != nextId) {
                      nextId = IntStream.range(0, nextId)
                              .filter(x -> !existsIds.contains(x))
                              .findFirst().orElse(nextId);
                  }
                  assert !existsIds.contains(nextId) : "new node id should not in zk path " + nodePath;
                  //建立新生成的节点
                  String nextNodePath = this.client.create()
                          .creatingParentsIfNeeded()
                          .withMode(CreateMode.EPHEMERAL)
                          .forPath(fullNodePrefix + nextId, getData());
                  return nextId; //返回服务id
              }
          }
      }

III.方案比较

    采用zookeeper方案是比较推荐的,可是zk的方案服务的最大数目是1024,对绝大数项目都知足了,可是若是在某种状况下,若是zookeeper没有起来,可是服务还要启动,就能够考虑mac地址方案、随机数方案和读取配置文件了,方案推荐的顺序为zk>mac>random>config;在实际项目中,能够融合多种方案,保证高可用。app

相关文章
相关标签/搜索