服務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

相關文章
相關標籤/搜索