記事ディレクトリ
序文
この記事では、プロジェクトに活かせることを目的に、esの基礎知識や注意点、基本操作を紹介します。
導入
中国語チュートリアル:基本概念 Elasticsearch 中国語ドキュメント (kilvn.com) https://docs.kilvn.com/elasticsearch/docs/192.html)
Elasticsearch は、Java で開発されたほぼリアルタイムの検索エンジンです。一般的なデータベース検索では、単語全体のみが一致します。たとえば、「kafka Partition copy」という単語は、「kafka Partition copy」などのこの単語のみと一致します。ステートメントは一致しません。多くのことはありますが、ES ではそれが可能です。Baidu 検索を使用するのと同じように、キーワードを検索できます。
コンセプト
インデックス (インデックス): mysql のテーブル名と同様に、小文字である必要があります。各インデックスにはマッピングがあり、ドキュメントのフィールド名とフィールド タイプを定義するために使用されます。
タイプ (type): 6.0 以降は非推奨となり、インデックスのタイプを表します。7.0 以降では、1 つのタイプ (_doc) のみを作成できます。
ドキュメント (ドキュメント): ドキュメント。インデックス内のデータの基本単位であり、json 形式で保存されます。ファイルの作成時にフィールド タイプが指定されていない場合、各フィールドは独自のタイプ (text、double、date、geo_point など) を持ちます。自動的に生成されますが、フィールドの型が違いすぎるとエラーが報告されます。もちろん、 geo_point 型 (空間座標型) と同様に、自分で指定することもできます。事前に指定しないと、double 配列型に変換され、各ドキュメントに一意の ID が生成されます。
ノード: 各 Elasticsearch インスタンスはノードであり、3 つのタイプに分けられます。
- マスター適格 - 各ノードはマスター適格ノードとして開始され、マスター ノードの選択に参加でき、マスター ノードである必要はなく、node.master:false によって無効にできます。
- data - データを保存するノード。シャード データの保存を担当します。
- 調整 - クライアントのリクエストを受信し、そのリクエストを適切なノードに送信し、最後に結果をまとめる責任を負います。各ノードは調整ノードです。
シャード: インデックス付けされたデータは複数のデータ フラグメントに分割され、他のノードに保存されます。各シャードは独立した「インデックス」とみなすこともできます。水平分割モードにより、ES を分散して並列に動作させることができます。パフォーマンス、つまり数を提供します。プライマリ シャードの数は作成後に変更することはできませんが、インデックスの再作成中には変更できます。
レプリカ: 災害耐性を向上させるために、各シャードは他のノードにバックアップ コピーを保存します。コピーは元のシャードが存在するノードおよびプライマリ シャードには保存されず、並列検索のためにすべてのコピーで実行されます。コピー の数量は作成後も変更できます。
インストール (Docker 以外)
エラスティックサーチ
-
ダウンロードダウンロード センター - Elastic Chinese Community (elasticsearch.cn)
-
サーバーにアップロードして解凍します (ここでは 8.0.0 をダウンロードしました)
-
設定 (ここでは root ユーザーでは開始できません)
-
解凍したファイルパスの権限を一般ユーザーに変更(rootユーザーでは起動できない版)
フォルダーと次のサブフォルダーをユーザー ali が所有するユーザー グループ ali として設定します。
chown -R ali:ali elasticsearch
-
config/elasticsearch.ymlを変更する
クラスター名
クラスタ名: エイリアス
次のクラスター ノードに対応するノード名
ノード名: ノード-1
サービスIP
ネットワーク.ホスト: 0.0.0.0
公開ポート
http.ポート: 9200
クラスタノード
cluster.initial_master_nodes: [“node-1”]
-
vim /etc/sysctl.conf
vm.max_map_count=655360
修正後も
sysctl -p
有効にする -
vim /etc/security/limits.conf
* soft nproc 2048
に変更されました* soft nproc 4096
* soft nofile 65536 * hard nofile 131072 * soft nproc 4096 * hard nproc 4096
*
すべてのユーザーソフト: 現在のシステムの実効設定値を指します。
ハード: システムで設定できる最大値を示します。nofile: 開いているファイルの最大数
noproc: プロセスの最大数
-
-
開始します。root から開始しないことを忘れないでください
通常ユーザーを切り替える
しかし
./bin/elasticsearch
#バックグラウンドスタート
./bin/elasticsearch -d
開始エラー 1:
Skipping security auto configuration because the node keystore file [/data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore] is not a readable regular file Exception in thread "main" org.elasticsearch.bootstrap.BootstrapException: java.nio.file.AccessDeniedException: /data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore Likely root cause: java.nio.file.AccessDeniedException: /data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:90
elasticsearch.keystore
このファイルはrootユーザー権限では読み取れないためです。[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-NmMCuHfE-1678515293253)(E:/ALI/Documents/%E5%BE%) 85%E5%8F%91 %E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230212161221952.png)]
一度設定する
chown -R ali:ali config/elasticsearch.keystore
開始エラー 2:
ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@27d5a580] unable to create manager for [/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog.json] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@52851b44[pattern=/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog-%i.json.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[SizeBasedTriggeringPolicy(size=1073741824)]), strategy=DefaultRolloverStrategy(min=1, max=4, useMax=true), advertiseURI=null, layout=co.elastic.logging.log4j2.EcsLayout@550a1967, filePermissions=null, fileOwner=null]] java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@27d5a580] unable to create manager for [/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog.json] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@52851b44[pattern=/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog-%i.json.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[SizeBasedTriggeringPolicy(size=1073741824)]), strategy=DefaultRolloverStrategy(min=1, max=4, useMax=true), advertiseURI=null, layout=co.elastic.logging.log4j2.EcsLayout@550a1967, filePermissions=null, fileOwner=null]] at org.apache.logging.log4j.core.appender.AbstractManager.getManager(AbstractManager.java:116) at org.apache.logging.log4j.core.appender.OutputStreamManager.getManager(OutputStreamManager.java:100) at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.getFileManager(RollingFileManager.java:217) at org.apache.logging.log4j.core.appender.RollingFileAppender$Builder.build(RollingFileAppender.java:146) at org.apache.logging.log4j.core.appender.RollingFileAppender$Builder.build(RollingFileAppender.java:62) at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:122) at org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:1120) at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:1045) at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:1037) at org.apache.logging.log4j.core.config.AbstractConfiguration.doConfigure(AbstractConfiguration.java:651) at org.apache.logging.log4j.core.config.AbstractConfiguration.initialize(AbstractConfiguration.java:247) at org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:293) at org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:626) at org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:302) at org.elasticsearch.common.logging.LogConfigurator.configure(LogConfigurator.java:222) at org.elasticsearch.common.logging.LogConfigurator.configure(LogConfigurator.java:118) at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:313) at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:166) at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:157) at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:77) at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:112) at org.elasticsearch.cli.Command.main(Command.java:77) at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:122) at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:80) 2023-02-12 03:15:28,044 main ERROR Unable to invoke factory method in class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender
これは、ログファイルも root 権限下にあるためで、最初に起動したときに使用した root アカウントであるはずで、その結果、初期化時にこれらのファイルは root アカウントの下に作成されましたが、その後は異常でした。非 root ユーザーに切り替える。
[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-UIC0vJMg-1678515293254)(E:/ALI/Documents/%E5%BE%) 85%E5%8F%91 %E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230212162305207.png)]
操作3: 最大仮想メモリ領域 vm.max_map_count [65530] が低すぎます。少なくとも [262144] まで増やしてください。
これは 3 番目と 4 番目のステップであり、構成がないか、効果がありません。
開始エラー 4:
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
これは、JVM のパラメータが古いためです。esディレクトリの
config/jvm.options
vi config/jvm.options を次
のように-XX:+UseConcMarkSweepGC
変更する必要があります。-XX:+UseG1GC
-
ポートを開く
firewall-cmd --add-port=9200/tcp --permanent
ファイアウォール-cmd --reload
-
IP アクセス: http://192.168.17.128:9200 に失敗しました
[WARN ][o.e.x.s.t.n.SecurityNetty4HttpServerTransport] [node-1] received plaintext http traffic on an https channel, closing connection Netty4HttpChannel{ localAddress=/192.168.17.128:9200, remoteAddress=/192.168.17.1:60067}
-
ssl をオフにする (vim config/elasticsearch.yml)
この構成は起動後にのみ使用可能であり、解凍後は使用できません。
xpack.security.enabled: false
-
次に、ip:9200 で確認し、JSON が表示されれば成功です。
[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-DSV9l8kL-1678515293254) (E:/ALI/Documents/%E5%BE%) 85%E5%8F%91 %E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230212163531591.png)]
API
_猫
cat: コマンドパラメータを表示
一般的に、はっきりとは思い出せませんが、次http://192.168.17.128:9200/_cat
のパスを渡すことで es の API コマンドを表示できます。
インデックスの追加/作成
[put] http://ip:port/{インデックス名}
この方法では、インデックスを直接作成できます。フィールドに他の要件がない場合、この方法も最も簡単です。
データを挿入する
【投稿】http://ip:port/{インデックス名}/_doc/[id]
id は空でも構いません。空の場合は es によって生成されます。
パラメータ:
{
"id":"1",
"name":"搜索",
"age":1
}
クエリインデックスの設定
[get] http://ip:port/{インデックス名}
alias,mappings,settings
もちろん、この返品の理由は個別に問い合わせることができます
インデックスの作成時にフィールド属性タイプが設定されていない場合、es は自動的に照合して設定します。
{
"t_biz_test": {
"aliases": {
},
"mappings": {
"properties": {
"age": {
"type": "long"
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"settings": {
"index": {
"routing": {
"allocation": {
"include": {
"_tier_preference": "data_content"
}
}
},
"number_of_shards": "1",
"provided_name": "t_biz_test",
"creation_date": "1676192760043",
"number_of_replicas": "1",
"uuid": "g0b6I59oR1a8pT0VIAQ3FA",
"version": {
"created": "8000099"
}
}
}
}
}
地図
[get] http://ip:port/{インデックス名}/_mappings
指定されたフィールドのマッピングを表示する
[get] http://ip:port/{インデックス名}/_mapping/field/name
構成
[get] http://ip:port/{インデックス名}/_settings
エイリアス
[get] http://ip:port/{インデックス名}/_alias
マッピングを設定する
[put] http://ip:port/{インデックス名}/_mapping
既存のすべてのマッピングを変更するには、マッピングを追加することのみが可能であり、変更する場合は再作成することのみが可能です。
-
既存のインデックスの場合、フィールド マッピングを追加します
{ "properties": { // 这个字段可以存在,但要和旧的一样 "t_name": { "type": "text" } } }
-
[put] http://ip:port/{インデックス名}
これはインデックスを作成する場合と同じで、設定するインデックスが存在しないと成功します。
改訂
IDで置き換える
[put] http://ip:port/{インデックス名}/_doc/{id}
パラメータ: json オブジェクト
知らせ:
新しい方法で修復するとバージョンは上がりますが、修正はされません、新しい方法では一部の部分ほど効率が悪くなります。すべて同じ手順ですが、更新するバージョンが増えます。
部分的な更新
【投稿】http://ip:port/{インデックス名}/_update/{id}
このメソッドはフィールドが変更されたときに使用されます。つまり、フィールドが変更されていないときは更新されません。更新がある場合は、指定したフィールドと値についてのみ更新されます。
注: 部分的に更新された新機能は、代替品よりも優れています。
パラメータの形式:
doc はドキュメント オブジェクトを意味し、age はフィールド名を意味します
{
"doc":{
"age":11
}
}
消去
IDで削除
【削除】http://ip:port/{インデックス名}/_doc/{id}
条件で削除
【post】http://ip:port/{インデックス名}/_delete_by_query
{
"query":{
"term":{
"id":1
}
}
}
すべて削除
{
"query":{
"match_all": {
}
}
}
インデックスの削除
【削除】http://ip:port/{インデックス名}
IDによるクエリ
【get】http://ip:port/{インデックス名}/_doc/{id}
結果
{
"_index": "t_biz_test",
"_id": "dUodRYYB4WypEctHyN4J",
"_version": 1,
"_seq_no": 8,
"_primary_term": 1,
"found": true,
"_source": {
"id": "",
"name": "xx",
"age": x
}
}
検索
検索応答フォーマット
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
take - Elasticsearch がクエリの実行に要した時間 (ミリ秒単位)
timed_out - 検索リクエストがタイムアウトしたかどうか
_shards - 検索されたシャードの数と、成功したシャード、失敗したシャード、スキップされたシャードの数の内訳。
max_score - 見つかった最も関連性の高いドキュメントのスコア
Hist.total.value - 見つかった一致するドキュメントの数
Hist.sort - ドキュメントの並べ替え位置 (関連性スコアによって並べ替えられていない場合)
Hist._score - ドキュメントの関連性スコア(match_all を使用する場合は適用されません)
クエリは_search
サフィックスで終わり、デフォルトでは 10 を返します。
【get/post】 http://ip:port/{索引}/_search
クエリパラメータの形式は次のとおりです。クエリで始まり、用語、検索タイプ、ID はフィールド名を示します。
{
"query":{
"term":{
"name":"xxx"
}
}
}
キーワード検索(一致)
[get] http://ip:port/{インデックス名}/_search?q=age:11
ここではgetリクエストを使用しており、パラメータはq=*
渡される形式で渡されます。q=字段名:值
ハイライト
[投稿] http://ip:port/{インデックス名}/_search
パラメータ
{
"query":{
// 模糊匹配
"match":{
"name":"搜"
}
},
"highlight":{
"fields":{
"name":{
}
}
}
}
highlight
ここでもう 1 つのパラメータを渡す必要があります
結果は次のようになります。検索された単語にはマークが付けられます
"hits": [
{
"_index": "t_biz_test",
"_id": "dEoARYYB4WypEctHFt5e",
"_score": 0.2876821,
"_source": {
"id": "1",
"name": "搜索",
"age": 1
},
"highlight": {
"name": [
"<em>搜</em>索"
]
}
}
]
構造化クエリ
用語 (完全一致)
[投稿] http://ip:port/{インデックス名}/_search
パラメータ
{
"query":{
"term":{
"name":"搜索"
}
}
}
用語 (複数の値の完全一致を許可)
[投稿] http://ip:port/{インデックス名}/_search
パラメータ
{
"query":{
"terms":{
"name":["ds","dd"]
}
}
}
mysql の場合と同様に、用語は複数の値に一致する可能性がありますin
range (範囲クエリ)
[投稿] http://ip:port/{インデックス名}/_search
- gt - より大きい
- gte - 以上
- lt - 未満
- lte - 以下
パラメータ
{
"query":{
"range":{
"age": {
"gte":3
}
}
}
}
ここでは年齢がフィールド名です
存在します (フィールドを含めるかどうか)
[投稿] http://ip:port/{インデックス名}/_search
パラメータ
{
"query":{
"exists":{
"field": "name"
}
}
}
name
フィールドを含むドキュメントがクエリされる限り
match (単語分割一致検索)
[投稿] http://ip:port/{インデックス名}/_search
パラメータ
{
"query":{
"match":{
"name": "搜"
}
}
}
フルテキスト フィールドを照合する場合、実際のクエリの前にアナライザーを使用して検索値をセグメント化し、単語の分割後に単語の分割結果を 1 つずつ照合します。ただし、一致フィールドが正確な値である場合は、次のような結果になります。数値、日付、ブール値、または文字列 not_analyzed を使用すると、検索する指定された値が得られます。
**注意:** match の値にスペースが含まれている場合は、人為的に 2 つの単語に置き換えられ、単語が含まれている限りクエリが実行されます。
例
{
"query":{
"match":{
"name": "搜 掉"
}
}
}
結果:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0417082,
"hits": [
{
"_index": "t_biz_test",
"_id": "dEoARYYB4WypEctHFt5e",
"_score": 1.0417082,
"_source": {
"id": "1",
"name": "搜索",
"age": 1
}
},
{
"_index": "t_biz_test",
"_id": "dUodRYYB4WypEctHyN4J",
"_score": 0.8781843,
"_source": {
"id": "2",
"name": "会锁掉",
"age": 2
}
}
]
}
}
マッチフレーズ
match とよく似ていますが、match フィールド内のスペースを無視し、単語全体のマッチングを実行します。
好き
{
"query":{
"match_phrase":{
"name": "搜 掉"
}
}
}
[検索][ドロップ]ではなく[検索]に一致する場合
ブール
[投稿] http://ip:port/{インデックス名}/_search
複数の条件を処理する場合に使用されます
{
"query":{
"bool":{
"must": {
"term":{
"age":1
}
},
"must_not":{
"match":{
"name":"掉"
}
},
"should":{
"term":{
"age":2
}
}
}
}
}
bool - ユーザーが複数の条件の結果を結合するためのブール ロジック
-
must - 複数の条件が正確に一致し、mysql の および と同等
-
must_not - 複数の条件一致の否定、mysql not と同等
-
should - mysql と同等、または、
-
Must と並べると、スコア マッチングの計算として理解できます。つまり、Must 条件が一致した後、Should 条件が一致し、Should 条件が 1 位にランクされます。
-
Must では、次のように、Must が一致した後、Should 条件のいずれか 1 つが同時に満たされる必要があります。たとえば、誰が 10 ~ 20 歳、男性であるかをクエリする場合、Must 条件は男性条件です。そして、対象となるのは年齢層です
{ "query": { "bool": { "must": [ { "term": { "name": "搜" } }, { "bool": { "should": [ { "term": { "age": 2 } } ] } } ] } } }
-
フィルター
[投稿] http://ip:port/{インデックス名}/_search
{
"query": {
"bool": {
"filter":{
"range":{
"age":{
"gte":2
}
}
}
}
}
}
プレフィックス (プレフィックス一致)
キーワードにのみ適用され、大文字と小文字が区別されます
[投稿] http://ip:port/{インデックス名}/_search
{
"query":{
"prefix":{
"name.keyword":"搜"
}
}
}
重合
平均値(avg)/最大値(max)/最小値(min)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs":{
"pinjunzhi":{
// 自定义平均值字段名
"avg":{
// 平均值(avg),最大值(max),最小值(min)
"field": "age"
}
}
}
}
{
"took": 84,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [...]
},
"aggregations": {
"pinjunzhi": {
// 计算值
"value": 2.5
}
}
}
重複排除の統計
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"cardinality": {
// 去重
"field": "age"
}
}
}
}
たとえば、製品注文実績テーブル。これを使用して、何社が注文したかを調べることができます。
extend_stats (拡張統計)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"extended_stats": {
"field": "age"
}
}
}
}
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
...
]
},
"aggregations": {
"pinjunzhi": {
"count": 4, // 计数
"min": 1.0, // 最小值
"max": 4.0, // 最大值
"avg": 2.5, // 平均值
"sum": 10.0,
"sum_of_squares": 30.0,
"variance": 1.25,
"variance_population": 1.25,
"variance_sampling": 1.6666666666666667,
"std_deviation": 1.118033988749895,
"std_deviation_population": 1.118033988749895,
"std_deviation_sampling": 1.2909944487358056,
"std_deviation_bounds": {
"upper": 4.73606797749979,
"lower": 0.2639320225002102,
"upper_population": 4.73606797749979,
"lower_population": 0.2639320225002102,
"upper_sampling": 5.081988897471611,
"lower_sampling": -0.0819888974716112
}
}
}
}
value_count (値値カウント統計)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"value_count": {
"field": "age"
}
}
}
}
フィールドが固定の場合の数値フィールド
用語 (単語の集合体)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"terms": {
"field": "age"
}
}
}
}
MySQLと同様(グループ化後、数をカウント)
select count(*) from t_biz_test group by age
top_hits (最も一致する重みの集計)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"terms": {
"field": "age"
},
"aggs": {
"count": {
"top_hits": {
"size": 2
}
}
}
}
}
}
{
"took": 16,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
...
]
},
"aggregations": {
"pinjunzhi": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 4,
"doc_count": 2,
"count": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "t_biz_test",
"_id": "d0qzRYYB4WypEctHad4n",
"_score": 1.0,
"_source": {
"id": "4",
"name": "搜天",
"age": 4
}
},
{
"_index": "t_biz_test",
"_id": "eErORYYB4WypEctH0N4r",
"_score": 1.0,
"_source": {
"id": "5",
"name": "搜天2",
"age": 4
}
}
]
}
}
},
{
"key": 1,
"doc_count": 1,
"count": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "t_biz_test",
"_id": "dEoARYYB4WypEctHFt5e",
"_score": 1.0,
"_source": {
"id": "1",
"name": "搜索",
"age": 1
}
}
]
}
}
},
{
"key": 2,
"doc_count": 1,
"count": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "t_biz_test",
"_id": "dUodRYYB4WypEctHyN4J",
"_score": 1.0,
"_source": {
"id": "2",
"name": "会锁掉",
"age": 2
}
}
]
}
}
},
{
"key": 3,
"doc_count": 1,
"count": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "t_biz_test",
"_id": "dkofRYYB4WypEctHKt7H",
"_score": 1.0,
"_source": {
"id": "3",
"name": [
"ds",
"dd"
],
"age": 3
}
}
]
}
}
}
]
}
}
}
最初のいくつかのデータを取得し、サイズによって制御します。
range (範囲統計)
[投稿] http://ip:port/{インデックス名}/_search
{
"aggs": {
"pinjunzhi": {
"range":{
"field":"age",
"ranges":[
{
"from":1,
"to":2
},
{
"from":3,
"to":6
}
]
}
}
}
}
"aggregations": {
"pinjunzhi": {
"buckets": [
{
"key": "1.0-2.0",
"from": 1.0,
"to": 2.0,
"doc_count": 1
},
{
"key": "3.0-6.0",
"from": 3.0,
"to": 6.0,
"doc_count": 3
}
]
}
}
応答のフォーマット
ブラウザー ツールを使用してクエリを実行する場合、応答は json 形式ではなく、URL の後にパラメーターを追加できます。pretty
例:
かわいらしさを加えた後
応答フィールドを指定する
_search は get と post で使用できます。基本的にはすべて同じです。ここでは post の例を示します。
{
"_source":["id", "name"]
}
id,name
2 つのフィールドのみを表示
バッチクエリ
【post】http://ip:port/{インデックス名}/_mget
{
"ids":["dEoARYYB4WypEctHFt5e", "dUodRYYB4WypEctHyN4J"]
}
ページング
浅いページネーション
[get] http://ip:port/{インデックス名}/_search?size=1&from=2
- サイズ - ページあたりのデータ量
- from - 何行スキップするか
また
[投稿] http://ip:port/{インデックス名}/_search
{
"size":1,
"from":2
}
注: _search の get リクエストはリクエストボディをサポートしています
たとえば、10 個のデータの場合、id は 0 から 9, まで配列されておりsize=1&from=2
、id=3 のデータが返されます (0, 1 をスキップして 1 個のデータを返します)。
欠点は、たとえば、データが 100 ページある場合、50 ページのデータをクエリすることは、0 ページから 50 ページまでのデータを検索し、0 ~ 49 ページを破棄して最後のページをクエリすることと同じであることです。テーブル全体をクエリすることと同じです
深いページネーション (スクロール)
[投稿] http://ip:port/{インデックス名}/_search?scroll=5m
{
"size":1,
"from":0
}
このページング メソッド ishi は、scroll_id を通じて次のページのコンテンツを取得します。from は 0 に設定する必要があります。
応答:
{
"_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmpIMk5tOEs2Umx5SndQQ08yMTNmRGcAAAAAAAAAkRZTT3UzVXR2VlNjcWZ4anhhdWNKdlZ3",
"took": 2,
"timed_out": false,
"_shards": {
...
}
...
}
ページング後、別のインターフェイスが要求される
【投稿】http://ip:port/_search/scroll
{
"scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmpIMk5tOEs2Umx5SndQQ08yMTNmRGcAAAAAAAAAkRZTT3UzVXR2VlNjcWZ4anhhdWNKdlZ3",
"scroll": "5m"
}
知らせ:
- ページネーション URL の後、インデックス名なし
- スクロールは多くのリソースを消費し、履歴スナップショットを生成するため、使用しないときは表示して削除する必要があります
スクロールIDを削除
【削除】http://ip:port/_search/scroll/{scroll_id}
すべてのscroll_idを削除
【削除】http://ip:port/_search/scroll/_all
深いページネーション 2 (search_after)
[投稿] http://ip:port/{インデックス名}/_search
{
"size": 2,
"from": 0,
"query": {
"match_all":{
}
},
"sort": [
{
"id.keyword": {
"order": "desc"
}
},
{
"name.keyword": {
"order": "desc"
}
}
],
"search_after": [
"6",
"阿萨德"
]
}
注意点:
- search_after は、設定された値をカーソルとして使用してページネーションを実行します。上記のクエリの場合、最初に sort で並べ替えてから、search_after をカーソルとして使用してページネーションを実行します。
- from 値は 0 のみです
- search_after の値は、結果の前のページの最後の結果の並べ替え値です。つまり、search_after の値と並べ替え値は同じである必要があります。
エイリアスを追加する
【投稿】http://ip:port/_alias
{
"actions": [
{
"add": {
"index": "t_biz_test",
"alias": "t_biz_test_alias"
}
}
]
}
追加中に削除することもできます
{
"actions": [
{
"add": {
"index": "t_biz_test",
"alias": "t_biz_test_alias"
}
},
{
"remove": {
"index": "t_biz_test",
"alias": "t_biz_test_alias"
}
}
]
}
特記事項: 最大返品数を問い合わせる
es はデフォルトで 10,000 個のデータを返し、超過したデータは返されません
[put] http://ip:port/{インデックス名}/_settings
{
"max_result_window":100000
}
またはインデックスの作成時に設定します
[put] http://ip:port/{インデックス名}
{
"settings": {
"max_result_window": 100000
},
"aliases": {
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
その後、クエリ時にパラメータを追加できます
{
"track_total_hits": true,
"query":{
"term":{
"dataId":"1623260762069680129"
}
}
}
アナライザ
アナライザーは 3 つの主要部分で構成されます。
- 文字フィルター文字フィルターは、文字ストリームがトークナイザーに渡される前の前処理であり、特殊文字の処理、マッピングの実行、待機 (0 個以上) を行うことができます。
- Tokenizer は文を複数の単語に分割し、単語の位置とオフセットを記録します (存在するのは 1 つだけです)。
- トークン フィルタートークン フィルター。単語 (小文字など) を処理およびフィルターします (0 個以上)。
文字フィルター
文字ストリームがトークナイザーに到達する前に、複数の前処理ステップが存在する場合があります。これは、Kafka のストリーム処理に似ています。
いくつかの文字フィルターが組み込まれています。
- html_strip - (スクリプトではなくストリップ) は、HTML 文字をクリアしたり、HTML 文字をデコードしたりできる HTML スクリプト用の文字フィルターです。
- マッピング- 指定された文字を特定の文字列に置き換えることができる文字マッピング フィルター。
- pattern_replace - 正規表現フィルター。正規表現で一致した文字列を特定の文字列に置き換えます。
HTML要素フィルター(html_strip)
【取得/投稿】http://ip:port/_analyze
{
"tokenizer":"keyword",
"char_filter":[{
"type":"html_strip", // 使用html过滤器,会过滤相关的html元素
"escaped_tags":["H1"] // 需要保留的html标签
}],
"text":"<p><H1>这是什么 &</H1></p>"
}
文字マッピングフィルター (マッピング)
代替フォーマットxx=>xx
【取得/投稿】http://ip:port/_analyze
{
"tokenizer":"keyword",
"char_filter":[{
"type":"mapping",
"mappings":[
"这是=>为"
]
}],
"text":"<p><H1>这是什么 <em></H1></p>"
}
正規表現フィルター (pattern_replace)
【取得/投稿】http://ip:port/_analyze
{
"tokenizer":"keyword",
"char_filter":[{
"type":"pattern_replace",
"pattern":"(http://){1,}",
"replacement":"http://"
}],
"text":"http://http://http://localhost:8080?token=xxx"
}
併用
{
"tokenizer": "keyword",
"char_filter": [
// 去除html
{
"type": "html_strip"
},
// 替换ip
{
"type": "mapping",
"mappings": [
"localhost=>192.168.17.128"
]
},
// 去除错误的地址
{
"type": "pattern_replace",
"pattern": "(http://){1,}",
"replacement": "http://"
},
// 去掉token参数
{
"type": "pattern_replace",
"pattern": "token.+&",
"replacement": ""
}
],
"text": "<a hef=\"http://baidu.com\">http://http://http://localhost:8080?token=xxx&name=ali</a>"
}
トークナイザー
内蔵トークナイザー
- 標準- 単語による分類、小文字処理
- シンプル- 文字以外は区切り文字として分割され、小文字に変換されます。
- 停止- シンプルと同じ
- ホワイトスペース- 空白区切り
- pattern - 正規表現で
\W+
一致させる分詞 - 言語- 30 を超えるシーン音声トークナイザーを提供します
内蔵トークナイザー
標準(単語ごとに分類、小文字)
【取得/投稿】http://ip:port/_analyze
パラメータ
{
"analyzer": "pinyin",
"text":"I'M HAPPY with you"
}
応答:
シンプル (文字以外は区切り文字として分割され、小文字に変換されます)
デフォルトは、カンマ、スペース、アンダースコアなどの文字以外です。
【取得/投稿】http://ip:port/_analyze
パラメータ
{
"analyzer": "simple",
"text":"I'm happy tomorrow"
}
stop (デフォルトでは simple と同じ)
デフォルトでは、ストップワードはカンマ、スペース、アンダースコアなどの文字以外です。
【取得/投稿】http://ip:port/_analyze
{
"analyzer": "stop",
"text":"I'm happy tomorrow"
}
カスタムストップワード
【置く】http://ip:port/filter_stop_index
{
"settings": {
"analysis": {
"analyzer": {
"cus_stop":{
"type":"stop",
"stopwords":["end","结束"]
}
}
}
}
}
【投稿】http://ip:port/filter_stop_index/_analyze
{
"analyzer":"cus_stop",
"text":"I'M HAPPY_with you 结束 and end"
}
ホワイトスペース (空白は区切られています)
【取得/投稿】http://ip:port/_analyze
{
"analyzer": "whitespace",
"text":"I'm happy tomorrow"
}
パターン (デフォルトの\W+
単語分割)
【取得/投稿】http://ip:port/_analyze
{
"analyzer": "pattern",
"text":"I'm happy tomorrow"
}
カスタム表現は\W+|_
(単語以外の文字)です。
【置く】http://ip:port/filter_pattern_index
{
"settings": {
"analysis": {
"analyzer": {
"cus_pattern":{
"type":"pattern",
"pattern":"\\W+|_",
"lowercase":true
}
}
}
}
}
言語
【取得/投稿】http://ip:port/_analyze
{
"analyzer": "chinese",
"text":"I'm happy tomorrow"
}
トークナイザー
インストール
ダウンロード アドレスリリースmedcl/elasticsearch-analysis-ik GitHub
es 8.0.0リリースに対応 · medcl/elasticsearch-analysis-ik · GitHub
elasticsearch-8.0.0/plugin
の下にik
フォルダーを作成します- 解凍したコンテンツを ik フォルダーにコピーします
- リブート
【取得/投稿】http://ip:port/_analyze
{
"analyzer": "ik_max_word", // 还可以使用 ik_smart,ik_max_word的拆分更细,能拆除更多单词
"text":"我明天很开心"
}
「明日」と「幸せ」が正しくセグメント化されていることがわかります
カスタム辞書
単語として使用できる状況がある場合、構成辞書をカスタマイズできます
-
プラグインディレクトリに移動します
elasticsearch-8.0.0/plugins/ik/config
-
カスタム辞書 (例: .dic で終わる) を 1 行に 1 単語ずつ作成します。ここでは単語として
cus.dic
追加します。很开心
-
IKAnalyzer.cfg.xml
辞書をファイルに追加する -
リブート
再試行リクエスト
ピンインのワード ブレーカー
对应es 8.0.0リリース v8.0.0 · medcl/elasticsearch-analysis-pinyin · GitHub
elasticsearch-8.0.0/plugin
の下にpinyin
フォルダーを作成します- 解凍されたコンテンツをピンインフォルダーに Cp します。
- リブート
まず、ピンインのワード ブレーカーには 2 つのルールがあります。
- ピンイン - 中国語の文字をピンインに変換します
- pinyin_first_letter - 中国語のピンインの最初の文字を抽出します
{
"analyzer": "pinyin",
"text":"我明天很开心"
}
何が実現できますか? 格納された中国語文字とピンイン検索を実現できますが、インデックスを作成する際にトークナイザーをピンインで指定する必要があります
インデックステストを作成します
-
インデックスを作成する
http://192.168.17.128:9200/t_biz_test_pinyin
{ "mappings": { "properties": { "id": { "type": "text" }, "name": { "type": "text", "analyzer": "ik_max_word", "search_analyzer":"ik_smart" }, "age":{ "type": "long" } } } }
-
データを挿入する
トークンフィルター
トークンフィルターはトークナイザー以降の処理です 複数設定可能で、内蔵されているものも多数あります ほとんどのシーンはキャラクターフィルターで処理可能です このトークンフィルターはスキルの拡張コンテンツとして利用可能です詳細については、「Token Filters Elasticsearch Chinese Documentation (kilvn.com)」を参照してください。
長さフィルター
【投稿】http://ip:port/_analyze
{
"tokenizer": "pattern",
"filter": [
{
"type": "length",
"min": 2,
"max": 3
}
],
"text": "I'M HAPPY_with you 结束 and end"
}
カスタムアナライザー
トークナイザーには 3 つの設定があります
- インデックスを作成するときに、
settings
グローバル トークナイザーを次のように設定します。 - フィールドにトークナイザーを指定します
- API設定の変更
- 作成時に指定される
[put] http://ip:port/{インデックス名}
文字フィルター、トークナイザー、および単語分割フィルターは以下に設定されています。文字フィルターと単語分割フィルターは両方ともアナライザーと同じレベルで定義する必要があります。
設定後、フィルタリングされた単語を通過したデータはほぼデータになります。
{
"settings": {
// 分析
"analysis": {
// 分析器
"analyzer": {
// 自定义分词器名称
"ik": {
// 字符过滤器
"char_filter": [
"html_strip",
"cus_mapping",
"cus_pattern_replace"
],
// 分词器
"tokenizer": "ik_max_word",
// 添加自定义的分词过滤器length
"filter": [
"cus_length"
]
},
"pinyin": {
"tokenizer": "pinyin"
}
},
// 定义字符过滤器
"char_filter": {
// 自定义映射过滤器
"cus_mapping": {
"type": "mapping",
"mappings": [
"这是=>为"
]
},
// 自定义正则过滤器
"cus_pattern_replace": {
"type": "pattern_replace",
"pattern": "(http://){1,}",
"replacement": "http://"
}
},
//定义长度分词过滤器
"filter": {
// 自定义长度过滤器
"cus_length": {
"type": "length",
"min": 2,
"max": 3
}
}
}
},
"aliases": {
},
"mappings": {
"properties": {
"id": {
"type": "text"
},
"name": {
"type": "text",
"analyzer": "ik",
"search_analyzer": "ik"
},
"age": {
"type": "long"
}
}
}
}
-
インデックスはすでに存在しており、新しく追加されたフィールドに対してのみ指定できます
[put] http://ip:port/{インデックス名}/_mapping
{ "properties": { // 这个字段可以存在,但要和旧的一样 "name": { "type": "text", "analyzer": "ik_max_word", // 入数据时,尽量分词 "search_analyzer": "ik_smart" // 查询时不过多分词处理 } } }
-
設定の変更 (変更、動的変更はサポートされていないため、インデックスを閉じる必要があります)
-
【post】http://ip:port/{インデックス名}/_close
-
[put] http://ip:port/{インデックス名}/_settings
{ "analysis": { "analyzer": { "ik": { "tokenizer": "pinyin" } } } }
-
[投稿] http://ip:port/{インデックス名}/_open
-
テスト
単語ではwww
検索baidu
できません
再インデックス
コピーレプリケーションと同様、ターゲットインデックスにデータが存在する場合は上書きされます。
【投稿】http://ip:port/_reindex
{
"source": {
"index": "t_biz_test"
},
"dest": {
"index": "t_biz_data_write"
}
}
これはデータ移行に使用でき、クエリされたデータをターゲット インデックスにコピーできます。ソース インデックスの名前はターゲット インデックスの名前とは異なる必要があります。
{
"source": {
"index": "t_biz_test"
},
"dest": {
"index": "t_biz_data_write",
// internal:强制覆盖数据,external:创建缺失的文档
"version_type": "internal",
// 创建缺失的文档,已存在的文档会出现版本冲突
"op_type":"create"
}
}
データの一部をターゲット テーブルにコピー、またはコピーし、クエリを通じてソース データをクエリします。
{
"source": {
"index": "t_biz_data_write",
"query":{
"match":{
"name":"=2"
}
}
},
"dest": {
"index": "t_biz_test2"
//"version_type": "external"
//"op_type":"create"
}
}
インデックスの再作成が非常に遅く、タイムアウト (デフォルトでは 30 秒) が発生する場合は、次の処理を試してください。
- データのバッチサイズを設定します。
{
"source": {
"index": "t_biz_data_write",
// 批量大小,最大10000
"size":4000
},
"dest": {
"index": "t_biz_test2"
//"version_type": "external"
//"op_type":"create"
}
}
- スライスサイズを設定する
注: スライスの数 = スライスの数の場合、パフォーマンスは最高になります。そのため、ソースにインデックスが 1 つしかない場合、スライス = スライスの数、複数のインデックスがある場合、スライス = スライスの最小数になります。
しかし、この断片化の設定に関しては、私が望んでいた答えは得られませんでした。
40万件のデータをテストしました(テストは正確ではなく、中間値が何度も取られます)
- ソース インデックスとターゲット インデックスは両方とも 9 シャードで、1 シャードと 9 シャードの再インデックス時間は基本的に同じで、約 8 秒です。
- ソース インデックスとターゲット インデックスは両方とも 1 シャード、再インデックス 1 割り当て、アップおよびダウンは 10 秒です
- ソース インデックス 9 割り当て、ターゲット インデックス 1 シャード、再インデックス 9 シャード、約 14
- ソース インデックス 9 割り当て、ターゲット インデックス 1 シャード、再インデックス 1 シャード、約 20 秒、場合によっては 40 秒
- ソース インデックス 1 割り当て、ターゲット インデックス 9 シャード、再インデックス 9 シャード、約 20 秒
- ソース インデックス 1 割り当て、ターゲット インデックス 9 シャード、再インデックス 1 シャード、約 18 秒
-
部数が0に変更されました
[get] http://ip:port/{インデックス名}
// 响应里有这个两个东西,分片数据和副本数 "number_of_shards": "1", // 创建索引时,可以指定该值为0 "number_of_replicas": "1",
集まる
2 台のマシンと現在のステーション グループ クラスタを準備します。以下は元の構成です。
# 集群名称
cluster.name: ali-es
# 节点名称
node.name: node-1
# 保留地址到网络,可以被其他服务器发现
network.host: 0.0.0.0
# 端口
http.port: 9200
# 以master启动的节点
cluster.initial_master_nodes: ["node-1"]
# 安全认证(这里我关闭了)
xpack.security.enabled: false
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
Elasticsearch テクニカル分析 (6): 自動検出メカニズム - Zen Discoveryedit - JaJian - Blog Garden (cnblogs.com)
クラスタ構成は以下の通りですので、それぞれの構成を変更してください
- ノード名を変更します:node.name=xxx
- 通信ポートの追加:transport.port:9300
- 検出可能なホストのリストを追加します: Discovery.seed_hosts: ["192.168.17.128:9300", "192.168.17.129:9300", "192.168.17.130:9300"]
- ノードの役割を追加します(マスター:マスターノード、データ:データバックアップノード):node.roles: [master,data]
# 集群名称
cluster.name: ali-es
# 节点名称
node.name: node-1
node.roles: [master,data]
# 保留地址到网络,可以被其他服务器发现
network.host: 0.0.0.0
# 端口
http.port: 9200
# 节点间通信端口
transport.port: 9300
# 以master启动的节点,如果这里设置的节点都挂了,那么,该节点就无法组成集群(处于异常不可以状态)
cluster.initial_master_nodes: ["node-1","node-2","node-3"]
# 能够可发现节点主机列表,9300是通信端口,不是9200,不写,就默认通信端口
discovery.seed_hosts: ["192.168.17.128:9300","192.168.17.129:9300","192.168.17.130:9300"]
# 备选主节点最小个数,一般设置为备选主节点数/2+1
discovery.zen.minimum_master_nodes: 2
# 解决跨域问题配置
http.cors.enabled: true
http.cors.allow-origin: "*"
# 安全认证(这里我关闭了)
xpack.security.enabled: false
xpack.security.enrollment.enabled: true
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
初めて成功させるには、いくつかの手順が必要です。
-
解凍したファイルパスの権限を一般ユーザーに変更(rootユーザーでは起動できない版)
フォルダーと次のサブフォルダーをユーザー ali が所有するユーザー グループ ali として設定します。
chown -R ali:ali elasticsearch
-
config/elasticsearch.ymlを変更する
クラスター名
クラスタ名: エイリアス
次のクラスター ノードに対応するノード名
ノード名: ノード-1
サービスIP
ネットワーク.ホスト: 0.0.0.0
公開ポート
http.ポート: 9200
クラスタノード
cluster.initial_master_nodes: [“node-1”]
-
vim /etc/sysctl.conf
vm.max_map_count=655360
修正後も
sysctl -p
有効にする -
vim /etc/security/limits.conf
* soft nproc 2048
に変更されました* soft nproc 4096
* soft nofile 65536 * hard nofile 131072 * soft nproc 4096 * hard nproc 4096
*
すべてのユーザーソフト: 現在のシステムの実効設定値を指します。
ハード: システムで設定できる最大値を示します。nofile: 開いているファイルの最大数
noproc: プロセスの最大数 -
開始します。root から開始しないことを忘れないでください
通常ユーザーを切り替える
しかし
./bin/elasticsearch
#バックグラウンドスタート
./bin/elasticsearch -d
開始エラー 1:
Skipping security auto configuration because the node keystore file [/data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore] is not a readable regular file Exception in thread "main" org.elasticsearch.bootstrap.BootstrapException: java.nio.file.AccessDeniedException: /data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore Likely root cause: java.nio.file.AccessDeniedException: /data/elasticsearc/elasticsearch-8.0.0/config/elasticsearch.keystore at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:90
elasticsearch.keystore
このファイルはrootユーザー権限では読み取れないためです。[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-NmMCuHfE-1678515293253)(E:/ALI/Documents/%E5%BE%) 85%E5%8F%91 %E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230212161221952.png)]
一度設定する
chown -R ali:ali config/elasticsearch.keystore
開始エラー 2:
ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@27d5a580] unable to create manager for [/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog.json] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@52851b44[pattern=/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog-%i.json.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[SizeBasedTriggeringPolicy(size=1073741824)]), strategy=DefaultRolloverStrategy(min=1, max=4, useMax=true), advertiseURI=null, layout=co.elastic.logging.log4j2.EcsLayout@550a1967, filePermissions=null, fileOwner=null]] java.lang.IllegalStateException: ManagerFactory [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$RollingFileManagerFactory@27d5a580] unable to create manager for [/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog.json] with data [org.apache.logging.log4j.core.appender.rolling.RollingFileManager$FactoryData@52851b44[pattern=/data/elasticsearc/elasticsearch-8.0.0/logs/ali-es_index_search_slowlog-%i.json.gz, append=true, bufferedIO=true, bufferSize=8192, policy=CompositeTriggeringPolicy(policies=[SizeBasedTriggeringPolicy(size=1073741824)]), strategy=DefaultRolloverStrategy(min=1, max=4, useMax=true), advertiseURI=null, layout=co.elastic.logging.log4j2.EcsLayout@550a1967, filePermissions=null, fileOwner=null]] at org.apache.logging.log4j.core.appender.AbstractManager.getManager(AbstractManager.java:116) at org.apache.logging.log4j.core.appender.OutputStreamManager.getManager(OutputStreamManager.java:100) at org.apache.logging.log4j.core.appender.rolling.RollingFileManager.getFileManager(RollingFileManager.java:217) at org.apache.logging.log4j.core.appender.RollingFileAppender$Builder.build(RollingFileAppender.java:146) at org.apache.logging.log4j.core.appender.RollingFileAppender$Builder.build(RollingFileAppender.java:62) at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:122) at org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:1120) at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:1045) at org.apache.logging.log4j.core.config.AbstractConfiguration.createConfiguration(AbstractConfiguration.java:1037) at org.apache.logging.log4j.core.config.AbstractConfiguration.doConfigure(AbstractConfiguration.java:651) at org.apache.logging.log4j.core.config.AbstractConfiguration.initialize(AbstractConfiguration.java:247) at org.apache.logging.log4j.core.config.AbstractConfiguration.start(AbstractConfiguration.java:293) at org.apache.logging.log4j.core.LoggerContext.setConfiguration(LoggerContext.java:626) at org.apache.logging.log4j.core.LoggerContext.start(LoggerContext.java:302) at org.elasticsearch.common.logging.LogConfigurator.configure(LogConfigurator.java:222) at org.elasticsearch.common.logging.LogConfigurator.configure(LogConfigurator.java:118) at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:313) at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:166) at org.elasticsearch.bootstrap.Elasticsearch.execute(Elasticsearch.java:157) at org.elasticsearch.cli.EnvironmentAwareCommand.execute(EnvironmentAwareCommand.java:77) at org.elasticsearch.cli.Command.mainWithoutErrorHandling(Command.java:112) at org.elasticsearch.cli.Command.main(Command.java:77) at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:122) at org.elasticsearch.bootstrap.Elasticsearch.main(Elasticsearch.java:80) 2023-02-12 03:15:28,044 main ERROR Unable to invoke factory method in class org.apache.logging.log4j.core.appender.RollingFileAppender for element RollingFile: java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.RollingFileAppender
これは、ログファイルも root 権限下にあるためで、最初に起動したときに使用した root アカウントであるはずで、その結果、初期化時にこれらのファイルは root アカウントの下に作成されましたが、その後は異常でした。非 root ユーザーに切り替える。
[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-UIC0vJMg-1678515293254)(E:/ALI/Documents/%E5%BE%) 85%E5%8F%91 %E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230212162305207.png)]
操作3: 最大仮想メモリ領域 vm.max_map_count [65530] が低すぎます。少なくとも [262144] まで増やしてください。
これは 3 番目と 4 番目のステップであり、構成がないか、効果がありません。
開始エラー 4:
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
これは、JVM のパラメータが古いためです。esディレクトリの
config/jvm.options
vi config/jvm.options を次
のように-XX:+UseConcMarkSweepGC
変更する必要があります。-XX:+UseG1GC
-
ポートを開く
firewall-cmd --add-port=9200/tcp --permanent
ファイアウォール-cmd --reload
-
ssl をオフにする (vim config/elasticsearch.yml)
この構成は起動後にのみ使用可能であり、解凍後は使用できません。
xpack.security.enabled: false
-
前回の単一ノードの実行で生成された
data
ファイルの下にあるものをすべて削除します。rm -rf data/*
-
非 root ユーザーが 3 つのノードを起動する
-
各サーバーのポート 9200 9300 を開きます
最終検査クラスター
クラスターのステータスを表示する
【get】http://ip:port/_cluster/health
{
"cluster_name": "ali-es", // 集群名称
"status": "green", //3种:red(没有主分片),yellow(主分片成功,没有分片副本),green(分配所有分片,正常状态)
"timed_out": false, // 如果fasle,则响应在timeout参数指定的时间段内返回(默认30s)
"number_of_nodes": 3, // 总节点数
"number_of_data_nodes": 3, // 数据节点数
"active_primary_shards": 1, // 存活的主分片数
"active_shards": 2, // 存活的分片数,一般是primary_shards的2倍
"relocating_shards": 0, // 迁移中的分片数
"initializing_shards": 0, // 初始化的分片数
"unassigned_shards": 0, // 未分配的分片数
"delayed_unassigned_shards": 0, // 延迟未分配的分片数
"number_of_pending_tasks": 0, // 等待处理的任务数
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0 // 集群中可用分片的百分比
}
【get】http://ip:port/_cat/nodes
node.role : dm値データ、マスター
master : * はマスターノードを示します
スプリングブーツ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
private final ElasticsearchOperations operations;
private final RestHighLevelClient highLevelClient;
以下の例は私のものです都不是用JPA
が、JPA が良くないということではなく、状況に応じて使用してください。
JPA を使用する場合、インデックスは自動的に作成されますが、ネイティブ API と同様に、特別なフィールドに対する特別な要件は自分で設定する必要があります。
インデックスを作成する
ここのツールは、マッピング オブジェクトと設定オブジェクトに対応する API をカプセル化しているため、ここではマッピングと設定内のオブジェクトを動的に検査するだけで済みます。また、ここでは 2 つの構築方法も提供されています。XcontentBuilder``json
これはインデックスを作成するためのパラメータです
{
"settings": {
// 分析
"analysis": {
...
}
},
"aliases": {
},
"mappings": {
"properties": {
...
}
}
}
XContentBuilder でパラメータを構築する方法
これは json に似ており、startObject() が始まり、endObject() が終わりで、クロージャー オブジェクトを形成します。
geo_point
ここでは、geo_shape
インデックスを作成するときに直接指定する必要がある2 つの特別なオブジェクトを作成しました。
// 创建索引,这里使用builder构建方式
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject();
// Mappings参数对象下的properties属性
builder.startObject("properties");
builder.startObject("pro");
builder.field("type", "object");
builder.endObject();
builder.startObject("geoPoint");
builder.field("type", "geo_point");
builder.endObject();
builder.startObject("geoJson");
builder.field("type", "geo_shape");
builder.endObject();
builder.startObject("name");
builder.field("type", "text");
builder.field("analyzer", "ik_max_word");
builder.field("search_analyzer", "ik_smart");
builder.endObject();
builder.endObject();
builder.endObject();
CreateIndexRequest request = new CreateIndexRequest(index);
request.mapping(builder);
highLevelClient.indices().create(request, RequestOptions.DEFAULT);
上記の説明は実際には次のようになります。
{
"mappings": {
"properties": {
"pro":{
"type":"object"
},
"geoPoint":{
"type":"geo_point"
},
"geoJson":{
"type":"geo_shape"
},
"name":{
"type":"text",
"analyzer":"ik_max_word",
"search_analyzer":"ik_smart"
}
}
}
}
jsonでパラメータを構築する方法
String param = "{\n"
+ " \"mappings\": {\n"
+ " \"properties\": {\n"
+ " \"pro\":{\n"
+ " \"type\":\"object\"\n"
+ " },\n"
+ " \"geoPoint\":{\n"
+ " \"type\":\"geo_point\"\n"
+ " },\n"
+ " \"name\":{\n"
+ " \"type\":\"text\",\n"
+ " \"analyzer\":\"ik_max_word\",\n"
+ " \"search_analyzer\":\"ik_smart\"\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ "}";
JSONObject paramJson = JSON.parseObject(param);
CreateIndexRequest request = new CreateIndexRequest(index);
request.mapping(paramJson.getJSONObject("mappings"));
return highLevelClient.indices().create(request, RequestOptions.DEFAULT);
マッピング情報を取得する
return operations.indexOps(IndexCoordinates.of(index)).getMapping();
インデックスの削除
GetIndexRequest get = new GetIndexRequest(index);
if (highLevelClient.indices().exists(get, RequestOptions.DEFAULT)) {
DeleteIndexRequest request = new DeleteIndexRequest(index);
return highLevelClient.indices().delete(request, RequestOptions.DEFAULT);
}
return false;
再インデックス
ReindexRequest reindexRequest = new ReindexRequest();
reindexRequest.setDestIndex(destIndex);
reindexRequest.setSourceIndices(sourceIndex);
return highLevelClient.reindex(reindexRequest, RequestOptions.DEFAULT);
新しいデータ (es7)
注: geoPoint が入力されている場合、これは単なる点です。面または線をクエリする必要がある場合は、それを見つけることはできませんが、点も geoShape に入力されている場合は、点、線、および面をクエリできます。つまり、半径クエリの場合は geoPoint のみをクエリできますが、サーフェスとラインはクエリできません。
public Object addTestData(String index) {
List<DataAll> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
DataAll temp = new DataAll();
temp.setName("name-" + i);
temp.setDataName("dataname-" + i);
temp.setDataId("dataid=2-" + i);
temp.setGeoPoint(randomGeoPoint());
GeoPoint point = temp.getGeoPoint();
GeometryEntity geometryEntity = new GeometryEntity(new Double[]{
point.getLon(), point.getLat()});
temp.setGeoJson(geometryEntity);
list.add(temp);
}
return esService.saveAll(list, index);
}
private GeoPoint randomGeoPoint() {
return new GeoPoint(RandomUtil.randomDouble(18.1, 30.0), RandomUtil.randomDouble(101.0, 120.1));
}
新しいデータ (es8)
ES8はJavaクライアントを使用して動作する必要がありますが、ここでは新しいデータを追加するときのみJavaクライアントを使用しますか?他の操作は既存の API で操作できるので、今のところ異常は見つかっていませんが、新規追加する場合のみ例外が発生し、新規追加する場合のみ es8 を修正します。
まず es クライアントを設定します。ここでは Spring の設定を変更せずに直接使用しています。
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.4.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.4.1</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.1</version>
</dependency>
@Configuration
public class Es8Config {
@Value("${spring.elasticsearch.rest.uris}")
private String uris;
@Bean
public ElasticsearchClient elasticsearchClient() {
Assert.notBlank(uris, "es配置无效");
int i = uris.indexOf(":");
int i2 = uris.lastIndexOf(":");
String http = uris.substring(0, i);
String ip = StringUtils.removeStart(uris.substring(i + 1, i2), "//");
int port = Integer.parseInt(uris.substring(i2 + 1));
System.out.println();
RestClient client = RestClient.builder(new HttpHost(ip, port, http)).build();
ElasticsearchTransport transport = new RestClientTransport(client, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
public Object addTestData2(String index) throws IOException {
List<DataAll> list = new ArrayList<>();
DataAll temp = new DataAll();
temp.setName("name-1000001");
temp.setDataName("dataname-1000001");
temp.setDataId("dataid=2-1000001");
GeometryEntity geometryEntity = new GeometryEntity();
geometryEntity.setGeometryEntity(getGeometry());
temp.setGeoJson(geometryEntity);
list.add(temp);
return esService.saveAll(list, index);
}
/**
* 这里我直接通过网络下载,是熟悉一下知识,不是一定要获取网络文件
*/
private JSONObject getGeometry() throws IOException {
URL url = new URL("https://geo.datav.aliyun.com/areas_v3/bound/510104.json");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(10000);
urlConnection.setRequestProperty("Content-Type", "application/octet-stream");
urlConnection.setRequestMethod(HttpMethod.GET.name());
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
urlConnection.connect();
if (urlConnection.getResponseCode() != 200) {
throw new RuntimeException("下载geoJson失败");
}
try (BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
int b = 0;
String s = null;
StringBuilder sb = new StringBuilder();
while ((s = br.readLine()) != null) {
sb.append(s);
}
JSONObject jsonObject = JSON.parseObject(sb.toString());
JSONArray features = jsonObject.getJSONArray("features");
JSONObject jsonObject1 = features.getJSONObject(0);
return jsonObject1.getJSONObject("geometry");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
エイリアスを追加する
public Object reAlias(String oldIndex, String newIndex) throws IOException {
// 添加别名映射关系
IndicesAliasesRequest.AliasActions addAction = buildAliasAction(EsIndexProperties.getAlias().getIndexName(),
newIndex,
IndicesAliasesRequest.AliasActions.Type.ADD);
IndicesAliasesRequest request = new IndicesAliasesRequest();
request.addAliasAction(addAction);
// 判断是有要移除的索引,有则添加到action
GetIndexRequest get = new GetIndexRequest(oldIndex);
if (highLevelClient.indices().exists(get, RequestOptions.DEFAULT)) {
IndicesAliasesRequest.AliasActions removeAction = buildAliasAction(
EsIndexProperties.getAlias().getIndexName(), oldIndex, IndicesAliasesRequest.AliasActions.Type.REMOVE);
request.addAliasAction(removeAction);
}
return highLevelClient.indices().updateAliases(request, RequestOptions.DEFAULT);
}
/**
* 构建action对象
* @param alias 别名
* @param index 索引
* @param type action类型
*/
private IndicesAliasesRequest.AliasActions buildAliasAction(String alias, String index,
IndicesAliasesRequest.AliasActions.Type type) {
IndicesAliasesRequest.AliasActions action = new IndicesAliasesRequest.AliasActions(type);
action.index(index);
action.alias(alias);
return action;
}
getById
return operations.get(id, DataAll.class, EsIndexProperties.getAlias());
お問い合わせ
一般的なクエリは次のようになります。
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.termQuery("name", "d"));
operations.search(builder.build(), DataAll.class, IndexCoordinates.of(index));
ここでは、名前フィールドのタイプをキーワードに設定し、直接クエリできるようにします。そうでなく、他の人がテキストを必要とする場合は、次のように記述するのが最善です
builder.withQuery(QueryBuilders.termQuery("name.keyword", "d"));
クエリされる名前の値が配列 (mysql の in) の 1 つである場合
builder.withQuery(QueryBuilders.termQuery("name.keyword", Arrays.asList("d", "ddd")));
あいまいクエリ一致は次のとおりです。
builder.withQuery(QueryBuilders.matchQuery("name.keyword", Arrays.asList("d", "ddd")));
ワイルドカードクエリ
builder.withQuery(QueryBuilders.wildcardQuery("name.keyword", "510*"));
プレフィックスクエリ
builder.withQuery(QueryBuilders.prefixQuery("name.keyword", "51"));
したがって、クエリを実行する場合は、それを直接NativeSearchQueryBuilder
使用して、すべてがサポートされています。
注: es はデフォルトで 10,000 個のデータをクエリし、10,000 個を超えるデータは返しません。このとき、es の max_resource_window を設定する必要があり、同時にクエリを実行するときにパラメータを取得する必要がありますbuild.setTrackTotalHits(true);
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
builder.withQuery(QueryBuilders.prefixQuery("name.keyword", "51"));
NativeSearchQuery build = builder.build();
build.setTrackTotalHits(true);
return operations.search(build, DataAll.class, EsIndexProperties.getIndex());
地理情報の位置クエリ
geo_point
これら2 つのタイプは以前に定義されておりgeo_shape
、地理的位置をクエリするために使用されます。 geo_point は点を表し、 geo_shape は形状を表します。
geo_point に対応する型は公式で決められておりGeoPoint
、データを格納する際に緯度経度を検証するものですが、公式からも提供されており、org.elasticsearch.common.geo.GeoUtils
自分で緯度経度を検証できるようになっています。
public static boolean isValidLatitude(double latitude) {
return !Double.isNaN(latitude) && !Double.isInfinite(latitude) && !(latitude < -90.0) && !(latitude > 90.0);
}
public static boolean isValidLongitude(double longitude) {
return !Double.isNaN(longitude) && !Double.isInfinite(longitude) && !(longitude < -180.0) && !(longitude > 180.0);
}
次に geo_shape があります。この型に対応する型を作成する必要があります。これは、GeoJson 形式と WKT 形式の 2 つの形式をサポートしています。
まず geojson について話しましょう。以下は標準の geoJson で、geometry
この部分は es によって保存されます。
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": {
"prop0": "value0"
}
}
]
}
type
したがって、 2 つのプロパティを含むオブジェクトを定義する限りは可能ですcoordinates
。
コード例:
public class GeometryEntity implements Serializable {
private static final long serialVersionUID = -112510376095434074L;
private String type = "Polygon";
private Object coordinates;
}
なお、WKTの形式は次のようになります。座標はスペースで区切られ、複数の座標はカンマで区切られ、座標で記述されるオブジェクトは括弧で囲まれ、先頭の型で座標の形状を定義します。
POINT(107.2 23.5)
LINESTRING(107.2 23.5,108.6 22.6)
POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))
MULTIPOINT(3.5 5.6, 4.8 10.5)
公式は、私たちが変換するための統計も提供しますorg.elasticsearch.geometry.utils.WellKnownText
GeographyValidator geographyValidator = new GeographyValidator(true);
// 这里true:强制类型转换(对坐标数据),geographyValidator:geo验证器
WellKnownText wellKnownText = new WellKnownText(true, geographyValidator);
wellKnownText.toWKT(geo对象)
コード例
double[] x = new double[5];
double[] y = new double[5];
for (int i = 0; i < 4; i++) {
x[i] = 101 + i;
y[i] = 21 + i;
}
// 面 首尾左边相同,即保证闭环
x[4] = x[0];
y[4] = y[0];
Polygon p = new Polygon(new LinearRing(x, y));
GeographyValidator geographyValidator = new GeographyValidator(true);
// 这里true:强制类型转换(对坐标数据),geographyValidator:geo验证器
WellKnownText wellKnownText = new WellKnownText(true, geographyValidator);
String s = wellKnownText.toWKT(p);
System.out.println(s);
ジオポイントとジオシェイプを定義する
public class DataAll implements Serializable {
private static final long serialVersionUID = -59319460795559295L;
/** 资源主键 */
@Field
private String dataId;
/** 资源名称 */
@Field
private String dataName;
/** 名称 */
@Field
private String name;
// geoShape字段,
@Field
private GeometryEntity geoJson;
@GeoPointField
@Field
private GeoPoint geoPoint;
/** 创建时间 */
@Field
private Long createTime;
}
@Data
public class GeometryEntity implements Serializable {
private static final long serialVersionUID = -112510376095434074L;
private String type = "Polygon";
private Object coordinates;
public GeometryEntity() {
}
public GeometryEntity(Double[] point) {
this.type = GeoShapeType.POINT.shapeName();
this.coordinates = Arrays.asList(point[0], point[1]);
}
public GeometryEntity(List<Double[]> coordinates) {
this.coordinates = Collections.singletonList(coordinates);
}
public void setPoint(Double[] point) {
this.type = GeoShapeType.POINT.shapeName();
coordinates = point;
}
public final void setLineString(List<List<Double[]>> line) {
if (line.size() > 1) {
// 多线
this.type = GeoShapeType.MULTILINESTRING.shapeName();
// coordinates = line;
} else {
// 单线
this.type = GeoShapeType.LINESTRING.shapeName();
}
coordinates = line;
}
public void setPolygon(List<List<Double[]>> polygon) {
if (polygon.size() > 1) {
// 多面
this.type = GeoShapeType.MULTIPOLYGON.shapeName();
List<List<List<Double[]>>> cor = new ArrayList<>();
for (List<Double[]> doubles : polygon) {
List<List<Double[]>> temp = new ArrayList<>();
temp.add(doubles);
cor.add(temp);
}
coordinates = cor;
} else {
this.type = GeoShapeType.POLYGON.shapeName();
List<List<Double[]>> cor = new ArrayList<>();
cor.add(polygon.get(0));
coordinates = cor;
}
}
public void setGeometry(List<List<Double[]>> geometry) {
boolean single = false;
for (List<Double[]> pol : geometry) {
if (isLineString(pol)) {
single = true;
break;
}
}
if (single) {
setLineString(geometry);
} else {
setPolygon(geometry);
}
}
public static boolean isLineString(List<Double[]> pol) {
Double[] first = pol.get(0);
Double[] last = pol.get(pol.size() - 1);
return !first[0].equals(last[0]) || !first[1].equals(last[1]);
}
}
範囲クエリ
リクエストパラメータ
@Data
public class DataAllScopeSearchRequest implements Serializable {
private static final long serialVersionUID = -1330158592736514960L;
private String index;
private Circle circle;
private List<Point> points;
private Line line;
@Data
public static class Line implements Serializable {
private static final long serialVersionUID = -4321687487898249724L;
/** 线缓存宽度 */
private Double radi;
private List<Point> points;
}
@Data
public static class Circle implements Serializable {
private static final long serialVersionUID = 4878467793495368658L;
/** 半径(单位米) */
private String radi;
/** 经度 */
private Double lon;
/** 维度 */
private Double lat;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Point implements Serializable {
private static final long serialVersionUID = 8329115569185013087L;
private Double lat;
private Double lon;
}
}
達成
public SearchHits<DataAll> searchForScope(DataAllScopeSearchRequest request) {
String geoField = DataAll.getGeoPointField();
String geoJson = DataAll.getGeoJsonField();
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolquery = new BoolQueryBuilder();
// 半径范围查询,不能查询到面
if (request.getCircle() != null) {
DataAllScopeSearchRequest.Circle ci = request.getCircle();
boolquery.filter(QueryBuilders.geoDistanceQuery(geoField)
.distance(ci.getRadi())
.geoDistance(GeoDistance.PLANE)
.point(new GeoPoint(ci.getLat(), ci.getLon())));
}
// 多边形查询,可以查询到点和面
if (CollectionUtils.isNotEmpty(request.getPoints())) {
List<DataAllScopeSearchRequest.Point> points = delRepeatPoint(request.getPoints());
if (points.size() < 4) {
throw new RuntimeException("面积查询最少3个不同的点");
}
Polygon p = buildPolygon(points);
GeoShapeQueryBuilder b = new GeoShapeQueryBuilder(geoJson, p);
b.relation(ShapeRelation.INTERSECTS);
boolquery.filter(b);
}
// 线查询
if (request.getLine() != null && CollectionUtils.isNotEmpty(request.getLine().getPoints())) {
Polygon p = buildLineBufferArea(request);
GeoShapeQueryBuilder b = new GeoShapeQueryBuilder(geoJson, p);
b.relation(ShapeRelation.INTERSECTS);
boolquery.filter(b);
}
builder.withQuery(boolquery);
NativeSearchQuery build = builder.build();
// 全部查询
build.setTrackTotalHits(true);
return operations.search(build, DataAll.class, IndexCoordinates.of(request.getIndex()));
}
/**
* 去除 首尾之前重复的点,防止连续重复的点出现
*/
private List<DataAllScopeSearchRequest.Point> delRepeatPoint(List<DataAllScopeSearchRequest.Point> points) {
if (points.size() < 3) {
return points;
}
DataAllScopeSearchRequest.Point first = points.get(0);
DataAllScopeSearchRequest.Point last = points.get(points.size() - 1);
double lastX = 0;
double lastY = 0;
ArrayList<DataAllScopeSearchRequest.Point> p = new ArrayList<>();
for (DataAllScopeSearchRequest.Point point : points.subList(1, points.size() - 1)) {
if (point.getLon() == lastX && point.getLat() == lastY) {
log.warn("不允许连续的相同的点");
continue;
}
lastX = point.getLon();
lastY = point.getLat();
p.add(new DataAllScopeSearchRequest.Point(point.getLat(), point.getLon()));
}
p.add(0, first);
p.add(p.size(), last);
return p;
}
/**
* 构建多边形
*/
private static Polygon buildPolygon(List<DataAllScopeSearchRequest.Point> points) {
double[] x = new double[points.size()];
double[] y = new double[points.size()];
int i = 0;
for (DataAllScopeSearchRequest.Point point : points) {
x[i] = point.getLon();
y[i] = point.getLat();
++i;
}
// 面
LinearRing linearRing = new LinearRing(x, y);
return new Polygon(linearRing);
}
/**
* 构建线缓存面
*/
private static Polygon buildLineBufferArea(DataAllScopeSearchRequest request) {
Coordinate[] coordinates = new Coordinate[request.getLine().getPoints().size()];
List<DataAllScopeSearchRequest.Point> linePoints = request.getLine().getPoints();
for (int i = 0; i < linePoints.size(); i++) {
coordinates[i] = new Coordinate(linePoints.get(i).getLon(), linePoints.get(i).getLat());
}
CoordinateSequence coordinateSequence = new CoordinateArraySequence(coordinates);
LineString lineString = new LineString(coordinateSequence, new GeometryFactory());
BufferOp bufferOp = new BufferOp(lineString);
double distance = request.getLine().getRadi() / LINE_BUFFER_SCA * 360;
com.vividsolutions.jts.geom.Geometry geometry = bufferOp.getResultGeometry(distance);
Coordinate[] cor = geometry.getCoordinates();
double[] x = new double[cor.length];
double[] y = new double[cor.length];
for (int i = 0; i < cor.length; i++) {
x[i] = cor[i].x;
y[i] = cor[i].y;
}
LinearRing linearRing = new LinearRing(x, y);
return new Polygon(linearRing);
}
ハイライトクエリ
public Object highlight(String index, String keyword) {
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
// 方法一:自定义高亮
HighlightBuilder highBuilder = new HighlightBuilder();
highBuilder.field("dataName");
highBuilder.preTags("<span style='color:red'>");
highBuilder.postTags("</span>");
builder.withHighlightBuilder(highBuilder);
// 方法二:默认高亮
// builder.withHighlightFields(new HighlightBuilder.Field("dataName"));
builder.withQuery(QueryBuilders.matchQuery("dataName", keyword));
NativeSearchQuery build1 = builder.build();
build1.setTrackTotalHits(true);
return restTemplate.search(build1, DataAll.class, IndexCoordinates.of(index));
}