ES动态映射导致的线上疑案

一. 事件背景

在凤巢的推广平台中,有对物料进行不同属性进行筛选排序的需求,由于物料的量级很大(单用户千万级物料),并且有根据物料关键词搜索和某些特定值搜索,所以需要一个全文检索的搜索引擎来满足物料筛选的需求。ElasticSearch是一个基于Lucene的搜索服务器,它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。由于它的实时搜索,稳定,可靠,快速,凤巢业务端利用它来实现全文检索的功能。
由于用户的物料是存储在MySql集群的,各种修改编辑操作也是对DB的操作,ES里检索的数据要保持一致性,必须要有数据流进行同步,于是我们就有了这样的一条数据流同步体系:
ES动态映射导致的线上疑案_第1张图片
这一次的新需求需要在fusion模块中进行开发,让它支持新的字段的解析并同步ES,于是这次的问题就是围绕fusion来展开的

二. 案发过程

在线下进行开发的过程中,正好遇到了一些需要在ES里新增的字段,并且需要在消费数据流模块fusion配上这几个字段的映射逻辑与同步规则。其中还涉及到了一些中间的变量,但是不需要新增ES的,于是在映射的配置文件xxx.yaml里也加上了这几个中间变量,但是没有修改ES的schema(因为不想让内存中计算的中间字段占用多余的存储空间)。于是设计完后在线下进行了代码的部署,于是很开心的进行了开发和测试。自己测试无误后,还邀请QA同学帮忙一起测试。等到大家都确认没有了问题之后,我们就开始上线消费数据流的fusion模块了。
但是没想到诡异的事情出现了:线上的数据流全量导入基准刷数据的时候特别慢,一直卡着。而且上线之后,测试了一些字段的数据流同步是正常畅通的。结果到了第二天,由于有事出行了,帮忙处理ES的同学立刻告诉我线上数据流同步出现了问题,并且有大量的重试消费日志打满了很多机器的磁盘。于是立刻回滚了上次上线的代码,数据流的同步渐渐恢复了正常。经过一系列日志的排查,定位到线上出现数据流同步,不断重试的问题为ES里没有加上那几个中间过渡字段。
但是奇怪的地方在于:同样的代码和配置进行上线,线下测试过没有问题,一上线却出了问题,于是立即进行问题的排查,以确保找到根本原因,保证下次上线的顺利。

三. 问题排查

1. 首先定位代码问题
对用于上线打包的代码和线下的代码(包括配置文件)进行了diff,发现代码确实是最新代码,配置也是相应的一致,说明不是有代码漏提上线的问题

2. 查看环境设置问题
进行了ES schema的check。线上出现了ES报错因为确实两个中间过渡字段,那么线下是否也没有那两个过渡字段呢?
在Sense执行查询ES 该倒排索引的映射结构

GET index/_mapping?pretty

很奇怪的事情被发现了,线下测试用户使用的索引别名竟然自己把中间的过渡字段给加上了,非测试用户的索引别名还是没有中间过渡字段的
于是开始查找为啥线下ES 会自己给测试用户的索引别名加上过渡字段,而导致线下的测试没有暴露出问题。

3. 怀疑是线下导入全量数据同步字段给ES了
对于这个问题首先想到的是可能是代码本身进行的操作,因为再上线同步增量的fusion模块之前,线下会有一个导入全量数据的操作。怀疑是在导入过程中,同步ES数据的时候,发现有ES不支持的字段导致给ES动态添加上的。但是我们很快就否定了这个原因。因为在线上尝试过导入部分用户的全量数据,都没有出现自动给用户的索引别名加字段的情况。

4. 怀疑是ES设置不一致的问题
现在怀疑到可能是线下ES有权限加上中间过渡字段,于是代码给自动同步过去,但是线上ES没有权限随便增加字段,于是代码没有同步成功。于是问了下有ES丰富开发经验,fusion模块的创始的大牛前辈,虽然我的想法是不对的,但是他提醒了我ES有动态设置这么一种设置。于是我离开回去查了下官方推荐的权威指南,果然找到了最终原因

5. 查询官方权威指南
权威指南链接
我查阅到了Elasticsearch 的dynamic属性

当 Elasticsearch 遇到文档中以前 未遇到的字段,它用 dynamic mapping 来确定字段的数据类型并自动把新的字段添加到类型映射。
有时这是想要的行为有时又不希望这样。通常没有人知道以后会有什么新字段加到文档,但是又希望这些字段被自动的索引。也许你只想忽略它们。如果Elasticsearch是作为重要的数据存储,可能就会期望遇到新字段就会抛出异常,这样能及时发现问题。

幸运的是可以用 dynamic 配置来控制这种行为 ,可接受的选项如下:
true 动态添加新的字段–缺省
false 忽略新的字段
strict 如果遇到新字段抛出异常
配置参数 dynamic 可以用在根 object 或任何 object 类型的字段上。你可以将 dynamic 的默认值设置为 strict , 而只在指定的内部对象中开启它, 例如:

PUT /my_index
{
    "mappings": {
        "my_type": {
            "dynamic":      "strict", 
            "properties": {
                "title":  { "type": "string"},
                "stash":  {
                    "type":     "object",
                    "dynamic":  true 
                }
            }
        }
    }
}

拷贝为 CURL在 SENSE 中查看 ,如果遇到新字段,对象 my_type 就会抛出异常。
而内部对象 stash 遇到新字段就会动态创建新字段。
使用上述动态映射, 你可以给 stash 对象添加新的可检索的字段:

PUT /my_index/my_type/1
{
    "title":   "This doc adds a new field",
    "stash": { "new_field": "Success!" }
}

拷贝为 CURL在 SENSE 中查看
但是对根节点对象 my_type 进行同样的操作会失败:

PUT /my_index/my_type/1
{
    "title":     "This throws a StrictDynamicMappingException",
    "new_field": "Fail!"
}

拷贝为 CURL在 SENSE 中查看
注意把 dynamic 设置为 false 一点儿也不会改变 _source 的字段内容。 _source 仍然包含被索引的整个JSON文档。只是新的字段不会被加到映射中也不可搜索。

6. 确认问题
为了确认是否是这个问题,于是立即在SENSE上查看了线上和线下的索引schema关于dynamic的设置

GET Index

果然发现线上的设置为”dynamic”:”strict”,线下没有这个设置,但是ES的默认设置是true.于是在线下添加的时候,会将中间字段动态映射到ES,但是到了线上,如果数据流同步过程中ES发现了同步过来的数据里有不认识的字段,就会有拒绝策略导致抛出异常。然后我们的数据流消费模块fusion会进行不断重试,但是一直重试失败,导致重试的日志将磁盘打满,并且消息还一直堆积。到了这里,终于真相大白了.

7. 问题解决
由于线上ES服务的依赖方较多,之前的配置最好是不要轻易修改,于是后来修改了线上ES的schema把那几个中间字段加上了。加上后问题得到解决。

四. 问题感悟

一定要慎重对待环境的配置!一定要慎重对待环境的配置!一定要慎重对待环境的配置!不然像这样的情况,由于线上线下ES的配置不一样,导致了线下测试没有测出问题,把不适应线上环境的代码带上线而出case.线下就是为了检验代码是否能符合预期的在线上运行,如果暂时的修改了一定要修改回来,否则会出现前人挖坑后人踩的这种情况。

你可能感兴趣的:(问题排查)