Nacos提供CMDB模块用于将用户的CMDB信息集成到Nacos中,然后配合用户选择的Selector进行节点的就近选择,从而提升性能。具体的实现和使用已经在文章Nacos通过CMDB实现就近访问提升性能中有过介绍。Nacos旧版本的Selector实现逻辑较为简单,主要是按照CMDB资源的label信息进行匹配,由于其属于内置的实现方式,就导致扩展性和灵活性比较差。所以,在2.1版本中我们将其重新设计和实现,在不改变功能的前提下,让用户可以自定义其业务所需Selector的实现逻辑。下面我将从新版本的Selector实现逻辑和使用两个步骤来带你更快的体验新版本的Selector。
Selector插件机制
工作原理
现有的Selector实际包含了两步操作。第一步,准备实例筛选所需的资源信息。第二步,执行筛选逻辑筛选实例。所以我们将Selector的功能进行解耦,将资源准备和筛选逻辑均抽象为独立的功能作为扩展点提供给用户实现,同时提供内置的CMDB资源类型的准备,用户可以灵活的根据业务所需来选择实现自己的功能。
抽象概念
由于功能点的拆分,新版本的Selector引入了一些SPI扩展点,下面简单介绍新版Selector中所提供的一些SPI接口。
SelectorContextBuilder
SelectorContextBuilder是前文所提到的资源准备SPI接口,用于提供给用户封装自定义的资源准备逻辑。用户可以通过实现此SPI接口,将自定义的资源信息加载到Nacos中,提供给对应资源的Selector进行资源筛选逻辑。
- build方法用于构建资源信息。
-
getContextType返回当前资源准备器的类型(如我们的CMDB资源实际对应我们内嵌至Nacos中的CMDB资源准备)。
SelectorContextBuilder SPI接口
Selector
Selector用于封装用户的实例筛选逻辑SPI接口,用户可以将外部传入的表达式封装到Selector,然后自定义业务所需筛选逻辑继续实例筛选。
- parse方法用于外部传入的表达式,封装到Selector中。
- select用于执行筛选逻辑。
- getType返回当前Selector类型,同时会在Nacos控制台中展示。
-
getContextType返回当前Selector所需的资源类型,对应前文中提到的SelectorContextBuilder#getContextType。
值得注意的是,我们的Selector#select方法的入参会来自对应的SelectorContextBuilder#build的返回值,且会根据ContextType一一对应。
Selector SPI接口
同时我们内置了CMDB资源的准备,主要是和Nacos内置的CMDB插件进行整合,提供服务的CMDB资源信息。如果用户想要直接使用Nacos准备的CMDB资源而不需要自己准备资源,那么可以继承我们预留的AbstractCmdbSelector。
AbstractCmdbSelector CMDB资源类型的Selector
此类型的Selector会使用Nacos内置的CMDB资源,用户只需要实现doParse和doSelect方法即可使用CMDB资源作为入参实现自己的实例筛选逻辑,适用于大部分用户的业务场景。
快速入门
实现一个基于CMDB资源的Selector
具体步骤如下:
1.新建一个maven工程,引入依赖nacos-api:
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-api</artifactId>
<version>2.1.0</version>
</dependency>
2.引入打包插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
3.定义实现类,继承com.alibaba.nacos.api.selector.AbstractCmdbSelector,并实现相关方法。
public class ExampleCmdbSelector extends AbstractCmdbSelector<Instance> {
private List<String> tags = new ArrayList<>();
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
@Override
protected List<Instance> doSelect(CmdbContext<Instance> context) {
return context.getProviders().stream()
.filter(ci -> tags.contains(ci.getEntity().getName()))
.map(CmdbContext.CmdbInstance::getInstance)
.collect(Collectors.toList());
}
@Override
protected void doParse(String expression) throws NacosException {
tags.addAll(Arrays.asList(expression.split(",")));
}
@Override
public String getType() {
return "example";
}
}
4.在src/main/resource/目录下新建目录:META-INF/services
5.在src/main/resources/META-INF/services目录下新建文件com.alibaba.nacos.api.selector.Selector,并在文件里将第三步中创建的实现类全名写入该文件:
6.执行maven打包:
mvn package assembly:single -Dmaven.test.skip=true
7.将target目录下的包含依赖的jar包上传到nacos CMDB插件目录:
{nacos.home}/plugins/selector
8.重启nacos Server,即可加载到你的Selector插件到Nacos中,同时可以在Nacos控制台中找到对应的Selector。
实现一个Tag资源类型的Selector
打包和配置SPI的流程按照上述流程,此处不再赘述。
实现自定义ContextBuilder。
如果IP地址为127.0.0.1,那么此实例标签为A,否则为B。
public class TagContextBuilder implements SelectorContextBuilder<TagContext, String, List<Instance>> {
@Override
public TagContext build(String consumer, List<Instance> provider) {
TagContext tagContext = new TagContext();
List<TagContext.TagInstance> tagInstances = provider.stream()
.map(i -> {
TagContext.TagInstance tagInstance = new TagContext.TagInstance();
tagInstance.setInstance(i);
if (i.getIp().equals("127.0.0.1")) {
tagInstance.setTag("A");
} else {
tagInstance.setTag("B");
}
return tagInstance;
})
.collect(Collectors.toList());
tagContext.setTagInstances(tagInstances);
return tagContext;
}
@Override
public String getContextType() {
return "TAG";
}
}
实现自定义资源类型Selector。
如果标签集合中包含实例的标签,那么返回此实例,否则不返回。
public class TagSelector implements Selector<List<Instance>, TagContext, String> {
List<String> tags = new ArrayList<>();
public List<String> getTags() {
return tags;
}
public void setTags(List<String> tags) {
this.tags = tags;
}
private String expression;
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
@Override
public Selector<List<Instance>, TagContext, String> parse(String expression) throws NacosException {
if (StringUtils.isBlank(expression)) {
return this;
}
this.expression = expression;
tags.addAll(Arrays.asList(expression.split(",")));
return this;
}
@Override
public List<Instance> select(TagContext context) {
return context.getTagInstances().stream()
.filter(tagInstance -> tags.contains(tagInstance.getTag()))
.map(TagContext.TagInstance::getInstance)
.collect(Collectors.toList());
}
@Override
public String getType() {
return "tag";
}
@Override
public String getContextType() {
return "TAG";
}
}
验证
我们向Nacos注册服务,IP为1.1.1.1。此时我们配置Tag为A的实例应该被查询出。
由于我们实例为1.1.1.1,并不是127.0.0.1,实例的Tag为B。所以查询结果为空。
我们更改表达式查询B标签的实例。
此时我们配置Tag为B的实例应该被查询出。
至此,我们已经完成了基于内置ContextBuilder的Selector实现和自定义资源类型的Selector实现。