ES使用遇到的問題

最近在做一個微服務項目,項目中基本用Redis和ES替代了Mysql,Mysql更像是一個備份。但是在開發(fā)中我遇到了一個ES查詢問題——ES的嵌套查詢。項目中存在一對多的關系,使用Mysql只要關聯(lián)查詢就行了,但是改用ES之后一直沒能直接實現(xiàn)我的需求,最后還是沒有解決我的問題,下面就簡單梳理一下。因為最近好幾個小伙伴問怎么搭梯子,所以最后會附上一個搭建梯子教程。
還是使用spring boot來整合ES,我根據(jù)項目的需求自己簡單的創(chuàng)建一個項目,一個一對多的查詢。
首先創(chuàng)建ES一個document(和Mysql的model基本一樣),代碼如下:

@Document(indexName = "team_index",type = "team_type", shards = 2, replicas = 1,createIndex = false)
public class TeamType {
    
    @Id
    @Field(type = FieldType.Keyword, store = true)
    private String id;

    @Field(type = FieldType.Keyword, store = true)
    private Long teamId;

    @Field(type = FieldType.Nested, store = true)
    private List<MemberType> memberTypes;

    @Field(type = FieldType.Keyword, store = true)
    private String skuCode;

    @Field(type = FieldType.Keyword, store = true)
    private String ownerId;

    @Field(type = FieldType.Keyword, store = true)
    private String status;

    @Field(type = FieldType.Integer, store = true)
    private Integer needAmount;
    // 省略部分字段
    ......
}

// MemberType
public class MemberType {

    @Id
    @Field(type = FieldType.Keyword, store = true)
    private String id;
    
    @Field(type = FieldType.Keyword, store = true)
    private String teamId;

    @Field(type = FieldType.Keyword, store = true)
    private String userId;

    @Field(type = FieldType.Boolean, store = true)
    private boolean captain;

    @Field(type = FieldType.Keyword, store = true)
    private String orderId;
    // 省略部分字段
    ......
}

根據(jù)代碼就可以看出在TeamType內(nèi)嵌套了一個MemberType列表,這是一個很簡單的一對多的關系。現(xiàn)在我的需求是這樣的我需要先對TeamType進行一次過濾查詢出滿足條件的對象,然后還需要對TeamType內(nèi)部的MemberType也進行一個過濾。

1、創(chuàng)建索引,添加數(shù)據(jù)

首先我啟動ES和kibana,然后通過調用接口方式創(chuàng)建索引并做好映射關系,當然也可以通過直接操作ES完成,不過既然是項目中使用我覺得自己寫個接口調用一下更方便一點,首先是創(chuàng)建索引:

    public Map<String, Object> addIndex() {
        Map<String,Object> resultMap = new HashMap<>();

        if (elasticsearchTemplate.indexExists("team_index")) {
            elasticsearchTemplate.deleteIndex("team_index");
        }
        elasticsearchTemplate.createIndex("team_index");
        elasticsearchTemplate.putMapping(TeamType.class);
        elasticsearchTemplate.refresh(TeamType.class);

        resultMap.put("success",true);
        resultMap.put("message","創(chuàng)建索引成功");
        return resultMap;
    }

接下來就是將數(shù)據(jù)從數(shù)據(jù)庫的數(shù)據(jù)存放到ES。在數(shù)據(jù)庫中我的Team和Member是分表存放的,二者通過Team的主鍵進行關聯(lián)。而在ES中只有TeamType一個類型,所以查詢從數(shù)據(jù)庫查詢出數(shù)據(jù)后需要自己拼裝一下。因為整個業(yè)務很簡單,封裝這一點也比較簡單,先從數(shù)據(jù)庫查詢所有Team,然后遍歷查詢所有的Member即可(或者使用之前說過的Mybatis一對多查詢),代碼如下:

    public void addDataToES() {
        List<Team> teamList = teamRepository.findAll();

        for(Team team : teamList) {
            List<Member> memberList = memberRepository.queryAllByTeamId(team.getId());
            List<MemberType> memberTypeList = toTypeList(memberList);

            TeamType teamType = new TeamType(team,memberTypeList);
            teamTypeRepository.save(teamType);
            teamTypeRepository.refresh();
        }
    }

現(xiàn)在索引建了、數(shù)據(jù)也存放了,接下來就是查詢

2、使用ES查詢

首先通過使用kibana來查詢,看下ES中的數(shù)據(jù)是否完整,

GET team_index/_search
{
  "query": {
    "match_all": {}
  }
}

因為數(shù)據(jù)比較少,所以就貼提下查詢的結果:

{
  "took": 62,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 2,
    "max_score": 1,
    "hits": [
      {
        "_index": "team_index",
        "_type": "team_type",
        "_id": "2",
        "_score": 1,
        "_source": {
          "id": "2",
          "teamId": 2,
          "memberTypes": [
            {
              "id": "5",
              "teamId": "2",
              "userId": "U22222222",
              "captain": false,
              "orderId": "O888888888",
              "createTime": "2019-07-09T05:18:00.303+0000",
              "updateTime": "2019-07-09T05:18:09.365+0000"
            },
            {
              "id": "4",
              "teamId": "2",
              "userId": "U11111111",
              "captain": false,
              "orderId": "O999999999",
              "createTime": "2019-07-09T05:17:34.585+0000",
              "updateTime": "2019-07-09T05:17:46.820+0000"
            }
          ],
          "skuCode": "8887545454514",
          "ownerId": "U00000001",
          "status": "success",
          "needAmount": 6,
          "createTime": "2019-07-09T05:18:56.112+0000",
          "updateTime": "2019-07-09T05:19:16.234+0000"
        }
      },
      {
        "_index": "team_index",
        "_type": "team_type",
        "_id": "1",
        "_score": 1,
        "_source": {
          "id": "1",
          "teamId": 1,
          "memberTypes": [
            {
              "id": "3",
              "teamId": "1",
              "userId": "U77777777",
              "captain": true,
              "orderId": "O333333333",
              "createTime": "2019-07-09T05:17:15.285+0000",
              "updateTime": "2019-07-09T05:17:25.145+0000"
            },
            {
              "id": "2",
              "teamId": "1",
              "userId": "U88888888",
              "captain": false,
              "orderId": "O222222222",
              "createTime": "2019-07-09T05:16:45.362+0000",
              "updateTime": "2019-07-09T05:16:57.684+0000"
            },
            {
              "id": "1",
              "teamId": "1",
              "userId": "U99999999",
              "captain": false,
              "orderId": "O111111111",
              "createTime": "2019-07-09T05:16:21.608+0000",
              "updateTime": "2019-07-09T05:16:32.736+0000"
            }
          ],
          "skuCode": "9944545412121",
          "ownerId": "U55555555",
          "status": "success",
          "needAmount": 10,
          "createTime": "2019-07-09T05:18:27.576+0000",
          "updateTime": "2019-07-09T05:18:50.830+0000"
        }
      }
    ]
  }
}

查詢結果出來是2個TeamType,現(xiàn)在需求是這樣的:先對TeamType進行一個過濾,然后對其內(nèi)部嵌套的MemberType再進行一個過濾。以上面的查詢結果為例,我要查詢"status"等于"success",然后內(nèi)部的MemberType的"captain"屬性為false。也就是說我期望的結果如下:

{
    "hits": {
        "total": 2,
        "max_score": 1,
        "hits": [
            {
                "_index": "team_index",
                "_type": "team_type",
                "_id": "2",
                "_score": 1,
                "_source": {
                    "id": "2",
                    "teamId": 2,
                    "memberTypes": [
                        {
                            "id": "5",
                            "teamId": "2",
                            "userId": "U22222222",
                            "captain": false,
                            "orderId": "O888888888",
                            "createTime": "2019-07-09T05:18:00.303+0000",
                            "updateTime": "2019-07-09T05:18:09.365+0000"
                        },
                        {
                            "id": "4",
                            "teamId": "2",
                            "userId": "U11111111",
                            "captain": false,
                            "orderId": "O999999999",
                            "createTime": "2019-07-09T05:17:34.585+0000",
                            "updateTime": "2019-07-09T05:17:46.820+0000"
                        }
                    ],
                    "skuCode": "8887545454514",
                    "ownerId": "U00000001",
                    "status": "success",
                    "needAmount": 6,
                    "createTime": "2019-07-09T05:18:56.112+0000",
                    "updateTime": "2019-07-09T05:19:16.234+0000"
                }
            },
            {
                "_index": "team_index",
                "_type": "team_type",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "id": "1",
                    "teamId": 1,
                    "memberTypes": [
                        {
                            "id": "2",
                            "teamId": "1",
                            "userId": "U88888888",
                            "captain": false,
                            "orderId": "O222222222",
                            "createTime": "2019-07-09T05:16:45.362+0000",
                            "updateTime": "2019-07-09T05:16:57.684+0000"
                        },
                        {
                            "id": "1",
                            "teamId": "1",
                            "userId": "U99999999",
                            "captain": false,
                            "orderId": "O111111111",
                            "createTime": "2019-07-09T05:16:21.608+0000",
                            "updateTime": "2019-07-09T05:16:32.736+0000"
                        }
                    ],
                    "skuCode": "9944545412121",
                    "ownerId": "U55555555",
                    "status": "success",
                    "needAmount": 10,
                    "createTime": "2019-07-09T05:18:27.576+0000",
                    "updateTime": "2019-07-09T05:18:50.830+0000"
                }
            }
        ]
    }
}

按照原有想法,先過濾"status"為"success"的,然后內(nèi)部去過濾"memberTypes.captain"為false。但是實際使用并不是這么回事,我使用嵌套查詢嘗試了一下,但是沒有得到期望的結果,查詢?nèi)缦拢?/p>

GET team_index/_search

{
  "query": {
    "bool": {
      "must": [
        {"match": {
          "status": "success"
        }},
        {
          "nested": {
            "path": "memberTypes",
            "query": {
              "match": {
                "memberTypes.captain": false
              }
            }
          }
        }
      ]
    }
  }
}

使用這個查詢出來的結果和不使用嵌套查詢結果是一樣的,當然也可能是因為我數(shù)據(jù)量少的原因。但是嵌套查詢條件確實沒有生效,查詢結果中依然有"captain"為true的數(shù)據(jù)。不知道是不是我的思路有問題,如果有了解ES的可以告知一下,網(wǎng)上找了很久也沒有找到合適的方法。如果根據(jù)結果來推導,會不會是這么一個邏輯,嵌套查詢查找"captain"為false的TeamType,也就是說只要TeamType中的memberTypes有一個滿足"captain"為false,那么就會將這個TeamType返回,而返回的結果是不過濾內(nèi)部的MemberType的。為此我又專門添加了一條TeamType數(shù)據(jù),并將其內(nèi)部的MemberType的"captain"全部設置成true,然后再次查詢,發(fā)現(xiàn)查詢的結果依然是全部數(shù)據(jù),也就是說嵌套的"captain"為false條件沒有生效,所以我的猜想是錯的。
目前完全進入了死胡同,感覺這么簡單的查詢,自己卻沒有辦法解決。最后對查詢的結果在代碼里面對其進行篩選,不過這個方法感覺有點low。 代碼如下:

    public Map<String, Object> queryMembers(Integer teamId) {
        Map<String,Object> resultMap = new HashMap<>();

        NestedQueryBuilder nestedQueryBuilder =
                new NestedQueryBuilder("memberTypes",termQuery("memberTypes.captain",false),ScoreMode.None);

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
                .must(termQuery("teamId",teamId)).must(nestedQueryBuilder);

        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withIndices("team_index")
                .withTypes("team_type")
                .withQuery(boolQueryBuilder)
                .build();

        List<TeamType> teamTypeList = new ArrayList<>();
        Iterable<TeamType> teamTypes = teamTypeRepository.search(searchQuery);
        Iterator<TeamType> iterator = teamTypes.iterator();
        while (iterator.hasNext()) {
            TeamType teamType = iterator.next();
            List<MemberType> memberTypeList = teamType.getMemberTypes();

            teamType.setMemberTypes(memberTypeList.stream().filter(m -> !m.isCaptain()).collect(Collectors.toList()));
            teamTypeList.add(teamType);
        }

        resultMap.put("result",teamTypeList);

        return resultMap;
    }

不知道是不是自己使用查詢的姿勢不正確,但是不管是在java代碼還是在kibana里面操作,都無法得到自己想要的結果,最后只能在代碼中多結果再次進行一次過濾。官方文檔中的說明也不是很清楚,自己看著感覺疑問挺多的。自己網(wǎng)上找了一些嵌套查詢的例子,但是都只給查詢的DSL,并沒有返回相應的結果。
自己也不知道怎么解決了,問了幾個同事也都沒有解決,如果哪位小伙伴對這個問題比較熟悉,希望指點一下,自己現(xiàn)在也不知道到底問題處在哪里了。


另外有好幾個小伙伴問我自建梯子的問題,因為我現(xiàn)在用的GCP服務器在海外(以前在日本,現(xiàn)在我遷到印度了),所以如果自建梯子必須要有海外的服務器,網(wǎng)上有如何薅羊毛的教程,一般都是一年或者送一定金額,具體可以網(wǎng)上百度一下。比如AWS、GCP,但是AWS速度比較慢,GCP的話目前好像國內(nèi)沒法注冊了。你也可以自己買一個小型機,一月好像5刀左右吧。我將搭梯子的教程放到github,我以前在簡書發(fā)過一次AWS的,但是被官方給鎖定了,所以就和這次的項目代碼放我的github了,有需要的可以自取。公眾號如果不能打開github鏈接,建議到簡書查看鏈接。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,665評論 1 32
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,974評論 0 9
  • 今天看到一位朋友寫的mysql筆記總結,覺得寫的很詳細很用心,這里轉載一下,供大家參考下,也希望大家能關注他原文地...
    信仰與初衷閱讀 4,834評論 0 30
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 4,050評論 0 7
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,248評論 0 4

友情鏈接更多精彩內(nèi)容