7、记一次 Elasticsearch 数据结构设计不合理造成的写入超时问题

最近遇到一个需求,大致内容是:要通过 Elasticsearch 存储 A、B 两部分数据,A 是存在重复数据的,需要与 B 进行比较,从而把 A 的重复数据找到并输出到结果文件。

目标很明确,重点就在于设计 Elasticsearch 文档的数据结构了,最初的设计结构是这样的:

{
    "_index":"filter_a_index",
    "_type":"_doc",
    "_id":"xxxxxxxxxxx_yyyyyy_202207",
    "_score":1,
    "_source":{
        "aFlag":1,
        "bFlag":1,
        "comparKey":"xxxxxxxx_yyyyyy_202207",
        "aObj":{
            "aList":{
                "唯一订单号1":"唯一订单号1|xxxxxxxxxxx|yyyyyy|20220701032432|2|4|1000",
                "唯一订单号2":"唯一订单号2|xxxxxxxxxxx|yyyyyy|20220702235959|1|9|1000"
            },
            "update_date":"20220706"
        },
        "bObj":{
            "bList":{
                "唯一订单号1":"唯一订单号1|xxxxxxxxxxx|yyyyyy|20220701032432|2|4|1000"
            },
            "update_date":"20220706"
        }
    }
}

为了方便描述和梳理,我把实际的 pkey、xxxObj、xxxFlag 等这类字段都替换成了 comparKey、aObj、aFlag 说明。

这里,我把"唯一订单号"字段作为 key,其他字段用 | 拼接作为 value,这样就组装成一个 map,如果遇到相同的 comparKey 则会把新的 map 加入到 aList,这样是符合在 ES 存储重复数据的要求,看上去没什么问题。

在执行 Job 任务测试时发现,过段时间日志会一直报 60s 超时,无法写入 ES,如下:

接着,就是漫长的问题排查过程了,从代码是否存在 bug ,到怀疑 ES 集群环境问题,经过反复测试,最终找到了原因,真的是一波三折啊。原因在于,ES 文档的 key 的数量超过了预设值 5W 个,超过这个数量就无法写入ES了,可通过这个语句查看该索引的 mappings:

GET filter_a_index

如图:

使用"唯一订单号"字段当做 key,当 key 的数量一旦超过 5W 限制就无法写入 ES,这就是原因所在了。然后,经过一番分析,本着越简单越好的原则,又把数据结构调整成了这样:

{
    "_index":"filter_a_index",
    "_type":"_doc",
    "_id":"xxxxxxxxxxx_yyyyyy_202207",
    "_score":1,
    "_source":{
        "aFlag":1,
        "bFlag":1,
        "comparKey":"xxxxxxxx_yyyyyy_202207",
        "aObj":[
            "唯一订单号1|xxxxxxxxxxx|yyyyyy|20220701032432|2|4|1000",
            "唯一订单号2|xxxxxxxxxxx|yyyyyy|20220702235959|1|9|1000"    
        ],
        "bObj":[
            "唯一订单号1|xxxxxxxxxxx|yyyyyy|20220701032432|2|4|1000"
        ]
    }
}

到这儿,又遇到了如何定义 aObj 和 bObj 的数据类型问题了。我想要的 aObj 和 bObj 是数组类型,而 ES 支持的数组是数组对象(Nested),而不是数组字符串,也就是说 Nested 类型支持的是这样存储数据:

"aObj":[
    {
        "orderId":"唯一订单号1",
        "mobile":"xxxxxxxxxxx",
        ......
        "payAmount":1000
    },
    {
        "orderId":"唯一订单号2",
        "mobile":"xxxxxxxxxxx",
        ......
        "payAmount":1000
    }
]

于是,我在创建索引时,只能把  aObj 和 bObj 设置成文本类型了,索引创建语句如下:

PUT filter_a_index
{
    "settings": {
       "index": {
    "number_of_shards": "9",
    "number_of_replicas": "0",
    "refresh_interval": "3s",
    "translog": {
      "durability": "async",
      "flush_threshold_size": "1024mb",
      "sync_interval": "120s"
    },
    "merge": {
      "scheduler": {
        "max_thread_count": "2"
      }
    },
    "mapping": {
      "total_fields": {
        "limit": 50000
      }
    }
  }
    },
     "mappings": {
       "properties": { 
           "comparKey": { "type": "keyword" },
           "aObj": { "type": "text" },
           "aFlag": { "type": "long" },
           "bObj": { "type": "text" },
           "bFlag": { "type": "long" }
       }
     }
}

最后,在代码里使用 ES scripts 实现设置 aObj 、bObj 成数组,并存储重复的数据,核心代码逻辑大致如下:

Script script = new Script(ScriptType.INLINE,
         "painless",
          "if(!(ctx._source.aObj instanceof List)){ctx._source.aObj = [];}" +
                 "if(params.aFlag==1){ctx._source.aObj.addAll(params.aObj);}" +
                 "ctx._source.aFlag = params.aFlag",
          params);

UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(comparKey)
        .withAbortOnVersionConflict(false)
        .withRetryOnConflict(100)
        .withRefreshPolicy(RefreshPolicy.IMMEDIATE)
        .withScript(script.getIdOrCode())
        .withScriptedUpsert(true)
        .withParams(script.getParams());

restTemplate.update(updateQueryBuilder.build(),      
                    restTemplate.getIndexCoordinatesFor(XxxxxInfo.class));

最终存储到 ES 实现的效果如下:

为了实现这个需求,可谓是一波三折啊,通过这次教训也明白了 ES 文档对 key 数量的限制,也学到了如何不用数组对象(Nested)而实现保存字符串到数组的方式。ES 脚本确实很强大,值得深入学习了解。吃一堑长一智,下次应该就不会再出现这样的问题了,哈哈哈~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值