(高级)ShardingSphere源码解析-微内核架构及分布式主键实现

ShardingShere源码值得一读,因为涉及到很多中间件的集成及比较骚的使用方式

如何快速阅读ShardingSphere源码

ShardingSphere的目录结构

在这里插入图片描述
如何快速把握 ShardingSphere 的代码结构呢?这是我们剖析源码时需要回答的第一个问题,为此我们需要梳理剖析 ShardingSphere 框架代码结构的系统方法。主要包括6大系统方法。
在这里插入图片描述

基于可扩展性设计阅读源码

ShardingSphere 在设计上采用了微内核架构模式来确保系统具有高度的可扩展性,并使用了 JDK 提供的 SPI 机制来具体实现微内核架构。在 ShardingSphere 源代码的根目录下,存在一个独立工程 shardingsphere-spi。显然,从命名上看,这个工程中应该包含了 ShardingSphere 实现 SPI 的相关代码。该工程中存在一个 TypeBasedSPI 接口,它的类层结构比较丰富,课程后面将要讲到的很多核心接口都继承了该接口,包括实现配置中心的 ConfigCenter、注册中心的 RegistryCenter 等,如下所示:
在这里插入图片描述
这些接口的实现都遵循了 JDK 提供的 SPI 机制。在我们阅读 ShardingSphere 的各个代码工程时,一旦发现在代码工程中的 META-INF/services 目录里创建了一个以服务接口命名的文件,就说明这个代码工程中包含了用于实现扩展性的 SPI 定义。

ShardingSphere 中实现微内核架构的方式就是直接对 JDK 的 ServiceLoader 类进行一层简单的封装,并添加属性设置等自定义的功能,其本身并没有太多复杂的内容。

当然,可扩展性的表现形式不仅仅只有微内核架构一种。在 ShardingSphere 中也大量使用了回调(Callback)机制以及多种支持扩展性的设计模式。掌握这些机制和模式也有助于更好地阅读 ShardingSphere 源码。

基于分包设计原则阅读源码

分包(Package)设计原则可以用来设计和规划开源框架的代码结构。对于一个包结构而言,最核心的设计要点就是高内聚和低耦合。我们刚开始阅读某个框架的源码时,为了避免过多地扎进细节而只关注某一个具体组件,同样可以使用这些原则来管理我们的学习预期。

以 ShardingSphere 为例,我们在分析它的路由引擎时发现了两个代码工程,一个是 sharding-core-route,一个是 sharding-core-entry。从代码结构上讲,尽管这两个代码工程都不是直接面向业务开发人员,但 sharding-core-route 属于路由引擎的底层组件,包含了路由引擎的核心类 ShardingRouter。

而 sharding-core-entry 则位于更高的层次,提供了 PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 类,分包结构如下所示:

在这里插入图片描述
图中我们可以看到两个清晰的代码结构层次关系,这是 ShardingSphere 中普遍采用的分包原则中,具有代表性的一种,即根据类的所属层级来组织包结构。

基于基础开发规范阅读源码

对于 ShardingSphere 而言,在梳理它的代码结构时有一个非常好的切入点,那就是基于 JDBC 规范。我们知道 ShardingSphere 在设计上一开始就完全兼容 JDBC 规范,它对外暴露的一套分片操作接口与 JDBC 规范中所提供的接口完全一致。只要掌握了 JDBC 中关于 DataSource、Connection、Statement 等核心接口的使用方式,就可以非常容易地把握 ShardingSphere 中暴露给开发人员的代码入口,进而把握整个框架的代码结构。

我们来看这方面的示例,如果你是刚接触到 ShardingSphere 源码,要想找到 SQL 执行入口是一件有一定难度的事情。在 ShardingSphere 中,存在一个 ShardingDataSourceFactory 工厂类,专门用来创建 ShardingDataSource。ShardingDataSource 就是一个 JDBC 规范中的 DataSource 实现类:

public final class ShardingDataSourceFactory {
    
    public static DataSource createDataSource(
            final Map<String, DataSource> dataSourceMap, final ShardingRuleConfiguration shardingRuleConfig, final Properties props) throws SQLException {
        return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
    }
}

通过这个工厂类,我们很容易就找到了创建支持分片机制的 DataSource 入口,从而引出其背后的 ShardingConnection、ShardingStatement 等类。相当于重写了DataSource,Connection,Statement等类。

事实上,在 ShardingSphere 中存在一批 DataSourceFactory 工厂类以及对应的 DataSource 类:

在这里插入图片描述

基于核心执行流程阅读源码

事实上,还有一个比较容易理解和把握的方法可以帮我们梳理代码结构,这就是代码的执行流程。任何系统行为都可以认为是流程的组合。通过分析,看似复杂的代码结构一般都能梳理出一条贯穿全局的主流程。只要我们抓住这条主流程,就能把握框架的整体代码结构。

那么,对于 ShardingSphere 框架而言,什么才是它的主流程呢?这个问题其实不难回答。事实上,JDBC 规范为我们实现数据存储和访问提供了基本的开发流程。我们可以从 DataSource 入手,逐步引入 Connection、Statement 等对象,并完成 SQL 执行的主流程。这是从框架提供的核心功能角度梳理的一种主流程。

对于框架内部的代码组织结构而言,实际上也存在着核心流程的概念。最典型的就是 ShardingSphere 的分片引擎结构,整个分片引擎执行流程可以非常清晰的分成五个引擎组成,分别是解析引擎、路由引擎、改写引擎、执行引擎和归并引擎:

在这里插入图片描述
ShardingSphere 对每个引擎都进行了明确地命名,在代码工程的组织结构上也做了对应的约定,例如 sharding-core-route工程用于实现路由引擎;sharding-core-execute 工程用于实现执行引擎;sharding-core-merge 工程用于实现归并引擎等。这是从框架内部实现机制角度梳理的一种主流程。

在软件建模领域,可以通过一些工具和手段对代码执行流程进行可视化,例如 UML 中的活动图和时序图。在后续的课时中,我们会基于这些工具帮你梳理 ShardingSphere 中很多有待挖掘的代码执行流程。

基于框架演进过程阅读源码

比如在 ShardingSphere 中,数据脱敏功能的实现实际上并不是独立的,而是依赖于 SQL 改写引擎。我们可以快速来到 BaseShardingEngine 类的 rewriteAndConvert 方法中:

    private Collection<RouteUnit> rewriteAndConvert(final String sql, final List<Object> parameters, final SQLRouteResult sqlRouteResult) {
        //构建SQLRewriteContext
     SQLRewriteContext sqlRewriteContext = new SQLRewriteContext(metaData.getRelationMetas(), sqlRouteResult.getSqlStatementContext(), sql, parameters);
     //构建ShardingSQLRewriteContextDecorator对SQLRewriteContext进行装饰
     new ShardingSQLRewriteContextDecorator(shardingRule, sqlRouteResult).decorate(sqlRewriteContext);
     //判断是否根据数据脱敏列进行查询
     boolean isQueryWithCipherColumn = shardingProperties.<Boolean>getValue(ShardingPropertiesConstant.QUERY_WITH_CIPHER_COLUMN);
        //构建EncryptSQLRewriteContextDecorator对SQLRewriteContext进行装饰
     new EncryptSQLRewriteContextDecorator(shardingRule.getEncryptRule(), isQueryWithCipherColumn).decorate(sqlRewriteContext);
     //生成SQLTokens
     sqlRewriteContext.generateSQLTokens();return result;
    }

注意,这里基于装饰器模式实现了两个SQL装饰器SQLRewriteContextDecorator,一个是 ShardingSQLRewriteContextDecorator,另一个是 EncryptSQLRewriteContextDecorator,而后者是在前者的基础上完成装饰工作。也就是说,我们首先可以单独使用 ShardingSQLRewriteContextDecorator来完成对SQL的改写操作。

随着架构的演进,我们也可以在原有 EncryptSQLRewriteContextDecorator的基础上添加新的面向数据脱敏的功能,这就体现了一种架构演进的过程。通过阅读这两个装饰器类,以及SQL改写上下文对象 SQLRewriteContext,我们就能更好地把握代码的设计思想和实现原理:

在这里插入图片描述

基于通用外部组件阅读源码

秉承技术原理存在相通性。这点同样可以帮助我们更好地阅读 ShardingSphere 源码。
在 ShardingSphere 中集成了一批优秀的开源框架,包括用于实现配置中心和注册中心的Zookeeper、Apollo、Nacos,用于实现链路跟踪的 SkyWalking,用于实现分布式事务的Atomikos 和 Seata 等。Seata 还支持 TCC 和 Saga 模式,但支持的主要方式是 AT。

我们先以分布式事务为例,ShardingSphere 提供了一个 sharding-transaction-core 代码工程,用于完成对分布式事务的抽象。然后又针对基于两阶段提交的场景,提供了 sharding-transaction-2pc 代码工程,以及针对柔性事务提供了 sharding-transaction-base 代码工程。而在 sharding-transaction-2pc 代码工程内部,又包含了如下所示的 5 个子代码工程。

在这里插入图片描述
在翻阅这些代码工程时,会发现每个工程中的类都很少,原因就在于,这些类都只是完成与第三方框架的集成而已。所以,只要我们对这些第三方框架有一定了解,阅读这部分代码就会显得非常简单。

再举一个例子,我们知道 ZooKeeper(CP模型) 可以同时用来实现配置中心和注册中心。作为一款主流的分布式协调框架,基本的工作原理就是采用了它所提供的临时节点以及监听机制。基于 ZooKeeper 的这一原理,我们可以把当前 ShardingSphere 所使用的各个 DataSource 注册到 ZooKeeper 中,并根据 DataSource 的运行时状态来动态对数据库实例进行治理,以及实现访问熔断机制。事实上,ShardingSphere 能做到这一点,依赖的就是 ZooKeeper 所提供的基础功能。只要我们掌握了这些功能,理解这块代码就不会很困难,而 ShardingSphere 本身并没有使用 ZooKeeper 中任何复杂的功能。

梳理ShardingSphere中的核心技术体系

基础架构、分片引擎、分布式事务以及治理与集成等 4 个方面。

基础架构

我们认为 ShardingSphere 所实现的微内核架构和分布式主键可以归到基础架构。

分片引擎

分片引擎是 ShardingSphere 最核心的技术体系,包含了解析引擎、路由引擎、改写引擎、执行引擎、归并引擎和读写分离等 6 大主题,我们对每个主题都会详细展开。分片引擎在整个 ShardingSphere 源码解析内容中占有最大篇幅。

对于解析引擎而言,我们重点梳理 SQL 解析流程所包含的各个阶段;对于路由引擎,我们将在介绍路由基本原理的基础上,给出数据访问的分片路由和广播路由,以及如何在路由过程中集成多种分片策略和分片算法的实现过程;改写引擎相对比较简单,我们将围绕如何基于装饰器模式完成 SQL 改写实现机制这一主题展开讨论;而对于执行引擎,首先需要梳理和抽象分片环境下 SQL 执行的整体流程,然后把握 ShardingSphere 中的 Executor 执行模型;在归并引擎中,我们将分析数据归并的类型,并阐述各种归并策略的实现过程;最后,我们将关注普通主从架构和分片主从架构下读写分离的实现机制。

分布式事务

针对分布式事务,我们需要理解 ShardingSphere 中对分布式事务的抽象过程,然后系统分析在 ShardingSphere 中如何基于各种第三方框架集成强一致性事务和柔性事务支持的实现原理。

治理与集成

在治理和集成部分,从源码角度讨论的话题包括数据脱敏、配置中心、注册中心、链路跟踪以及系统集成。

对于数据脱敏,我们会在改写引擎的基础上给出如何实现低侵入性的数据脱敏方案;配置中心用来完成配置信息的动态化管理,而注册中心则实现了数据库访问熔断机制,这两种技术可以采用通用的框架进行实现,只是面向了不同的业务场景,我们会分析通用的实现原理以及面向业务场景的差异性;ShardingSphere 中实现了一系列的 Hook 机制,我们将基于这些 Hook 机制以及 OpenTracing 协议来剖析实现数据访问链路跟踪的工作机制;当然,作为一款主流的开源框架,ShardingSphere 也完成与 Spring 以及 SpringBoot 的无缝集成,对系统集成方式的分析可以更好地帮助我们使用这个框架。

微内核架构方式实现系统扩展性

什么是微内核架构?

微内核是一种典型的架构模式 ,区别于普通的设计模式,架构模式是一种高层模式,用于描述系统级的结构组成、相互关系及相关约束。微内核架构在开源框架中的应用也比较广泛,除了 ShardingSphere 之外,在主流的 PRC 框架 Dubbo 中也实现了自己的微内核架构。那么,在介绍什么是微内核架构之前,我们有必要先阐述这些开源框架会使用微内核架构的原因。

为什么要使用微内核架构

就架构设计而言,扩展性是软件设计的永恒话题。而要实现系统扩展性,一种思路是提供可插拔式的机制来应对所发生的变化。当系统中现有的某个组件不满足要求时,我们可以实现一个新的组件来替换它,而整个过程对于系统的运行而言应该是无感知的,我们也可以根据需要随时完成这种新旧组件的替换。

比如在下个课时中我们将要介绍的 ShardingSphere 中提供的分布式主键功能,分布式主键的实现可能有很多种,而扩展性在这个点上的体现就是, 我们可以使用任意一种新的分布式主键实现来替换原有的实现,而不需要依赖分布式主键的业务代码做任何的改变 。

在这里插入图片描述
从组成结构上讲, 微内核架构包含两部分组件:内核系统和插件 。这里的内核系统通常提供系统运行所需的最小功能集,而插件是独立的组件,包含自定义的各种业务代码,用来向内核系统增强或扩展额外的业务能力。在 ShardingSphere 中,前面提到的分布式主键就是插件,而 ShardingSphere 的运行时环境构成了内核系统。

在这里插入图片描述

那么这里的插件具体指的是什么呢?这就需要我们明确两个概念,一个概念就是经常在说的 API ,这是系统对外暴露的接口。而另一个概念就是 SPI(Service Provider Interface,服务提供接口),这是插件自身所具备的扩展点。就两者的关系而言,API 面向业务开发人员,而 SPI 面向框架开发人员,两者共同构成了 ShardingSphere 本身。

在这里插入图片描述
可插拔式的实现机制说起来简单,做起来却不容易,我们需要考虑两方面内容。一方面,我们需要梳理系统的变化并把它们抽象成多个 SPI 扩展点。另一方面, 当我们实现了这些 SPI 扩展点之后,就需要构建一个能够支持这种可插拔机制的具体实现,从而提供一种 SPI 运行时环境 。

如何实现微内核架构

基于 SPI 的约定,创建一个单独的工程来存放服务接口,并给出接口定义。请注意 这个服务接口的完整类路径为 com.tianyilan.KeyGenerator ,接口中只包含一个获取目标主键的简单示例方法。

package com.tianyilan; 
public interface KeyGenerator{ 
    String getKey(); 
}

针对该接口,提供两个简单的实现类,分别是基于 UUID 的 UUIDKeyGenerator 和基于雪花算法的 SnowflakeKeyGenerator。为了让演示过程更简单,这里我们直接返回一个模拟的结果,真实的实现过程我们会在下一课时中详细介绍。

public class UUIDKeyGenerator implements KeyGenerator { 
    @Override 
    public String getKey() { 
       return "UUIDKey"; 
    } 
} 
public class SnowflakeKeyGenerator implements KeyGenerator { 
    @Override 
    public String getKey() { 
       return "SnowflakeKey"; 
    } 
}

接下来的这个步骤很关键, 在这个代码工程的 META-INF/services/ 目录下,需要创建一个以服务接口完整类路径 com.tianyilan.KeyGenerator 命名的文件 ,文件的内容是指向该接口所对应的两个实现类的完整类路径 com.tianyilan.UUIDKeyGenerator 和 com.tianyilan. SnowflakeKeyGenerator。

我们把这个代码工程打成一个 jar 包,然后新建另一个代码工程,该代码工程需要这个 jar 包,并完成如下所示的 Main 函数。

import java.util.ServiceLoader; 
import com.tianyilan. KeyGenerator; 
public class Main { 
    public static void main(String[] args) { 
       ServiceLoader<KeyGenerator> generators = ServiceLoader.load(KeyGenerator.class); 
       for (KeyGenerator generator : generators) { 
           System.out.println(generator.getClass()); 
           String key = generator.getKey(); 
           System.out.println(key); 
       } 
    } 
}

现在,该工程的角色是 SPI 服务的使用者,这里使用了 JDK 提供的 ServiceLoader 工具类来获取所有 KeyGenerator 的实现类。现在在 jar 包的 META-INF/services/com.tianyilan.KeyGenerator 文件中有两个 KeyGenerator 实现类的定义。执行这段 Main 函数,我们将得到的输出结果如下:

	class com.tianyilan.UUIDKeyGenerator 
	UUIDKey 
	class com.tianyilan.SnowflakeKeyGenerator 
	SnowflakeKey

如果我们调整 META-INF/services/com.tianyilan.KeyGenerator 文件中的内容,去掉 com.tianyilan.UUIDKeyGenerator 的定义,并重新打成 jar 包供 SPI 服务的使用者进行引用。再次执行 Main 函数,则只会得到基于 SnowflakeKeyGenerator 的输出结果。

至此, 完整 的 SPI 提供者和使用者的实现过程演示完毕。我们通过一张图,总结基于 JDK 的 SPI 机制实现微内核架构的开发流程:

在这里插入图片描述

ShardingSphere 中的微内核架构基础实现机制

在 ShardingSphere 源码的根目录下,存在一个独立的工程 shardingsphere-spi。显然,从命名上看,这个工程中应该包含了 ShardingSphere 实现 SPI 的相关代码。我们快速浏览该工程,发现里面只有一个接口定义和两个工具类。我们先来看这个接口定义 TypeBasedSPI:

public interface TypeBasedSPI { 
    //获取SPI对应的类型 
    String getType(); 
    //获取属性 
    Properties getProperties(); 
    //设置属性 
    void setProperties(Properties properties); 
}

从定位上看,这个接口在 ShardingSphere 中应该是一个顶层接口,我们已经在上一课时给出了这一接口的实现类类层结构。接下来再看一下 NewInstanceServiceLoader 类,从命名上看,不难想象该类的作用类似于一种 ServiceLoader,用于加载新的目标对象实例:

public final class NewInstanceServiceLoader { 
    private static final Map<Class, Collection<Class<?>>> SERVICE_MAP = new HashMap<>(); 
    //通过ServiceLoader获取新的SPI服务实例并注册到SERVICE_MAP中
    public static <T> void register(final Class<T> service) { 
        for (T each : ServiceLoader.load(service)) { 
            registerServiceClass(service, each); 
        } 
    } 
    @SuppressWarnings("unchecked") 
    private static <T> void registerServiceClass(final Class<T> service, final T instance) { 
        Collection<Class<?>> serviceClasses = SERVICE_MAP.get(service); 
        if (null == serviceClasses) { 
            serviceClasses = new LinkedHashSet<>(); 
        } 
        serviceClasses.add(instance.getClass()); 
        SERVICE_MAP.put(service, serviceClasses); 
    } 
    @SneakyThrows 
    @SuppressWarnings("unchecked") 
    public static <T> Collection<T> newServiceInstances(final Class<T> service) { 
        Collection<T> result = new LinkedList<>(); 
        if (null == SERVICE_MAP.get(service)) { 
            return result; 
        } 
        for (Class<?> each : SERVICE_MAP.get(service)) { 
            result.add((T) each.newInstance()); 
        } 
        return result; 
    } 
}

在上面这段代码中, 首先看到了熟悉的 ServiceLoader.load(service) 方法,这是 JDK 中 ServiceLoader 工具类的具体应用。同时,注意到 ShardingSphere 使用了一个 HashMap 来保存类的定义以及类的实例之 间 的一对多关系,可以认为,这是一种用于提高访问效率的缓存机制。

最后,我们来看一下 TypeBasedSPIServiceLoader 的实现,该类依赖于前面介绍的 NewInstanceServiceLoader 类。 下面这段代码演示了 基于 NewInstanceServiceLoader 获取实例类列表,并根据所传入的类型做过滤:

    //使用NewInstanceServiceLoader获取实例类列表,并根据类型做过滤 
    private Collection<T> loadTypeBasedServices(final String type) { 
        return Collections2.filter(NewInstanceServiceLoader.newServiceInstances(classType), new Predicate<T>() { 
            @Override 
            public boolean apply(final T input) { 
                return type.equalsIgnoreCase(input.getType()); 
            } 
        }); 
    }

TypeBasedSPIServiceLoader 对外暴露了服务的接口,对通过 loadTypeBasedServices 方法获取的服务实例设置对应的属性然后返回:

	//基于类型通过SPI创建实例 
    public final T newService(final String type, final Properties props) { 
        Collection<T> typeBasedServices = loadTypeBasedServices(type); 
        if (typeBasedServices.isEmpty()) { 
            throw new RuntimeException(String.format("Invalid `%s` SPI type `%s`.", classType.getName(), type)); 
        } 
        T result = typeBasedServices.iterator().next(); 
        result.setProperties(props); 
        return result; 
	}

同时,TypeBasedSPIServiceLoader 也对外暴露了不需要传入类型的 newService 方法,该方法使用了 loadFirstTypeBasedService 工具方法来获取第一个服务实例:

	//基于默认类型通过SPI创建实例 
    public final T newService() { 
        T result = loadFirstTypeBasedService(); 
        result.setProperties(new Properties()); 
        return result; 
	} 
    private T loadFirstTypeBasedService() { 
        Collection<T> instances = NewInstanceServiceLoader.newServiceInstances(classType); 
        if (instances.isEmpty()) { 
            throw new RuntimeException(String.format("Invalid `%s` SPI, no implementation class load from SPI.", classType.getName())); 
        } 
        return instances.iterator().next(); 
	}

这样,shardingsphere-spi 代码工程中的内容就介绍完毕。 这部分内容相当于是 ShardingSphere 中所提供的插件运行时环境 。

shardingsphere分布式主键实现

在Mybatis中嵌入了GenerateKey的实现方法。同样,shardingsphere实现了一个 GeneratedKey 类。请注意,该类位于 sharding-core-route 工程下。我们先看该类提供的 getGenerateKey 方法:

    public static Optional<GeneratedKey> getGenerateKey(final ShardingRule shardingRule, final TableMetas tableMetas, final List<Object> parameters, final InsertStatement insertStatement) { 
        //找到自增长列 
     Optional<String> generateKeyColumnName = shardingRule.findGenerateKeyColumnName(insertStatement.getTable().getTableName()); 
        if (!generateKeyColumnName.isPresent()) { 
            return Optional.absent(); 
        } 
         
        //判断自增长类是否已生成主键值 
        return Optional.of(containsGenerateKey(tableMetas, insertStatement, generateKeyColumnName.get()) 
                ? findGeneratedKey(tableMetas, parameters, insertStatement, generateKeyColumnName.get()) : createGeneratedKey(shardingRule, insertStatement, generateKeyColumnName.get())); 
	} 

这段代码的逻辑在于先从 ShardingRule 中找到主键对应的 Column,然后判断是否已经包含主键:如果是则找到该主键,如果不是则生成新的主键。今天,我们的重点是分布式主键的生成,所以我们直接来到 createGeneratedKey 方法:

private static GeneratedKey createGeneratedKey(final ShardingRule shardingRule, final InsertStatement insertStatement, final String generateKeyColumnName) { 
        GeneratedKey result = new GeneratedKey(generateKeyColumnName, true); 
        for (int i = 0; i < insertStatement.getValueListCount(); i++) { 
            result.getGeneratedValues().add(shardingRule.generateKey(insertStatement.getTable().getTableName())); 
        } 
        return result; 
	} 

在 GeneratedKey 中存在一个类型为 LinkedList 的 generatedValues 变量,用于保存生成的主键,但实际上,生成主键的工作转移到了 ShardingRule 的 generateKey 方法中,我们跳转到 ShardingRule 类并找到这个 generateKey 方法:

    public Comparable<?> generateKey(final String logicTableName) { 
        Optional<TableRule> tableRule = findTableRule(logicTableName); 
        if (!tableRule.isPresent()) { 
            throw new ShardingConfigurationException("Cannot find strategy for generate keys."); 
        } 
         
        //从TableRule中获取ShardingKeyGenerator并生成分布式主键 
        ShardingKeyGenerator shardingKeyGenerator = null == tableRule.get().getShardingKeyGenerator() ? defaultShardingKeyGenerator : tableRule.get().getShardingKeyGenerator(); 
        return shardingKeyGenerator.generateKey(); 
	} 

这里的 ShardingKeyGenerator 显然就是真正生成分布式主键入口,让我们来看一下。

ShardingKeyGenerator

接下来我们分析 ShardingKeyGenerator 接口,从定义上看,该接口继承了 TypeBasedSPI 接口:

public interface ShardingKeyGenerator extends TypeBasedSPI {     
    Comparable<?> generateKey(); 
} 

来到 TableRule 中,在它的一个构造函数中找到了 ShardingKeyGenerator 的创建过程:

shardingKeyGenerator = containsKeyGeneratorConfiguration(tableRuleConfig) 
                ? new ShardingKeyGeneratorServiceLoader().newService(tableRuleConfig.getKeyGeneratorConfig().getType(), tableRuleConfig.getKeyGeneratorConfig().getProperties()) : null; 

这里有一个 ShardingKeyGeneratorServiceLoader 类,该类定义如下:

public final class ShardingKeyGeneratorServiceLoader extends TypeBasedSPIServiceLoader<ShardingKeyGenerator> { 
     
    static { 
        NewInstanceServiceLoader.register(ShardingKeyGenerator.class); 
    } 
     
    public ShardingKeyGeneratorServiceLoader() { 
        super(ShardingKeyGenerator.class); 
    } 
} 

我们不难理解 ShardingKeyGeneratorServiceLoader 类的作用。ShardingKeyGeneratorServiceLoader 继承了 TypeBasedSPIServiceLoader 类,并在静态方法中通过 NewInstanceServiceLoader 注册了类路径中所有的 ShardingKeyGenerator。然后,ShardingKeyGeneratorServiceLoader 的 newService 方法基于类型参数通过 SPI 创建实例,并赋值 Properties 属性。

通过继承 TypeBasedSPIServiceLoader 类来创建一个新的 ServiceLoader 类,然后在其静态方法中注册相应的 SPI 实现,这是 ShardingSphere 中应用微内核模式的常见做法,很多地方都能看到类似的处理方法。

我们在 sharding-core-common 工程的 META-INF/services 目录中看到了具体的 SPI 定义:
在这里插入图片描述
可以看到,这里有两个 ShardingKeyGenerator,分别是 SnowflakeShardingKeyGenerator 和 UUIDShardingKeyGenerator,它们都位于org.apache.shardingsphere.core.strategy.keygen 包下。

ShardingSphere 中的分布式主键实现方案

在 ShardingSphere 中,ShardingKeyGenerator 接口存在一批实现类。除了前面提到的 SnowflakeShardingKeyGenerator 和UUIDShardingKeyGenerator,还实现了 LeafSegmentKeyGenerator 和 LeafSnowflakeKeyGenerator 类,但这两个类的实现过程有些特殊,我们一会再具体展开。

UUIDShardingKeyGenerator

我们先来看最简单的 ShardingKeyGenerator,即 UUIDShardingKeyGenerator。UUIDShardingKeyGenerator 的实现非常容易理解,直接采用 UUID.randomUUID() 的方式产生分布式主键:

public final class UUIDShardingKeyGenerator implements ShardingKeyGenerator { 
     
    private Properties properties = new Properties(); 
     
    @Override 
    public String getType() { 
        return "UUID"; 
    } 
     
    @Override 
    public synchronized Comparable<?> generateKey() { 
        return UUID.randomUUID().toString().replaceAll("-", ""); 
    } 
} 

参考

atomikos官网:https://www.atomikos.com/
atomikos使用教程:http://www.tianshouzhi.com/api/tutorials/distributed_transaction/386
seata分布式事务官网:https://github.com/seata/

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值