服务id生成方案

I.问题

    在微服务系统中,系统的业务逻辑会分布在不同的服务中,不同的服务也可以起多个,那么如何标识每一个服务,需要对每一个服务起一个唯一id,该id需要具有唯一性,以及满足一定的顺序性,例如按照服务启动的顺序生成服务id号,如果每个服务都使用了雪花算法,雪花算法中的节点id如何生成?实现方案有4种。

II.方案

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

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

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

     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 nodes = this.client.getChildren().usingWatcher((Watcher) event -> LOG.info("Got event [{}]", event)).forPath(nodePath);
    
                  LOG.info("got temp nodes [{}]", nodes);
                  //找到服务节点中,数目最大的节点
                  List 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); //找到数组中的最大值
                  if (max == -1){
                      nextId = 1;
                  }else if (max == existsIds.size()){
                      nextId = max + 1;
                  }else{
                      for (int i = 1;i

III.方案比较

    采用zookeeper方案是比较推荐的,但是zk的方案服务的最大数目是1024,对绝大数项目都满足了,但是如果在某种情况下,如果zookeeper没有起来,但是服务还要启动,就可以考虑mac地址方案、随机数方案和读取配置文件了,方案推荐的顺序为zk>mac>random>config;在实际项目中,可以融合多种方案,保证高可用,并且可以在本地开发环境或者测试环境切换不同方案。

你可能感兴趣的:(微服务,ide)