CSDN话题挑战赛第2期
参赛话题:Java技术分享
引言
优化一 :ES6.8.2 版本父子嵌套文档的三层聚合统计(空间换时间),将父文档需要聚合的字段,在子文档也多存一份,便于高基数的多层聚合
优化二 :ES6.8.2 版本没有Hash字段类型,通过引入插件 mapper-murmur3 插件实现
参考文档 https://developer.aliyun.com/article/801993在高基数下,Hash字段类型的统计更占优势,但注意统计结果的 key 是哈希后的 值
为什么要使用这个技术?
提示:ES的父子嵌套文档(JOIN类型)是分开存储的,但可以保持字段一致,当存在需要分别聚合父文档指定字段,再聚合子文档指定多个字段的时候,可以用空间换时间的方式,将该字段数据在子文档多冗余存一份,当cardinality基数并不算高(未超过1W+)的场景下,有利于实现这种类型需求。
1,该技术的优点:支持超过size=1000 + 的桶聚合需求,支持ES 643 ~ ES 682的统计需求;
2,使用该技术后,既满足业务需求,又未浪费太多存储资源,且该种方式可复用,不受ES版本升级后的关于父子文档关联聚合 功能降低的影响;
如:增强了代码可读性和健壮性
技术案例,实战分享
提示:业务上存在一个需求,数据结构为父 join子文档类型,父文档为一条电商评论的主评论,子文档为该电商评论下的 标签文档,ES字段值和字段名参考如下 模板(template)
PUT _template/test_fuzi_msg
{
"index_patterns": ["test_fuzi*"],
"order" : 0,
"settings": {
"number_of_shards": 1
},
"mappings": {
"_doc": {
"properties": {
"mid": {
"type": "keyword"
},
"createdAt": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"flag": {
"type": "join",
"eager_global_ordinals": true,
"relations": {
"MESSAGE": "COMMENT",
"COMMENT": [
"ASPECT",
"COMMENT"
]
}
},
"connectionId": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
}
> 数据量级
>![在这里插入图片描述] 
原查询在643版本上可用, 需要先统计父文档的 connectionId字段,再统计子文档的aspect字段,再统计每一个aspect标签的 正负面escore字段, 由于整体数据量过多,超过4000W,尽管 父文档的 connectionId 的基数不高,才 4000+ , 但在父子级关联查询存在缺陷的ES 682版本,这种统计相当耗时且 会超时。
ES的查询语句代码
#### 原语句1 ES682不支持统计 include过长
GET c22a603d22d24628a64631370e5e7ea3/_search
{
"size" : 0,
"query" : {
"bool" : {
"must" : [
{
"term" : {
"flag" : {
"value" : "COMMENT",
"boost" : 1.0
}
}
},
{
"range" : {
"datetime" : {
"from" : "2021-11-24 00:00:00",
"to" : "2022-12-23 23:59:59",
"include_lower" : true,
"include_upper" : true,
"format" : "yyyy-MM-dd HH:mm:ss",
"boost" : 1.0
}
}
}
},
"aggregations" : {
"userConnectionIdTerms" : {
"terms" : {
"field" : "connectionId.keyword",
"size" : 200,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"_count" : "desc"
},
{
"_term" : "asc"
}
]
},
"aggregations" : {
"aspectChildren" : {
"children" : {
"type" : "ASPECT"
},
"aggregations" : {
"aspectTerms" : {
"terms" : {
"field" : "aspect2.keyword",
"size" : 173,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"_count" : "desc"
},
{
"_term" : "asc"
}
],
"include" : [
"5G网络使用",
"Breeno(语音助手)",
"GPS",
"HeyTap大会员",
"HeyTap帐号",
"Hyper Boost加速引擎",
"OPPO+/OPPO社区",
"OPPO小游戏",
"OPPO金融",
"OPPO(HeyTap)支付",
"OSIE超清视效",
"TOF",
"USB调试",
"VOOC闪充移动电源",
"WiFi问题",
"logo",
"中框",
"主题商店",
"云服务",
"低质量应用",
"便签",
"信息",
"信息泄露",
"充电",
"充电方式",
"充电温度",
"充电速度",
"免打扰模式",
"全局搜索/下拉搜索",
"全屏多任务",
"兼容性类",
"内置软件其他问题",
"出厂标配(外观/品质)",
"出厂标配(错漏重)",
"分屏",
"加载类",
"升降结构",
"卡座",
"卡顿",
"发热",
"后台清理",
"听筒音效",
"呼吸灯",
"响铃/振动",
"图片/视频/音频类",
"基础ROM其他问题",
"声音效果",
"备份与恢复",
"天气",
"存储",
"安全其他问题",
"定屏",
"定时开关机",
"导航定位",
"导航键",
"屏保",
"屏功能类",
"屏外观类",
"屏幕",
"屏幕录制",
"屏幕背光时间/自动息屏时间",
"屏幕锁定",
"屏组件",
"屏触摸类",
"广告",
"应用分身",
"开关机类不良",
"开发者业务",
"录音",
"微信类",
"快充/闪充",
"快应用",
"快捷控制中心",
"恢复出厂设置",
"息屏时钟",
"悬浮球",
"截屏",
"手势体感",
"手感",
"手机管家",
"手电筒",
"扬声器",
"扬声器音效",
"折叠结构",
"护眼模式/夜间护眼",
"拨号",
"按键",
"按键类",
"接口",
"摄像头",
"支付类",
"收音机",
"数据丢失",
"数据网络其他问题",
"数据网络功能",
"整体外观",
"文件管理",
"新机激活引导",
"无线连接其他问题",
"日历",
"时钟",
"显示类",
"智能侧边栏",
"智能家居",
"智能手环",
"智能解锁",
"来电",
"来电归属地",
"查找手机",
"桌面类",
"游戏中心",
"游戏充值类",
"游戏其他问题",
"游戏内容",
"游戏功能类",
"游戏加速",
"游戏手柄",
"滑动结构",
"电子邮件",
"电池容量",
"电池续航",
"电池质量",
"电话本",
"电量管理",
"登录类",
"相册",
"相机",
"短视频",
"碰划伤",
"积分",
"系统升级异常",
"系统界面美观度",
"红包助手",
"结构其他问题",
"网速",
"耗电",
"耳机转接线",
"耳机音效",
"自带浏览器",
"自拍杆",
"蓝牙耳机",
"蓝牙音箱",
"解锁安全",
"计步类",
"计算器",
"话筒音效",
"语言和输入法",
"负一屏",
"车载闪充",
"软件商店下载/安装",
"软件商店其他问题",
"辅助功能/无障碍功能",
"运营商",
"进液",
"进灰/异物",
"远程守护",
"连接电脑",
"通信其他问题",
"通知中心与状态栏",
"通讯网络类",
"通话",
"配件其他问题",
"重启",
"钱包",
"锁屏杂志",
"闪退",
"阅读/书城",
"防摔",
"音乐播放",
"颜色",
"飞行模式",
"黑屏/自动关机",
"默认"
],
"exclude" : [ ]
},
"aggregations" : {
"escoreTerms" : {
"terms" : {
"field" : "escore",
"size" : 10,
"min_doc_count" : 1,
"shard_min_doc_count" : 0,
"show_term_doc_count_error" : false,
"order" : [
{
"_count" : "desc"
},
{
"_term" : "asc"
}
]
}
}
}
}
}
}
}
}
}
}
GET c22a603d22d24628a64631370e5e7ea3/_search
{
"aggs": {
"connectionId_count": {
"cardinality": {
"field": "connectionId.keyword"
}
}
}
}
结果:
"aggregations": {
"connectionId_count": {
"value": 4356
}
}
优化后语句
#### 方案 >> 使用子文档的connectionId 统计 - 优化HasChild
GET c22a603d22d24628a64631370e5e7ea3/_search
{
"size": 0,
"query": {
"bool": {
"must": [
{
"term": {
"flag": {
"value": "COMMENT",
"boost": 1
}
}
},
{
"range": {
"datetime": {
"from": "2020-11-24 00:00:00",
"to": "2022-12-23 23:59:59",
"include_lower": true,
"include_upper": true,
"format": "yyyy-MM-dd HH:mm:ss",
"boost": 1
}
}
}
]
}
},
"aggregations": {
"aspectChildren": {
"children": {
"type": "ASPECT"
},
"aggregations": {
"aspectFilter": {
"filter": {
"bool": {
"must": [
{
"terms": {
"aspect2.keyword": {
"index": "aspect_keyword_slow_es7",
"type": "_doc",
"id": "5",
"path": "aspect"
}
}
}
]
}
},
"aggs": {
"userConnectionIdTerms": {
"terms": {
"field": "connectionId.keyword",
"size": 100000,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"collect_mode": "breadth_first",
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_term": "asc"
}
]
},
"aggregations": {
"aspectTerms": {
"terms": {
"field": "aspect2.keyword",
"size": 126,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"collect_mode": "breadth_first",
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_term": "asc"
}
]
},
"aggregations": {
"escoreTerms": {
"terms": {
"field": "escore",
"size": 4,
"execution_hint": "map",
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": false,
"order": [
{
"_count": "desc"
},
{
"_term": "asc"
}
]
}
}
}
}
}
}
}
}
}
}
}
}
其中filter部分的多个标签名,单独储存一个索引,使用数组类型;
"terms": {
"aspect2.keyword": {
"index": "aspect_keyword_slow_es7",
"type": "_doc",
"id": "5",
"path": "aspect"
}
}
### 1. 这里使用了模板_template
### 2. 利用ES的高效关联查询方式 look up
### 3. 统计结果测试 16G 8核的 两台 ES682版本的集群,测试 4300W数据的 结果为 5秒左右
PUT _template/aspect_keyword
{
"index_patterns": ["aspect_keyword*"],
"order" : 0,
"settings": {
"number_of_shards": 1
},
"mappings": {
"_doc": {
"properties": {
"aspect": {
"type": "keyword"
},
"createdAt": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
PUT aspect_keyword
GET aspect_keyword/_mapping
PUT aspect_keyword_test/_doc/4
{
"createdAt": "2022-09-29 17:10:00",
"aspect":"USB调试"
}
PUT aspect_keyword_slow_es7/_doc/5
{
"createdAt": "2022-10-15 17:10:00",
"aspect": [
"音质好坏",
"佩戴舒适程度",
"做工",
"电池续航",
"外观颜值",
"物流速度",
"性价比",
"主动降噪效果",
"服务态度",
"价格",
"噪音",
"佩戴稳定性",
"颜色",
"长时间佩戴舒适性",
"蓝牙稳定性",
"蓝牙连接速度",
"包装精美度",
"打假",
"重量尺寸",
"耳机品质",
"物流包装完好性",
"其他延迟",
"材质",
"通话效果",
"耳机续航",
"耳帽",
"隔音效果",
"耳机外观颜值",
"退换货",
"运动佩戴效果",
"充电速度",
"响应时间",
"触控操作",
"游戏延迟",
"电影/游戏效果",
"品控",
"便携程度",
"音量",
"耳机适配性",
"耐脏性",
"充电盒续航",
"音量调节",
"漏音",
"充电线/充电口",
"APP",
"智能功能",
"磁吸开关",
"操作功能",
"佩戴重量",
"弹窗快速配对连接",
"耳机端操作",
"耳机充电速度",
"环境声模式",
"按键操作",
"充电盒外观颜值",
"产品熟悉程度",
"左右音量差异",
"独立使用",
"电量查看",
"音频编码协议",
"提示效果",
"风噪",
"耐磨损程度",
"语音助手体验",
"听诊器效应",
"多个设备连接",
"蓝牙配对速度",
"防水性",
"人声降噪效果",
"无线充电",
"种草/明星代言",
"空间音频",
"充电闪光灯指示",
"左右电量差异",
"拿取&盒子操作",
"智能降噪",
"配件缺失",
"听歌、看视频延迟",
"通话降噪",
"生态",
"电量显示",
"按键品质",
"APP兼容性",
"OTA升级",
"说明书",
"充电盒充电速度",
"降噪耳压",
"操作自定义",
"发声单元",
"录音",
"低音降噪效果",
"软件功能",
"材质类型",
"产品防伪验真",
"定位",
"低电量提示",
"降噪可调性",
"降噪开关",
"杜比音效",
"来电提醒",
"发热",
"hi_res/小金标",
"耳机发热",
"降噪稳定性",
"充电盒发热"
]
}
难点分析
提示:分析技术难点,通俗易懂的表达。
1、 父子文档的嵌套类型join本就存在一些问题,父子文档分开存储,关联的统计支持的量级很低,经过测试,上述语句最多支持 100Size的桶聚合;
2、当前业务的复杂程度在于,每篇文章的connnectionId基数并不高,所以统计上使用 keyword类型理论上应该可以完全支撑 ,但前提是最好在同一个索引中存储,才能比较好的支撑此种统计需求 ;
3、所以在子文档中,也存储父文档的对应 connnectionId,使得所有的统计聚合需求都在子文档中实现;
4、在现有业务基础上,优化长 terms查询,例如使用look up查询; 优化广度聚合,使用 “execution_hint”: "map"等方式,综合优化该调用接口;
技术小结
提示:学到了什么技术
在次优化过程中,主要弄清楚了ES对父子级结构支撑的有限性,通过空间换时间的方案,去解决通用性问题,且有尝试TEXT类型的fielddata=true属性;有尝试基于高基数的 Hash类型字段的处理方式
mapper-murmur3 插件实践一把
第一步:插件安装
bin/elasticsearch-plugin install mapper-murmur3
第二步:导入Demo测试
PUT my_index
{
"mappings": {
"properties": {
"my_field": {
"type": "keyword",
"fields": {
"hash": {
"type": "murmur3"
}
}
}
}
}
}
提示:能够应用到什么场景等等。
主要应用于需要使用父结构的字段作为第一层桶,再来统计子结构文档的需求;