ステージ 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
集計構文 (メトリック集計)
上では、ホテルをブランド別にグループ化し、バケットを形成しました。次に、バケット内のホテルについて計算を行う必要があります。各ブランドのユーザー評価min
、max
、avg
同等の値を取得します。
これには、統計集計などの集計を使用する必要があります。 、などの結果Metric
を取得できます。min
max
avg
構文は次のとおりです。
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 つのステップに分かれています。
①解凍②仮想マシン内のディレクトリ
にアップロード ③再起動④テストelasticsearch
plugin
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.オートコンプリートクエリ
elasticsearch
Completion 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
インデックス ライブラリにはピンイン単語セグメンターが設定されていないため、インデックス ライブラリの構成を変更する必要があります。でも私たちは知っていますインデックス ライブラリは変更できません。削除して再作成することのみが可能です。。
さらに、自動補完用のフィールドを追加し、そこにブランド、提案、都市などを自動補完プロンプトとして入力する必要があります。
要約すると、私たちはしなければならないことには以下が含まれます:
-
ホテル インデックス データベース構造を変更し、カスタム ピンイン単語セグメンターをセットアップする
-
インデックス ライブラリの名前とすべてのフィールドを変更し、カスタムの単語セグメンターを使用します。
-
インデックス ライブラリは、新しいフィールド候補を追加します。そのタイプは補完タイプで、カスタムの単語セグメンターを使用します。
-
ブランドとビジネスを含む提案フィールドを HotelDoc クラスに追加します
-
ホテルのデータベースにデータを再インポートする
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
追加し、その中に、などの情報を入れます。suggestion
List<String>
brand
city
business
コードは以下のように表示されます。
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-demo
elasticsearch
データを変更するための外部インターフェイスを提供します。- ホテル運営業務は完了しつつありますデータベース操作その後、
hotel-demo
提供されたインターフェイスを直接呼び出し、
3.1.2.非同期通知
オプション 2: 非同期通知
プロセスは次のとおりです。
hotel-admin
mysql
データベースのデータを追加、削除、変更した後、MQ
メッセージを送信しますhotel-demo
MQ
メッセージ受信後のelasticsearch
データ変更を監視して完了する
3.1.3.モニターbinlog
オプション 3: 監視binlog
プロセスは次のとおりです。
- 機能
mysql
をオンにするbinlog
mysql
完了した追加、削除、変更操作はbinlog
、hotel-demo
canal
監視の変化に基づいてbinlog
、elasticsearch
コンテンツはリアルタイムに更新されます
3.1.4.選択
方法 1: 同期呼び出し
- 利点: 実装が簡単、粗雑
- 短所: 高度なビジネス結合
方法 2: 非同期通知
- 利点: 低カップリング、平均的な実装難易度
- 短所: mq の信頼性に依存する
方法 3: モニタリングbinlog
- 利点: サービスを完全に分離
- 短所: 開くと
binlog
データベースの負担が増加し、実装がより複雑になります
3.2.データ同期の実装
MQ
実装mysql
とelasticsearch
データ同期を利用する
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-demo
rabbitmq
<!--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-demo
、cn.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-admin
cn.itcast.hotel.constatnts
3.2.3
amqp
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.service
のIHotelService
サービスを追加および削除します
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-demo
cn.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 は独自のクラスターを形成します。node3
node1
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-2
node2
- 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-1
、shard-0
レプリカノードはありません。node1
したがって、上記のデータを次の場所に移行する必要がありますnode2
。node3
まとめ: