你知道SPI吗?

源起

都知道现在Service Mesh(服务网格)比较火,其实还有Database Mesh: 数据库网格,Message Mesh:消息机制,Cache Mesh:缓存这些网格,其中Database Mesh中是https://shardingsphere.apache.org/

什么是SPI?

Service Provider Interface (SPI)是一种为了被第三方实现或扩展的API。它可以用于实现框架扩展或组件替换。

ShardingSphere在数据库治理模块使用SPI方式载入注册中心,进行实例熔断和数据库禁用。 目前,ShardingSphere内部支持Zookeeper和Etcd两种常用的注册中心。 此外,您可以使用其他第三方注册中心,并通过SPI的方式注入到ShardingSphere,从而使用该注册中心,实现数据库治理功能。

为什么要用SPI呢?

Apache ShardingSphere之所以采用SPI方式进行扩展,是出于整体架构最优设计考虑。 为了让高级用户通过实现Apache ShardingSphere提供的相应接口,动态将用户自定义的实现类加载其中,从而在保持Apache ShardingSphere架构完整性与功能稳定性的情况下,满足用户不同场景的实际需求。

以下均有提供SPI接口

数据脱敏

数据脱敏的接口用于规定加解密器的加密、解密、类型获取、属性设置等方式。 主要接口有两个:ShardingEncryptor和ShardingQueryAssistedEncryptor,其中ShardingEncryptor的内置实现类有AESShardingEncryptor和MD5ShardingEncryptor。 有关加解密介绍,请参考数据脱敏。

分布式主键

分布式主键的接口主要用于规定如何生成全局性的自增、类型获取、属性设置等。 主要接口为ShardingKeyGenerator,其内置实现类有UUIDShardingKeyGenerator和SnowflakeShardingKeyGenerator。 有关自增主键的介绍,请参考分布式主键。

注册中心

注册中心的接口主要用于规定注册中心初始化、存取数据、更新数据、监控等行为。 主要接口为RegistryCenter,其内置实现类有Zookeeper, ETCD。相关介绍请参考注册中心。

https://shardingsphere.apache.org/document/current/cn/features/spi/

事务

在支持两阶段事务方面。通过SPI机制整合主流的XA事务管理器,默认Atomikos,可以选择使用Narayana和Bitronix。
https://shardingsphere.apache.org/document/current/cn/features/transaction/function/2pc-xa-transaction/

扩展Dubbo中集群容错策略Cluster(是SPI接口)

我们可以通过自己实现SPI接口,实现自己的策略,比如可以指定IP调用。

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
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.rpc.cluster;

import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.cluster.support.FailoverCluster;

/**
* Cluster. (SPI, Singleton, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Computer_cluster">Cluster</a>
* <a href="http://en.wikipedia.org/wiki/Fault-tolerant_system">Fault-Tolerant</a>
*
*/
@SPI(FailoverCluster.NAME)
public interface Cluster {

/**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

http://ifeve.com/dubbo-set-ip-call/

线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

双亲委派模型并不能解决所有的类加载器问题,比如,Java 提供了很多服务提供者接口,允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP 等,这些 SPI 的接口由核心类库提供,却由第三方实现,这样就存在一个问题:

SPI 的接口是 Java 核心库的一部分,是由 BootstrapClassLoader 加载的;SPI 实现的 Java 类一般是由 AppClassLoader 来加载的。BootstrapClassLoader 是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给 AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。

线程上下文类加载器( ContextClassLoader)正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是 AppClassLoader。在核心类库使用 SPI 接口时,传递的类加载器使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。

线程上下文类加载器在很多 SPI 的实现中都会用到。但在 JDBC 中,你可能会看到一种更直接的实现方式,比如,JDBC 驱动管理 java.sql.DriverManager 中的 loadInitialDrivers()方法中,你可以直接看到 JDK 是如何加载驱动的:

1
2
3
4
5
6
7
8
9
10
for (String aDriver : driversList) {  
try {
println("DriverManager.Initialize: loading " + aDriver);
// 直接使用 AppClassLoader
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}

一篇介绍线程上下文类加载器的博客:https://blog.csdn.net/yangcheng33/article/details/52631940

参考

https://shardingsphere.apache.org/document/current/cn/features/orchestration/supported-registry-repo/
https://skyao.io/talk/201905-servicemesh-development-trend/

-------------本文结束感谢您的阅读-------------