Contents

分布式ID生成器-GeDid

源起

​ 尝试设计实现一个分布式id工具,已是好几年前的事了,这里是历史的痕迹。

​ 最初实现了基于redis(jedis客户端)的版本,而后在19年左右,考虑接入Zookeeper版本的。但是一直没有着手去做。

​ 最近借助一次"翻新"的机会,把它又拿出来,考虑完成Zookeeper版本的实现。因spring-boot-x已经有了很多的基础,也便于Gedid在springboot环境下使用,与springboot的整合和利用已有的springboot资源,所以考虑把Gedid迁移至spring-boot-x,以至于进行了大量的改造。多次调整之后,变有了现在的结果。也有了这个文章的整理。

​ 这个文章目的:把Gedid的使用场景和使用方式道明白。

开始

引入依赖

1
2
3
4
5
<dependency>
    <groupId>org.openingo.boot</groupId>
    <artifactId>spring-boot-x</artifactId>
    <version>${spring-boot-x.version}</version>
</dependency>

在启动类上加入@EnableExtension

目前最新版本 https://img.shields.io/maven-central/v/org.openingo.boot/spring-boot-x.svg

ID引擎

目前支持引擎如下:

  • EtcdIdEngine since v4.3.0 [ 2021.7.3更新 ]

    • 基于PutResponsePrev_Kvversion;
    • 对应的id类型是Long
    • 支持业务隔离,对应的businessName就是业务的标识;
    • 使用jetcd客户端;
    • etcd存储的数据key格式为:"gedid-{businessName}",可以通过此查询对应的数据; since v4.3.1
  • SnowflakeIdEngine since v4.3.0 [ 2021.7.3更新 ]

    • 对应的id类型是Long
    • 天然支持业务隔离;
  • RedisIdEngine

    • 基于redis的incr指令;
    • 对应的id类型是Long
    • 支持业务隔离,对应的businessName就是业务的标识;
    • 支持lettucejedis,默认为lettuce;
    • redis存储的数据key格式为:"gedid:{businessName}",可以通过此查询对应的数据; since v4.3.1
  • ZookeeperIdEngine

    • 基于CreateMode.PERSISTENT节点的version(ZookeeperIdEngineMode#DATA_VERSION)或zxmid(ZookeeperIdEngineMode#DATA_ZX_MID);可以注入不同的Bean配置使用的mode,如下:

      1
      2
      3
      4
      
      @Bean
      public ZookeeperIdEngineMode zookeeperIdEngineMode() {
        return ZookeeperIdEngineMode.DATA_ZX_MID;
      }
      
    • 对应的Id类型是Long

    • 支持业务隔离,对应的businessName就是业务的标识;

    • 使用curator客户端;

      使用ZookeeperIdEngineMode#DATA_VERSION时,对应id受限于integer类型的version的取值范围

      使用ZookeeperIdEngineMode#DATA_ZX_MID时,对应id可能会因为zk的重启出现较大范围的跳跃。

    • Zookeeper存储的path数据格式为:"/gedid-{businessName}",可以通过此查询对应的数据; since v4.3.1

  • UuidEngine

    • 基于java.util.UUID#randomUUID;
    • 对应id类型为String
    • 天然支持业务隔离;

使用

配置说明

  • EtcdIdEngine [ 2021.7.3更新 ]

    1
    2
    3
    4
    5
    6
    7
    
    openingo:
      gedid:
        engine:
          etcd:
            endpoints: http://localhost:2379
            user: user
            password: password
    

    多个endpoint用英文,分割

  • SnowflakeIdEngine [ 2021.7.3更新 ]

    无需配置;默认workerIddataCenterId都是0L,可以通过重新注入SnowflakeIdEngine到SpringIoC的方式调整二者,二者取值均需<=31L

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Configuration
    	static class SnowflakeConfig {
    
          @Bean
          @ConditionalOnMissingBean
          SnowflakeIdEngine snowflakeIdEngine() {
              return new SnowflakeIdEngine(12L, 13L);
          }
    	}
    
  • RedisIdEngine

    spring-boot-starter-data-redis配置类似,配置前缀为openingo.gedid.engine.redis

  • ZookeeperIdEngine

    spring-cloud-starter-zookeeper-discovery的配置类似,配置前缀为openingo.gedid.engine.zookeeper

  • UuidEngine

    无需配置

  • 配置示例

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    openingo:
      gedid:
        engine:
          zookeeper:
            connect-string: localhost:2182
          redis:
            cluster:
              nodes: localhost:7291,localhost:7292,localhost:7293,localhost:7294,localhost:7295,localhost:7296
          etcd:
            endpoints: http://localhost:2379
            user: user
            password: password
    

    示例中使用了2182端口对应的zk,与默认的2181区分,目的是为了说明与默认配置的隔离;redis使用的集群配置。

    redis和Zookeeper的配置前缀与默认的配置完全隔离,目的是将业务使用的redis和Zookeeper和GeDid使用的redis、Zookeeper隔离开,如此配置不会造成业务与GeDid是相互影响。当然也可以配置成完全一样的服务器信息。

业务配置

  • 根据业务情况,参考如下配置DidLoader 【推荐】

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    /**
     * IdLoaderConfig
     *
     * @author Qicz
     * @since 2021/6/24 16:03
     */
    @Configuration
    public class IdLoaderConfig implements DidLoaderConfigurer {
    
    	@Bean
    	public ZookeeperIdEngineMode zookeeperIdEngineMode() {
          return ZookeeperIdEngineMode.DATA_ZX_MID;
    	}
    
    	@Override
    	public void configureDidLoader(DidLoader didLoader) {
          // business abc
          didLoader.follow("redis://abc");
          // business a
          didLoader.follow("redis", "a");
          // business b
          didLoader.follow("redis", "b");
          // business zk
          didLoader.follow("zookeeper", "zk");
          // business uuid
          didLoader.follow("uuid", "uuid");
    	}
    }
    
  • 业务中使用

    1
    2
    3
    4
    5
    6
    
    @Autowired
    DidLoader didLoader;
    
    didLoader.next(businessName);  // 下一个Id
    didLoad.nextToLong(businessName); // 下一个Long Id
    didLoad.nextToString(businessName); //下一个String Id
    
  • 动态配置DidLoader

    某些特定的情况,可能需要动态的配置业务的id生成,虽然推荐使用【推荐】方式配置,但仍旧提供了对应的支持。

    1
    2
    3
    4
    
    @Autowired
    DidLoader didLoader;
    
    this.didLoader.follow("redis", businessName);
    

高级使用

BusinessURI

在【推荐】中看到了如下的配置

1
didLoader.follow("redis://abc");

这是一种基于BusinessURI的配置方式。与标准URI语法完全一致,只是具体含义有些差异。以redis://abc举例:

  • URISchema

    示例中的redis即就是URI的Schema,在BusinessURI中,其对应着使用的具体IdEngine。比如redis即会使用engineNameredis的IDEngine。默认可用的Schema有redis,zookeeper,uuid分别对应着RedisIDEngine,ZookeeperIdEngine,UuidEngine

  • URIHost

    示例中的abc即就是URI的Host,在BusinessURI中,其对应着follow的businessName。这也是didLoader.next(businessName); 的基础。

自定义IdEngine

org.openingo.spring.extension.gedid.engine.IDidEngine定义了实现一个IdEngine的基本要素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * IDidEngine
 *
 * @author Qicz
 * @since 2021/6/25 10:48
 */
public interface IDidEngine<T> {

	String GEDID = "gedid";

	/**
	 * Follow The business with name.
	 * @param businessName business name
	 * @param startId the first id
	 */
	void follow(String businessName, T startId);

	/**
	 * get the embellished business name
	 * @param businessName the business name
	 * @return embellished business name
	 */
	default String getEmbellishedName(String businessName) {
		return businessName;
	}

	/**
	 * get fixed start id
	 *
	 * @param startId not all startId
	 * @return {@link T} default return startId self
	 */
	default T getFixedStartId(T startId) {
		return startId;
	}

	/**
	 * the next id
	 * @param businessName the business name
	 * @return Next id.
	 */
	T next(String businessName);

	/**
	 * throw onw unsupportedOperationException
	 * @param operation unsupported operation
	 */
	default void unsupportedOperation(String operation) {
		throw new UnsupportedOperationException(String.format("`%s` : The `%s` Operation is not supported!", this.engineName(), operation));
	}

	/**
	 * current engine's name
	 * @return the engine name
	 */
	String engineName();
}

engineName()可以随意定义除了默认Schema之外的任意的名称。

自定义IdEngine示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * QiczEngine
 *
 * @author Qicz
 * @since 2021/6/25 17:41
 */
public class QiczEngine implements IDidEngine<String>  {

	@Override
	public void follow(String businessName, String startId) {

	}
  
  @Override
	public String getEmbellishedName(String businessName) {
		return businessName;
	}

	@Override
	public String getFixedStartId(String startId) {
		return "";
	}

	@Override
	public String next(String businessName) {
		return UUID.randomUUID().toString();
	}

	@Override
	public String engineName() {
		return "qicz";
	}
}

此处示例,以UUID为例。特别注意,自定义示例需要注入到SpringIoC才可以正常使用。

配置自定义IdEngine
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/**
 * IdLoaderConfig
 *
 * @author Qicz
 * @since 2021/6/24 16:03
 */
@Configuration
public class IdLoaderConfig implements DidLoaderConfigurer {

	@Override
	public void configureDidLoader(DidLoader didLoader) {
		// business abc
		didLoader.follow("qicz://business-abcd");
    ...
	}
}

使用qicz引擎follow业务business-abcd

结语

至此,关于的GeDid目前的所有都在这里了。后续有变动将在本文中陆续更新。