I.问题
在微服务系统中,系统的业务逻辑会分布在不同的服务中,不同的服务也可以起多个,那么如何标识每一个服务,需要对每一个服务起一个唯一id,该id需要具有唯一性,以及满足一定的顺序性,例如按照服务启动的顺序生成服务id号,如果每个服务都使用了雪花算法,雪花算法中的节点id如何生成?实现方案有4种。
II.方案
-
配置文件
在配置文件中增加一个服务nodeId配置(简单粗暴,不推荐,万不得已才使用)application: #别的服务配置其他nodeId nodeId: 1
-
使用随机数
当需要生成唯一id,在一定返回内,生成随机数,例如在1-1024中生成随机数。return new Random().nextInt(1024) + 1;
-
获取当前服务所在机器的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; }
-
使用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); //找到数组中的最大值 if (max == -1){ nextId = 1; }else if (max == existsIds.size()){ nextId = max + 1; }else{ for (int i = 1;i<max;i++){ if (!existsIds.contains(i)){ nextId = i; break; } } } 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;在实际项目中,可以融合多种方案,保证高可用,并且可以在本地开发环境或者测试环境切换不同方案。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。