es版本6.2.4
父子文档,可以理解为关系型数据库中的一对多的关系。使用logstash同步MySQL数据,有时候需要同步父子文档。
父子文档相比嵌套文档较灵活,但只适用于“一对大量”且这个“一”不是海量的应用场景,该方式比较耗内存和CPU,这种方式查询比嵌套方式慢5~10倍,且需要使用特定的has_parent和has_child过滤器查询语法,查询结果不能同时返回父子文档(一次join查询只能返回一种类型的文档)。而受限于父子文档必须在同一分片上,ES父子文档在滚动索引、多索引场景下对父子关系存储和联合查询支持得不好,而且子文档type删除比较麻烦(子文档删除必须提供父文档ID)。
存宽表是个不错的方案,纠结该多宽也十分讲究!
建议:一个宽表维护业务主表的基本信息及其强依赖的扩展信息。
https://blog.csdn.net/alex_xfboy/article/details/97900734
https://blog.csdn.net/alex_xfboy/article/details/89841553
(1)创建含 join 类型数据的索引(使用mapping)
PUT datacatalog
{
"mappings": {
"_doc": {
"properties": {
"join_field": {
"type": "join",
"relations": {
"datacatalog":"datacatalog_theme_trade"
}
}
}
}
}
}
索引:datacatalog,type:_doc,parent-child 关系:“datacatalog”: “datacatalog_theme_trade”
(2)logstash 配置:
input {
jdbc {
# mysql 数据库链接,shop为数据库名
jdbc_connection_string => "jdbc:mysql://ip:3306/kf_data_open?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
# 用户名和密码
jdbc_user => "root"
jdbc_password => "12345678"
# 驱动
jdbc_driver_library => "/usr/local/logstash-6.2.4/config/mysql-connector-java-5.1.42.jar"
# 驱动类名
jdbc_driver_class => "com.mysql.jdbc.Driver"
#是否分页
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
#直接执行sql语句,通过id > :sql_last_value去判断上次执行到了哪里
statement =>"select catalog_id,catalog_name,catalog_status, page_views, data_provide,create_time, update_time
from kf_data_catalog where update_time > :sql_last_value"
#使用列值进行查询记录追踪
use_column_value => true
#设置跟踪记录的列为:id,要先设置use_column_value=true
tracking_column => "update_time"
#默认为number,如果为日期必须声明为timestamp
tracking_column_type => "timestamp"
# 执行的sql 文件路径+名称
#statement_filepath => "filepath"
#设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 索引类型
type => "datacatalog"
#插件会在last_run_metadata_path参数所指示的元数据文件中,持久化sql_last_value参数
last_run_metadata_path=>"/usr/local/logstash-6.2.4/last_run_metadata_path_File/datacatalog_last_run_metadata"
}
jdbc {
# mysql 数据库链接,shop为数据库名
jdbc_connection_string => "jdbc:mysql://ip:3306/kf_data_open?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
# 用户名和密码
jdbc_user => "root"
jdbc_password => "12345678"
# 驱动
jdbc_driver_library => "/usr/local/logstash-6.2.4/config/mysql-connector-java-5.1.42.jar"
# 驱动类名
jdbc_driver_class => "com.mysql.jdbc.Driver"
#是否分页
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
# 执行的sql 文件路径+名称
statement =>"select DISTINCT b.property_id, b.catalog_id,a.dic_label,b.dic_val from kf_dictionaries a INNER JOIN kf_catalog_tag b on a.dic_val=b.dic_val where a.p_id in (1 ,2)
and b.property_id > :sql_last_value"
#使用列值进行查询记录追踪
use_column_value => true
#设置跟踪记录的列为:updated
tracking_column => "property_id"
# 默认为number,如果为日期必须声明为timestamp
#tracking_column_type => "timestamp"
#statement_filepath => "/hw/elasticsearch/logstash-6.2.4/bin/test.sql"
#设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 索引类型
type => "datacatalog_theme_trade"
#插件会在last_run_metadata_path参数所指示的元数据文件中,持久化sql_last_value参数
last_run_metadata_path=>"/usr/local/logstash-6.2.4/last_run_metadata_path_File/datacatalog_theme_trade_last_run_metadata"
}
}
filter {
if [type]=="datacatalog" {
mutate {
add_field => { "join_field" => "datacatalog" }
}
}
if [type]=="datacatalog_theme_trade" {
mutate {
add_field => {"[join_field][name]" => "datacatalog_theme_trade"}
#catalog_id 子表的父id
add_field => {"[join_field][parent]" => "%{catalog_id}"}
}
}
}
output {
#将数据库的数据输出在控制台
stdout {
codec => json_lines
}
#将数据库数据存储到es
if [type]=="datacatalog" {
elasticsearch {
#ESIP地址与端口
hosts => "ip:9200"
#ES索引名称(自己定义的)
index => "datacatalog"
document_type => "_doc"
#自增ID编号
document_id => "%{catalog_id}"
}
}
if [type]=="datacatalog_theme_trade" {
elasticsearch {
#ESIP地址与端口
hosts => "ip:9200"
#ES索引名称(自己定义的)
index => "datacatalog"
document_type => "_doc"
#自增ID编号 这里使用系统默认设置的id,是因为子文档和父文档都存一个索引里,怕主键相同出现覆盖
#document_id => "%{property_id}"
routing => "%{catalog_id}"
}
}
}
springboot整合的es,好像不能用Join查询。
使用原生的:
public class DataCatalogESUtil {
private static String clusterName="test";
private static String ip="ip";
private static String port="9300";
private static TransportClient client;
static {
try {
// 指定集群名,默认为elasticsearch,如果改了集群名,这里一定要加
Settings settings = Settings.builder()
.put("cluster.name", clusterName)
.build();
client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName(ip), Integer.valueOf(port)));
} catch (Exception e) {
throw new RuntimeException(String.format("连接ES失败:%s", e.getMessage()));
}
}
public static void main(String[] args) {
//根据子查父 查询一个主题或行业下的所有目录
//childQuery();
//获取所有目录(父文档)
//getAllDataCatalog();
//父查子 获取一个目录有那些主题或者行业
parentQuery();
}
//父子关系 其实就是多对多的关系
// 数据目录定义为父,主题/行业定义为子,这样就可以实现查询数据目录下有哪些主题/行业(父查子)
//或者查询主题下有那些目录(子查父)
//根据子查父 查询一个主题或行业下的所有目录
//https://blog.csdn.net/lsr40/article/details/102462989
public static void childQuery() {
//设置查询条件 查询主题theme_nyaq下的目录
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("dic_val", "theme_nyaq");
//datacatalog_theme_trade 主题/行业的文档(子)
HasChildQueryBuilder hasParentQueryBuilder =
new HasChildQueryBuilder("datacatalog_theme_trade",matchQueryBuilder , ScoreMode.Max);
//执行查询 datacatalog表示索引 _doc表示类型
SearchResponse searchResponse = client.prepareSearch("datacatalog")
.setTypes("_doc")
.setQuery(hasParentQueryBuilder)
.execute()
.actionGet();
//遍历查询结果
SearchHits hits = searchResponse.getHits();
List<Object> result = Lists.newArrayList();
for (SearchHit hit : hits) {
Map<String, Object> source = hit.getSourceAsMap();
result.add(JSON.toJSON(source));
}
result.forEach(a->{
System.out.println(a);
});
}
//根据子查父 获取所有目录(可设置分页,排序)
public static void getAllDataCatalog(){
//设置分页参数
// int pageSize=5;
// int currentPage=1;
//设置查询条件 全查
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
//datacatalog_theme_trade 主题/行业的文档(子)
HasChildQueryBuilder hasChildQueryBuilder =
new HasChildQueryBuilder("datacatalog_theme_trade",matchAllQueryBuilder, ScoreMode.Max);
//执行查询 datacatalog表示索引 _doc表示类型
SearchResponse searchResponse = client.prepareSearch("datacatalog")
.setTypes("_doc")
// .addSort("catalog_id", SortOrder.ASC) //排序 ASC DESC
.setQuery(hasChildQueryBuilder)
// .setFrom((currentPage-1) * pageSize)
// .setSize(pageSize) // 设置分页
.execute()
.actionGet();
//遍历查询结果
SearchHits hits = searchResponse.getHits();
List<Object> result = Lists.newArrayList();
for (SearchHit hit : hits) {
Map<String, Object> source = hit.getSourceAsMap();
result.add(JSON.toJSON(source));
}
result.forEach(a->{
System.out.println(a);
});
}
//父查子 获取一个目录有那些主题或者行业
public static void parentQuery(){
//设置查询条件 查询catalog_id为5的目录
MatchQueryBuilder catalog_id = QueryBuilders.matchQuery("catalog_id", "5");
//datacatalog 表示数据目录文档(父) true暂时不知作用
HasParentQueryBuilder hasParentQueryBuilder =
new HasParentQueryBuilder("datacatalog",catalog_id , true);
//执行查询 datacatalog表示索引 _doc表示类型
SearchResponse searchResponse = client.prepareSearch("datacatalog")
.setTypes("_doc")
// .addSort("catalog_id", SortOrder.ASC) //排序 ASC DESC
.setQuery(hasParentQueryBuilder)
// .setFrom((currentPage-1) * pageSize)
// .setSize(pageSize) // 设置分页
.execute()
.actionGet();
//遍历结果
SearchHits hits = searchResponse.getHits();
List<Object> result = Lists.newArrayList();
for (SearchHit hit : hits) {
Map<String, Object> source = hit.getSourceAsMap();
result.add(JSON.toJSON(source));
}
result.forEach(a->{
System.out.println(a);
});
}
}
java api 参考来源:
https://blog.csdn.net/lsr40/article/details/102462989
多表查询我最终还是没选择父子文档这种方式,就因为查询结果不能同时返回父子文档,编码时很不方便
另一种多表查询解决方式:
https://blog.csdn.net/weixin_42412601/article/details/103756695