Elasticsearch - 기본 사항(참고)

기사 디렉토리

一、 엘라스틱서치

방대한 양의 데이터에서 필요한 데이터를 빠르게 찾을 수 있도록 도와주는 강력한 오픈 소스 검색 엔진입니다.

Kibana, Logstash 및 Beats, 즉 Elastic 스택(ELK)과 결합됩니다. 로그 데이터 분석, 실시간 모니터링 및 기타 분야에서 널리 사용됩니다.

Elastic Stack의 핵심은 데이터의 저장, 검색, 검색을 담당하는 Elasticsearch입니다. Logstash와 Beats는 데이터 캡처에 있습니다. Kibana는 데이터 시각화에 관한 것입니다.

2. 포워드 인덱스와 인버티드 인덱스

우리는 다음과 같은 형태를 가지고 있습니다

ID 제목 가격
1 기장 전화 123
2 화웨이 휴대폰 124
화웨이 충전기 124
4 기장 충전기 124

기존 데이터베이스는 정방향 인덱스를 사용합니다.

  • mysql의 인덱스는 기본 키 ID를 쿼리할 때 멀티 포크 트리를 통해 빠르게 잠글 수 있는 정방향 인덱스입니다.
  • 퍼지 검색, 특히 이런 '%mobile%' 의 경우 인덱스로 갈 수 없고 하나씩만 검색할 수 있어 효율성이 상대적으로 떨어집니다.

그러나 반전 인덱스의 경우 다음과 같습니다.

  • 문서(문서): 각 데이터 조각은 문서입니다.
  • 용어: 문서가 의미론적으로 구분되는 단어
기입 문서 ID
기장 1,2
휴대전화 1,2
화웨이 2,3
충전기 3,4
  1. "Huawei 휴대 전화"를 검색하려는 경우 "Huawei"와 "휴대 전화"로 구분됩니다.
  2. 화웨이: 2, 3, 모바일: 1, 2
  3. 따라서 하나씩 찾는 것보다 빠릅니다.
  4. 동시에 다른 항목에 나타나는 ID가 많을수록 연관성이 높아집니다.

3. 엘라스틱서치와 MySQL

3.1 문서 문서

문서 저장 지향적입니다. 이는 상품 데이터의 일부이거나 데이터베이스의 주문 정보일 수 있습니다.
문서 데이터는 Json 형식으로 직렬화되어 Elasticsearch에 저장됩니다. 개념적으로 Mysql의 데이터 행으로 간주할 수 있습니다.
아래는 4개의 문서입니다.

{
    
    
	"id":1, 
	"title":"小米手机",
	"price":123
}
{
    
    
	"id":2,
	"title":"华为手机",
	"price":124
}
{
    
    
	"id":3,
	"title":"华为充电器",
	"price":124
}
{
    
    
	"id":4,
	"title":"小米充电器",
	"price":124
}

3.2 인덱스 라이브러리 인덱스

동일한 유형의 문서 모음입니다. 동일한 구조에 따라 그룹화됩니다.
예를 들어, 사용자 색인, 제품 색인 및 주문 색인에 따라. 개념적으로는 Mysql에서 user 테이블, product 테이블, order 테이블로 볼 수 있습니다.

3.3 MySQL과의 비교

MySQL 엘라스틱서치 설명하다
테이블 색인 인덱스 라이브러리는 테이블과 유사한 문서 모음입니다.
문서 문서는 행과 유사한 데이터 조각이지만 MongoDB와 유사한 Json 형식입니다.
필드 필드, 데이터베이스의 열과 유사한 JSON 문서의 필드
개요 매핑 매핑은 리터럴 필드 제약 조건과 같은 인덱스의 문서에 대한 제약 조건입니다. 데이터베이스 테이블 구조와 유사합니다.
SQL DSL DSL은 Elasticsearch에서 제공하는 Json 스타일의 요청문으로 Elasticsearch를 운영하고 CRUD를 구현하는 데 사용됩니다.

Mysql: 트랜잭션 유형 작업에서 더 우수하여 데이터 일관성 및 보안을 보장합니다.
Elasticsearch: 방대한 데이터 검색, 분석, 계산에 능합니다.

3.4 공통 아키텍처

CRUD
CUD
R
SYN
user
server
Mysql
Elasticsearch

4. 배포 및 토크나이저

4.1 단일 ES 배포

또한 Kibana 컨테이너를 배포해야 하므로 ES를 Kibana 컨테이너에 연결해야 합니다. 먼저 docker로 근거리 통신망을 만들어 봅시다. 관련 정보는 docker network 상세 설명 및 튜토리얼을
참고하세요.

도커 네트워크 생성 네트워크 이름

다음으로 미러를 다운로드합니다.

도커 풀 탄성 검색:7.6.2
도커 풀 키바나:7.6.2

시작하다

docker run -d --name es -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -e "discovery.type=single-node" -v es-data:/usr/share/elasticsearch/data -v es-plugins:/ usr/share/elasticsearch/plugins --privileged --network es-net -p 9200:9200 -p 9300:9300 elasticsearch:7.6.2

포트를 연 후 브라우저에서 입력하십시오. 포트 9200이 다음과 같은 구조이면 성공적으로 시작됩니다.
여기에 이미지 설명 삽입

Kibana 배포(하위 버전은 ELASTICSEARCH_URL임)

docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://es:9200 --network=network name -p 5601:5601 kibana:7.6.2

시작 후 잠시 기다리면 속도가 느려질 수 있습니다. 그런 다음 5601을 방문하십시오.

메시지가 표시되면

기본 인덱스 패턴이 없습니다. 계속하려면 패턴을 선택하거나 만들어야 합니다.
프롬프트에 따라 패턴을 만드세요.

그런 다음 이 페이지로 이동합니다.
여기에 이미지 설명 삽입

4.2 IK 토크나이저 사용

ES 기본 단어 분리기는 중국어 단어 분할에 상대적으로 좋지 않습니다.
테스트하고 페이지 왼쪽에 입력해 보겠습니다.

POST /_analyze
{ "analyzer": "chinese", "text": "중국 토크나이저 테스트" } POST /_analyze { "analyzer": "표준", "text": "표준 토크나이저 테스트" }







말 그대로 찾을 것이다

여기에 이미지 설명 삽입

컨테이너 안으로 들어가 IK 토크나이저를 설치해 봅시다.

docker exec -it es /bin/bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2 .zip
종료
도커 재시작 es

7.6.2는 설치된 elasticsearch이므로 자신의 버전으로 바꾸십시오.

물론 github의 설치 주소 에서 다운 받아 준비된 데이터 볼륨(/var/lib/docker/volumes/es-plugins/_data)에 업로드할 수도 있습니다. 그런 다음 다시 시작하십시오.

IK 토크나이저의 두 가지 모드

  • ik_smart: 최소 분할
  • ik_max_word: 가장 정밀한 분할

최소한의 슬라이싱:
여기에 이미지 설명 삽입
최고의 슬라이싱
여기에 이미지 설명 삽입

4.3 사전 시소러스의 확장

확장 동의어 사전을 확장하려면 Ik 토크나이저 디렉토리의 구성에서 IKAnalyzer.cfg.xml 파일 만 수정하면 됩니다.
여기에 이미지 설명 삽입

그림과 같이

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict"></entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

우리는 사전 파일에 가입합니다

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict"></entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords">stopword.dic</entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

그런 다음 추가/금지하려는 단어를 쓰기 위해 현재 디렉토리에 파일을 만듭니다(없을 경우).

우리는 여기에서 "de"와 "land"라는 의미 없는 두 단어를 금지합니다.
여기에 이미지 설명 삽입
다음과 같은 효과를 볼 수 있습니다
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

5. 연산 인덱스 라이브러리

5.1 인덱스 라이브러리 생성

다음 속성을 포함합니다.

  • 유형: 필드 데이터 유형, 일반적인 단순 유형:
    • 문자열: 텍스트(분할할 수 있는 텍스트), 키워드(정확한 값, 예: 브랜드, 국가, IP 주소)
    • 값: long, integer, short, byte, double, float
    • 부울: 부울
    • 날짜: 날짜
    • 개체: 개체
    • 배열은 없지만 필드는 여러 값을 가질 수 있습니다.
  • index: 인덱스 생성 여부, 기본값은 true입니다. 즉, 모든 필드가 검색에 참여합니다.
  • 분석기: 그런 종류의 토크나이저 사용
  • 속성: "이름"과 같은 이 필드의 하위 필드

데이터:

{
    
    
	"score": 62,
	"rank": 128,
	"name":{
    
     
			"firstName":"小明",
			"lastName": "王"
		}
	"history_rank":[126,125,129],
	"info": "位于XX省XX市XX区XX路X号"
}

색인:

# 创建索引库
# 创建索引库
PUT /yjx23332
{
    
    
  "mappings":{
    
    
	  "properties":{
    
    
		  "info":{
    
    
			  "type":"text",
			  "analyzer": "ik_smart"
		  },
		  "email":{
    
    
			  "type":"keyword",
			  "index":false
		  },
		  "name":{
    
    
		    "type":"object",
			  "properties":{
    
    
				  "firstName":{
    
    
					  "type":"keyword"
				  },
				  "secondName":{
    
    
					  "type":"keyword"
				  }
			  }
		  }
	  }
	}
}

여기에 이미지 설명 삽입

5.2 인덱스 라이브러리 추가, 삭제 및 수정

문의

GET /索引库名称

삭제

DELETE /索引库名称

일반적으로 인덱스 라이브러리를 수정할 수 없으며 ES는 인덱스 라이브러리에 따라 반전 인덱스를 설정합니다.
하지만 필드를 추가할 수 있습니다.

PUT /索引库名称/_mapping
{
    
    
	"properties":{
    
    
		"新字段名称":{
    
    
			"type":"integer"
		}
	}
}

여기에 이미지 설명 삽입

6. 문서 작업

6.1 증가

ID가 추가되지 않으면 임의로 생성됩니다.

POST /索引库名称/_doc/文档id
{
    
    
	"字段1":"值1",
	"字段2":"值2",
	"字段3":{
    
    
		"子属性1":"值3",
		"子属性2":"值4"
	},
	...//
}

6.2 삭제

DELETE /索引库名称/_doc/文档id

6.3 변경

6.3.1 전체 수정

기존 문서를 삭제하고 새 문서를 추가하며
ID가 존재하지 않는 경우 바로 추가됩니다.

PUT /索引库名称/_doc/文档id
{
    
    
	"字段1":"值1",
	"字段2":"值2",
	"字段3":{
    
    
		"子属性1":"值3",
		"子属性2":"值4"
	},
	...//
}

6.3.2 증분 수정, 지정된 필드 값 수정

POST /索引库名称/_update/文档id
{
    
    
	"doc":{
    
    
		"字段1":"新的值"
	}
}

6.4 확인

GET /索引库名称/_doc/文档id

6.5 DSL 쿼리 문서

DSL 공식 문서
범주

  • 모두 쿼리: 조건 없이 일반적인 테스트를 위한 쿼리이며 모두는 페이징되지 않습니다.
    • 매치_올
  • 전체 텍스트 검색 쿼리(전체 텍스트): 단어 세그멘터를 사용하여 사용자 입력 콘텐츠를 세그먼트화한 다음 역색인 데이터베이스에서 일치시킵니다.
    • 일치: 단일 필드
    • multi_match: 다중 필드, 최대한 일치를 사용하고 copy_to를 통해 여러 조건을 하나의 필드에 넣습니다.
  • 정확한 쿼리: 정확한 항목 값을 기준으로 데이터를 검색하며 일반적으로 키워드, 숫자, 날짜, 부울 및 기타 유형의 필드를 검색합니다. (분사가 필요하지 않음)
    • 신분증
    • 범위: 값 범위를 기반으로 하는 쿼리
    • 용어: 용어의 정확한 값을 기반으로 하는 쿼리
  • 지리적 쿼리(geo): 위도 및 경도 정보 기반 쿼리
    • geo_shape
  • 복합 쿼리: 위의 쿼리 조건을 조합하여 쿼리 조건을 조합
    • 부울: 하나 이상의 쿼리 조합
    • function_score: 계산 함수 쿼리, 제어 문서 관련성 계산, 제어 문서 순위.

6.6 DSL 쿼리 형식

DSL 공식 문서

GET /china_area/_search
{
    
    
	"query":{
    
    
		"查询类型":{
    
    
			"查询条件":"条件值"
		}
	}
}

이 부분은 8.6을 실행하고 데이터를 삽입한 후에 수행하는 것을 권장합니다 .

6.6.1 모두 일치

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

6.6.2 경기

GET /china_area/_search
{
    
    
  "query": {
    
    
    "match": {
    
    
      "level":3
    }
  }
}

6.6.3 multi_match

가능한 한 일치를 사용하고 복합 인덱스와 유사한 방식으로 쿼리할 여러 필드를 하나로 넣습니다.


GET /china_area/_search
{
    
    
  "query": {
    
    
    "multi_match": {
    
    
      "query":"北京",
      "fields":["mergerName","name"]
    }
  }
}

6.6.4 용어

GET /china_area/_search
{
    
    
  "query": {
    
    
    "term": {
    
    
      "name":{
    
    
        "value":"昌平区"
      }
    }
  }
}

6.6.5 범위

  • 그테 >=
  • lte <=
  • gt >
  • lt <
GET /china_area/_search
{
    
    
  "query": {
    
    
    "range": {
    
    
      "id":{
    
    
        "gte":7,
        "lte":16
      }
    }
  }
}

6.6.6 geo_shape

도형에는 점, 선, 다각형, 원 좌표가 포함됩니다
. 도형 관계를 구성하는 좌표
에는 다음이 포함됩니다. 쿼리 내 도형이 감싸고 분리된 쿼리 도형이 겹치지 않고 교차 쿼리 도형이 겹칩니다.
문서 ElasticSearch 지리적 유형을 참조하세요. 필드 - geo_point 및 geo_shape 적용 예제

GET /china_area/_search
{
    
    
  "query": {
    
    
    "geo_shape": {
    
    
      "location": {
    
    
        "shape": {
    
    
          "type": "",
          "coordinates": []
        }
      },
      "relation": "within"
    }
  }
}

6.6.7 function_score:

  • TF(용어빈도) = 해당 용어가 등장하는 횟수/문서 내 총 용어 수
    단, 북경을 검색할 때와 같이 이러한 정보가 있는 경우에는 여러 용어가 존재하며 하나만 포함된다는 의미이다. 점수가 오르지 않는다는 것
  • TF-IDF 알고리즘
    IDF(역 문서 빈도) = Log(총 문서 수 / 용어를 포함하는 총 문서 수)
    점수= ∑ in \sum^n_iTF*IDF
  • BM25 알고리즘은 단어 빈도에 영향을 받지 않고 점점 더 커집니다. \sum^n_i에서
    score(Q,d)= ∑log(1+(N-n+0.5)/(n+0.5))* fi f_i에프/( fi + k 1 ∗ ( 1 − b + b ∗ ( dl ) / avgdl ) f_i+k_1*(1-b+b*(dl)/avgdl)에프+케이1( 1-+( d l ) / vg d l ) ) _
GET /china_area/_search
{
    
    
  "query": {
    
    
    "function_score": {
    
    
      "query": {
    
    //原始查询条件
        "match": {
    
    
          "name": "北"
        }
      },
      "functions": [
        {
    
    
          "filter": {
    
    //过滤条件,当符合条件,才会被重新划分
            "term":{
    
    
              "id":"100372"
            }
          },
          //算分函数,结果被称为function score,会与query score运算,得到新算分,常见的有:
          //weight:给定常量值,作为函数结果
          //field_value_factor:用文档中的某个字段值作为函数结果
          //random_score:随机生成一个值,作为函数结果
          //script_score:自定义计算公式,公式结果作为函数结果
          "weight": 10
        }
      ],
      //加权模式,定义function score 与 query score的运算方式:multiply相乘、replace function score 替换 query score。其他 sum、avg、max、min
      "boost_mode": "multiply"
    }
  }
  
}

여기에 이미지 설명 삽입

6.6.8 부울 쿼리

  • must: 일치해야 합니다.
  • should: 하위 쿼리와 선택적으로 일치하거나
  • must_not: 일치하지 않아야 함, 채점에 참여하지 않음, not
  • 필터: 일치해야 함, 점수 계산에 참여하지 않음, 캐시에 저장됨, 향후 사용 시 효율성이 더욱 향상됨
GET /china_area/_search
{
    
    
  "query":{
    
    
    "bool":{
    
    
      "must":[
        {
    
    "term":{
    
    "first": "B"}}
      ],
      "filter": [
        {
    
    "range":{
    
    "id":{
    
    "gte":5}}}
      ]
    }
  }
}

6.7 결과 처리

6.7.1 정렬

정렬을 설정하면 ES는 채점을 포기합니다.
키워드(사전 순서), 값, 날짜, 지리적 좌표를 지원합니다.

GET /china_area/_search
{
    
    
  "query": {
    
    
    "match_all": {
    
    }
  },
  "sort":[
    {
    
    
      "first":{
    
    
        "order": "desc"
      }
    },
    {
    
    
      "level": {
    
    
        "order": "asc"
      }
    }
  ]
}
GET /china_area/_search
{
    
    
  "query": {
    
    
    "match_all": {
    
    }
  },
  "sort":[
    {
    
    
      "first":{
    
    
        "order": "desc"
      }
    },
    {
    
    
      "_geo_distance": {
    
    
        "location": {
    
    
          "lat": 40,
          "lon": 70
        }, 
        "order": "asc",
        "unit": "km",
        "distance_type": "arc",
        "ignore_unmapped": false
      }
    }
  ]
}

6.7.2 페이징

기본적으로 상위 10개 데이터가 반환되며, 더 많은 데이터를 조회하려면 페이징 매개변수를 수정해야 합니다.

+ 크기에서:

  • from 및 size 매개변수를 수정하여 페이지를 매길 결과를 제어합니다.
  • 무작위 페이지 매김 지원
  • 깊이 페이징 문제, 기본 줄은 10000입니다.
GET /china_area/_search
{
    
    
  "query": {
    
    
    "match_all": {
    
    }
  },
  "sort":{
    
    
    "id":"asc"
  }, 
  "from":2,//开始位置,默认0
  "size":10//需要的大小
}

딥 페이징 문제:
ES는 분산되어 있으므로 딥 페이징 문제에 직면하게 됩니다.

  • X 샤드가 있는 경우 페이징 시 각 샤드는 정렬하여 from + size 의 결과 를 생성 하고 그 결과를 코디네이팅 노드 로 반환합니다 . 조정 노드는 ( from + size ) * X 결과를 집계한 다음 정렬하고 마지막으로 from ~ size 에서 결과를 선택합니다 .
  • 검색 페이지 수가 너무 많으면 + 크기의 결과 세트가 커질 수록 메모리 및 CPU 소비가 높아집니다. 따라서 ES에서 설정한 상한은 10000입니다.

공식 딥 페이징 솔루션:

  • search after: 페이징 시 정렬이 필요하며 마지막 정렬 값부터 데이터의 다음 페이지를 쿼리하는 것이 원칙입니다. 공식적으로 추천
    • 쿼리 상한 없음(단어 쿼리 크기는 10000을 초과하지 않음)
    • 페이지 단위로 뒤로만 쿼리할 수 있으며 임의 페이지 넘김을 지원하지 않습니다.
    • 페이지를 넘기기 위해 휴대폰을 아래로 스크롤하는 등 임의의 페이지 넘기기에 대한 수요 검색이 없습니다.
  • scroll: 정렬된 데이터의 스냅샷을 형성하여 메모리에 저장하는 것이 원칙입니다. 공식적으로 사용되지 않습니다.
    • 쿼리 상한 없음(단어 쿼리 크기는 10000을 초과하지 않음)
    • 추가 메모리 소비가 있으며 검색 결과가 실시간이 아닙니다.
    • 방대한 데이터 획득과 진 이모. ES7.1부터는 권장하지 않으며, 애프터서치 솔루션 사용을 권장합니다.

6.7.3 하이라이트

검색 결과에서 검색 결과 키워드가 강조 표시됩니다.
원칙:

  • 검색 결과의 키워드는 태그로 표시됩니다.
  • 페이지의 태그에 CSS 스타일 추가
GET /china_area/_search
{
    
    
  "query": {
    
    
    "term": {
    
    
      "first":{
    
    
        "value":"B"
      }
    }
  },
  "highlight": {
    
    
    "fields":{
    
    
      "name": {
    
    
        "pre_tags":"<em>",//前置标签
        "post_tags":"</em>"//后置标签
        , "require_field_match": "false"//不需要匹配
      } 
    }    
  }
}

일곱, RestClient 작업 인덱스 라이브러리

7.1 준비

공식적으로 다른 언어로 클라이언트를 제공하고, DSL 문을 조합하고, http 요청을 통해 ES로 보낼 수 있도록 도와줍니다.
공식 주소

Java용 LowLevelClient 및 HighLevelClient에는 두 가지 버전이 있습니다. HighLevelClient는 LowLevelClient를 기반으로 더욱 캡슐화되어 사용이 더욱 편리해졌습니다.

저자는 여기에서 데이터를 사용합니다. 국가, 지역, 도, 시, 시, SQL
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

데이터베이스를 가져온 후 먼저 해당 인덱스 라이브러리를 만듭니다.

여기서 "all"은 결합된 인덱스 이름으로 이해할 수 있으며, 하단에 정의하여 상위 클래스 ID, 이름, 이니셜, 레벨로 동시에 검색할 수 있습니다.
검색에 포함되지 않도록 병음 색인을 false로 설정합니다.

PUT /china_area
{
    
    
  "mappings": {
    
    
    "properties": {
    
    
      "id":{
    
    
        "type": "keyword"
      },
      "pid":{
    
    
        "type":"keyword",
        "copy_to": "all"
      },
      "short_name":{
    
    
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "name":{
    
    
        "type":"text",
        "analyzer": "ik_max_word"
      },
      "merger_name":{
    
    
        "type":"text",
        "analyzer": "ik_max_word"
      },
      "level":{
    
    
        "type": "integer",
        "copy_to": "all"
      },
      "pinyin":{
    
    
        "type":"keyword",
        "index":false
      },
      "phone_code":{
    
    
        "type":"keyword"
      },
      "zip_code":{
    
    
        "type": "keyword"
      },
      "first":{
    
    
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
    
    
        "type": "geo_point"
      },
      "area_code":{
    
    
        "type": "keyword"
      },
      "all":{
    
    
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
  
  
}

다음으로 Spring-plus를 사용하여 데이터베이스를 분석하고 코드를 생성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
    </parent>
    <groupId>org.example</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.6</version>
        </dependency>
    </dependencies>
</project>
package com.elastictest;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

public class generator {
    
    
    public static void main(String[] args){
    
    
        FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/数据库?useUnicode=true&characterEncoding=UTF-8&&useSSL=false", "账户", "密码")
                .globalConfig(builder -> {
    
    
                    builder.author("yjx23332") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("E:\\soft\\test\\src\\main\\java"); // 指定输出目录
                })
                .packageConfig(builder -> {
    
    
                    builder.parent("com.elastictest") // 设置父包名
                            .moduleName("") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "E:\\soft\\test\\src\\main\\resources")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
    
    
                    builder.addInclude("china_area") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();

    }
}

여기에 이미지 설명 삽입

마지막으로 다시 가져올 수 있습니다. 서비스 버전과 일치해야 합니다. 작성자는 7.6.2를 사용하므로 여기에서는 이 버전을 사용하여 기본 버전을 재정의합니다.

	<properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <elasticsearch.version>7.6.2</elasticsearch.version>
    </properties>

 		<dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>

7.2 Elasticsearch에 연결

다음으로 junit을 소개합니다.

		<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

그리고 테스트 파일 아래에 생성하십시오.

package com.elastictest;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class ChinaAreaIndexTest{
    
    
    private RestHighLevelClient restHighLevelClient;
    @Test
    void testInit(){
    
    
        System.out.println(restHighLevelClient);
    }
    @BeforeEach
    void setUp(){
    
    
        this.restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("地址:端口")));
    }
    @AfterEach
    void tearDown() throws IOException {
    
    
        this.restHighLevelClient.close();
    }
}

다음과 같이 연결에 성공합니다.
여기에 이미지 설명 삽입

7.3 인덱스 라이브러리 생성

먼저 이전 생성을 삭제합시다.
그런 다음 기본 프로젝트에 상수 클래스를 빌드하여 쿼리 문을 넣습니다.
여기에 이미지 설명 삽입

package com.elastictest.constants;

public class ChinaArea {
    
    
    public static final String  MAPPING_TEMPLATE = "{" +
            "\"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"pid\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"short_name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\":\"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      },\n" +
            "      \"merger_name\":{\n" +
            "        \"type\":\"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      },\n" +
            "      \"level\":{\n" +
            "        \"type\": \"integer\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"pinyin\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"index\":false\n" +
            "      },\n" +
            "      \"phone_code\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"zip_code\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"first\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"area_code\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }" +
            "}";
}

다음으로 테스트 파일에 계속 추가합니다.

	@Test
    void testCreateChinaArea()throws IOException{
    
    
        //1.创建request对象
        CreateIndexRequest createIndexRequest = new CreateIndexRequest("china_area");
        //2.请求参数
        createIndexRequest.source(MAPPING_TEMPLATE, XContentType.JSON);
        //3.发起请求
        restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    }

실행 후 Kibana를 통해 성공 여부를 확인합니다.

여기에 이미지 설명 삽입

7.4 인덱스 라이브러리 삭제 및 인덱스 라이브러리 존재 여부 확인

같은 방법으로

 	@Test
    void testExistsChinaArea()throws IOException {
    
    
        GetIndexRequest getIndexRequest = new GetIndexRequest("china_area");
        boolean exists = restHighLevelClient.indices().exists(getIndexRequest,RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    @Test
    void testDeleteChinaArea()throws IOException{
    
    
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("china_area");
        restHighLevelClient.indices().delete(deleteIndexRequest,RequestOptions.DEFAULT);
    }

여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

8. RestClient 운영 문서

8.1 준비

테스트 의존성을 소개합니다

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.7</version>
        </dependency>

yml 파일에서 구성

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    password: 密码
    username: 账号
    url: jdbc:mysql://127.0.0.1:3306/world?useUnicode=true&characterEncoding=UTF-8&&useSSL=false

스캔 추가

package com.elastictest;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.elastictest.mapper")
public class MainApplication {
    
    
    public static void main(String[] args){
    
    
        SpringApplication.run(MainApplication.class,args);
    }
}

mysql 개체에서 elasticsearch 개체(DTO)로 변환하기 위해 다음 클래스를 만듭니다.

package com.elastictest.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;

@Data
@ApiModel(value = "ChinaArea对象", description = "")
public class ChinaAreaDoc implements Serializable {
    
    

    @ApiModelProperty("ID")
    private Integer id;

    @ApiModelProperty("父id")
    private Integer pid;

    @ApiModelProperty("简称")
    private String shortName;

    @ApiModelProperty("名称")
    private String name;

    @ApiModelProperty("全称")
    private String mergerName;

    @ApiModelProperty("层级 0 1 2 省市区县")
    private Byte level;

    @ApiModelProperty("拼音")
    private String pinyin;

    @ApiModelProperty("长途区号")
    private String phoneCode;

    @ApiModelProperty("邮编")
    private String zipCode;

    @ApiModelProperty("首字母")
    private String first;

    @ApiModelProperty("位置")
    private String location;

    public ChinaAreaDoc(){
    
    }

    public ChinaAreaDoc(ChinaArea chinaArea){
    
    
        this.id = chinaArea.getId();
        this.first = chinaArea.getFirst();
        this.location = chinaArea.getLat() +  "," + chinaArea.getLng();
        this.level = chinaArea.getLevel();
        this.mergerName = chinaArea.getMergerName();
        this.phoneCode = chinaArea.getPhoneCode();
        this.pid = chinaArea.getPid();
        this.pinyin = chinaArea.getPinyin();
        this.zipCode = chinaArea.getZipCode();
        this.name = chinaArea.getName();
        this.shortName = chinaArea.getShortName();
    }
}

8.2 ID로 데이터 추가

테스트 클래스를 하나 더 만들어보자
자동주입을 사용하고 싶기 때문에 기존에 사용하던 방법을 사용할 수 없으니 null 포인터를 피하기 위해 이렇게 작성해보자.

package com.elastictest;

import com.alibaba.fastjson2.JSON;
import com.elastictest.entity.ChinaArea;
import com.elastictest.entity.ChinaAreaDoc;
import com.elastictest.service.IChinaAreaService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;


@SpringBootTest
@RunWith(SpringRunner.class)
public class ChinaAreaTest {
    
    
    @Autowired
    private IChinaAreaService iChinaAreaService;
    private RestHighLevelClient restHighLevelClient;

    private void initial(){
    
    
        this.restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("地址:端口")
        ));
    }


    @Test
    public void testAddDocumentById()throws IOException{
    
    
        initial();
        ChinaArea chinaArea = iChinaAreaService.getById(9);
        ChinaAreaDoc chinaAreaDoc = new ChinaAreaDoc(chinaArea);

        IndexRequest indexRequest = new IndexRequest("china_area").id(chinaAreaDoc.getId().toString());
        indexRequest.source(JSON.toJSONString(chinaAreaDoc), XContentType.JSON);
        restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
    }
}

여기에 이미지 설명 삽입

8.3 ID로 데이터 조회

	@Test
    public void testGetDocumentById()throws IOException{
    
    
        initial();
        GetRequest getRequest = new GetRequest("china_area","9");
        GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
        String json = getResponse.getSourceAsString();
        System.out.println(json);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
    }

8.4 ID로 데이터 수정

  • 쓰기와 동일한 작업인 글로벌 업데이트는 오래된 문서를 자동으로 삭제하고 새 문서를 추가합니다.
  • 부분 업데이트
	@Test
    public void testUpdateDocumentById()throws IOException{
    
    
        initial();
        UpdateRequest updateRequest = new UpdateRequest("china_area","9");
        updateRequest.doc(
                "level", 1,
                "phoneCode","011"
        );
        restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
    }

여기에 이미지 설명 삽입

8.5 ID로 문서 삭제

	@Test
    public void testDeleteDocumentById()throws IOException{
    
    
        initial();
        DeleteRequest deleteRequest = new DeleteRequest("china_area","9");
        restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
    }

여기에 이미지 설명 삽입

8.6 ID로 일괄 추가

먼저 Mybatis 페이지네이션 플러그인을 사용하여 페이징을 얻습니다.

package com.elastictest.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfig{
    
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //物理分页
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //防止恶意全表操作
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}
@Test
    public void testBulk() throws IOException{
    
    
        initial();
        long total = iChinaAreaService.count();
        //每页100条
        long page = total / 100;
        for(int i = 1;i <= page + 1;i++){
    
    
            BulkRequest bulkRequest =new BulkRequest();
            Page<ChinaArea> result = iChinaAreaService.page(new Page<ChinaArea>(i,100,total,false));
            List<ChinaArea> chinaAreas = result.getRecords();
            for(ChinaArea  chinaArea: chinaAreas){
    
    
                ChinaAreaDoc chinaAreaDoc = new ChinaAreaDoc(chinaArea);
                bulkRequest.add(new IndexRequest("china_area").id(chinaAreaDoc.getId().toString()).source(JSON.toJSONString(chinaAreaDoc),XContentType.JSON));
            }
            restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
        }
        if(restHighLevelClient != null)
            restHighLevelClient.close();
    }

8.7 문서 질의

8.7.1 단순 시작

	@Test
    public void testMatchAll() throws IOException{
    
    
        initial();
        //1.准备Request
        SearchRequest searchRequest = new SearchRequest("china_area");
        //2.组织DSL参数,match_all
        searchRequest.source().query(QueryBuilders.matchAllQuery());
        //3. 发送请求,得到响应结果
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
        //4. 解析结果
        SearchHits searchHits = searchResponse.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("共有:" + total + "条数据");
        SearchHit[] hits= searchHits.getHits();
        for(SearchHit hit : hits){
    
    
            ChinaAreaDoc chinaAreaDoc = JSON.parseObject(hit.getSourceAsString(),ChinaAreaDoc.class);
            System.out.println(chinaAreaDoc.getId() + ":" + chinaAreaDoc.getName());
        }
    }

여기에 이미지 설명 삽입

8.7.2 QueryBuilder

//match
QueryBuilders.matchQuery("name","北京")
QueryBuilders.matchQuery("name","[\"北京\",\"上海\"]")

//term
QueryBuilders.termQuery("first","B")

//range
QueryBuilders.rangeQuery("id").gte("100")

//function_score
 QueryBuilders.functionScoreQuery(
        QueryBuilders.matchQuery("name","北京"), new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
    
    
              new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.rangeQuery("level").gte(1), ScoreFunctionBuilders.weightFactorFunction(5)),
         }
)
 


//bool
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(QueryBuilders.termQuery("first","B"));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("id").gte("100"));
searchRequest.source().query(boolQueryBuilder);

8.7.3 검색 요청

//查询
searchRequest.source().query();
//分页
searchRequest.source().from(0).size(5)
//排序
searchRequest.source().sort("id", SortOrder.DESC)
searchRequest.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint("31.21,121.5"))
                            .order(SortOrder.ASC)
                            .unit(DistanceUnit.KILOMETERS))
//高亮
searchRequest.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false))
//检查是否有高亮
SearchHit[] hits= searchHits.getHits();
for(SearchHit hit : hits){
    
    
   ChinaAreaDoc chinaAreaDoc = JSON.parseObject(hit.getSourceAsString(),ChinaAreaDoc.class);
   System.out.println(chinaAreaDoc.getId() + ":" + chinaAreaDoc.getName() + ":" + chinaAreaDoc.getLevel() + ":" + chinaAreaDoc.getZipCode());
   Map<String, HighlightField> highlightFields = hit.getHighlightFields();
   if(!CollectionUtils.isEmpty(highlightFields)){
    
    
       System.out.println(highlightFields.get("name").getFragments()[0].toString());
   }
}

8.8 시도

반환 데이터 만들기

package com.elastictest.entity.vo;

import lombok.Data;

import java.util.List;

@Data
public class PageResVo<T> {
    
    
    private Long total;
    private List<T> data;
}


매개변수를 수신할 프런트 엔드 만들기

package com.elastictest.entity.vo;

import lombok.Data;

@Data
public class PageReqVo {
    
    
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private Boolean isAsc;
}

컨트롤 레이어

package com.elastictest.controller;

import com.elastictest.entity.ChinaAreaDoc;
import com.elastictest.entity.vo.PageReqVo;
import com.elastictest.entity.vo.PageResVo;
import com.elastictest.service.IChinaAreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author yjx23332
 * @since 2022-08-17
 */
@RestController
@RequestMapping("/chinaArea")
public class ChinaAreaController {
    
    
    @Autowired
    private IChinaAreaService iChinaAreaService;

    @PostMapping("/list")
    public PageResVo<ChinaAreaDoc> search(@RequestBody PageReqVo params){
    
    
        return  iChinaAreaService.search(params);
    }
}


할당 ES 연결

package com.elastictest.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;

@Configuration
public class ElasticSearchConfig {
    
    
    @Bean
    public RestHighLevelClient restHighLevelClient(){
    
    
        return new RestHighLevelClient(RestClient.builder(HttpHost.create("IP:端口")));
    }
}

서비스 임플


package com.elastictest.service.impl;

import com.alibaba.fastjson2.JSON;
import com.elastictest.entity.ChinaArea;
import com.elastictest.entity.ChinaAreaDoc;
import com.elastictest.entity.vo.PageReqVo;
import com.elastictest.entity.vo.PageResVo;
import com.elastictest.mapper.ChinaAreaMapper;
import com.elastictest.service.IChinaAreaService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yjx23332
 * @since 2022-08-17
 */
@Service
@Slf4j
public class ChinaAreaServiceImpl extends ServiceImpl<ChinaAreaMapper, ChinaArea> implements IChinaAreaService {
    
    
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Override
    public PageResVo<ChinaAreaDoc> search(PageReqVo params) {
    
    
        try {
    
    
            SearchRequest searchRequest = new SearchRequest("china_area");

            String key = params.getKey();
            if (StringUtils.isNullOrEmpty(key)) {
    
    
                searchRequest.source().query(QueryBuilders.matchAllQuery());
            } else {
    
    
                searchRequest.source().from((page - 1) * size).size(size)
                .sort(params.getSortBy(), params.getIsAsc()?SortOrder.ASC:SortOrder.DESC);
            
            }
            int page = params.getPage();
            int size = params.getSize();
            searchRequest.source().from((page - 1) * size).size(size);
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            return handleResponse(searchResponse);
        }catch (IOException e){
    
    
            log.error("{}",e.getMessage());
            throw new RuntimeException(e);
        }
    }
    private PageResVo<ChinaAreaDoc> handleResponse(SearchResponse searchResponse){
    
    
        SearchHits searchHits = searchResponse.getHits();
        long total = searchHits.getTotalHits().value;
        SearchHit[] hits= searchHits.getHits();
        PageResVo<ChinaAreaDoc> pageResVo = new PageResVo<>();
        pageResVo.setTotal(searchHits.getTotalHits().value);
        List<ChinaAreaDoc> chinaAreaDocs = new ArrayList<>();
        for(SearchHit hit : hits){
    
    
            chinaAreaDocs.add(JSON.parseObject(hit.getSourceAsString(),ChinaAreaDoc.class));
        }
        pageResVo.setData(chinaAreaDocs);
        return pageResVo;
    }
}

다음 시작 후 POSTMAN을 사용하여 테스트하십시오.
여기에 이미지 설명 삽입

9. 데이터 집계

공식 문서 주소
집계(aggregation)는 문서 데이터의 통계, 분석 및 운영을 실현할 수 있습니다. 세 가지 일반적인 유형이 있습니다.

  • 버킷 집계: 문서를 그룹화하는 데 사용
    • 용어 집계: 문서 필드 값으로 그룹화
    • 날짜 히스토그램: 날짜 래더별로 그룹화
  • 메트릭 집계: 최대값, 최소값, 평균값 등과 같은 일부 값을 계산하는 데 사용됩니다.
    • 평균: 평균
    • Max: 최대값 찾기
    • Min: 최소값 찾기
    • 통계: 최대, 최소, 평균, 합계 등을 동시에 찾습니다.
  • 파이프라인(pipeline) 집계: 다른 집계 결과를 집계의 기준으로 사용합니다.

참여 분야:

  • 예어
  • 날짜
  • 부울

9.1 DSL 구현

9.1.1 버킷 집계

GET /china_area/_search
{
    
    
  "size":0,//设置不包含文档,只包含聚合结果
  "aggs":{
    
    	//定义聚合:
    "levelAgg":{
    
    //聚合名称
      "terms": {
    
    //聚合类型
        "field": "level",//参与聚合的字段
        "order":{
    
    
          "_count": "desc"
        },
        "size": 20 //希望获取的聚合结果数量
      }  
    }
  }
  
}

여기에 이미지 설명 삽입

9.1.2 메트릭 집계

GET /china_area/_search
{
    
    
  "size":0,
  "aggs":{
    
    
    "pidAgg":{
    
    
      "terms": {
    
    
        "field": "pid",
        "order":{
    
    
          "levelAgg.avg":"asc"
        },
        "size": 20
      },
      "aggs":{
    
    
        "levelAgg":{
    
    
          "stats": {
    
    
            "field":"level"
          }
        }
      }
    }
  }
}

여기에 이미지 설명 삽입

9.2 RestClient 구현

9.2.1 버킷

	@Test
    public void testAggregation()throws IOException{
    
    
        initial();
        SearchRequest searchRequest = new SearchRequest("china_area");
        searchRequest.source().size(0);
        searchRequest.source().aggregation(AggregationBuilders.terms("level_agg").field("level").size(20));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
        if(restHighLevelClient != null)
            restHighLevelClient.close();
        Aggregations aggregations = searchResponse.getAggregations();
        Terms levelTerms = aggregations.get("level_agg");
        List<? extends Terms.Bucket> buckets = levelTerms.getBuckets();
        for(Terms.Bucket bucket :buckets){
    
    
            System.out.println(bucket.getKeyAsNumber() + ":" +bucket.getDocCount());
        }
    }

10. 자동 완성

병음 토크나이저 플러그인을 설치해야 합니다.

두 가지 설치 방법이 있는데 토크나이저와 같은 사용법으로 하나는 (/var/lib/docker/volumes/es-plugins/_data)에 다운로드하여 업로드하는 것이고 다른 하나는 다음 단계에 따라 직접 설치하는 것입니다.
버전 번호는 자신의 버전 번호와 일치해야 합니다.
병음 단어 분리기 github 주소
컨테이너 직접 설치 단계

docker exec -it es /bin/bash
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-pinyin/releases/download/v7.6.2/elasticsearch-analysis-pinyin-7.6.2 .zip
종료
도커 재시작 es

시험

GET /_analyze
{
    
    
  "text":["这家饭店的菜还不错"],
  "analyzer": "pinyin"
  
}

여기에 이미지 설명 삽입

10.1 커스텀 토크나이저

Elasticsearch의 단어 세분화(분석기)는 세 부분으로 구성됩니다.

  • 문자 필터: 토크나이저 전에 텍스트 처리, 삭제 및 교체 문자 입력
  • 토크나이저: 텍스트를 특정 빈에 따라 용어로 자릅니다. 예를 들어 키워드 ik_smart
  • 토크나이저 필터: 토크나이저에서 출력한 항목을 추가로 처리합니다. 예를 들어, 대소문자 변환, 동의어 처리, 병음 처리 등

인덱스 라이브러리를 생성할 때 "설정"을 통해 사용자 지정 토크나이저를 구성합니다.

PUT /test
{
    
    
  "settings": {
    
    
    "analysis": {
    
    
      "analyzer": {
    
    //自定义分词器
        "my_analyzer":{
    
    //自定义分词器名称
          "tokenizer":"ik_max_word",
          "filter":"pinyin"
        }
      }
    }
  }
}

그런 다음 필터를 추가로 정의할 수 있습니다.

PUT /test
{
    
    
  "settings": {
    
    
    "analysis": {
    
    
      "analyzer": {
    
    
        "my_analyzer":{
    
    
          "tokenizer":"ik_max_word",
          "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
        }
      }
    }
  }
}

다음과 같이 생성되었습니다.

PUT /test
{
    
    
  "settings": {
    
    
    "analysis": {
    
    
      "analyzer": {
    
    
        "my_analyzer":{
    
    
          "tokenizer":"ik_max_word",
          "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": {
    
    
      "name":{
    
    
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}

다음과 같이 테스트

GET /test/_analyze
{
    
    
  "text":["这家饭店的菜还不错"],
  "analyzer": "my_analyzer"
  
}

여기에 이미지 설명 삽입
Pinyin 토크나이저는 반전된 색인을 만들 때 사용하기에 적합하지만 검색에는 적합하지 않습니다. 다음 예를 들어 설명합니다.


POST /test/_doc/1
{
    
    
  "id":1,
  "name":"狮子"
}

POST /test/_doc/2
{
    
    
  "id":2,
  "name":"虱子"
}

GET /test/_search
{
    
    
  "query":{
    
    
    "match":{
    
    
      "name":"掉入狮子笼子怎么办"
    }
  }
}

모두 찾아드리겠습니다. 사자와 이가 같은 병음을 가지고 있기 때문입니다.

따라서 생성 초기에 역인덱스 구축을 위한 토크나이저와 검색을 위한 토크나이저를 지정해야 합니다.

PUT /test
{
    
    
  "settings": {
    
    
    "analysis": {
    
    
      "analyzer": {
    
    
        "my_analyzer":{
    
    
          "tokenizer":"ik_max_word",
          "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": {
    
    
      "name":{
    
    
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

그런 다음 다시 수행하면 문제가 없음을 알 수 있습니다.

10.2 자동 완성 쿼리

완료 제안 쿼리: 사용자 입력 콘텐츠의 시작 부분에 있는 항목을 일치시켜 반환합니다. 불완전한 쿼리의 효율성을 향상시키기 위해 문서의 필드 유형에 몇 가지 제약이 있습니다.

  • 완료 쿼리에 참여하는 필드는 완료 유형이어야 합니다.
  • 필드의 내용은 일반적으로 여러 항목으로 구성된 배열을 완성하는 데 사용됩니다.
PUT /test
{
    
    
  "settings": {
    
    
    "analysis": {
    
    
      "analyzer": {
    
    
        "my_analyzer":{
    
    
          "tokenizer":"ik_max_word",
          "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": {
    
    
      "title":{
    
    
        "type": "completion"
      }
    }
  }
}

POST test/_doc
{
    
    
  "title":["sony","WH-1000XM3"]
}


POST test/_doc
{
    
    
  "title":["SK-II","PITERA"]
}



POST test/_doc
{
    
    
  "title":["Nintendo","switch"]
}

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

여기에 이미지 설명 삽입

우리가 그것을 사용해야 할 때, 우리는 "제안"이라는 새로운 파일을 만들고, 제안을 입력하고, 우리가 원하는 불완전한 정보를 입력하고, 그것을 별도로 쿼리할 수 있습니다.

10.3 RestClient 완료 쿼리

	@Test
    public void testSuggestion() throws IOException{
    
    
        initial();
        SearchRequest searchRequest = new SearchRequest("test");
        searchRequest.source().suggest(new SuggestBuilder().addSuggestion(
                "title_suggest", SuggestBuilders.completionSuggestion("title")
                        .prefix("s")
                        .skipDuplicates(true)
                        .size(10)
        ));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
        Suggest suggest = searchResponse.getSuggest();
        CompletionSuggestion completionSuggestion = suggest.getSuggestion("title_suggest");
        List<CompletionSuggestion.Entry.Option> options = completionSuggestion.getOptions();
        for(CompletionSuggestion.Entry.Option option: options){
    
    
            String text = option.getText().string();
            System.out.println(text);
        }
    }

11. 데이터 동기화

데이터베이스 동기화 문제: 데이터베이스 데이터가 변경되면 그에 따라 elasticsearch도 변경되어야 합니다. 하지만 마이크로서비스의 경우 데이터를 관리하는 서비스와 검색하는 서비스가 같은 서비스에 있지 않을 수 있으니 데이터 동기화는 어떻게 구현해야 할까.

옵션 1. 동시 통화

新增数据
1写入数据库
2更新索引库接口
3更新elasticsearch
服务1
数据管理服务
MySQL
数据搜索服务
elasticsearch

이점:

  • 단순하고 조잡한 실현

결점:

  • 높은 수준의 비즈니스 결합

솔루션 2. 비동기 알림

新增数据
1.1写入数据库
1.2发布消息
2.1监听消息
2.2更新elasticsearch
服务1
数据管理服务
MySQL
MQ
数据搜索服务
elasticsearch

이점:

  • 일반적으로 구현하기 어려운 낮은 결합도

결점:

  • MQ의 신뢰성에 의존

솔루션 3, binlog 모니터링

新增数据
1.1写入数据库
2.1监听binlog
2.2通知数据变更
2.3更新elasticsearch
服务1
数据管理服务
MySQL
canal
数据搜索服务
elasticsearch

이점:

  • 서비스를 완전히 분리

결점:

  • binlog를 활성화하면 데이터베이스에 대한 부담이 증가하고 구현이 더 복잡해집니다.

MQ는 mysql 및 elasticsearch 데이터 동기화를 구현합니다.

이전 코드를 기반으로 메시지를 보내는 로직을 추가합니다.
MQ 종속성을 추가하고 해당 매개변수를 구성합니다.
관련 사용법은 MessageQueue 메시지 대기열 - 기본 사항(참고)을 참조하세요.

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
spring:
	rabbitmq:
	    host: 
	    port: 5672
	    virtual-host: /
	    username: 
	    password: 

package com.elastictest.constants;


/***
 * @author  yjx23332
 * @date 2022-8-19
 */
public class MqConstants {
    
    
    /**
     * 交换机配置
     * */
    public final  static String CHINA_AREA_EXCHANGE = "china.area.topic";
    /**
     * 监听新增和修改的队列
     */
    public final static String CHINA_AREA_INSERT_QUEUE = "china.area.insert.queue";
    /**
     * 监听删除的队列
     */
    public final static String CHINA_AREA_DELETE_QUEUE = "china.area.delete.queue";
    /**
     * 监听或修改的RountingKey
     */
    public final static String CHINA_AREA_INSERT_KEY = "china.area.insert";
    /**
     * 删除的RountingKey
     */
    public final static String CHINA_AREA_DELETE_KEY = "china.area.delete";
}

package com.elastictest.controller;

import com.elastictest.constants.MqConstants;
import com.elastictest.entity.ChinaArea;
import com.elastictest.entity.ChinaAreaDoc;
import com.elastictest.entity.vo.PageReqVo;
import com.elastictest.entity.vo.PageResVo;
import com.elastictest.service.IChinaAreaService;
import org.apache.ibatis.annotations.Delete;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author yjx23332
 * @since 2022-08-17
 */
@RestController
@RequestMapping("/chinaArea")
public class ChinaAreaController {
    
    
    @Autowired
    private IChinaAreaService iChinaAreaService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PutMapping
    public void saveOrUpdateArea(@RequestBody ChinaArea chinaArea){
    
    
        iChinaAreaService.saveOrUpdate(chinaArea);
        rabbitTemplate.convertAndSend(MqConstants.CHINA_AREA_EXCHANGE,MqConstants.CHINA_AREA_INSERT_KEY,chinaArea);
    }
    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id){
    
    
        iChinaAreaService.removeById(id);
        rabbitTemplate.convertAndSend(MqConstants.CHINA_AREA_EXCHANGE,MqConstants.CHINA_AREA_DELETE_KEY,id);
    }
}

여기의 그림은 편리하고 공급자와 소비자가 함께 모여 있으며 시뮬레이션 프로세스입니다.

package com.elastictest.listener;

import com.alibaba.fastjson2.JSON;
import com.elastictest.constants.MqConstants;
import com.elastictest.entity.ChinaArea;
import com.elastictest.entity.ChinaAreaDoc;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class RabbitMQListener {
    
    
    @Autowired
    RestHighLevelClient restHighLevelClient;

    @RabbitListener(
            bindings = @QueueBinding(
                value = @Queue(name = MqConstants.CHINA_AREA_INSERT_QUEUE),
                exchange = @Exchange(value = MqConstants.CHINA_AREA_EXCHANGE,type = ExchangeTypes.TOPIC),
                 key = {
    
    MqConstants.CHINA_AREA_INSERT_KEY}
            )
    )
    public void listenInsertQueue(ChinaArea chinaArea){
    
    
        try {
    
    
            ChinaAreaDoc chinaAreaDoc = new ChinaAreaDoc(chinaArea);
            IndexRequest indexRequest = new IndexRequest("china_area").id(chinaAreaDoc.getId().toString());
            indexRequest.source(JSON.toJSONString(chinaAreaDoc), XContentType.JSON);
            restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        }catch (IOException ex){
    
    
            throw new RuntimeException(ex);
        }
    }
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(name = MqConstants.CHINA_AREA_DELETE_QUEUE),
                    exchange = @Exchange(value = MqConstants.CHINA_AREA_EXCHANGE,type = ExchangeTypes.TOPIC),
                    key = {
    
    MqConstants.CHINA_AREA_DELETE_KEY}
            )
    )
    public void listenDeleteQueue(Long id){
    
    
        try {
    
    
            DeleteRequest deleteRequest = new DeleteRequest("china_area").id(id.toString());
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        }catch (IOException ex){
    
    
            throw new RuntimeException(ex);
        }
    }
}

실행 후 MQ를 먼저 확인하자
여기에 이미지 설명 삽입

여기에 이미지 설명 삽입

그런 다음 POSTMAN으로 테스트합니다.
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입

12. ES 클러스터

스토리지를 위한 독립형 Elasticsearch는

  • 대용량 데이터 저장 문제
  • 단일 실패 지점 문제

대책:

  • 인덱스 라이브러리를 논리적으로 N개의 샤드(샤드)로 분할하여 여러 노드에 저장
  • 백업을 위해 조각난 데이터를 다른 노드에 저장(복제본)

12.1 배포


동일한 클러스터 이름인 docker-compose를 통한 신속한 배포는 자동으로 클러스터 Modify linux 로 어셈블됩니다 .

vi /etc/sysctl.conf

다음 정보 추가

vm.max_map_count=262144

그런 다음 다음 명령을 사용하여 로드하십시오.

sysctl -p

version: '3.2'
services:
  es01:
    image: elasticsearch:7.6.2
    container_name: es01
    environment:
      - node.name=es01
      #集群名称
      - cluster.name=es-docker-cluster
      #另外两个节点的IP地址,docker容器互联直接用容器名
      - discovery.seed_hosts=es02,es03
      #参加选举
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: elasticsearch:7.6.2
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data02:/usr/share/elasticsearch/data
    ports:
      - 9201:9200
    networks:
      - elastic
  es03:
    image: elasticsearch:7.6.2
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - data03:/usr/share/elasticsearch/data
    ports:
      - 9202:9200
    networks:
      - elastic
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local
networks:
  elastic:
    driver: bridge

~ 후에

도커 작성 -d

여기에 이미지 설명 삽입
笔者服务器内存不够了,接下来的部分就没办法实操了。
여기에 이미지 설명 삽입

记得开启端口。

12.2 集群监控

对于集群,kibana也可以监控集群,但是需要较复杂的配置。
因此使用cerebro监控比较方便。

12.3 指定分片与备份

分片以及他的备份一般不会再同一个服务上,避免一起挂掉。

PUT /test
{
    
    
	"settings":{
    
    
		"number_of_shards":3,//发片数量
		"number_of_replicas":1//每一片副本数量
	},
	"mappings":{
    
    
		"propertries":{
    
    
			...
		}
	}
}

12.4 ES节点角色

主节点属于备选节点

节点类型 配置参数 默认值 节点职责
master eligible node.master true 备选主节点:可以管理和记录集群状态、决定分片在哪个节点、处理创建核删除索引库的请求
data node.data true 数据节点:存储数据、搜索、聚合、CRUD
ingest node.ingest true 数据存储之前预处理
coordinating 上面3个参数都为false,则为协调节点节点 路由请求到其他节点,合并其他节点处理的结果,返回给用户

每个ES节点默认拥有以上4个配置,同时担任4个责任。

同时所有节点都必须有协调节点角色作为基础责任。节点要么是纯粹只做协调的节点或者是协调+其他角色的节点。

请求
LB
coordinating1
coordinating2
coordinating3
data1
data2
data3
master-eligible
master-eligible2
master-eligible3

12.5 ES集群脑裂与故障转移

默认每个节点都是master eligible节点,一旦master节点宕机,其他候选节点会选一个成为主节点。
当主节点与其他节点网络故障时,可能发生脑裂问题。因为可能会有两个主节点出现。
为了避免脑裂,

  • 主节点不要作为数据节点
  • 数据节点不要具备选举功能
  • 不少于3个节点具备成为主节点的资格
  • 投票数超过(候选节点+1)/2才能成为主节点(因此数目最好是奇数)。

可以通过配置来管理选票数目,es7.0后默认配置。

discovery.zen.minimum_master_nodes

  • 挂掉的节点如果是数据节点或者说有数据节点的职责,主节点会核对挂掉的数据有哪些,然后从各个健康节点的备份中找到,再次备份到健康的节点上,保证数目为设定的数目。
  • 如果挂掉的主节点再上线,此时它不会成为主节点。
  • 중단된 데이터 노드가 다시 온라인 상태가 되면 마스터 노드는 다른 노드에서 필요한 데이터를 마스터 노드로 전송함과 동시에 다른 노드에 없어야 할 중복 백업을 삭제합니다.

스플릿 브레인은 클러스터에서 더 자주 고려되는 문제로, redis 마스터-슬레이브 모드의 클러스터 장애 조치(주관 및 객관적 오프라인, 장애 조치를 위한 마스터 센티넬 선택)와 비교할 수 있습니다.

12.6 ES 쿼리 프로세스

Redis와 유사한 분산 클러스터. 데이터 세트의 균형을 유지하기 위해 데이터는 다른 샤드에 저장됩니다. 계산식을 통해 샤드에 속하는 것으로 계산된다.

샤드 = 해시(_라우팅)%number_of_shards

  • _routing: 기본값은 문서 ID입니다.
  • 알고리즘은 샤드 개수와 관련이 있기 때문에 인덱스 라이브러리가 생성되면 샤드 개수를 수정할 수 없습니다.

쿼리는 두 단계로 나뉩니다.

  • 분산 단계: 분산 단계에서 조정 노드는 각 샤드에 요청을 배포합니다.
  • 수집 단계: 수집 단계에서 조정 노드는 데이터 노드의 검색 결과를 요약하고 최종 결과로 처리하여 사용자에게 반환합니다.

참조

[1] 다크호스 프로그래머 자바 마이크로서비스
[2] Docker 네트워크 상세 설명, 튜토리얼
[3] Elasticsearch Guide
[4] 공식 문서의 RestClient 부분
[5] 공식 문서의 DSL 부분
[6] 공식 문서의 Aggregation 부분

추천

출처blog.csdn.net/weixin_46949627/article/details/126343573