前言
ES插件应用到ES很多地方,如:报警、分词、安全。。。。 但这些插件都是在技术层面的,业务层面肯定是缺失的,需要我们来补充
1、跟据某个业务字段或是业务规则来打分,打分高的排前面
2、跟据数据库字段来生成索引的mapping
3、异构数据的同步索引,跟据规则处理异构数据
4、跟据某些业务规则来触发告警
5、更多。。。。。。。。。
因此,我们很想自已来开发ES插件来满足业务需求,但ES插件在开发上确实不方便,如:权限严格、不支持热加载需要重启、对初学者不友好、版本升级(特别是大版本)后不向下兼容 等。所以我设想增加一层,如果我们能做一个插件平台,它与es打交道,其它的业务插件都在此平台上运行。这样就能一劳永逸地解决上述问题。
es插件平台开发
所谓的插件平台,这是对业务层面来说的,其实它仍然是一个es插件。现在以es6.3.2这个版本进行说明es如何开发这个es插件,下载es6.3.2:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-3-2 ,解压后就可通过bin/elasticsearch 启动es,前提是你安装好了jdk。我们开发好的es插件是放到 "plugins"目录下的,这个目录初始为空,es默认并没有带有任何处置插件。整个项目的示例代码在: https://github.com/rjzjh/tams-es-plugin 。现在来一步步分解这个项目。
要开发一个es插件,在maven项目中引入依赖包:
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.3.2</version>
</dependency>
还需要引入一个打包用的maven插件:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>${project.build.directory}/releases/</outputDirectory>
<descriptors>
<descriptor>${basedir}/src/assembly/plugin.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
这个插件就是跟据plugin.xml文件来进行打包,最后放在tagert/releases 目录下生成一个zip包。这个zip包就是我们想要的插件,只要把这个zip解压到"plugins"目录,重启es,这个插件就生效了。
它的插件总入口在类:net.wicp.tams.common.es.plugin.TamsRestHandlerPlugin
public class TamsRestHandlerPlugin extends Plugin implements ActionPlugin {
public static KeyConfigManager keyConfigManager;
public static CommonService executor;
public static URLClassLoader cloasLoader;
@Override
public List<RestHandler> getRestHandlers(final Settings settings, final RestController restController,
final ClusterSettings clusterSettings, final IndexScopedSettings indexScopedSettings,
final SettingsFilter settingsFilter, final IndexNameExpressionResolver indexNameExpressionResolver,
final Supplier<DiscoveryNodes> nodesInCluster) {
Logger logger = Loggers.getLogger(TamsRestHandlerPlugin.class, settings);
logger.info("-------------------begin plugin------------------------------------");
Properties props = IOUtil.fileToProperties("/plugin-default.properties", TamsRestHandlerPlugin.class);
Conf.overProp(props);
logger.info("-----size:{}", Conf.copyProperties().size());
logger.info("path={},packagebase={}", Conf.get("common.es.plugin.path"),
Conf.get("common.es.plugin.packagebase"));
TamsRestHandlerPlugin.executor = new CommonService();
String path = Conf.get("common.es.plugin.path");
path = (StringUtil.isNull(path) || "none".equals(path))
? IOUtil.mergeFolderAndFilePath(IOUtil.getCurFolder(TamsRestHandlerPlugin.class).getParent(), "/plugin")
: path;
File pluginDir = new File(path);
if (!pluginDir.exists()) {
try {
FileUtils.forceMkdir(pluginDir);
} catch (IOException e) {
logger.error("创建插件目录:" + path + "失败", e);
}
}
String[] packages = Conf.get("common.es.plugin.packagebase").split(",");
ClassLoadManager classLoadManager = new ClassLoadManager(path, 2, packages);
// ClassLoadManager classLoadManager = new ClassLoadManager(path, 2, packages);
TamsRestHandlerPlugin.cloasLoader = classLoadManager.getCloasLoader();
TamsRestHandlerPlugin.keyConfigManager = new KeyConfigManager(TamsRestHandlerPlugin.cloasLoader);
TamsRestHandlerPlugin.executor.setBusiManager(classLoadManager);
TamsRestHandlerPlugin.executor.setConfigManager(TamsRestHandlerPlugin.keyConfigManager);
logger.info("-------------------------tams plugins start sucess------------------------------");
// return singletonList(new RestPutMappingAction(settings, restController));
return Arrays.asList(new ConnectorRest(settings, restController));
}
}
在入口类继承虚类:org.elasticsearch.plugins.Plugin 。要开发不同类型的接口需要实现 不同的接口,我现在想开发一个rest接口形式的插件,对外会提供http接口.那么我实现了接口:ActionPlugin。这个插口有一个方法必须实现“getRestHandlers”,这就是插件的扩展点了。这里面我做的很简单,就是引入了“common-connector”模块,自定义了ClassLoadManager,这个classload会对“Conf.get(“common.es.plugin.packagebase”)”这个配置项配置的目录中的业务插件(下节会讲)进行懒加载。
此插件现在仅开发了一个hander:“ConnectorRest”,这个hander是对cat进行了扩展,所以它继承了虚类:“org.elasticsearch.rest.action.cat.AbstractCatAction”,这个方式是指示此hander会对哪个url进行处理:
@Override
protected void documentation(StringBuilder sb) {
sb.append("/_cat/tams/connector\n");
}
构造函数里指示对请求的类型的要求,支持post和get:
public ConnectorRest(Settings settings, RestController controller) {
super(settings);
controller.registerHandler(GET, Conf.get("common.es.plugin.connector.url"), this);
controller.registerHandler(POST, Conf.get("common.es.plugin.connector.url"), this);
}
而处理逻辑较为简单:
@Override
protected RestChannelConsumer doCatRequest(RestRequest request, NodeClient client) {
Thread.currentThread().setContextClassLoader(TamsRestHandlerPlugin.cloasLoader);
BytesReference content = request.content();
String contextstr = new String(content.toBytesRef().bytes);
// logger.info("contextstr=" + contextstr);
JSONObject inputparam = JSONObject.parseObject(contextstr);
CusDynaBean inputBean = keyConfigManager.getInputBeanInputBody(inputparam);
// logger.info("transactionNo=" + inputBean.getStrValueByName("transactionNo"));
String appKey = inputparam.getJSONObject(XMLNameSpace.ControlInfo).getString(Request.requestCommand);
IBusiApp bean = executor.getBusiManager().getBean(appKey);
if (AbsBusiApp.class.isAssignableFrom(bean.getClass())) {
((AbsBusiApp) bean).setNodeClient(client);
((AbsBusiApp) bean).setLogger(logger);
}
CusDynaBean outbean = executor.exe(inputBean, bean);
RestResponse response = new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE,
outbean.getJsonObj().toJSONString());
return channel -> {
try {
channel.sendResponse(response);
} catch (final Exception e) {
channel.sendResponse(new BytesRestResponse(channel, e));
}
};
}
它会把请求的参数按“common-connector”模块的参数要求进行组装并调用相关的实现逻辑,然后把“common-connector”模块的结果原样返回给浏览器。
经过此处理逻辑后,所有的只要满足“common-connector”模块的处理逻辑,放到此插件配置的自定义了ClassLoadManager能读取到的目录,就可以在浏览器调用了。而示例代码中的"tams-es-plugin-mapping"模块就是这么一个满足“common-connector”模块的处理逻辑。这个模块的pom文件要注意的是需引入maven插件:
<plugin>
<groupId>net.wicp.tams</groupId>
<artifactId>ts-maven-plugin</artifactId>
<executions>
<execution>
<id>plugintar</id>
<phase>package</phase>
<goals>
<goal>plugin</goal>
</goals>
</execution>
</executions>
</plugin>
它会把"tams-es-plugin-mapping"模块打包为一个完整的tar包,这样就方便部署了,只需把这个tar包解压到ClassLoadManager能读取到的目录就完成了部署。
插件平台配置
“plugin-default.properties”文件是其默认的配置,如果有不同可做修改调整,其中这几个配置较为重要:
common.es.plugin.packagebase=net.wicp.tams.common.es.plugin,
common.es.plugin.path=/data/es/plugins
common.es.plugin.connector.url=/_cat/tams/connector
url:这个插件平台hander响应的地址
path: 这个指示业务业务插件存放的目录,就是自定义的classload可以加载的目录。
packagebase:指示业务插件会存放在哪个包下面,这样可以缩小搜索范围。类似于spring的component-scan。
插件部署
在运行此插件平台,需要在部署运行前还需要添加java的相关策略,修改文件:$JAVA_HOME\jre\lib\security\java.policy添加下列策略:
permission java.util.PropertyPermission "*", "read,write";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "closeClassLoader";
permission java.io.FilePermission "/data/es/plugins", "read";
permission java.io.FilePermission "/data/es/plugins", "write";
permission java.io.FilePermission "<<ALL FILES>>" , "read,write";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.net.SocketPermission "*:*","accept,connect,resolve";
在总的目录 D:\source\github\tams-es-plugin下执行mvn命令:mvn clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true ,把tams-es-plugin-platform模块的target\releases目录下tams-es-plugin-platform-3.6.5.zip文件解压到 es插件的plugins目录下。完成了插件平台的部署
业务插件插件部署:把tams-es-plugin-mapping模块target目录下的tams-es-plugin-mapping.tar文件解压到配置项:“common.es.plugin.connector.url” 所配置的文件夹“/data/es/plugins”目录下。完成业务插件的部署
业务插件的调试
在业务插件开发和故障定位过程中,单步调试必不可少,但插件的运行依赖于ES,所以不能像正常代码一样以main方法为入口的调试,但它是java代码,那么利用远程调试也能达到一样的效果。
要进行远程调试,首先修改elasticsearch.bat(window环境),elasticsearch(linux环境)启动参数,加上“-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9999” 其中9999是指远程调试端口。然后启动es,那么启动程序会进入等待状态:
配置远程启动(Remote Java Application):注意port就是上面配置的端口号
启动后就会进到我们设置的断点处进行单步调试:
再看es的控制台已加载到我们的插件平台了(tamsRest):
功能测试
插件平台
插件平台“tams-es-plugin-platform”主要的功能是管理好自定义的classload,实现对所有业务插件的热加载。它也是业务插件与ES的一个桥梁。它规范了业务插件的http接口必需满足“common-connector”模块对输入输出参数的定义。它也定义了一个功能:重载加所有的业务插件,这样就可以实现热加载了。
其中参数:“requestCommand”:“Refresh” 是指示要调用的命令的,其它的参数随便填。
业务插件
业务插件“tams-es-plugin-mapping”是被插件平台所管理的业务插件,它主要实现了下列功能:
- 跟据数据库的表生成对应的用于生成ES静态mapping用的Json字符串。数据库的字段类型与ES的类型会有一个默认的映射关系。
这个json就是指示创建索引时的字段名和字段类型,可以跟据需求进行修改,然后使用下面的功能进行索引的创建。
- 在ES上创建静态索引,它的mapping是跟据上面生成的json字符串来生成的,中间可以修改。
- 把上面2个功能聚合为一个功能,跟据数据库的表直接生成索引。
第二、第三个功能对ES的影响: