33 链路跟踪:如何基于 Hook 机制以及 OpenTracing 协议实现数据访问链路跟踪?

今天我们来讨论 ShardingSphere 中关于编排治理的另一个主题,即链路跟踪。在分布式系统开发过程中,链路跟踪是一项基础设施类的功能。作为一款分布式数据库中间件,ShardingSphere 中也内置了简单而完整的链路跟踪机制。

链路跟踪基本原理和工具

在介绍具体的实现过程之前,我们有必要先来了解一些关于链路跟踪的理论知识。

1.链路跟踪基本原理

分布式环境下的服务跟踪原理上实际上并不复杂,我们首先需要引入两个基本概念,即 TraceId 和 SpanId。

  • TraceId

TraceId 即跟踪 Id。在微服务架构中,每个请求生成一个全局的唯一性 Id,通过这个 Id 可以串联起整个调用链,也就是说请求在分布式系统内部流转时,系统需要始终保持传递其唯一性 Id,直到请求返回,这个唯一性 Id 就是 TraceId。

  • SpanId

除了 TraceId 外,我们还需要 SpanId,SpanId 一般被称为跨度 Id。当请求到达各个服务组件时,通过 SpanId 来标识它的开始、具体执行过程和结束。对于每个 Span 而言,它必须有开始和结束两个节点,通过记录开始 Span 和结束 Span 的时间戳统计其 Span 的时间延迟。

整个调用过程中每个请求都要透传 TraceId 和 SpanId。每个服务将该次请求附带的 SpanId 作为父 SpanId 进行记录,并且生成自己的 SpanId。一个没有父 SpanId 的 Span 即为根 Span,可以看成调用链入口。所以要查看某次完整的调用只需根据 TraceId 查出所有调用记录,然后通过父 SpanId 和 SpanId 组织起整个调用父子关系。事实上,围绕如何构建 Trace 和 Span 之间统一的关联关系,业界也存在一个通用的链接跟踪协议,这就是 OpenTracing 协议。

2.OpenTracing 协议和应用方式

OpenTracing 是一种协议,也使用与上面介绍的类似的术语来表示链路跟踪的过程。通过提供平台无关、厂商无关的 API,OpenTracing 使得开发人员能够方便的添加或更换链路跟踪系统的实现。目前,诸如 Java、Go、Python 等主流开发语言都提供了对 OpenTracing 协议的支持。

我们以 Java 语言为例来介绍 OpenTracing 协议的应用方式,OpenTracing API 中存在相互关联的最重要的对象,也就是 Tracer 和 Span 接口。

对于 Tracer 接口而言,最重要就是如下所示的 buildSpan 方法,该方法用来根据某一个操作创建一个 Span 对象:

1
SpanBuilder buildSpan(String operationName);

我们看到上述 buildSpan 方法返回的实际上是一个 SpanBuilder 对象,而 SpanBuilder 中则存在一组 withTag 重载方法,用于为当前 Span 添加一个标签。标签的作用是供用户进行自定义,可以用来检索查询的标记,是一组键值对。withTag 方法的其中一种定义如下所示:

1
SpanBuilder withTag(String key, String value);

我们可以为一个 Span 添加多个 Tag,当把 Tag 添加完毕之后,我们就可以调用如下所示的 start 方法来启动这个 Span:

1
Span start();

注意这个方法会返回一个 Span 对象,一旦获取了 Span 对象,我们就可以调用该对象中的 finish 方法来结束这个 Span,该方法会为 Span 自动填充结束时间:

1
void finish();

基于以上 OpenTracing API 的介绍,在日常开发过程中,我们在业务代码中嵌入链路跟踪的常见实现方法可以用如下所示的代码片段进行抽象:

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
//从 OpenTracing 规范的实现框架中获取 Tracer 对象



Tracer tracer = new XXXTracer();



//创建一个 Span 并启动



Span span = tracer.buildSpan("test").start();



//添加标签到 Span 中



span.setTag(Tags.COMPONENT, "test -application");



//执行相关业务逻辑



//完成 Span



span.finish();



//可以根据需要获取 Span 中的相关信息



System.out.println("Operation name = " + span.operationName());



System.out.println("Start = " + span.startMicros());



System.out.println("Finish = " + span.finishMicros());

事实上,ShardingSphere 集成 OpenTracing API 的做法基本与上述方法类似,让我们一起来看一下。

ShardingSphere 中的链路跟踪

对于 ShardingSphere 而言,框架本身并不负责如何采集、存储以及展示应用性能监控的相关数据,而是将整个数据分片引擎中最核心的 SQL 解析与 SQL 执行相关信息发送至应用性能监控系统,并交由其处理。

换句话说,ShardingSphere 仅负责产生具有价值的数据,并通过标准协议递交至第三方系统,而不对这些数据做二次处理。

ShardingSphere 使用 OpenTracing API 发送性能追踪数据。支持 OpenTracing 协议的具体产品都可以和 ShardingSphere 自动对接,比如常见的 SkyWalking、Zipkin 和Jaeger。在 ShardingSphere 中,使用这些具体产品的方式只需要在启动时配置 OpenTracing 协议的实现者即可。

1.通过 ShardingTracer 获取 Tracer 类

ShardingSphere 中,所有关于链路跟踪的代码都位于 sharding-opentracing 工程中。我们先来看 ShardingTracer 类,该类的 init 方法完成了 OpenTracing 协议实现类的初始化,如下所示:

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
public static void init() {



//从环境变量中获取 OpenTracing 协议的实现类配置



String tracerClassName = System.getProperty(OPENTRACING_TRACER_CLASS_NAME);



Preconditions.checkNotNull(tracerClassName, "Can not find opentracing tracer implementation class via system property `%s`", OPENTRACING_TRACER_CLASS_NAME);



try {



//初始化 OpenTracing 协议的实现类



init((Tracer) Class.forName(tracerClassName).newInstance());



} catch (final ReflectiveOperationException ex) {



throw new ShardingException("Initialize opentracing tracer class failure.", ex);



}



}

我们通过配置的 OPENTRACING_TRACER_CLASS_NAME 获取 OpenTracing 协议实现类的类名,然后通过反射创建了实例。例如,我们可以配置该类为如下所示的 Skywalking 框架中的 SkywalkingTracer 类:

1
org.apache.shardingsphere.opentracing.tracer.class=org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer

当然,ShardingTracer 类也提供了通过直接注入 OpenTracing 协议实现类的方法来进行初始化。实际上上述 init 方法最终也是调用了如下所示的 init 重载方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void init(final Tracer tracer) {



if (!GlobalTracer.isRegistered()) {



GlobalTracer.register(tracer);



}



}

该方法把 Tracer 对象存放到全局的 GlobalTracer 中。GlobalTracer 是 OpenTracing API 提供的一个工具类,使用设计模式中的单例模式来存储一个全局性的 Tracer 对象。它的变量定义、register 方法以及 get 方法如下所示:

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
58
59
60
61
62
63
64
65
66
67
68
69
private static final GlobalTracer INSTANCE = new GlobalTracer();



public static synchronized void register(final Tracer tracer) {



if (tracer == null) {



throw new NullPointerException("Cannot register GlobalTracer <null>.");



}



if (tracer instanceof GlobalTracer) {



LOGGER.log(Level.FINE, "Attempted to register the GlobalTracer as delegate of itself.");



return; // no-op



}



if (isRegistered() && !GlobalTracer.tracer.equals(tracer)) {



throw new IllegalStateException("There is already a current global Tracer registered.");



}



GlobalTracer.tracer = tracer;



}







public static Tracer get() {



return INSTANCE;



}

采用这种方式,初始化可以采用如下方法:

1
ShardingTracer.init(new SkywalkingTracer());

而获取具体 Tracer 对象的方法则直接调用 GlobalTracer 的同名方法即可,如下所示:

1
2
3
4
5
6
7
8
9
public static Tracer get() {



return GlobalTracer.get();



}

2.基于 Hook 机制填充 Span

一旦获取 Tracer 对象,我们就可以使用该对象来构建各种 Span。ShardingSphere 采用了Hook 机制来填充 Span。说道 Hook 机制,我们可以回想《15 | 解析引擎:SQL 解析流程应该包括哪些核心阶段(上)?》中的相关内容,在如下所示的 SQLParseEngine 类的 parse 方法中用到了 ParseHook:

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
public SQLStatement parse(final String sql, final boolean useCache) {



//基于 Hook 机制进行监控和跟踪



ParsingHook parsingHook = new SPIParsingHook();



parsingHook.start(sql);



try {



//完成 SQL 的解析,并返回一个 SQLStatement 对象



SQLStatement result = parse0(sql, useCache);



parsingHook.finishSuccess(result);



return result;



} catch (final Exception ex) {



parsingHook.finishFailure(ex);



throw ex;



}



}

注意到上述代码中创建了一个 SPIParsingHook,并实现了 ParsingHook 接口,该接口的定义如下所示:

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
public interface ParsingHook {







//开始 Parse 时进行 Hook



void start(String sql);







//成功完成 Parse 时进行 Hook



void finishSuccess(SQLStatement sqlStatement);







//Parse 失败时进行 Hook



void finishFailure(Exception cause);



}

SPIParsingHook 实际上是一种容器类,将所有同类型的 Hook 通过 SPI 机制进行实例化并统一调用,SPIParsingHook 的实现方式如下所示:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public final class SPIParsingHook implements ParsingHook {







private final Collection<ParsingHook> parsingHooks = NewInstanceServiceLoader.newServiceInstances(ParsingHook.class);







static {



NewInstanceServiceLoader.register(ParsingHook.class);



}







@Override



public void start(final String sql) {



for (ParsingHook each : parsingHooks) {



each.start(sql);



}



}







@Override



public void finishSuccess(final SQLStatement sqlStatement, final ShardingTableMetaData shardingTableMetaData) {



for (ParsingHook each : parsingHooks) {



each.finishSuccess(sqlStatement, shardingTableMetaData);



}



}







@Override



public void finishFailure(final Exception cause) {



for (ParsingHook each : parsingHooks) {



each.finishFailure(cause);



}



}



}

这里,我们看到了熟悉的 NewInstanceServiceLoader 工具类。这样,我们一旦实现了 ParsingHook,就会在执行 SQLParseEngine 类的 parse 方法时将 Hook 相关的功能嵌入到系统的执行流程中。

另外,OpenTracingParsingHook 同样实现了 ParsingHook 接口,如下所示:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public final class OpenTracingParsingHook implements ParsingHook {







private static final String OPERATION_NAME = "/" + ShardingTags.COMPONENT_NAME + "/parseSQL/";







private Span span;







@Override



public void start(final String sql) {



//创建 Span 并设置 Tag



span = ShardingTracer.get().buildSpan(OPERATION_NAME)



.withTag(Tags.COMPONENT.getKey(), ShardingTags.COMPONENT_NAME)



.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)



.withTag(Tags.DB_STATEMENT.getKey(), sql).startManual();



}







@Override



public void finishSuccess(final SQLStatement sqlStatement) {



//成功时完成 Span



span.finish();



}







@Override



public void finishFailure(final Exception cause) {



//失败时完成 Span(



ShardingErrorSpan.setError(span, cause);



span.finish();



}



}

我们知道 Tracer 类提供了 buildSpan 方法创建自定义的 Span 并可以通过 withTag 方法添加自定义的标签。最后,我们可以通过 finish 方法类关闭这个 Span。在这个方法中,我们看到了这些方法的具体应用场景。

同样,在《21 | 执行引擎:分片环境下 SQL 执行的整体流程应该如何进行抽象?》中,我们在的 SQLExecuteCallback 抽象类的 execute0 方法中也看到了 SQLExecutionHook 的应用场景,SQLExecutionHook 接口的定义如下所示:

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
public interface SQLExecutionHook {







//开始执行 SQL 时进行 Hook



void start(String dataSourceName, String sql, List<Object> parameters, DataSourceMetaData dataSourceMetaData, boolean isTrunkThread, Map<String, Object> shardingExecuteDataMap);







//成功完成 SQL 执行时进行 Hook



void finishSuccess();







//SQL 执行失败时进行 Hook



void finishFailure(Exception cause);



}

在 ShardingSphere 中,同样存在一套完整的体系来完成对这个接口的实现,包括与 SPIParsingHook 同样充当容器类的 SPISQLExecutionHook,以及基于 OpenTracing 协议的 OpenTracingSQLExecutionHook,其实现过程与 OpenTracingParsingHook 一致,这里不再做具体展开。

从源码解析到日常开发

在今天的内容中,我们可以从 ShardingSphere 的源码中提炼两个可以用于日常开发过程的开发技巧。如果我们需要自己实现一个用于分布式环境下的链路监控和分析系统,那么 OpenTracing 规范以及对应的实现类是你的首选。

基于 OpenTracing 规范,业界存在一大批优秀的工具,例如 SkyWalking、Zipkin 和 Jaeger 等,这些工具都可以很容易的集成到你的业务系统中。

另一方面,我们也看到了 Hook 机制的应用场景和方式。Hook 本质上也是一种回调机制,我们可以根据需要提炼出自身所需的各种 Hook,并通过 SPI 的方式动态加载到系统中,以满足不同场景下的需要,ShardingSphere 为我们如何实现和管理系统中的 Hook 实现类提供了很好的实现参考。

小结与预告

今天的内容围绕 ShardingSphere 中的链路跟踪实现过程进行了详细展开。我们发现在 ShardingSphere 中关于链路跟踪的代码并不多,所以为了更好地理解链路跟踪的实现机制,我们也花了一些篇幅介绍了链路跟踪的基本原理,以及背后的 OpenTracing 规范的核心类。

然后,我们发现 ShardingSphere 在业务流程的执行过程中内置了一批 Hook,这些 Hook 能够帮助系统收集各种监控信息,并通过 OpenTracing 规范的各种实现类进行统一管理。

这里给你留一道思考题:ShardingSphere 中如何完成与 OpenTracing 协议的集成?

在下一个课时中,我们将介绍 ShardingSphere 源码解析部分的最后一个主题,即 ShardingSphere 的内核如何与 Spring 框架进行无缝集成以降低开发人员的学习成本。

35 结语:ShardingSphere 总结及展望

终于到了专栏的最后一讲。今天,我们将对整个 ShardingSphere 课程进行总结和展望。作为一款在业界领先的分布式数据库中间件,ShardingSphere 受到越来越多人的追捧,它可以为我们提供多项核心功能,并帮忙我们构建完整的分库分表解决方案。

首先,我们还是总结一下专栏中讲解过的 ShardingSphere 核心功能,然后再梳理我在写作过程中的一些思考和心得,最后,我会向你讲解 ShardingSphere 4.X 版本至未来 5.X 版本的演进变化。

ShardingSphere 核心功能

ShardingSphere 官网展示了数据分片、分布式事务、数据库治理等三大块核心功能,对于这些功能,我分别在本专栏的第四部分、第五部分、第六部分都进行了详细介绍,你可回顾重温一遍。

1.数据分片

数据分片是 ShardingSphere 的基本功能。ShardingSphere 支持常规的基于垂直拆分和水平拆分的分库分表操作。在分库分表的基础上,ShardingSphere 也实现了基于数据库主从架构的读写分离机制,而且这种读写分离机制可以和数据分片完美地进行整合。

另一方面,作为一款具有高度可扩展性的开源框架,ShardingSphere 也预留了分片扩展点,开发人员可以基于需要实现分片策略的定制化开发。

2.分布式事务

分布式事务用于确保分布式环境下的数据一致性,这也是 ShardingSphere 区别于普通分库分表框架的关键功能,并且该功能使得分布式事务能够称为一种分布式数据库中间件。

ShardingSphere 对分布式事务的支持首先体现在抽象层面上。ShardingSphere 抽象了一组标准化的事务处理接口,并通过分片事务管理器 ShardingTransactionManager 进行统一管理。同样,在扩展性上,我们也可以根据需要实现自己的 ShardingTransactionManager 从而对分布式事务进行扩展。在事务类型上,ShardingSphere 也同时支持强一致性事务和柔性事务。

当具备数据分片和分布式事务功能之后,相当于就可以基于 ShardingSphere 实现日常的分库分表操作了。但这还不够,因为我们需要对系统中的数据库资源,以及服务的运行时状态进行跟踪和监控。因此,ShardingSphere 中也提供了多种有助于我们进行数据库治理的技术体系。

3.数据库治理

如果你一直在学习我们的专栏,相信你已经知道使用 ShardingSphere 的主要手段就是利用它的配置体系。关于配置信息的管理,我们可以基于配置文件完成配置信息的维护,这在 ShardingSphere 中都得到了支持。

更进一步,在ShardingSphere 中,它还提供了配置信息动态化管理机制,即可支持数据源、表与分片及读写分离策略的动态切换。而对于系统中当前正在运行的数据库实例,我们也需要进行动态的管理。在具体应用场景上,我们可以基于注册中心完成数据库实例管理、数据库熔断禁用等治理功能。

一旦 ShardingSphere 被应用到生产环境,开发和运维人员都需要关注通过 ShardingSphere 所执行的 SQL 语句的执行情况,以及 ShardingSphere 内核的运行时状态。在 ShardingSphere 中,使用 OpenTracing API 发送性能追踪数据。而在 SQL 解析与 SQL 执行等核心环节,ShardingSphere 都会把采集到的运行时数据通过标准协议提交到链路跟踪系统供我们进行分析和监控。

关于数据库治理的最后一项核心功能是数据脱敏。严格意义上讲,与其说数据脱敏是一项数据库治理功能,不如说它更多的是一项面向业务场景的特定功能。数据脱敏是业务系统中确保数据访问安全的常见需求,我们需要实现对原文数据进行加密并存储在数据库中。而在用户查询数据时,它又从数据库中取出密文数据并解密,最终将解密后的原始数据返回给用户。

ShardingSphere 对这一数据脱敏过程实现了自动化和透明化,开发人员无须关注数据脱敏的实现细节。

ShardingSphere 课程总结

总结完介绍的 ShardingSphere 各项核心功能,我们再来总结整个专栏所讲解内容的特色和与其他专栏之间的差异化。这里,我分为以下三大亮点。

本专栏的一大亮点在于提供了完整的案例代码来介绍 ShardingSphere 中的上述功能。

这个案例系统足够简单,可以让你从零开始就能理解和掌握其中的各项知识点;同时这个案例系统又足够完整,涉及的各个核心功能我们都提供了相关的配置项和示例代码,供大家在日常开发过程中进行参考。

本专栏的最核心内容是 ShardingSphere 的源码解析,这部分内容占据了整个专栏 2/3 的篇幅,可以说是课程的精髓所在。

一方面,我们给出了微内核架构,以及分布式主键的设计理念和实现方法,更重要的是,我们对 ShardingSphere 中介绍的各项核心功能都从源码出发给出了详细的设计思想和实现机制。

另一方面,针对数据分片,我们剖析了其中所涉及的解析引擎、路由引擎、改写引擎、执行引擎、归并引擎和读写分离。而对于分布式事务和数据库治理,我们也结合应用场景分析了各个技术组件的底层原理,确保你能够不仅知其然,更能知其所以然。

本专栏在设计上也还有一个亮点,就是源码解析各个课时中的 “从源码解析到日常开发” 这部分内容。

本课程的一大目标,是通过系统化地讲解框架源码,帮忙你深入理解 ShardingSphere 实现原理,但这并不是唯一目标,我更希望你能从中收获实践技能,做到学有所用。

所以我在每一课时的“从源码解析到日常开发”部分,都会根据该课时内容梳理若干条工程实践。这些工程实践,有些是设计思想的提炼,有些是工具框架的应用技巧,还有一些则是可以直接应用到业务开发过程中的模板代码。

我希望你能通过对 ShardingSphere 这款优秀开源框架的学习,能够掌握好系统架构设计和实现过程中的方法和技巧,并把这些工程实践应用到日常的开发工作中。

从 ShardingSphere 4.X 到 5.X

最后,我们来对 ShardingSphere 的发展做一些展望。

本课程应用的是 ShardingSphere 4.X,而在当下,ShardingSphere 的开发团队正在紧锣密鼓地开发 5.X 版本。5.X 版本是 ShardingSphere 发展过程中的一个大版本,所涉及的内部功能与其对外 API 也将面临大规模的优化和调整。

同时,5.X 版本也添加了多项新的核心功能,让 ShardingSphere 生态圈更加丰富。到目前为止,5.X 版本已经设计和实现了包括弹性伸缩和影子库压测在内的多项核心功能,让我们一起分别看一下这两个功能。

1.弹性伸缩功能

5.X 版本首先要介绍的是它的弹性伸缩功能,对应的模块名称为 ShardingSphere-Scaling。随着业务规模的快速变化,我们可能需要对现有的分片集群进行弹性扩容或缩容。这个过程看似简单,实现起来却非常复杂。

为此,ShardingSphere 给出了一站式的通用型解决方案。这个方案支持各类用户自定义的分片策略,并能减少用户在数据伸缩及迁移时的重复工作及业务影响。弹性伸缩功能实际上在 4.1.0 版本时便已经开始引进导入,但它一直处于 alpha 开发阶段,所提供的也只是基础伸缩功能。在后续的规划中,ShardingSphere 计划通过半自动伸缩、断点续传和全自动伸缩等多个里程碑阶段来完成整个功能体系。

2.影子库压测

5.X 版本引入的第二个功能是影子库压测,这个功能的背景来自如何对系统进行全链路压测。在数据库层面,为了保证生产数据的可靠性与完整性,需要做好数据隔离,将压测的数据请求打入影子库,以防压测数据写入生产数据库,从而对真实数据造成污染**。**

在 ShardingSphere 中,我们可以通过数据路由功能将压测所需要执行的 SQL 路由到与之对应的数据源中。与数据脱敏一样,ShardingSphere 实现影子库压测的开发方式也是配置一个影子规则。

此外,ShardingSphere 还在规划和实施强一致多副本等功能,让我们一起期待这些功能早日发布。

作为业内关于 ShardingSphere 的第一门系统化专栏,《ShardingSphere 核心原理精讲》凝练着我基于 ShardingSphere 进行数据分库分表和治理工作的多年实践经验,整个专栏从酝酿到启动,再到上线也历经了小半年的时间,伴随着这个过程,我把 ShardingSphere 的源代码系统地梳理了一遍,并对内部的设计思想和实现原理也做了详细的提炼和总结。

总体而言,ShardingSphere 是一款代码质量非常高的开源框架,尤其是其中关于对 JDBC 规范的兼容、分片引擎的阶段化执行过程,以及各种辅助性的服务编排和治理等诸多功能,都让我的工作受益良久。

相信这些宝贵的“知识财富”也能一直伴随你,让你的职业生涯越走越远,越走越广。最后,祝大家在各自的岗位上都能够更上一层楼!