ElasticSearch

基本概念

  1. Node: ES 集群的基本服务单元,每个 ElasticSearch 实例则为一个节点。
  2. Cluster: 具有相同 cluster.name 的一组节点构成一个集群 (Cluster)。
    ps: 同一集群内节点名不能重复,但集群名一定相同。
  3. Index: ES 会索引所有字段,处理过后写入反向索引 (Inverted Index)。
    ps: ES 数据管理顶层单位为 Index 索引,查找数据时直接查找该反向索引。 (可以类比为数据库,Index 名字必须小写)。
  4. Document: Index 里面的单条记录称为文档(Document)。ps:
    ps: Index 内的结构尽可能保证相同,有利于提高搜索效率。
  5. Type: 在 Index 里面用来虚拟逻辑分组,用于过滤 Document。比如weather的 Index 按城市(北京/上海)进行分组。
    ps: 不同的 Type 应该具有相似的结构,id字段中不能一个 Type 中为数值型,另一个为字符串型;两个性质不同的数据,应该存放为两个 Index 而不是存放成一个 Index 中的两个 Type。
  6. Shards: 分片,索引数据量太大时,单个节点物理性能受限,将索引上的数据水瓶拆分,拆分出来的每个数据部分称之为一个分片。
    ps: ES的每个分片都是 Lucene 中的一个索引文件,因此每个分片必须有一个主分片和零到多个副本分片;创建索引的时候需要指定分片数量(ES默认为一个索引创建5个主分片,并分别为每个分片创建一个副本),分片数量一旦确定就不能更改。
  7. Replicas: 备份/副本,副本是对主分片的备份,这种备份是精确的复制模式。当构建索引进行写入操作时,首先在主分片上完成数据的索引,然后数据会从主分片分发到备份分片进行索引。
    ps: 备份利弊是共存的:备份可以提高 ES 系统的高可用性;如果备份分片数目设置太多,则在写操作时会增加数据同步的负担。
  8. Settings: Settings 是对集群中索引的定义信息。
    ps: 例如一个索引的默认分片数,副本数目
  9. Mapping: Mapping 中保存 了定义索引中字段 (Field)的存储类型、分次放时、是否存储信息等等,类似于关系数据库中的表结构信息
    ps: 在 ES 中,Mapping 是可以动态识别的。如果没有特殊需求,则不需要手动创建 Mapping ,因为在ES可以根据数据格式自动识别其类型;特殊情况除外:定义使用其他分词器,是否分词,是否存储等。一个索引的 Mapping 创建,若已经存储了数据,就不可以再修改。
  10. Analyzer: 字段分词方式的定义。一个 Analyzer 由一个 Toeknizer 和零到多个 Filter 组成。
    ps: 在 ES 中,默认的标准 Analyzer 包含一个标准的 Tokenizer 和三个 Filter,即Standard Token Filter, Lower Case Token Filter 和 Stop Token Filter。

mapping类型

字符串类型: textkeyword
数值类型:longintegershortbytedoublefloatscaled_float
日期类型:date
布尔类型:boolean
二进制类型:binary
等等…


关于索引基本操作

method url 地址 描述
PUT losthost:9200/索引名称/类型名称/文档id 创建文档(指定文档 ID)
POST localhost:9200/索引名称/类型名称 创建文档(随机文档ID)
POST localhost:9200/索引名称/类型名称/文档id/_update 修改文档
DELET localhost:9200/索引名称/类型名称/文档id 删除文档
GET localhost:9200/索引名称/类型名称/文档id 查询文档通过文档id
POST localhost:9200/索引名称/类型名称/_search 查询所有数据
 # Create:
PUT /索引名/·类型名·/文档ID
{请求体}

# 直接创建库
PUT /test1/type/1
{
"name":"北京邮电大学",
"age":59
}

# 根据索引规则创建库
PUT /test2
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "text"
},
"birthday": {
"type": "date"
}
}
}
}


# 查询 index 信息
GET test2

# 创建文档
PUT /test3/_doc/1
{
"name":"樊大头",
"age":24,
"birth":"1996-10-02"
}
# 获取创建 index 的信息
GET test3

# 获取 ES 当前信息
GET _cat/health
GET _cat/indices?v

# 修改索引:使用PUT,覆盖索引!


# Delete:
# 删除索引
DELETE test1

ik分词器

ik_samrt:最少切分

GET _analyze
{
"analyzer": "ik_smart",
"text":"北京邮电大学"
}

ik_max_word 最细粒度划分

GET _analyze
{
"analyzer": "ik_max_word",
"text": "北京邮电大学"
}

ik分词器可以配置自己的词典,使分词器进行分词。


关于文档的基本操作

基本操作

  1. 添加数据

    PUT /at0m/user/1
    {
    "name": "at0m",
    "age": 23,
    "desc": "哈哈哈哈哈哈哈",
    "tags": [
    "程序猿",
    "学生"
    ]
    }
  2. 获取数据

GET at0m/user/1
  1. 更新数据

put方法(不推荐): 如果不传递值,就会被覆盖。

PUT /at0m/user/1
{
"name": "0x4154304D",
"age": 24,
"desc": "coding coding coding",
"tags": [
"程序猿",
"学生"
]
}

post _update

POST at0m/user/1/_update
{
"doc":{
"desc":"what what what"
}
}
  1. 简单搜索
GET at0m/user/1

简单的条件查询

GET at0m/user/_search?q=name:unique

ps: ES 搜索默认返回10条结果,可以通过size这个字段来改变这个设置。from字段指定位移:从xx位置开始返回结果(默认为0)

  1. 复杂搜索
# 名字为 unique 的文档

GET at0m/user/_search
{
"query": {
"match": {
"name": "unique"
}
}
}

hit: 索引和文档信息,查询结果总数,查询出来的具体文档,数据中的内容都可遍历出来,可以通过score判断最匹配的结果。

# 过滤,只显示 name 和 desc

GET at0m/user/_search
{
"query": {
"match": {
"name": "Unique"
}
},
"_source": [
"name",
"desc"
]
}

排序

# 根据年龄升序排列 sort desc降序 asc升序

GET at0m/user/_search
{
"query": {
"match": {
"name": "Unique"
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
]
}

分页查询

# 分页查询 from:从第几条数据开始 size:返回多少条数据

GET at0m/user/_search
{
"query": {
"match": {
"name": "Unique"
}
},
"sort": [
{
"age": {
"order": "asc"
}
}
],
"from": 0,
"size": 20
}

布尔查询

# 多条件查询
must(and) 所有条件都要符合 where name = "unique" and age = 23

GET at0m/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "unique"
}
},
{
"match": {
"age": "23"
}
}
]
}
}
}

# should(or) 只要有一个条件符合 where name = "unique" or age = 23

GET at0m/user/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "unique"
}
},
{
"match": {
"age": "24"
}
}
]
}
}
}

# not where age <> 24

GET at0m/user/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"age": "24"
}
}
]
}
}
}

过滤器

# filter过滤查询 查询 gte:大于等于/lte:小于等于/gt:大于/lt:小于
GET at0m/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "unique"
}
}
],
"filter": [
{
"range": {
"age": {
"gte": 10,
"lte": 30
}
}
}
]
}
}
}

匹配多个条件

# 多个条件使用空格隔开, 只要满足一个条件就会被查出,可以通过分值判断。

GET at0m/user/_search
{
"query": {
"match": {
"tags": "大 学"
}
}
}
  • term 查询:直接通过倒排索引制定的词条进行精确查找
    分词;
    直接查找精确的值
    match:会使用分词器进行解析!(先分析文档,再通过分析的文档进行查询!)

两个类型 text keyword

  • keyword 类型字段当作一个整体:不会被分词器解析
  • text 会被分词器解析

多个值匹配的精确查询

# 精确查询多个值 
GET testdb/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"t1": {
"value": "22"
}
}
},{
"term": {
"t1": {
"value": "33"
}
}
}
]
}
}
}

高亮查询

# 搜索相关结果可以高亮显示

GET at0m/user/_search
{
"query": {
"match": {
"name": "unique"
}
},
"highlight": {
"fields": {
"name":{}
}
}
}

# 自定义高亮条件

GET at0m/user/_search
{
"query": {
"match": {
"name": "unique"
}
},
"highlight": {
"pre_tags": "<p class='key' style ='coloer:res'>",
"post_tags": "</p>",
"fields": {
"name":{}
}
}
}

Java api

  1. Maven依赖
<!-- ES7 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.1</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.1</version>
</dependency>
  1. 初始化/关闭客户端连接
// 创建连接
public void InitES() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
}

// 关闭连接
public void closeES() {
try {
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}

// 连接通用设置
// 1.超时设置
public void initESWithTimeout(){
RestClientBuilder clientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http"));
clientBuilder.setRequestConfigCallback(
builder -> builder.setSocketTimeout(10000).setSocketTimeout(60000));
}

// 2.线程数设置 当前设置为 Runtime.getRuntime().availableProcessors() 返回的结果
public void setThreadNumber(int number) {
RestClientBuilder clientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http")
);
clientBuilder.setHttpClientConfigCallback(
builder -> builder.setDefaultIOReactorConfig(
IOReactorConfig.custom().setIoThreadCount(
Runtime.getRuntime().availableProcessors()).build())
);
}
  1. 创建索引
public void indexDocument(String indexName, String Document) {
// 构建 IndexRequest
IndexRequest indexRequest = new IndexRequest(indexName);
indexRequest.id(Document);
// 提供 Map 形式的文档,自动转换为JSON格式
indexRequest.source(jsonMap);
try {
// 发起 index 请求
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
// 解析 IndexResponse 请求
String index = indexResponse.getIndex();
String id = indexResponse.getId();
switch (indexResponse.getResult()) {
case CREATED:
System.out.println("create");
break;
case NOOP:
System.out.println("noop");
break;
case DELETED:
System.out.println("deleted");
break;
case UPDATED:
System.out.println("updated");
break;
case NOT_FOUND:
System.out.println("not_found");
break;

}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 索引查询
public void getDocument(String indexName, String document) {
// 构建 GetRequest
GetRequest getRequest = new GetRequest(indexName, document);
try {
// 发起 get 请求
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
// 解析 GetResponse
String index = getResponse.getIndex();
String id = getResponse.getId();
// 判断所有文档是否存在
if (getResponse.isExists()) {
long version = getResponse.getVersion();
// 以字符串返回文档
String sourceAsString = getResponse.getSourceAsString();
// 以 Map 返回文档
Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
// 以 byte[] 返回文档
byte[] sourceAsBytes = getResponse.getSourceAsBytes();
}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 文档存在性校验
public void checkExistIndexDocuments(String indexName, String document) {
// 构建 GetRequest
GetRequest getRequest = new GetRequest(indexName, document);
// 禁用提取源
getRequest.fetchSourceContext(new FetchSourceContext(false));
// 禁用提取字段
getRequest.storedFields("_none_");
try {
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
if (exists) {
System.out.println("ok");
} else {
System.out.println("no");
}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 删除文档索引
public void deleteDocument(String indexName, String document){
// 构建 DeleteRequest
DeleteRequest deleteRequest = new DeleteRequest(indexName, document);
try {
// 发起 delete 请求
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
// 解析 DeleteResponse
String index = deleteResponse.getIndex();
String id = deleteResponse.getId();
long version = deleteResponse.getVersion();
// 获取分片信息
ReplicationResponse.ShardInfo shardInfo = deleteResponse.getShardInfo();
if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
System.out.println("successful not enough");
}
if (shardInfo.getFailed() > 0) {
// 解析分失败原因
for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
String reason = failure.reason();
System.out.println(reason);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 更新文档索引
public void updateDocument(String indexName, String document) {
// 构建 UpdateRequest
UpdateRequest updateRequest = new UpdateRequest(indexName, document);
// 以 Map 的的json来更新
updateRequest.doc(jsonMap);
try {
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
// 解析 UpdateResponse
String index = updateResponse.getIndex();
String id = updateResponse.getId();
long version = updateResponse.getVersion();
switch (updateResponse.getResult()) {
case CREATED:
System.out.println("create");
break;
case NOOP:
System.out.println("noop");
break;
case DELETED:
System.out.println("deleted");
break;
case UPDATED:
System.out.println("updated");
break;
case NOT_FOUND:
System.out.println("not_found");
break;
default:
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}

// upsert: 如果被更新的文档不存在,则可以使用 upsert 方法将某些内容定义为新文档
updateRequest.upsert(jsonString, XcontentType.JSON);
  1. 获取文档索引的词向量
public void termVectorsDocument(String indexName, String document, String field) {
// 构建 TermVectorsRequest
TermVectorsRequest termVectorsRequest = new TermVectorsRequest(indexName, document);
// 设置检索信息字段
termVectorsRequest.setFields(field);
try {
TermVectorsResponse termVectorsResponse = client.termvectors(termVectorsRequest, RequestOptions.DEFAULT);
// 解析 TermVectorsResponse
String index = termVectorsResponse.getIndex();
String id = termVectorsResponse.getId();
boolean found = termVectorsResponse.getFound();
List<TermVectorsResponse.TermVector> list = termVectorsResponse.getTermVectorsList();
for (TermVectorsResponse.TermVector termVector : list) {
String fieldName = termVector.getFieldName();
int docCount = termVector.getFieldStatistics().getDocCount();
long sumTotalTermFreq = termVector.getFieldStatistics().getSumTotalTermFreq();
long sumDocFreq = termVector.getFieldStatistics().getSumDocFreq();
if (termVector.getTerms() == null) {
continue;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 批量处理
public void bulkRequest(String indexName) {
// 构造 BulkRequest
BulkRequest bulkRequest = new BulkRequest(indexName);
// 添加请求
bulkRequest.add(indexRequst);
bulkRequest.add(getRequset);
try {
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse == null) {
return;
}
for (BulkItemResponse bulkItemResponse : bulkResponse) {
DocWriteResponse response = bulkItemResponse.getResponse();
switch (bulkItemResponse.getOpType()) {
// 生成索引
case INDEX:
case CREATE:
IndexResponse indexResponse = (IndexResponse) response;
break;
// 删除索引
case DELETE:
DeleteResponse deleteResponse = (DeleteResponse) response;
break;
// 更新索引
case UPDATE:
UpdateResponse updateResponse = (UpdateResponse) response;
break;
default:
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
  1. 聚合搜索
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(indexName).searchType(typeName);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermsAggregationBuilder aggregation =AggregationBuilders.terms("project_id").field("project_id").size(size);
searchSourceBuilder.aggregation(aggregation);
searchRequest.source(searchSourceBuilder);

分词器

每个分词器(不论是内置还是自定义)都由三部分组成,字符过滤器(character filters) / 分词器(tokenizers) / 分词过滤器(token filters) 。

Character Filters

字符过滤器将原始文本作为字符流接收,并且可以通过添加/删除/更改字符来转换流。

  • 去除HTML标签
  • 语言转换等等

ps: 每个analyzer中可以存在[0-N]Character Filter,原始文本会根据顺序依次应用字符流。

HTML Strip 过滤器
// 例子
GET /_analyze
{
"tokenizer": "keyword",
"char_filter": [
"html_strip"
],
"text": "<p>I&apos;m so <b>happy</b>!</p>"
}

[ \nI'm so happy!\n ]

// 加入到 analyzer 中
PUT /my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"html_strip"
]
}
}
}
}
}

// 自定义过滤符号
PUT my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "keyword",
"char_filter": [
"custom_html_filter"
]
}
},
"char_filter": {
"custom_html_filter": {
"type": "html_strip",
"escaped_tags": [
"b"
]
}
}
}
}
}
Mapping 过滤器

映射过滤器接收接收key-value的映射,每当遇到和key相同的字符串的时候,利用value来替换。匹配采用的是贪婪匹配法=>最长的匹配模式。支持替换为空字符串。

// 例子
GET /_analyze
{
"tokenizer": "keyword",
"char_filter": [
{
"type": "mapping",
"mappings": [
"٠ => 0",
"١ => 1",
"٢ => 2",
"٣ => 3",
"٤ => 4",
"٥ => 5",
"٦ => 6",
"٧ => 7",
"٨ => 8",
"٩ => 9"
]
}
],
"text": "My license plate is ٢٥٠١٥"
}

[ My license plate is 25015 ]

配置参数:

  • mappings :(必填*,字符串数组)映射数组,每个元素的形式为key => value。必须指定此参数,或者必须指定mappings_path参数。
  • mappings_path : (必需*,字符串)包含key => value映射的文件的路径。此路径必须是绝对路径或相对于配置位置的路径,并且文件必须是UTF-8编码的。文件中的每个映射必须以换行符分隔。必须指定此参数或mappings参数。
PUT /my-index-000001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"char_filter": [
"mappings_char_filter"
]
}
},
"char_filter": {
"mappings_char_filter": {
"type": "mapping",
"mappings": [
":) => _happy_",
":( => _sad_"
]
}
}
}
}
}
Pattern replace 过滤器

pattern_replace字符过滤器使用正则表达式来匹配应用指定的替换字符串替换的字符。替换字符串可以使用正则表达式匹配。

匹配参数:

  • pattern: Java 正则表达式 (Required)
  • replacement: 替换字符串,可以使用$ 1 .. $ 9语法引用捕获组
  • flag: Java正则表达式标志。标志应以管道分隔
// 例子1
PUT my-index-00001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"char_filter": [
"my_char_filter"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "pattern_replace",
"pattern": "(\\d+)-(?=\\d)",
"replacement": "$1_"
}
}
}
}
}

POST my-index-00001/_analyze
{
"analyzer": "my_analyzer",
"text": "My credit card is 123-456-789"
}

[ My, credit, card, is, 123_456_789 ]

// 例子2
PUT my-index-00001
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "standard",
"char_filter": [
"my_char_filter"
],
"filter": [
"lowercase"
]
}
},
"char_filter": {
"my_char_filter": {
"type": "pattern_replace",
"pattern": "(?<=\\p{Lower})(?=\\p{Upper})",
"replacement": " "
}
}
}
},
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "my_analyzer"
}
}
}
}

POST my-index-00001/_analyze
{
"analyzer": "my_analyzer",
"text": "The fooBarBaz method"
}

[ the, foo, bar, baz, method ]
Tokenizer

Tokenizer 接收字符流,将其分解为单个Token(通常为单个单词),然后输出令牌流。例如,whitespace分词器在看到 " " 都会将文本拆分。文本"Quick brown fox!"=> [Quick, brown, fox!]。分词器还负责记录每个term的顺序或位置以及该term表示的原始单词的开始和结束字符偏移量。

ps: 每个analyzer中必须存在1个Character Filter

Word ORiented Tokenizers

Standard Tokenizer:标准分词器将文本划分为单词边界上的术语,这由Unicode文本分段算法定义。它删除大多数标点符号。这是大多数语言的最佳选择。
Letter Tokenizer:字母分词器在遇到非字母字符时会将文本分为多个Terms
[Lowercase Tokenizer]:小写分词器与字母分词器一样,在遇到非字母的字符时将文本分为多个词项,但也会将所有词项小写。
[Whitespace Tokenizer]:每当遇到任何空白字符时,空白令牌生成器会将文本划分为多个术语。
[UAX URL Email Tokenizer]:uax_url_email分词器类似于Standard 分词器,不同之处在于,它将URL和电子邮件地址识别为单个分词。
[Classic Tokenizer]:Classic分词器是英语的基于语法的分词器。
[Thai Tokenizer]:泰语分词器将泰语文本分成单词。

Partial Word Tokenizers

这些分词器将文本或单词分成小片段,以实现部分单词匹配:
[N-Gram Tokenizer]:当遇到任何指定字符列表(例如空格或标点符号)时,ngram分词器可以将文本分解为单词,然后返回每个单词的n-grams:连续字母的滑动窗口, e.g. quick[qu, ui, ic, ck].
[Edge N-Gram Tokenizer]:edge_ngram令牌生成器在遇到指定字符列表(例如空格或标点符号)中的任何一个时,可以将文本分解为单词,然后返回每个单词的n-gram锚定到单词开头的单词, e.g. quick[q, qu, qui, quic, quick].

Structured Text Tokenizers

以下标记器通常与标识符,电子邮件地址,邮政编码和路径之类的结构化文本一起使用,而不是全文本:
[Keyword Tokenizer]:关键字分词器是一个”noop”标记器,它接受给出的任何文本,并输出与单个Term完全相同的文本。它可以与Token filter``(lowercase)组合以对分析进行归一化。
[Pattern Tokenizer]: 模式令牌生成器使用正则表达式在与单词分隔符匹配时将文本拆分为Term,或将匹配的文本捕获为Term
[Simple Pattern Tokenizer]:simple_pattern分词器使用正则表达式捕获匹配的文本作为Term。它使用正则表达式功能的受限子集,并且通常比[Pattern Tokenizer]更快。
[Char Group Tokenizer]:char_group分词器可通过分割的字符集进行配置,通常比运行正则表达式消耗更低。
[Simple Pattern Split Tokenizer]:simple_pattern_split分词器使用与simple_pattern分词器相同的受限正则表达式子集,但是在匹配时拆分输入,而不是将匹配返回为条件。
[Path Tokenizer]:path_hierarchy分词器采用类似于文件系统路径的层次结构值,在路径分隔符上拆分,并为树中的每个组件发出术语,e.g. /foo/bar/baz[/foo, /foo/bar, /foo/bar/baz ].

Token filters

token filter接收令牌流,并可以添加,删除或更改令牌。例如,lowercasetoken filter将所有令牌转换为小写,stoptoken filter从流中删除常见的词(停用词),例如, synonymtoken filter将同义词引入流。

ps: 每个analyzer中可以存在[0-N]Character Filter,原始文本会根据顺序依次应用字符流。

分词器综合例子
"analysis": {
// 分词器由 一个分词器(tokenizer) + [0-n]个字符过滤器(filter) + [0-n]个char_filter构成
/**
* 分词器:将输入文本按照一定的策略进行分解,并建立倒排索引。
*/
"analyzer": {
"ngram_analyzer": {
"filter": [
"trim",
"lowercase",
"filter_ngram"
],
"tokenizer": "tokenizer_split_by_line"
}
},
"filter": {
"filter_ngram": {
"type": "ngram",
"min_gram": "4",
"max_gram": "40"
}
},
"tokenizer": {
"tokenizer_split_by_line": {
"pattern": "\n",
"type": "pattern"
}
}
}

mapping 设置

mapping : 定义文档及其包含字段存储和索引的过程

  • 哪些域应该视为全文检索字段

  • 哪些字段应该包含数字/日期/地理位置

  • 日期格式

  • 自定义规则来动态映射

数据类型


kibana api

  1. 查询
// 查询
POST /babel_test/_search
{
//"explain": true,
"_source": { "excludes": [ "content*" ] },
"query": {
"bool": { "must": [
{ "term": {"project_id": "3" }},
{ "bool": { "should": [
{ "term": {"content": "longyuan"}},
{ "term": {"content_online": "longyuan"}}
] }}
] }
},
"from": 20,"size": 20
}
  1. 创建索引
// PUT / babel_test 
{
"settings": {
"index": {
"analysis": {
// 分词器由 一个分词器(tokenizer) + [0-n]个字符过滤器(filter) + [0-n]个char_filter构成
/**
* 分词器:将输入文本按照一定的策略进行分解,并建立倒排索引。
*/
"analyzer": {
"ngram_analyzer": {
"filter": [
"trim",
"lowercase",
"filter_ngram"
],
"tokenizer": "tokenizer_split_by_line"
}
},
"filter": {
"filter_ngram": {
"type": "ngram",
"min_gram": "4",
"max_gram": "40"
}
},
"tokenizer": {
"tokenizer_split_by_line": {
"pattern": "\n",
"type": "pattern"
}
}
},
"max_ngram_diff": "36",
// 分片数目
"number_of_shards": "5",
// 副本数目
"number_of_replicas": "1"
}
},
// 表内属性值管理,
"mappings": {
"properties": {
"owner": {
"store": true,
"type": "keyword"
},
"cluster": {
"store": true,
"type": "keyword"
},
"update_time": {
"format": "yyy-MM-dd HH:mm:ss",
"store": true,
"type": "date"
},
"create_time": {
"format": "yyy-MM-dd HH:mm:ss",
"store": true,
"type": "date"
},
"project_id": {
"store": true,
"type": "long"
},
"content_online": {
"analyzer": "ngram_analyzer",
"store": true,
"type": "text"
},
"last_index_time": {
"format": "yyy-MM-dd HH:mm:ss",
"store": true,
"type": "date"
},
"name": {
"analyzer": "ngram_analyzer",
"store": true,
"type": "text"
},
"id": {
"type": "keyword"
},
"content": {
"analyzer": "ngram_analyzer",
"store": true,
"type": "text"
}
}
}
}
  1. 删除索引
DELETE /babel_test
  1. 信息查看
// 查看索引的shard信息
GET _cat/indices?v

// 查看索引信息
GET _cat/indices?v

其他

搜索结果不稳定

​ 造成原因:ES 以循环的方式选择查询应访问的分片,连续两次查询的请求会分配到了同一分片的不同副本上。索引的统计是计算分数的重要组成部分,由于删除了文档,所以不同副本分片上的索引统计信息可能不同(由于删除或者更新文档,分片上的文档仅仅是标记为删除,只有等到下次合并文档的时候才会删除该文档。这些已删除的文档将会影响索引统计结果
​ 解决方案:利用一个字符串来表示已登陆的用户作为首选项。这样可以确保给定用户的所有查询始终会在相同的分片上,因此查询获得的分数是一样的。
​ 副作用(优点):如果两个文档获得相同的score,那么它们会按照其内部Lucence文档ID进行排序(与_id无关)。相同分片的副本之间,文档的ID可能不同。所以通过搜索同一分片来保证从而取得的score是相同的。


弃用Type的原因

最初,将ES中的index类比为 SQL数据库中的databasetype类比为table。这种比喻比喻是不确切的。因为,在同一关系型数据库下,不同数据表中的相同字段一般是无关的。但是在ES中,不同type下的统一字段是相同的。
存储在同一索引中具有很少或没有相同字段的不同实体会导致数据稀疏并干扰Lucene有效压缩文档的能力。(在两个不同的 type 中,如果其中的一个 type 存在另一个 type 中不存在的字段,不存在对应字段的 type 依然会对 document 建立索引)。
替代type的方案
1. 每个index中仅存放一种document
优点:数据可以更密集,因此Lucene中使用的压缩技术可以发挥出更好的作用;全文搜索中,评分的统计更加准确(因为每个index中只有一种document)
ps:针对原来在同一index中的不同type,可以针对不同的index进行分片和备份。
2. 自定义 type 字段


REST Client Vs Transport Client

REST:程序更加的松耦合,更加轻量化;比Transport Client更加稳定(需要和ES版本完全匹配);


RestLowLevelClient Vs RestHighLevelClient

RestLowLevelClient:允许通过HTTP与Elasticsearch集群进行通信。将请求编组,将响应反编组交给用户处理;与所有Elasticsearch版本兼容。
RestHighLevelClient:基于LowLevelClient;提供更多API,并负责请求的编排与响应的反编排
ps:就好比是,一个是传自己拼接好的字符串,并且自己解析返回的结果;而另一个是传对象,返回的结果也已经封装好了,直接是对象,更加规范了参数的名称以及格式,更加面对对象一点(类比:低级是面向过程编程,高级是面向对象编程)