当前位置:   article > 正文

ZooKeeper 实战(六) - 分布式ID实现方案_- 分布式id生成

- 分布式id生成

ZooKeeper 实战(六) - 生成分布式ID

1.何为分布式ID

分布式唯一ID指在分布式系统中用于标识和区分各个实体、资源或事件的唯一标识符。由于分布式系统可能包含多个节点和多个并发操作,需要确保在整个系统中每个实体都具有唯一的标识,避免冲突和重复的情况。

分布式系统唯一ID的设计通常需要满足以下要求:

  1. 唯一性:每个ID在整个分布式系统中都是唯一的,不会发生冲突。
  2. 高并发性:ID的生成应该是高并发的,能应对绝大部分(接近于100%)的并发场景,不会成为系统的性能瓶颈。
  3. 可读性:ID尽可能具有可读性,方便开发人员和用户进行识别和使用。
  4. 可扩展性:能够适应分布式系统的扩展性需求,支持产生大规模、高并发的ID。
  5. 可排序性:ID可以按照一定规则进行排序,方便查询和排序操作。主键的排序性也能提升数据库索引的效率。数据库的索引结构通常是基于B树或B+树的,有序的主键可以保持索引结构的有序性,减少数据的分裂和平衡,从而提高索引的维护效率和查询性能。

2.分布式ID方案

使用ZooKeeper实现生成分布式ID可以保证分布式系统中每个节点生成的ID是唯一且递增的。以下是使用ZooKeeper实现生成分布式ID的基本步骤:

  1. 创建ZooKeeper节点:在ZooKeeper集群中创建一个顺序节点来实现全局递增的功能。节点路径可以选择一个统一的命名规则,例如"/appName/distributeStr"。
  2. 获取序列ID:每个节点需要在生成ID的时候,向ZooKeeper集群发起一个创建节点的请求。在请求的路径中添加顺序节点的标志,例如"/appName/distributeStr/id_"。ZooKeeper将为每个节点创建一个唯一的有序节点,并返回节点的路径。
  3. 处理序列ID:获取到ZooKeeper返回的节点路径后,需要解析路径中的序列号,也可以附加上某些信息,作为生成的分布式ID。
  4. 使用分布式ID:使用解析出的分布式ID进行业务处理。分布式ID可以在多个节点上同时生成,并且保证每个节点生成的ID是唯一且递增的。

3.创建ZooKeeper节点

问题来了,我们要明确创建一个什么样的ZooKeeper节点呢?首先,要知道ID是用来标识一个实体的,而不是作用于整个系统。比如一个系统中,实体有用户、商品、订单等,但是我们并不需要保证用户、商品和订单之间的ID是唯一的,而是要保证同一类实体比如用户这一个类,即任意两个用户A和B的ID不能重复。所以我们可以把系统中用于产生分布式ID的Znode的粒度控制在实体维度,例如/app/user 节点是用户实体的分布式ID节点,/app/product 节点是商品实体的分布式ID节点等。

    /**
     * 分布式ID节点缓存
     */
    private Map<String,String> NodePathMap = new HashMap<>();

    /**
     * 生成分布式ID节点路径
     * @param module 模块名称
     * @return
     */
    public String getIdNodePath(String module){
        if (module == null || module.isBlank()){
            throw new NullPointerException("请设置模块名称");
        }
        if (NodePathMap.get(module) == null){
            if (appName == null || appName.isBlank()){
                throw new NullPointerException("请设置系统名称");
            }
            synchronized (IdGenerator.class){
                if (NodePathMap.get(module) == null){
                    NodePathMap.put(module,"/"+appName+"/"+module);
                }
            }
        }
        return NodePathMap.get(module);
    }

4.获取序列ID

先通过上一节中的临时顺序节点的路径,向ZooKeeper集群发起一个创建节点的请求并返回节点的名称,最后去除节点路径的前缀,获取最后的序列号。

		/**
		 * 分布式ID节点的前缀
		 */
		public static final String ID_PREFIX = "id_";

    /**
     * 创建临时节点,并返回Id
     * @param module
     * @return
     */
    public String createNodeId(String module){
        String idNodePath = getIdNodePath(module);
        try {
            String idPrefix = idNodePath + "/" + ID_PREFIX;
            String id = client.create() // 创建节点
                    .creatingParentsIfNeeded() // 如果需要,递归创建节点
                    .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) // 指定创建节点类型,使用临时顺序节点
                    .forPath(idPrefix); // 设置节点路径
            // 去除nodeId的前缀
            return id.replace(idPrefix,"");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

5.处理序列ID

在ZK生成的临时顺序节点ID的前附加时日期时间,以便直观显示此ID的生成日期。

		/**
		 * 日期格式
		 */
		public static final String DATE_FORMAT = "yyyyMMdd";

    /**
     * 获取当天的日期
     * 格式:yyyyMMdd
     * @return
     */
    public String datePrefix(){
        DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern(DATE_FORMAT);
        LocalDateTime now = LocalDateTime.now();
        return now.format(yyyyMMdd);
    }

    /**
     * 生成id
     * @param module 模块
     * @return
     */
    public String nextId(String module){
      	// id前缀以日期开头
        return datePrefix()+createNodeId(module);
    }

6.使用分布式ID

一切从简,直接把测试方法写在启动类中声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】

推荐阅读
相关标签