ElasticSearch 基本概念
Node: ES 集群的基本服务单元,每个 ElasticSearch 实例则为一个节点。
Cluster: 具有相同 cluster.name 的一组节点构成一个集群 (Cluster)。ps
: 同一集群内节点名不能重复,但集群名一定相同。
Index: ES 会索引所有字段,处理过后写入反向索引 (Inverted Index)。ps
: ES 数据管理顶层单位为 Index 索引,查找数据时直接查找该反向索引。 (可以类比为数据库,Index 名字必须小写)。
Document: Index 里面的单条记录称为文档(Document)。ps:ps
: Index 内的结构尽可能保证相同,有利于提高搜索效率。
Type: 在 Index 里面用来虚拟逻辑分组,用于过滤 Document。比如weather
的 Index 按城市(北京/上海)进行分组。ps
: 不同的 Type 应该具有相似的结构,id
字段中不能一个 Type 中为数值型,另一个为字符串型;两个性质不同的数据,应该存放为两个 Index 而不是存放成一个 Index 中的两个 Type。
Shards: 分片,索引数据量太大时,单个节点物理性能受限,将索引上的数据水瓶拆分
,拆分出来的每个数据部分称之为一个分片。ps
: ES的每个分片都是 Lucene 中的一个索引文件,因此每个分片必须有一个主分片和零到多个副本分片;创建索引的时候需要指定分片数量(ES默认为一个索引创建5个主分片,并分别为每个分片创建一个副本),分片数量一旦确定就不能更改。
Replicas: 备份/副本,副本是对主分片的备份,这种备份是精确的复制模式。当构建索引进行写入操作时,首先在主分片上完成数据的索引,然后数据会从主分片分发到备份分片进行索引。ps
: 备份利弊是共存的:备份可以提高 ES 系统的高可用性;如果备份分片数目设置太多,则在写操作时会增加数据同步的负担。
Settings: Settings 是对集群中索引的定义信息。ps
: 例如一个索引的默认分片数,副本数目
Mapping: Mapping 中保存 了定义索引中字段 (Field)的存储类型、分次放时、是否存储信息等等,类似于关系数据库中的表结构信息ps
: 在 ES 中,Mapping 是可以动态识别的。如果没有特殊需求,则不需要手动创建 Mapping ,因为在ES可以根据数据格式自动识别其类型;特殊情况除外:定义使用其他分词器,是否分词,是否存储等。一个索引的 Mapping 创建,若已经存储了数据,就不可以再修改。
Analyzer: 字段分词方式的定义。一个 Analyzer 由一个 Toeknizer 和零到多个 Filter 组成。 ps
: 在 ES 中,默认的标准 Analyzer 包含一个标准的 Tokenizer 和三个 Filter,即Standard Token Filter, Lower Case Token Filter 和 Stop Token Filter。
mapping类型 字符串类型: text
、keyword
数值类型:long
、integer
、short
、byte
、double
、float
、scaled_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
查询所有数据
PUT /索引名/·类型名·/文档ID {请求体} PUT /test1/type/1 { "name" :"北京邮电大学" , "age" :59 } PUT /test2 { "mappings" : { "properties" : { "name" : { "type" : "text" }, "age" : { "type" : "text" }, "birthday" : { "type" : "date" } } } } GET test2 PUT /test3/_doc/1 { "name" :"樊大头" , "age" :24, "birth" :"1996-10-02" } GET test3 GET _cat/health GET _cat/indices?v DELETE test1
ik
分词器
ik_samrt: 最少切分 GET _analyze { "analyzer" : "ik_smart" , "text" : "北京邮电大学" } ik_max_word 最细粒度划分 GET _analyze { "analyzer" : "ik_max_word" , "text" : "北京邮电大学" }
ik
分词器可以配置自己的词典,使分词器进行分词。
关于文档的基本操作
基本操作
添加数据
PUT /at0m/user/1 { "name": "at0m", "age": 23, "desc": "哈哈哈哈哈哈哈", "tags": [ "程序猿", "学生" ] }
获取数据
更新数据
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" } }
简单搜索
简单的条件查询
GET at0m/user/_search?q=name:unique
ps
: ES 搜索默认返回10条结果,可以通过size
这个字段来改变这个设置。from
字段指定位移:从xx位置开始返回结果(默认为0)
复杂搜索
# 名字为 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
Maven依赖
<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 >
初始化/关闭客户端连接
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(); } } public void initESWithTimeout () { RestClientBuilder clientBuilder = RestClient.builder( new HttpHost ("localhost" , 9200 , "http" )); clientBuilder.setRequestConfigCallback( builder -> builder.setSocketTimeout(10000 ).setSocketTimeout(60000 )); } 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()) ); }
创建索引
public void indexDocument (String indexName, String Document) { IndexRequest indexRequest = new IndexRequest (indexName); indexRequest.id(Document); indexRequest.source(jsonMap); try { IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT); 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(); } }
索引查询
public void getDocument (String indexName, String document) { GetRequest getRequest = new GetRequest (indexName, document); try { GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); String index = getResponse.getIndex(); String id = getResponse.getId(); if (getResponse.isExists()) { long version = getResponse.getVersion(); String sourceAsString = getResponse.getSourceAsString(); Map<String, Object> sourceAsMap = getResponse.getSourceAsMap(); byte [] sourceAsBytes = getResponse.getSourceAsBytes(); } } catch (IOException e) { e.printStackTrace(); } }
文档存在性校验
public void checkExistIndexDocuments (String indexName, String document) { 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(); } }
删除文档索引
public void deleteDocument (String indexName, String document) { DeleteRequest deleteRequest = new DeleteRequest (indexName, document); try { DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT); 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(); } }
更新文档索引
public void updateDocument (String indexName, String document) { UpdateRequest updateRequest = new UpdateRequest (indexName, document); updateRequest.doc(jsonMap); try { UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT); 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(); } } updateRequest.upsert(jsonString, XcontentType.JSON);
获取文档索引的词向量
public void termVectorsDocument (String indexName, String document, String field) { TermVectorsRequest termVectorsRequest = new TermVectorsRequest (indexName, document); termVectorsRequest.setFields(field); try { TermVectorsResponse termVectorsResponse = client.termvectors(termVectorsRequest, RequestOptions.DEFAULT); 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(); } }
批量处理
public void bulkRequest (String indexName) { 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(); } }
聚合搜索
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
字符过滤器将原始文本作为字符流接收,并且可以通过添加/删除/更改字符来转换流。
ps
: 每个analyzer
中可以存在[0-N]
个Character Filter
,原始文本会根据顺序依次应用字符流。
HTML Strip 过滤器 GET /_analyze { "tokenizer" : "keyword" , "char_filter" : [ "html_strip" ] , "text" : "<p>I'm so <b>happy</b>!</p>" } [ \nI'm so happy!\n ] 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正则表达式标志。标志应以管道分隔
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 ] 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 接收令牌流,并可以添加,删除或更改令牌。例如,lowercase
的 token filter 将所有令牌转换为小写,stop
token filter 从流中删除常见的词(停用词),例如, synonym
token filter 将同义词引入流。
ps
: 每个analyzer
中可以存在[0-N]
个Character Filter
,原始文本会根据顺序依次应用字符流。
分词器综合例子 "analysis" : { "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
查询
POST /babel_test/_search { "_source" : { "excludes" : [ "content*" ] } , "query" : { "bool" : { "must" : [ { "term" : { "project_id" : "3" } } , { "bool" : { "should" : [ { "term" : { "content" : "longyuan" } } , { "term" : { "content_online" : "longyuan" } } ] } } ] } } , "from" : 20 , "size" : 20 }
创建索引
{ "settings" : { "index" : { "analysis" : { "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" } } } }
删除索引
信息查看
// 查看索引的shard信息 GET _cat/indices?v // 查看索引信息 GET _cat/indices?v
其他
搜索结果不稳定
造成原因:ES 以循环的方式选择查询应访问的分片,连续两次查询的请求会分配到了同一分片的不同副本上。索引的统计是计算分数的重要组成部分,由于删除了文档,所以不同副本分片上的索引统计信息可能不同(由于删除或者更新文档,分片上的文档仅仅是标记为删除,只有等到下次合并文档的时候才会删除该文档。这些已删除的文档将会影响索引统计结果
) 解决方案:利用一个字符串来表示已登陆的用户作为首选项。这样可以确保给定用户的所有查询始终会在相同的分片上,因此查询获得的分数是一样的。 副作用(优点):如果两个文档获得相同的score
,那么它们会按照其内部Lucence文档ID进行排序(与_id无关)。相同分片的副本之间,文档的ID可能不同。所以通过搜索同一分片来保证从而取得的score
是相同的。
弃用Type的原因
最初,将ES中的index
类比为 SQL数据库中的database
,type
类比为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
:就好比是,一个是传自己拼接好的字符串,并且自己解析返回的结果;而另一个是传对象,返回的结果也已经封装好了,直接是对象,更加规范了参数的名称以及格式,更加面对对象一点(类比:低级是面向过程编程,高级是面向对象编程)