ステージ 8: 高度なサービス フレームワーク (第 6 章: ElasticSearch3)

ステージ 8: 高度なサービス フレームワーク (第 6 章: ElasticSearch3)

第 6 章:ElasticSearch

分散型検索エンジン 3

ここに画像の説明を挿入します

0.学習目標

ここに画像の説明を挿入します

1.データの集計

集約により、非常に便利に実装できるようになります。データの統計、分析、計算例えば:

  • 最も人気のある携帯電話のブランドは何ですか?
  • これらの携帯電話の平均価格、最高価格、最低価格はいくらですか?
  • これらの携帯電話の月間売上はいくらですか?

  これらの統計関数を実装する方がデータベースよりもはるかsqlに便利で、クエリ速度が非常に速く、リアルタイムの検索効果を実現できます。

1.1.集計の種類

集約には一般的に 3 つのタイプがあります。

  • バケットの集約:文書をグループ化し、各グループの数を数えるために使用されます。

    • TermAggregation:によるとドキュメントフィールドの値ブランド価値別のグループ化、国別のグループ化などのグループ化
    • Date Histogram:によると日付はしごグループ化(週に 1 グループ、月に 1 グループなど)

    集計フィールドはセグメント化されていません

  • メトリクスの集計: 以前はいくつかの値を計算する、最大値、最小値、平均値など。

    • Avg: 平均値
    • Max: 最大値を求める
    • Min: 最小値を見つける
    • Stats: 最大値、最小値、平均値、合計値などを同時に検索します。
  • パイプライン集約:他の集計結果に基づいて集計する

知らせ: 集計に参加するフィールドは、、、、、である必要がありkeywordます日期数值布尔类型;
(つまり、集計に参加するフィールドはすべてセグメント化できないフィールドです)

ここに画像の説明を挿入します

1.2. DSL はアグリゲーションを実装します

  ここで、すべてのデータに含まれるホテルのブランドの種類を数えたいと考えていますが、実際にはブランドに従ってデータをグループ化しています。このとき、ホテルのブランド名、つまりBucketアグリゲーションをもとに集計することも可能です。

1.2.1.Bucket集計構文 (バケット集計)

構文は次のとおりです。

GET /hotel/_search
{
    
    
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": {
    
     // 定义聚合
    "brandAgg": {
    
     //给聚合起个名字,随便起;
      "terms": {
    
     // 聚合的类型,按照字段值聚合,所以选择term,代表TermAggregation,按照文档字段值分组
        "field": "brand", // 参与聚合的字段
        "size": 20 // 希望获取的聚合结果数量
      }
    }
  }
}

その上重要なのは次のことです:集計名、集計タイプ、フィールド値;

    "brandAgg": {
    
     //给聚合起个名字,随便起;
      "terms": {
    
     // 聚合的类型,按照字段值聚合,所以选择term
        "field": "brand", // 参与聚合的字段

結果は以下のようになります。
ここに画像の説明を挿入します

1.2.2.集計結果のソート

  デフォルトでは、Bucket集計Bucketでは、 で示されるドキュメントの数がカウントされ_count、次のようになります。_count降順で並べ替え

  order 属性を指定して、集計の並べ替え方法をカスタマイズできます。

GET /hotel/_search
{
    
    
  "size": 0,  // 没置size为0,结果中不包含文档,只包含聚合结果
  "aggs": {
    
       // 定义聚合
    "brandAgg": {
    
       //给聚合起个名字,随便起;
      "terms": {
    
       // 聚合的类型,按照字民值聚合,所以选择term,代表TermAggregation,按照文档字段值分组
        "field": "brand",   // 参与案合的字段
        "order": {
    
    
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20   // 希望获取的聚合结果数量;
      }
    }
  }
}

1.2.3.集計範囲を制限する

  デフォルトでは、バケット集計はインデックス データベース内のすべてのドキュメントを集計しますが、実際のシナリオでは、ユーザーが検索条件を入力することになるため、集計は検索結果の集計である必要がありますそれで集計には修飾が必要です

我々はできる集約するドキュメントの範囲を制限するquery条件を追加するだけです。

GET /hotel/_search
{
    
    
  "query": {
    
    
    "range": {
    
    
      "price": {
    
    
        "lte": 200 // 只对200元以下的文档聚合
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    
    
    "brandAgg": {
    
    
      "terms": {
    
    
        "field": "brand",
        "size": 20
      }
    }
  }
}

今回、集約されたブランドの数は大幅に減少しました。
ここに画像の説明を挿入します

1.2.4.Metric集計構文 (メトリック集計)

  上では、ホテルをブランド別にグループ化し、バケットを形成しました。次に、バケット内のホテルについて計算を行う必要があります。ブランドユーザー評価minmaxavg同等の値を取得します

  これには、統計集計などの集計を使用する必要があります。 、などの結果Metricを取得できます。minmaxavg

構文は次のとおりです。

GET /hotel/_search
{
    
    
  "size": 0, 
  "aggs": {
    
     //定义聚合
    "brandAgg": {
    
       //给聚合起个名字,随便起
      "terms": {
    
       //terms聚合
        "field": "brand", 
        "size": 20  //希望获取的聚合结果数量
      },
      "aggs": {
    
     // 是brands聚合的子聚合,也就是分组后对每组分别计算
        "score_stats": {
    
     // 聚合名称
          "stats": {
    
     // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

  この集約は、集約内にネストされたサブ集約score_statsです各バケットで個別に計算する必要があるためです。brandAgg
ここに画像の説明を挿入します

  さらに、たとえば、各バケットのホテルの平均スコアによって集計結果を並べ替えることもできます。
ここに画像の説明を挿入します

1.2.5.まとめ

aggs集約を表しますが、query同级このqueryときの役割は何でしょうか?

  • 集約するドキュメントの範囲を制限する

集計には次の 3 つの要素が必要です。

  • アグリゲーション名
  • 集計タイプ
  • 集計フィールド

集約の構成可能なプロパティは次のとおりです。

  • size:集計結果の件数を指定
  • order:集計結果のソート方法を指定します
  • field:集計フィールドを指定します

ここに画像の説明を挿入します

1.3. RestAPI は集計を実装します

1.3.1.API 構文

集計条件そしてquery状態同じレベルで、これを使用してrequest.source()集計条件を指定する必要があります。
集計条件の構文:
ここに画像の説明を挿入します
集計結果もクエリ結果とは異なり、APIも特殊です。ただし、同じ JSON がレイヤーごとに解析されます。
ここに画像の説明を挿入します

1.3.2.ビジネスニーズ

ケース: ブランド、都市、星の評価の集約を実現する IUserService のメソッドを定義する 要件
: 検索ページ上のブランド、都市、およびその他の情報はページ上でハードコーディングされるべきではなく、ホテル データを集約することによって取得される必要があります。インデックスライブラリ:
ここに画像の説明を挿入します

分析:
  現在、ページ上の都市リスト、スターリスト、ブランドリストはハードコーディングされており、検索結果によって変更されません。ただし、ユーザーの検索条件が変化すると、検索結果もそれに応じて変化します。

  たとえば、ユーザーが「東方明珠塔」を検索した場合、検索されるホテルは上海の東方明珠塔の近くでなければなりません。したがって、都市は上海のみとなります。現時点では、都市リストには北京などの情報は表示されません。 、深セン、杭州。

  つまり、検索結果に含まれる都市、ページに表示される都市、検索結果に含まれるブランド、ページに表示されるブランド

  検索結果にどのブランドが含まれているかを確認するにはどうすればよいですか? 検索結果にどの都市が含まれているかを確認するにはどうすればよいですか?

  集計関数とバケット集計を使用して、ブランドと都市に基づいて検索結果内のドキュメントをグループ化すると、どのブランドとどの都市が含まれているかを知ることができます。

  検索結果が集計されるため、集計はスコープ付き集計つまり、集計条件と文書検索条件が一致します。

  ブラウザを見ると、フロントエンドが実際にそのようなリクエストを発行していることがわかります。
ここに画像の説明を挿入します
リクエストのパラメータは、検索ドキュメントのパラメータとまったく同じです

戻り値のタイプは、ページに表示される最終結果です。
ここに画像の説明を挿入します
結果はMap構造体です。

  • key文字列、都市、星評価、ブランド、価格です
  • value複数の都市の名前などのコレクションです

1.3.3.ビジネスの実現

ここに画像の説明を挿入します

次の要件を持つメソッドをcn.itcast.hotel.webパッケージに追加します。HotelController

  • リクエスト方法:POST
  • リクエストパス:/hotel/filters
  • リクエストパラメータ: RequestParams、ドキュメント検索のパラメータと一致
  • 戻り値の型:Map<String, List<String>>

コード:

    @PostMapping("filters")
    public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
    
    
        return hotelService.getFilters(params);
    }

ここで呼び出されるメソッドはまだ実装されていIHotelService中ませgetFiltersん。

cn.itcast.hotel.service.IHotelService新しいメソッドを次のように定義します

Map<String, List<String>> filters(RequestParams params);

このメソッドを次のように実装しますcn.itcast.hotel.service.impl.HotelService

@Override
public Map<String, List<String>> filters(RequestParams params) {
    
    
    try {
    
    
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.query只限定范围
        buildBasicQuery(params, request);
        // 2.2.设置size
        request.source().size(0);
        // 2.3.聚合
        buildAggregation(request);
        // 3.发出请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Map<String, List<String>> result = new HashMap<>();
        Aggregations aggregations = response.getAggregations();
        // 4.1.根据品牌名称,获取品牌结果
        List<String> brandList = getAggByName(aggregations, "brandAgg");
        result.put("品牌", brandList);
        // 4.2.根据品牌名称,获取品牌结果
        List<String> cityList = getAggByName(aggregations, "cityAgg");
        result.put("城市", cityList);
        // 4.3.根据品牌名称,获取品牌结果
        List<String> starList = getAggByName(aggregations, "starAgg");
        result.put("星级", starList);

        return result;
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

private void buildAggregation(SearchRequest request) {
    
    
    request.source().aggregation(AggregationBuilders
                                 .terms("brandAgg")
                                 .field("brand")
                                 .size(100)
                                );
    request.source().aggregation(AggregationBuilders
                                 .terms("cityAgg")
                                 .field("city")
                                 .size(100)
                                );
    request.source().aggregation(AggregationBuilders
                                 .terms("starAgg")
                                 .field("starName")
                                 .size(100)
                                );
}

private List<String> getAggByName(Aggregations aggregations, String aggName) {
    
    
    // 4.1.根据聚合名称获取聚合结果
    Terms brandTerms = aggregations.get(aggName);
    // 4.2.获取buckets
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 4.3.遍历
    List<String> brandList = new ArrayList<>();
    for (Terms.Bucket bucket : buckets) {
    
    
        // 4.4.获取key
        String key = bucket.getKeyAsString();
        brandList.add(key);
    }
    return brandList;
}

ここに画像の説明を挿入します

2.オートコンプリート_

ここに画像の説明を挿入します

ユーザーが検索ボックスに文字を入力すると、図に示すように、その文字に関連する検索語が表示されるようにする必要があります。
ここに画像の説明を挿入します
ユーザーが入力した文字に基づいて完全な入力を促すこの機能は、自動補完です。

ピンイン文字に基づいて推測する必要があるため、ピンイン単語分割機能が使用されます。

2.1.ピンイン単語セグメンター

  文字に基づいて完成させるには、文書をピンインに従って分割する必要があります。GitHub にはピンイン単語セグメンテーション プラグインがありelasticsearchます。アドレス: https://github.com/medcl/elasticsearch-analysis-pinyin
ここに画像の説明を挿入します

プレコース資料には、ピンイン単語セグメンターのインストール パッケージも提供されています。
ここに画像の説明を挿入します
インストール方法は IK 単語セグメンターと同じで、次の 3 つのステップに分かれています。

①解凍②仮想マシン内のディレクトリ
アップロード ③再起動④テストelasticsearchplugin
elasticsearch

詳細なインストール手順については、「IK ワード セグメンタのインストール プロセス」を参照してください。

テストの使用方法は次のとおりです。

POST /_analyze
{
    
    
  "text": "如家酒店还不错",  #要分词的内容;
  "analyzer": "pinyin"   #分词器
}

結果:
ここに画像の説明を挿入します

2.2.カスタム単語セグメンター

  デフォルトのピンイン トークナイザーは、各中国語の文字をピンインに分割します。ここで必要なのは、各エントリがピンインのグループを形成することなので、ピンイン トークナイザーにいくつかの変更を加える必要があります。パーソナライズされたカスタマイズ、形状カスタムトークナイザー

elasticsearch中央の単語セグメンタ ( analyzer) は 3 つの部分で構成されます。

  • character filters:tokenizerテキストを前に処理します。たとえば、文字を削除したり、文字を置き換えたりします。
  • tokenizer: テキストを一定のルールに従って単語に切り取ります ( term)。たとえばkeyword、単語の分割はありません。ik_smart
  • tokenizer filter:tokenizer出力エントリをさらに処理します。たとえば、大文字小文字の変換、同義語の処理、ピンインの処理などです。

ドキュメントがセグメント化されている場合、ドキュメントは次の 3 つの部分によって順番に処理されます。
ここに画像の説明を挿入します

インデックス ライブラリの作成時の設定を通じてカスタム アナライザー (単語セグメンター) を構成できます。:
カスタム トークナイザーを宣言するための構文は次のとおりです。

PUT /test   //创建名为test的索引库
{
    
    

  "settings": {
    
       //定义索引库的分词器的
    "analysis": {
    
    
    
      "analyzer": {
    
     // 自定义分词器
        "my_analyzer": {
    
      // 分词器名称
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      
      "filter": {
    
     // 自定义tokenizer filter
        "py": {
    
     // 过滤器名称
          "type": "pinyin", // 过滤器类型,这里是pinyin
		  "keep_full_pinyin": false,      //解决单个字拼的问题;
          "keep_joined_full_pinyin": true,      //全拼
          "keep_original": true,      //保留中文
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
      
    }
  },



  "mappings": {
    
          //mappings映射时
    "properties": {
    
    
      "name": {
    
    
        "type": "text",
        "analyzer": "my_analyzer",      //name使用自定义的my_analyzer分词器;analyzer在创建索引时使用
        "search_analyzer": "ik_smart"      //search_analyzer在搜索索引时使用;
      }
    }
  }
}

テスト: (結果にはピンインと中国語の文字が含まれます)
ここに画像の説明を挿入します

要約:

ピンイントークナイザーの使用方法?

  • pinyinワードセグメンターをダウンロードする
  • elasticsearch②解凍してディレクトリplugin置きます
  • ③再起動

トークナイザーをカスタマイズするにはどうすればよいですか?

  • ① インデックス ライブラリを作成するときは、settings次の 3 つの部分を含めることができます。
  • character filter
  • tokenizer
  • filter

ピンイン単語セグメンターを使用する際に注意すべき点は何ですか?

  • 同音異義語の検索を避けるには、インデックスの作成時にピンイン トークナイザーを使用し、検索時にはピンイン トークナイザーを使用しないでください。

2.3.オートコンプリートクエリ

elasticsearchCompletion Suggesterクエリは自動補完機能を実装するために提供されていますこのクエリは、ユーザーが入力した語で始まる用語を照合して返します。補完クエリの効率を向上させるために、ドキュメント内のフィールドのタイプにはいくつかの制約があります。

  • 完了クエリに参加するフィールドはcompletion次のタイプである必要があります。
  • フィールドの内容は通常、補完に使用される複数のエントリで形成された配列です。

たとえば、次のようなインデックス ライブラリです。

// 创建索引库
PUT test
{
    
    
  "mappings": {
    
    
    "properties": {
    
    
      "title":{
    
    
        "type": "completion"
      }
    }
  }
}

次に、次のデータを挿入します。

// 示例数据
POST test/_doc
{
    
    
  "title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
    
    
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
    
    
  "title": ["Nintendo", "switch"]
}

クエリ DSL ステートメントは次のとおりです。

// 自动补全查询
GET /test/_search
{
    
    
  "suggest": {
    
    
    "title_suggest": {
    
       //随意起的名称;
      "text": "s", // 关键字
      "completion": {
    
          //自动补全的类型
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

まとめ:
ここに画像の説明を挿入します

2.4.ホテル検索ボックスの自動補完を実装する

  現在、hotelインデックス ライブラリにはピンイン単語セグメンターが設定されていないため、インデックス ライブラリの構成を変更する必要があります。でも私たちは知っていますインデックス ライブラリは変更できません。削除して再作成することのみが可能です。

  さらに、自動補完用のフィールドを追加し、そこにブランド、提案、都市などを自動補完プロンプトとして入力する必要があります。

要約すると、私たちはしなければならないことには以下が含まれます

  1. ホテル インデックス データベース構造を変更し、カスタム ピンイン単語セグメンターをセットアップする

  2. インデックス ライブラリの名前とすべてのフィールドを変更し、カスタムの単語セグメンターを使用します。

  3. インデックス ライブラリは、新しいフィールド候補を追加します。そのタイプは補完タイプで、カスタムの単語セグメンターを使用します。

  4. ブランドとビジネスを含む提案フィールドを HotelDoc クラスに追加します

  5. ホテルのデータベースにデータを再インポートする

2.4.1.ホテルのマッピング構造を変更する

コードは以下のように表示されます。

// 酒店数据索引库
PUT /hotel
{
    
    



  "settings": {
    
          //定义分词器
    "analysis": {
    
    
    
      "analyzer": {
    
    
      
        "text_anlyzer": {
    
        //全文检索使用的分词器
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
    
          //自动补全使用的分词器
          "tokenizer": "keyword",
          "filter": "py"
        }
        
      },
      
      "filter": {
    
    
        "py": {
    
    
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
      
    }
  },



  "mappings": {
    
    
    "properties": {
    
    
      "id":{
    
    
        "type": "keyword"
      },
      "name":{
    
    
        "type": "text",
        "analyzer": "text_anlyzer",      //创建索引时使用的分词器
        "search_analyzer": "ik_smart",      //搜索时的分词器
        "copy_to": "all"
      },
      "address":{
    
    
        "type": "keyword",
        "index": false
      },
      "price":{
    
    
        "type": "integer"
      },
      "score":{
    
    
        "type": "integer"
      },
      "brand":{
    
    
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
    
    
        "type": "keyword"
      },
      "starName":{
    
    
        "type": "keyword"
      },
      "business":{
    
    
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
    
    
        "type": "geo_point"
      },
      "pic":{
    
    
        "type": "keyword",
        "index": false
      },
      "all":{
    
    
        "type": "text",
        "analyzer": "text_anlyzer",      //创建索引时使用的分词器"text_anlyzer"
        "search_analyzer": "ik_smart"      //搜索时使用的分词器"ik_smart"
      },
      "suggestion":{
    
          //自动补全的字段
          "type": "completion",
          "analyzer": "completion_analyzer"   //分词器;
      }
    }
  }
}

2.4.2.HotelDoc エンティティを変更する

  HotelDoc自動入力するにはフィールドを追加する必要があります。コンテンツにはホテルのブランド、都市、ビジネス地区などの情報を含めることができます。オートコンプリート フィールドの要件によれば、これらのフィールドの配列であることが最善です。

  したがって、タイプのフィールドHotelDoc追加し、その中に、などの情報を入れます。suggestionList<String>brandcitybusiness

コードは以下のように表示されます。

package cn.itcast.hotel.pojo;

@Data
@NoArgsConstructor
public class HotelDoc {
    
    
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
    
    
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        // 组装suggestion
        if(this.business.contains("/")){
    
    
            // business有多个值,需要切割
            String[] arr = this.business.split("/"); //数组;
            // 添加元素
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr); //批量添加,将数组中的元素一个一个的添加进集合;
        }else {
    
    
            this.suggestion = Arrays.asList(this.brand, this.business); //集合
        }
    }
}

ここに画像の説明を挿入します

2.4.3.再インポート

以前に作成したデータのインポート関数を再実行すると、新しいホテル データに提案が含まれていることがわかります。
ここに画像の説明を挿入します

2.4.4.オートコンプリートクエリ用のJavaAPI

以前は、自動クエリ完了用の DSL について学習しましたが、対応する Java API については学習していませんでした。例を次に示します。
ここに画像の説明を挿入します

上記の完了クエリのフィールドは独自のものとして記述する必要があります。
ここに画像の説明を挿入します

自動補完の結果も非常に特殊で、解析されたコードは次のとおりです。
ここに画像の説明を挿入します

2.4.5.検索ボックスの自動補完を実装する

フロントエンド ページを見ると、入力ボックスに入力すると、フロントエンドがajaxリクエストを開始することがわかります。
ここに画像の説明を挿入します
戻り値は、次のタイプの完全な用語のコレクションです。List<String>

1)新しいリクエストを受信するために、cn.itcast.hotel.webパッケージの下に新しいインターフェイスを追加します。HotelController

@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
    
    
    return hotelService.getSuggestions(prefix);
}

2)cn.itcast.hotel.serviceパッケージIhotelServiceの下にメソッドを追加します。

List<String> getSuggestions(String prefix);

3) 次cn.itcast.hotel.service.impl.HotelServiceの場所にメソッドを実装します。

@Override
public List<String> getSuggestions(String prefix) {
    
          //prefix是前端传过来的参数,是关键字
    try {
    
    
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
            "suggestions",
            SuggestBuilders.completionSuggestion("suggestion")   //补全字段
            .prefix(prefix)  //前段传过来的参数,是关键字
            .skipDuplicates(true)
            .size(10)
        ));
        // 3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Suggest suggest = response.getSuggest();
        // 4.1.根据补全查询名称,获取补全结果
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        // 4.2.获取options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        // 4.3.遍历
        List<String> list = new ArrayList<>(options.size());
        for (CompletionSuggestion.Entry.Option option : options) {
    
    
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

3.データの同期

  elasticsearchホテルのデータはmysqlデータベースから取得されるため、mysqlデータが変更されると、データelasticsearchも変更する必要があります。これは、ホテルelasticsearch間のデータ同期です。mysql
ここに画像の説明を挿入します

3.1. アイデア分析

一般的なデータ同期ソリューションは 3 つあります。

  • 同期呼び出し
  • 非同期通知
  • モニターbinlog

3.1.1.同期呼び出し

オプション 1: 同期呼び出し
ここに画像の説明を挿入します

基本的な手順は次のとおりです。

  • hotel-demoelasticsearchデータを変更するための外部インターフェイスを提供します。
  • ホテル運営業務は完了しつつありますデータベース操作その後、hotel-demo提供されたインターフェイスを直接呼び出し、

3.1.2.非同期通知

オプション 2: 非同期通知
ここに画像の説明を挿入します

プロセスは次のとおりです。

  • hotel-adminmysqlデータベースのデータを追加、削除、変更した後、MQメッセージを送信します
  • hotel-demoMQメッセージ受信後のelasticsearchデータ変更を監視して完了する

3.1.3.モニターbinlog

オプション 3: 監視binlog
ここに画像の説明を挿入します
プロセスは次のとおりです。

  • 機能mysqlをオンにするbinlog
  • mysql完了した追加、削除、変更操作はbinlog
  • hotel-democanal監視の変化に基づいてbinlogelasticsearchコンテンツはリアルタイムに更新されます

3.1.4.選択

方法 1: 同期呼び出し

  • 利点: 実装が簡単、粗雑
  • 短所: 高度なビジネス結合

方法 2: 非同期通知

  • 利点: 低カップリング、平均的な実装難易度
  • 短所: mq の信頼性に依存する

方法 3: モニタリングbinlog

  • 利点: サービスを完全に分離
  • 短所: 開くとbinlogデータベースの負担が増加し、実装がより複雑になります

3.2.データ同期の実装

MQ実装mysqlelasticsearchデータ同期を利用する

3.2.1. アイデア

  プレコース資料で提供されるプロジェクトをhotel-adminホテル管理用のマイクロサービスとして使用します。ホテル データを追加、削除、または変更する場合は、elasticsearchセンター内のデータに対して同じ操作を完了する必要があります。

ステップ:

  • プレコース資料で提供されるプロジェクトをインポートしhotel-admin、ホテル データを開始してテストします。CRUD
  • exchange(スイッチ)、queue(キュー)、を宣言します。RoutingKey
  • hotel-adminビジネスの追加、削除、変更を行ってメッセージの送信を完了します。
  • hotel-demoでメッセージ監視を完了し、elasticsearchデータを更新します
  • データ同期機能を開始してテストする

3.2.2.インポートデモ

授業前の資料で提供されたプロジェクトをインポートしますhotel-admin
ここに画像の説明を挿入します
実行後、次のサイトにアクセスします。http://localhost:8099
ここに画像の説明を挿入します

ホテルの機能が含まれますCRUD
ここに画像の説明を挿入します

3.2.3.スイッチとキューの宣言

MQ構造は図に示すとおりです。
ここに画像の説明を挿入します
チュートリアルでは、スイッチとキューはhotel-demoコンシューマで宣言されます。

1)依存関係を導入する

導入されたhotel-admin依存関係:hotel-demorabbitmq

<!--amqp-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

1)設定されたamqpアドレス

.ymlファイルに設定されたアドレスamqp:
ここに画像の説明を挿入します

2)キュースイッチ名の宣言

hotel-adminおよびhotel-demoパッケージcn.itcast.hotel.constatntsの下に新しいクラスを作成しますMqConstants

package cn.itcast.hotel.constatnts;

    public class MqConstants {
    
    
    /**
     * 交换机
     */
    public final static String HOTEL_EXCHANGE = "hotel.topic";
    /**
     * 监听新增和修改的队列
     */
    public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
    /**
     * 监听删除的队列
     */
    public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
    /**
     * 新增或修改的RoutingKey
     */
    public final static String HOTEL_INSERT_KEY = "hotel.insert";
    /**
     * 删除的RoutingKey
     */
    public final static String HOTEL_DELETE_KEY = "hotel.delete";
}

3)キュースイッチの宣言 (スイッチキュー、routingKey バインディング関係を定義)

hotel-democn.itcast.hotel.configパッケージの下に設定クラスを定義しMqConfig、キューとスイッチを宣言します。

package cn.itcast.hotel.config;

@Configuration
public class MqConfig {
    
    
    @Bean
    public TopicExchange topicExchange(){
    
          //交换机
        return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false); //true代表持久化
    }

    @Bean
    public Queue insertQueue(){
    
          //增加和修改的队列
        return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
    }

    @Bean
    public Queue deleteQueue(){
    
          //删除的队列;
        return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
    }

    @Bean
    public Binding insertQueueBinding(){
    
       //绑定关系;
    //insertQueue()队列绑定到topicExchange()交换机,使用MqConstants.HOTEL_INSERT_KEY这个RoutingKey
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY); 
    }

    @Bean
    public Binding deleteQueueBinding(){
    
       //绑定关系;
       //deleteQueue()队列绑定到topicExchange()交换机,使用MqConstants.HOTEL_DELETE_KEY这个RoutingKey
        return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
    }
}

3.2.4.MQメッセージの送信

hotel-demo一般的なcn.itcast.hotel.constatntsパッケージの  下には、キュースイッチ名の宣言さまざまな名前を使用するときにエラーが発生しないように、クラスをパッケージMqConstantsコピーし、同じ依存関係を   追加、同じアドレスを設定します。hotel-admincn.itcast.hotel.constatnts
3.2.3amqp

hotel-adminビジネスの追加、削除、および変更でそれぞれMQメッセージを送信します: (hotel-adminプロジェクトの下のcn.itcast.hotel.webパッケージの下のクラス内HotelController):

メッセージの送信に必要なものを挿入しますapi
ここに画像の説明を挿入します

ここに画像の説明を挿入します

3.2.5.MQメッセージの受信

hotel-demoメッセージを受信したMQときに行うべきことは次のとおりです。

  • 新增hotelメッセージ:渡されたidクエリhotel情報に従って、インデックス データベースにデータを追加します
  • 删除メッセージ:hotel渡されたデータに基づいてidインデックス データベース内のデータを削除します

1) まずhotel-demoパッケージ内cn.itcast.hotel.serviceIHotelServiceサービスを追加および削除します

void deleteById(Long id);

void insertById(Long id);

2)hotel-demoパッケージcn.itcast.hotel.service.implに基づいてビジネスを実装しますHotelService

@Override
public void deleteById(Long id) {
    
    
    try {
    
    
        // 1.准备Request
        DeleteRequest request = new DeleteRequest("hotel", id.toString());
        // 2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

@Override
public void insertById(Long id) {
    
    
    try {
    
    
        // 0.根据id查询酒店数据
        Hotel hotel = getById(id);
        // 转换为文档类型
        HotelDoc hotelDoc = new HotelDoc(hotel);

        // 1.准备Request对象
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
        // 2.准备Json文档
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        // 3.发送请求
        client.index(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

3) リスナーを作成し、パッケージ
に新しいクラスを追加しますhotel-democn.itcast.hotel.mq

package cn.itcast.hotel.mq;

@Component
public class HotelListener {
    
    
    @Autowired
    private IHotelService hotelService;

    /**
     * 监听酒店新增或修改的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
    public void listenHotelInsertOrUpdate(Long id){
    
    
        hotelService.insertById(id);
    }

    /**
     * 监听酒店删除的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
    public void listenHotelDelete(Long id){
    
    
        hotelService.deleteById(id);
    }
}

4.クラスター

ここに画像の説明を挿入します

単一のマシンでデータ ストレージを行う場合elasticsearch、必然的に 2 つの問題に直面することになります。大容量データストレージの問題単一障害点の問題

  • 大容量データストレージの問題: インデックス ライブラリを N 個のシャード ( shard) に論理的に分割し、複数のノードに保存します。
    ここに画像の説明を挿入します

  • 単一障害点の問題: シャーディングされたデータを別のノードにバックアップします ( replica )

ES クラスター関連の概念:

  • クラスター: 共通のクラスター名を持つノードのグループ。

  • ノード: クラスター内の Elasticsearch インスタンス

  • シャーディング: インデックスは、シャーディングと呼ばれるさまざまな部分に分割して保存できます。クラスター環境では、インデックスの異なるシャードを異なるノードに分割できます。

    問題の解決: データの量が多すぎて、単一ポイントのストレージ容量が制限されています。
    ここに画像の説明を挿入します

    ここでは、データを 3 つのスライス (shard0、shard1、shard2) に分割します。

  • プライマリ シャード ( Primary shard): レプリカ シャードの定義に関連します。

  • レプリカ シャード ( Replica shard) 各プライマリ シャードは 1 つ以上のレプリカを持つことができ、データはプライマリ シャードと同じです。

  データのバックアップにより高可用性を確保できますが、シャードごとにバックアップすると必要なノード数が 2 倍になり、コストが高くなりすぎます。
高可用性とコストの間のバランスを見つけるために、次のようにすることができます。

  • まず、データが断片化され、異なるノードに保存されます。
  • 次に、各シャードをバックアップして他のノードに配置し、相互バックアップを完了します。

これにより、必要なサービス ノードの数を大幅に削減できます。図に示すように、例として 3 つのシャードと各シャードに 1 つのバックアップを使用します。各シャードには 1 つのバックアップがあり、3 つのノードに保存されます
ここに画像の説明を挿入します

  • node0: シャード 0 と 1 を保存します
  • ノード1: シャード0と2を保存します
  • ノード2: 保存されたシャード1および2

4.1. ESクラスターの構築

事前準備資料の参考資料
ここに画像の説明を挿入します

第4章:
ここに画像の説明を挿入します

4.2.クラスターのスプリットブレイン問題

4.2.1. クラスターの責任の分割

elasticsearch中規模クラスター ノードにはさまざまな役割があります。
ここに画像の説明を挿入します

デフォルトでは、クラスター内のすべてのノードに上記の 4 つの役割があります。
ただし、実際のクラスタではクラスタの責任を分離する必要があります

  • master节点: CPU 要件は高いが、メモリ要件は低い
  • data节点: CPU とメモリの両方に対する高い要件
  • coordinating节点: ネットワーク帯域幅と CPU に対する高い要件

職務を分離することで、さまざまなノードのニーズに応じて、展開用にさまざまなハードウェアを割り当てることができます。そして、企業間の相互干渉を避けます。

クラスターの役割の一般的なes分割は次のとおりです。
ここに画像の説明を挿入します

4.2.2.スプリットブレイン問題

スプリット ブレインは、クラスター内のノードが切断されることによって発生します。
たとえば、クラスタでは、マスター ノードが他のノードとの接続を失います。
ここに画像の説明を挿入します
この時点で、マスター ノードがダウンしているとみなされるとnode2マスター ノードが再選出されます。ノード 3 が選出されると、クラスタは引き続きノードにサービスを提供します。外の世界では、ノード 2 とノード 3 は独自のクラスターを形成し、ノード 1 は独自のクラスターを形成します。node3node1
ここに画像の説明を挿入します
2 つのクラスターのデータは同期されておらず、データの差異が発生します。

ネットワークが復元されると、クラスター内に 2 つのマスター ノードが存在するため、クラスターのステータスが矛盾し、スプリット ブレイン状況が発生します。
ここに画像の説明を挿入します
  スプリットブレインの解決策はい、必須ですリーダーとして選出されるには、投票数が (適格なノードの数 + 1)/2 を超える必要があります。したがって、適格なノードの数は奇数であることが好ましい。対応する構成項目は Discovery.zen.minimum_master_nodes で、これは es7.0 以降のデフォルト構成となっているため、通常、スプリット ブレインの問題は発生しません。

  たとえば、3 つのノードで形成されたクラスターの場合、投票は(3 + 1) / 2、つまり 2 票を超える必要があります。ノード 3 はノード 2 とノード 3 からの票を受け取り、リーダーに選出されました。Node1 は独自の投票を 1 つしか持っていないため、選出されませんでした。クラスター内にはまだマスター ノードが 1 つだけあり、スプリット ブレインはありません。

4.2.3.概要

master eligibleノードの役割は何ですか?

  • クラスターリーダー選挙に参加する
  • マスター ノードは、クラスターのステータスを管理し、シャード情報を管理し、インデックス ライブラリの作成および削除のリクエストを処理できます。

dataノードの役割は何ですか?

  • データCRUD

coordinatorノードの役割は何ですか?

  • リクエストを他のノードにルーティングする
  • クエリ結果を結合してユーザーに返します。

4.3.クラスター分散ストレージ

新しいドキュメントを追加するときは、データのバランスを確保するために別のシャードに保存する必要があります。では、coordinating nodeデータをどのシャードに保存するかを決定するにはどうすればよいでしょうか?

4.3.1. 共有ストレージのテスト

3 つのデータを挿入します。
ここに画像の説明を挿入します

ここに画像の説明を挿入します

ここに画像の説明を挿入します
テストから、3 つのデータが異なるシャードにあることがわかります。
ここに画像の説明を挿入します

結果:
ここに画像の説明を挿入します

4.3.2. 共有ストレージの原則

elasticsearchアルゴリズムを使用して、hashドキュメントをどのシャードに保存するかを計算します。
ここに画像の説明を挿入します

例証します:

  • _routingデフォルトはドキュメントですid
  • アルゴリズムはシャードの数に関連しているため、インデックス ライブラリが作成されると、シャードの数は変更できません

新しいドキュメントを追加するプロセスは次のとおりです。
ここに画像の説明を挿入します

解釈:

  • 1)id=1新しいドキュメントを追加する
  • 2)演算idを実行しhash、結果が 2 の場合は、次の場所に保存する必要があります。shard-2
  • 3)shard-2プライマリ シャードはnode3ノード上にあり、データを次の場所にルーティングします。node3
  • 4) 文書を保存する
  • 5)ノード上の指定されたshard-2コピーを同期します。replica-2node2
  • 6) 結果をcoordinating-nodeノードに返す

4.4.クラスター分散クエリ

elasticsearchクエリは 2 つの段階に分かれています。

  • scatter phase分散ステージcoordinating node(調整ノード) はリクエストを各シャードに分散します。
  • gather phase集約段階、検索結果をcoordinating node要約しdata node、ユーザーに返される最終的な結果セットに処理します。

ここに画像の説明を挿入します

まとめ:
ここに画像の説明を挿入します

4.5. クラスターのフェイルオーバー

  クラスタ内のmasterノードはクラスタ内のノードの状態を監視し、ノードのダウンが検出された場合、ダウンしたノードの断片化されたデータを直ちに他のノードに移行してデータのセキュリティを確保します。フェイルオーバー

1) たとえば、クラスタ構造は図のようになります。
ここに画像の説明を挿入します
今、node1それがマスター ノードであり、他の 2 つのノードはスレーブ ノードです。

2) 突然node1障害が発生した場合:
ここに画像の説明を挿入します
ダウンタイム後の最初のことはマスターの再選出です。たとえば、「node2マスター
ここに画像の説明を挿入します
  node2ノードになった後、クラスターの監視ステータスがチェックされ、次のことがわかります。」を選択するとshard-1shard-0レプリカノードはありません。node1したがって、上記のデータを次の場所に移行する必要がありますnode2node3
ここに画像の説明を挿入します

まとめ:
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/weixin_52223770/article/details/128701275