ElasticSearch父子关联使用总结、数据分析使用总结

描述:主要使用ES对大量数据进行关联分析,由于各种原因,使用ES进行关联分析时(即使用ES的agg),需要重新组织下数据,形成父子文档,如分析个人数据,需要将个人数据(网购订单、通话记录、话费记录、房产、失信执行人等等、、、)整理成树形结构存入ES,在开发过程中遇到了各种坑,在这里总结下,希望能帮到大家,文末会附上java代码的工具类,覆盖多种情况,满足学习和开发。

前提:java6+、ElasticSearch5.5.0+、kibana5.5.0+。

以下是遇到的坑:

1、在ES中除了组织成父子文档外,需要分析的字段也可能是一个大PO,这时需要将这个字段的类型设为nested,但是ES默认的一个索引中的nested字段数量有限制,需要手动设置:

PUT aggstest5/_settings
{
    "index": {
       "mapping": {
          "nested_fields": {
            "limit": "100"
          }
        }
    }
}

2、由于数据量较大,但是ES提供的from\size方式查询默认最大查询数量有限,最多多少条来着??所以有需求一次取上千上万条数据需要手动设置一下(请考虑实际内存):

PUT _settings
{
    "index": {
        "max_result_window": "50000"
    }
}

3、常常需要只清空某个索引(库)里的某个type(表)的数据,而不是清除整个索引:

POST aggstest/type1/_delete_by_query?conflicts=proceed
{
  "query": {
    "match_all": {}
  }
}

4、添加子文档时,记得加上routing:

PUT aggstest/parenttype1/1
{
  "a":1
}
PUT aggstest/sontype1/1?routing=1
{
  "b":1
}

接下来是些与业务逻辑相关的坑了

5、时间类型的数据,ES聚合中提供data_range和range,但是有些不太够用,比如分析通话记录时,需要用到yyyy-MM-dd HH:mm:ss格式中的时(mm),或者用到yyyy-MM,或者其他组合,但由于es中时间始终是值类型(long),尽管es包装了下变成date类型,但是有些场景我们更想使用terms的聚合或简单的24小时制的range聚合。那么这里ES也有处理方案,

方案1:即script,可以用来自定义对数据的处理方法,比如计算用户年龄,由于数据中只有出生日期,就需要计算:

{
	"aggtest":{
	  "user": {
	    "age":{
	      "lang":"painless",
	      "script":"params.curYear - doc['ubYear'].value",
	      "params":[
	        {
	          "curYear":2018
	        }
	      ]
	    }
	  }
	}
}

方案2:es是基于luence的,所以也可以使用luence的一些语法,官方网址:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-expression.html

方案3:是不是觉得好麻烦,超级蛋疼,最简单的山寨方法,把日期类型的字段直接拆开,入库的时候多存几个字段,yyyy当成一个数字类型的字段,同理yyyy-MM存一个字符串,HH存一个数字,这样就简单多了,只要不是需求变态,基本够用了,不管是range还是terms都能用。

6、由于关联数据较多经常跨type取数据,将某一个人的信息全部取出来,形成一个大po,例如,现在有用户表user,子表运营商信息mobileinfo,运营商信息的子表通话记录callrecordsd,子表公积金信息funinfos,我要同时把这四张表都查出来:

GET aggstest/user/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "has_child": {
            "type": "fundinfos",
            "query": {
              "match_all": {}
            },
            "inner_hits":{}
          }
        },
        {
          "has_child": {
            "type": "mobileinfo",
            "query": {
              "bool": {
                "should": [
                  {
                    "has_child": {
                      "type": "callrecords",
                      "query": {"match_all": {}},
                      "inner_hits":{}
                    }
                  }
                ]
              }
            },
            "inner_hits":{}
          }
        }
      ]
    }
  }
}
关键在于inner_hits和has_children,其中inner_hits也可以设置from,size!

7、使用agg时,由于关联数据较多经常跨type分析(单表聚合不存在此问题),但是ES仅提供由父到子的聚合,而不提供子到父和子到子的聚合查询,例如,现在有用户表user,子表京东账户jingdonguser,京东账户的子表订单记录jingdongorder,

现在查询每个年龄段的京东总消费是多少

GET aggstest5/user/_search
{
  "size": 0,
  "aggs": {
    "ages": {
      "histogram": {
        "field": "ubYear", //生日年
        "interval": 1  //步长1年
      },
      "aggs": {
        "jingdong": {
          "children": {
            "type": "jingdonguser" //子表京东账户
          },
          "aggs": {
            "order": {
              "children": {
                "type": "jingdongorder"  //京东账户子表京东订单
              },
              "aggs": {
                "NAME": {
                  "sum": {
                    "field": "payAmount"  //对订单金额求和
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

结果是没问题的,但是发现没有,关联关系是通过children操作一层一层往下的,而且es在agg中并不提供parent操作,造成的结果就是,在现有的数据结构中,我不能查询不同订单金额范围是男的多还是女的多,因为我是从订单开始聚合分组,无法关联到parent中的男女信息。

方案1:改es的源码,因为既然有children,那么必定parent是可以实现的,只是由于es设计方案的多方考虑舍弃了这种操作(效率肯定很低)。

方案2:方案1太扎心,那就没办法了,又想要这种功能,现有数据结构又不支持,那就只有自己在内存中实现了,单表聚合和父到子聚合通过ES实现,子到子和子到父自己写方法实现。

接下来就是坑7中方案2遇到的一些问题,其实坑7中的方案2又是另一种解决方法了,又可以延伸出很多东西,java实现的分组聚合:

8、在上述方案2中,需要取大po,而坑6的解决方法有局限性,那就是es提供的from,size的局限不能一次查大量数据,也不适合全量遍历数据,但是我们现在需要取大量的数据和全量遍历,在不考虑内存和分布式的情况下,我单机一次从es中取十万百万条数据使用需要用到es提供的scroll方式读取数据,全量遍历数据也可以使用scroll方式,但是scroll方式貌似不保证准确性:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html

附上上述采坑的java代码:下载代码

PS:代码中,elasticsearchImpl是增删查改的工具,esindexmapping是把po映射到es的工具,

po关系描述需要用到注解,且必须有一个String id作主键,示例如下:

@ClassMeta(name="个人用户")
public class User {
	
	@FieldMeta(name="主键")
	private String id;
	
	@FieldMeta(name="姓名")
	private String userName;
	
	@FieldMeta(name="性别")
	private String userGender;
	
	@FieldMeta(name="民族")
	private String userNation;
	
	...
	
	//关联数据
	//公积金
	@NotMapping //表示es中并没有这个字段,只用来作关系描述
	@Child //是user的儿子
	@RelationshipOne2One //一对一的关系,user只有一个fundinfos
     private FundInfo fundinfos;	
@NotMapping@Child@RelationshipOne2Oneprivate Jingdong jingdonguser;@NotMapping@Child@RelationshipOne2Many //一对多的关系,user可能有多个税务违法信息private List<TaxPerson> taxperson;@NotMapping@Child@RelationshipOne2Many//一对多的关系,user可能有多个失信执行人信息private List<DishonestPerson> dishonestperson;...}
@ClassMeta(name = "公积金账户信息")
public class FundInfo {

    @FieldMeta(name="主键")
    private String id;
    //个人姓名
    @FieldMeta(name="个人姓名")
    private String fundName;
    //登录账号
    @FieldMeta(name="登录账号")
    private String fundAccount;
    @FieldMeta(name="证件号码")
    private String cardNum;
    @FieldMeta(name="证件类型")
    private String cardType;
    @FieldMeta(name="性别")
    private String sex;
    @FieldMeta(name="婚姻状况")
    private String marriage;
    @FieldMeta(name="公积金卡号")
    private String fundNum;
    @FieldMeta(name="缴费基数")
    private Float payBase;
    
    @NotMapping //es中并没有这个字段
    @Child 
    @RelationshipOne2Many //公积金账户信息有多个缴费信息
    private List<FundPayInfo> fundpayinfos;
	
    @NotMapping 
    @Parent //公积金账户的父亲是用户信息
    @RelationshipOne2One //一个公积金只对应一个user
    private User user;


}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值