theme: fancy
前置知识
参考链接:https://www.cnblogs.com/zwh0910/p/15459737.html
常见的ConditionalOnxxx开头的注解,我们称之为条件注解,用于控制当前bean是否被加载,如果不满足条件,则不加载当前bean
- ConditionalOnProperty:
- ConditionalOnResource
- ConditionalOnBean
- ConditionalOnClass
- ConditionalOnMissingBean
- ConditionalOnMissingClass
SPI机制
https://javaguide.cn/java/basis/spi.html#spi-和-api-有什么区别
https://pdai.tech/md/java/advanced/java-advanced-spi.html
SPI全称是Service Provider Interface,中文名为服务提供者接口,基于JDK内置的动态加载实现扩展点的机制(通过在类路径下的META-INF/services文件夹中查找文件,自动加载文件里所定义的类),这一机制使得框架扩展和替换组件更加方便。常见的使用SPI机制的场景有:日志接口log4j、数据库驱动(不同的数据库厂商实现定义的接口)以及Dubbo的扩展实现。
- 当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。
- 当服务的提供者实现接口后,需要在类路径下的META-INF/services目录(这个目录也可以自定义,在shenyu项目中就是META-INF/shenyu)里创建一个以服务接口命名的文件,文件内容就是这个接口的具体实现类路径。当其他程序需要这个服务时,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services中的配置文件,根据具体实现类的类名进行实例化加载。JDK中查找接口的实现类工具是:java.util.ServiceLoader。
Shenyu-nginx模块实现集群
该模块提供SDK,用于通过注册中心为OpenResty自动监听Apache Shenyu可用的网关实例节点。 在集群模式下,Apache Shenyu支持部署多个Shenyu实例,随时可能有新的实例上线或下线。因此,Apache Shenyu引入了服务发现 OpenResty 模块来帮助客户端检测可用Shenyu实例。目前Apache Shenyu已经支持Zookeeper、Nacos、Etcd和Consul。Client或LoadBalancer 可以通过这些Service注册中心获取可用的Shenyu实例。
首先,下载源码
git clone https://github.com/apache/shenyu-nginx
然后,构建源码并安装
cd shenyu-nginx luarocks make rockspec/shenyu-nginx-main-0.rockspec
接着,修改Nginx配置,创建并初始化Shenyu register模块,连接至目标注册中心。该模块将获取在同一个集群中注册到Etcd的 所有Shenyu实例。它与Etcd客户端一样监视(基于长轮询)Shenyu实例列表。 Etcd示例:
init_worker_by_lua_block {
local register = require("shenyu.register.etcd")
register.init({ balancer_type = "chash", etcd_base_url = "http://127.0.0.1:2379", })
}
最后,重启OpenResty
openresty -s reload
Bootstrap实例如何注册
注册中心初始化
Bootstrap启动后如何向admin注册实例信息,从Bootstrap的pom文件中发现如下依赖:
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-registry</artifactId>
<version>${project.version}</version>
</dependency>
# 除了这块依赖应该还需引入相应注册中心的依赖
org.apache.shenyu.springboot.starter.registry.ShenyuRegistryConfiguration,这个类是一个配置类,初始化了RegisterConfig和RegistryListener。RegistryListener实现了ApplicationListener:
public RegistryListener(final RegisterConfig config) {
String registerType = config.getRegisterType();
String serverLists = config.getServerLists();
if (StringUtils.isBlank(registerType) || StringUtils.isBlank(serverLists)) {
throw new ShenyuException("please config the registerType and serverList");
}
// 读取注册中心配置文件,所以注册中心用于注册网关实例、URI、元数据等
// 刚开始我以为网关实例注册和元数据注册是分别不同的注册配置
repository = ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config);
this.props = config.getProps();
String name = props.getProperty("name");
this.appName = StringUtils.isBlank(name) ? "shenyu-gateway" : name;
String host = props.getProperty("host");
// 获取网关实例的ip
this.host = StringUtils.isBlank(host) ? IpUtils.getHost() : host;
}
- 从初始化逻辑中可以看懂会根据注册中心配置,利用工厂模式创建一个ShenyuInstanceRegisterRepository。 ShenyuInstanceRegisterRepositoryFactory,可以发现使用SPI机制,加载ShenyuInstanceRegisterRepository所有的实现类,通过map存储,newAndInitInstance方法则根据注册类型从map中获取相应的网关实例注册类。
- 工厂模式:ShenyuInstanceRegisterRepositoryFactory
- 策略模式:ShenyuInstanceRegisterRepository作为顶层接口,子类实现这个接口,利用SPI将所有实现类注册至map,根据注册类型不同,获取相应的实现类执行具体逻辑;这种模式很常见,也很好用。
- REPOSITORY_MAP是线程安全的map,这里考虑了线程安全问题
- result.init:注册中心的初始化
public final class ShenyuInstanceRegisterRepositoryFactory {
private static final Map<String, ShenyuInstanceRegisterRepository> REPOSITORY_MAP = new ConcurrentHashMap<>();
public static ShenyuInstanceRegisterRepository newInstance(final String registerType) {
return REPOSITORY_MAP.computeIfAbsent(registerType, ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class)::getJoin);
}
public static ShenyuInstanceRegisterRepository newAndInitInstance(final RegisterConfig config) {
return REPOSITORY_MAP.computeIfAbsent(config.getRegisterType(), registerType -> {
ShenyuInstanceRegisterRepository result = ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin(registerType);
result.init(config);
return result;
});
}
}
网关实例注册至注册中心
org.apache.shenyu.springboot.starter.registry.RegistryListener
public void onApplicationEvent(final WebServerInitializedEvent event) {
if (!registered.compareAndSet(false, true)) {
return;
}
String configPort = props.getProperty("port");
int port = StringUtils.isBlank(configPort) ? event.getWebServer().getPort() : Integer.parseInt(configPort);
InstanceEntity instanceEntity = buildInstanceRegisterDTO(port);
repository.persistInstance(instanceEntity);
}
- WebServerInitializedEvent:tomcat启动之后,会发布ServletWebServerInitializedEvent事件,该类是WebServerInitializedEvent的子类。也就是说,当tomcat启动后,才注册网关实例至注册中心。
- registered是AtomicBoolean,线程安全,用于判断是否已经注册。
ShenyuInstanceRegisterRepository从字面意思理解是用于注册网关实例的类,有若干的实现类,实现类逻辑在shenyu-registry模块中:
以nacos为例,org.apache.shenyu.registry.nacos.NacosInstanceRegisterRepository#persistInstance逻辑如下:
@Override
public void persistInstance(final InstanceEntity instance) {
try {
Instance inst = new Instance();
inst.setWeight(1.0d);
inst.setEphemeral(true);
inst.setIp(instance.getHost());
inst.setPort(instance.getPort());
inst.setInstanceId(buildInstanceNodeName(instance));
inst.setServiceName(instance.getAppName());
namingService.registerInstance(instance.getAppName(), groupName, inst);
LOGGER.info("nacos client register success: {}", inst);
} catch (NacosException e) {
throw new ShenyuException(e);
}
}
- 使用nacos的namingService注册该实例信息