Qicz’s Thoughts HUB

The creative and technical writing. Do more, challenge more, know more, be more.

分布式ID生成器-GeDid

源起

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

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

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

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

开始

引入依赖

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

在启动类上加入@EnableExtension

目前最新版本 maven

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@Bean
      2public ZookeeperIdEngineMode zookeeperIdEngineMode() {
      3  return ZookeeperIdEngineMode.DATA_ZX_MID;
      4}
      
    • 对应的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更新 ]

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

    多个endpoint用英文,分割

  • SnowflakeIdEngine [ 2021.7.3更新 ]

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

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

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

  • ZookeeperIdEngine

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

  • UuidEngine

    无需配置

  • 配置示例

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

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

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

业务配置

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

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

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

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

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

高级使用

BusinessURI

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

1didLoader.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 * IDidEngine
 3 *
 4 * @author Qicz
 5 * @since 2021/6/25 10:48
 6 */
 7public interface IDidEngine<T> {
 8
 9	String GEDID = "gedid";
10
11	/**
12	 * Follow The business with name.
13	 * @param businessName business name
14	 * @param startId the first id
15	 */
16	void follow(String businessName, T startId);
17
18	/**
19	 * get the embellished business name
20	 * @param businessName the business name
21	 * @return embellished business name
22	 */
23	default String getEmbellishedName(String businessName) {
24		return businessName;
25	}
26
27	/**
28	 * get fixed start id
29	 *
30	 * @param startId not all startId
31	 * @return {@link T} default return startId self
32	 */
33	default T getFixedStartId(T startId) {
34		return startId;
35	}
36
37	/**
38	 * the next id
39	 * @param businessName the business name
40	 * @return Next id.
41	 */
42	T next(String businessName);
43
44	/**
45	 * throw onw unsupportedOperationException
46	 * @param operation unsupported operation
47	 */
48	default void unsupportedOperation(String operation) {
49		throw new UnsupportedOperationException(String.format("`%s` : The `%s` Operation is not supported!", this.engineName(), operation));
50	}
51
52	/**
53	 * current engine's name
54	 * @return the engine name
55	 */
56	String engineName();
57}

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

自定义IdEngine示例
 1/**
 2 * QiczEngine
 3 *
 4 * @author Qicz
 5 * @since 2021/6/25 17:41
 6 */
 7public class QiczEngine implements IDidEngine<String>  {
 8
 9	@Override
10	public void follow(String businessName, String startId) {
11
12	}
13  
14  @Override
15	public String getEmbellishedName(String businessName) {
16		return businessName;
17	}
18
19	@Override
20	public String getFixedStartId(String startId) {
21		return "";
22	}
23
24	@Override
25	public String next(String businessName) {
26		return UUID.randomUUID().toString();
27	}
28
29	@Override
30	public String engineName() {
31		return "qicz";
32	}
33}

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

配置自定义IdEngine
 1/**
 2 * IdLoaderConfig
 3 *
 4 * @author Qicz
 5 * @since 2021/6/24 16:03
 6 */
 7@Configuration
 8public class IdLoaderConfig implements DidLoaderConfigurer {
 9
10	@Override
11	public void configureDidLoader(DidLoader didLoader) {
12		// business abc
13		didLoader.follow("qicz://business-abcd");
14    ...
15	}
16}

使用qicz引擎follow业务business-abcd

结语

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