• [环境搭建] 【logstash对接问题】logstash对接ES报错
    求助!重启了logstash,就一直报这个错,多次重启都没用[2024-03-05T18:09:29,606][WARN ][logstash.outputs.elasticsearch][main] Attempted to resurrect connection to dead ES instance, but got an error. {:url=>"https://ty:xxxxxx@10.x.x.xx:24100/", :error_type=>LogStash::Outputs::ElasticSearch::HttpClient::Pool::HostUnreachableError, :error=>"Elasticsearch Unreachable: [https://ty_ywzx:xxxxxx@10.xxx.x.xxx:24100/][Manticore::ClientProtocolException] PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"}
  • [技术干货] Java中使用Elasticsearch服务
    Java中使用Elasticsearch服务的示例代码如下:首先,确保你已经安装了Elasticsearch,并且它正在运行。接下来,你需要添加Elasticsearch的Java客户端库到你的项目中。你可以使用Maven或Gradle来管理依赖项。Maven依赖项:<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.10.2</version> </dependency>Gradle依赖项:implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.10.2'接下来,你可以使用以下示例代码来连接到Elasticsearch并执行一些基本的操作:import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import java.io.IOException; public class ElasticsearchExample { public static void main(String[] args) { // 创建Elasticsearch客户端 RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http"))); try { // 构建查询请求 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchAllQuery()); searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); // 执行查询请求 GetRequest getRequest = new GetRequest("your_index_name", "your_document_id"); getRequest.source(searchSourceBuilder); GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); // 处理查询结果 if (getResponse.isExists()) { System.out.println("找到了匹配的文档!"); String sourceAsString = getResponse.getSourceAsString(); System.out.println(sourceAsString); } else { System.out.println("未找到匹配的文档。"); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭Elasticsearch客户端 try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } }在上面的示例代码中,我们首先创建了一个RestHighLevelClient对象来连接到Elasticsearch。然后,我们构建了一个查询请求,使用matchAllQuery()方法来匹配所有文档。接下来,我们执行查询请求并处理查询结果。最后,我们关闭了Elasticsearch客户端。请注意,你需要将your_index_name替换为你要查询的实际索引名称,将your_document_id替换为你要查询的实际文档ID。这只是一个简单的示例,你可以根据自己的需求进行更复杂的操作,例如搜索特定条件的文档、更新文档、删除文档等。
  • [技术干货] Solr和Elasticsearch对比
    搜索引擎是现代化的网络技术之一。搜索引擎的主要工作是在用户查询时返回包含相关信息的文档列表,它可以用于Web搜索,企业搜索和数据搜索等多个领域。Solr和Elasticsearch(ES)是两个受欢迎的开源搜索引擎,它们都基于Lucene,都提供了全文搜索、分布式架构和强大的API等特征。下面我们将对Solr和Elasticsearch进行详细的介绍。一、SolrSolr是一个开源的搜索平台,适用于 Web 搜索、企业搜索以及数据搜索等领域。它提供了全文搜索、分布式架构和强大的HTTP API等特点。Solr最初是由Apache Software Foundation开发并支持的,它是基于Lucene Java库构建而成的。Solr提供了一组充分证明了性能和可扩展性的核心功能,包括数据分片、复制、高可用性、集群管理、查询和索引优化。Solr的优点如下:大规模数据处理与搜索:Solr强大的文本分析器和查询模式的灵活性,使得 Solr 能够进行大量数据的处理与搜索。相对简单的安装:Solr的安装非常简单,它支持多种操作系统,提供了多种方式进行部署。多语言支持:Solr支持多语言搜索,它能够处理包括泰语、中文、日语、朝鲜语等在内的多种语言。二、Elasticsearch(ES)ES是一个基于开源搜索引擎Lucene的分布式、RESTful的搜索系统。它不但具有强大的搜索能力,还具有快速稳定的响应性和可扩展性。ES不仅支持全文搜索,还支持结构化数据搜索、地理数据搜索、大数据分析和监控等场景。ES创建的目的是提供一个容易扩展和管理的搜索引擎,它提供了许多强大和易用的功能,如近实时搜索、动态字段支持、自动索引、多租户和强大的文本搜索等。ES的优点如下:高性能:ES内部采用了许多优化技术,支持近实时搜索、分布式搜索和索引等优点,可处理大量数据的搜索请求。功能完善:ES支持多种数据类型,数据可导入到ES中,支持结构化和半结构化数据,并且拥有强大的聚合分析功能。可扩展性:ES基于分布式架构,每个节点都可以承载数据、进行存储和计算处理,可实现水平扩展。Solr和Elasticsearch的区别主要在于:架构设计:Solr采用独立的Solr服务器和Solr客户端,而ES基于分布式架构,节点可以担任多个角色。文档存储:Solr文档必须在对应的Shard中存储,而ES不同,ES文档可以存储到初始节点或分片节点中。数据同步:Solr通过复制索引的方式进行数据同步,ES通过传输可序列化的JSON数据进行同步。综上所述,Solr和Elasticsearch虽然都基于Lucene,但是它们的架构设计、文档存储和数据同步方式还是有所不同,所以在选择时需要根据实际情况进行权衡和选择
  • [基础组件] elasticsearch自定义分词器样例
    1 text_en_splitting_tight分词名称分词器和过滤器输入分词效果text_en_splitting_tight1.中英文根据空格分词2.替换成同义词,比如搜索北大换成北京大学3.删除停顿词,比如a an but4.把特殊符号去掉,比如wi-fi 替换成wifi5.大写转换成小写6.保护词免于被分词器修改7.英文单词复数变单数形势(比如dogs变成dog)8.避免重复处理我们 的祖国 名称 是 ChI_na, we are 北大 dogs我们的祖国名称是chinawe北京大学dog1.1 创建分词器curl -XPUT --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323?pretty" -H 'Content-Type:application/json' -d'{"settings": {"analysis": {"char_filter": {"my_char_filter": {"type": "mapping","mappings": ["北大 =>北京大学","_ => "]}},"filter": {"my_stopword": {"type": "stop","stopwords": ["a","an", "but","are"]}},"tokenizer":{"my_tokenizer":{"type":"pattern","pattern":"[ ]"}},"analyzer":{"text_en_splitting_tight":{"type":"custom","char_filter":["my_char_filter"],"filter":["my_stopword","lowercase"],"tokenizer":"my_tokenizer"}}}}}'1.2 输入查询curl -XGET --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323/_analyze?pretty" -H 'Content-Type:application/json' -d'{ "analyzer":"text_en_splitting_tight","text":"我们 的祖国 名称 是 ChI_na, we are 北大 dogs"}'2 text_general分词名称分词器和过滤器输入分词效果text_generalIndex1.自动给拆分成的单个词添加type2.删除停顿词,比如a an but3.大写转换成小写我们 的祖国 名称 是 ChI_na, we are 北大 dogs我们的祖国名称是chinawe北大dogsquery1.自动给拆分成的单个词添加type2.删除停顿词,比如a an but3.替换成同义词,比如搜索北大换成北京大学4.大写转换成小写我们 的祖国 名称 是 ChI_na, we are 北大 dogs我们的祖国名称是chinawe北京大学dogs2.1 分词创建-indexcurl -XPUT --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323?pretty" -H 'Content-Type:application/json' -d'{"settings": {"analysis": {"char_filter": {"my_char_filter": {"type": "mapping","mappings": ["_ => "]}},"filter": {"my_stopword": {"type": "stop","stopwords": ["a","an", "but","are"]}},"tokenizer":{"my_tokenizer":{"type":"pattern","pattern":"[ ]"}},"analyzer":{"text_general":{"type":"custom","char_filter":["my_char_filter"],"filter":["my_stopword","lowercase"],"tokenizer":"my_tokenizer"}}}}}'2.2 输入查询-indexcurl -XGET --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323/_analyze?pretty" -H 'Content-Type:application/json' -d'{ "analyzer":"text_general","text":"我们 的祖国 名称 是 ChI_na, we are 北大 dogs"}'2.3 分词创建-querycurl -XPUT --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323?pretty" -H 'Content-Type:application/json' -d'{"settings": {"analysis": {"char_filter": {"my_char_filter": {"type": "mapping","mappings": ["北大 =>北京大学","_ => "]}},"filter": {"my_stopword": {"type": "stop","stopwords": ["a","an", "but","are"]}},"tokenizer":{"my_tokenizer":{"type":"pattern","pattern":"[ ]"}},"analyzer":{"text_general":{"type":"custom","char_filter":["my_char_filter"],"filter":["my_stopword","lowercase"],"tokenizer":"my_tokenizer"}}}}}'2.4 输入查询-querycurl -XGET --tlsv1.2 --negotiate -k -u : "https://xx.xx:24100/h0323/_analyze?pretty" -H 'Content-Type:application/json' -d'{ "analyzer":"text_general","text":"我们 的祖国 名称 是 ChI_na, we are 北大 dogs"}'
  • [基础组件] ElasticSearch评分函数样例
    Lucene(或Elasticsearch)使用布尔模型(Boolean model) 查找匹配文档,并用一个名为实用评分函数(practical scoring function) 的公式来计算相关度。ES中的自定义评分机制function_score主要用于让用户自定义查询相关性得分,实现精细化控制评分的目的详细参考: https://www.elastic.co/guide/cn/elasticsearch/guide/current/practical-scoring-function.html1 创建索引curl -XPUT cid:link_02 创建mappingcurl -H "Content-Type: application/json" -XPUT cid:link_0/video/_mapping?include_type_name=true -d '{ "video": { "properties": { "title": { "type": "text", "analyzer": "snowball" }, "description": { "type": "text", "analyzer": "snowball" }, "views": { "type": "integer" }, "likes": { "type": "integer" }, "created_at": { "type": "date" } } }}'3 添加数据curl -H "Content-Type: application/json" -XPUT cid:link_0/video/1 -d '{ "title": "Sick Sad World: Cold Breeze on the Interstate", "description": "Is your toll collector wearing pants a skirt or nothing but a smile Cold Breeze on the Interstate next on Sick ", "views": 500, "likes":2, "created_at": "2023-04-22T08:00:00"}'curl -H "Content-Type: application/json" -XPUT cid:link_0/video/2 -d '{ "title": "Sick Sad World: The Severed Pianist", "description": "When he turned up his nose at accordion lessons, they cut off his inheritance molto allegro. The Severed Pianist, ne", "views": 6000, "likes": 100, "created_at": "2023-04-22T12:00:00"}'curl -H "Content-Type: application/json" -XPUT cid:link_0/video/3 -d '{ "title": "Sick Sad World: Avant Garde Obstetrician", "description": "Meet the avant-garde obstetrician who has turned his cast offs into art work. Severed Umbilical cord sculpture next,", "views": 100, "likes": 130, "created_at": "2023-04-22T23:00:00"}'4 计算分数错误样例:curl -H "Content-Type: application/json" -XPOST cid:link_0/video/_search -d '{ "query": { "function_score": { "query": { "match": { "_all": "severed" } }, "script_score": { "script": "_score * Math.log(doc['likes'].value + doc['views'].value + 1)" } } }}'正确样例,注意单引号 \u0027A、使用ES内置的script_score方法计算分数curl -X GET "cid:link_0/video/_search?pretty" -H 'Content-Type: application/json' –d '{ "query": { "function_score": { "query": { "match": { "_all": "severed" } }, "script_score": { "script": { "source": "Math.log(2 + doc[\u0027likes\u0027].value)" } } } }}'输出结果:{ "took" : 3, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 0, "relation" : "eq" }, "max_score" : null, "hits" : [ ] }}B、使用衰减函数linear计算分数curl -H "Content-Type: application/json" -XPOST cid:link_0/video/_search -d ' { "query": { "function_score": { "functions": [ { "linear": { "views": { "origin": 5000, "scale": 2500 } } }, { "linear": { "likes": { "origin": 200, "scale": 90 } } } ] } }}'输出结果:{ "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 0.35555556, "hits": [ { "_index": "searchtub_2", "_type": "video", "_id": "2", "_score": 0.35555556, "_source": { "title": "Sick Sad World: The Severed Pianist", "description": "When he turned up his nose at accordion lessons, they cut off his inheritance molto allegro. The Severed Pianist, ne", "views": 6000, "likes": 100, "created_at": "2023-04-22T12:00:00" } }, { "_index": "searchtub_2", "_type": "video", "_id": "3", "_score": 0.012222222, "_source": { "title": "Sick Sad World: Avant Garde Obstetrician", "description": "Meet the avant-garde obstetrician who has turned his cast offs into art work. Severed Umbilical cord sculpture next,", "views": 100, "likes": 130, "created_at": "2023-04-22T23:00:00" } }, { "_index": "searchtub_2", "_type": "video", "_id": "1", "_score": 0, "_source": { "title": "Sick Sad World: Cold Breeze on the Interstate", "description": "Is your toll collector wearing pants a skirt or nothing but a smile Cold Breeze on the Interstate next on Sick ", "views": 500, "likes": 2, "created_at": "2023-04-22T08:00:00" } } ] }}
  • [技术干货] SpringBoot 整合 ElasticSearch 进行各种高级查询
    一、简介在之前的文章中, 我们详细的介绍了 ElasticSearch 的安装与使用,详细大家对 ElasticSearch 有了初步的认识。本文将重点介绍 SpringBoot 整合 ElasticSearch 做搜索引擎,实现亿量级数据的快速查询。废话不多说,直接上代码!二、代码实践本文采用的SpringBoot版本号是2.1.0.RELEASE,服务端 es 的版本号是6.8.2,客户端采用的是官方推荐的Elastic Java High Level Rest Client版本号是6.8.2,方便与SpringBoot的版本兼容。es 最大的亮点就是查询非常丰富,可以在上亿的数据里面快速搜索出目标数据,查询如果实现呢?请看下文单条件精确查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 堆代码 duidaima.com* 单条件精确查询* @throws IOException*/@Testpublic void search0() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.termsQuery("name", "赵里"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}多条件精确查询,取并集 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 多条件精确查询,取并集* @throws IOException*/@Testpublic void search1() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.termsQuery("name", "张", "陈"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}范围查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 范围查询,包括from、to* @throws IOException*/@Testpublic void search2() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.rangeQuery("age").from(20).to(32));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}/*** 范围查询,不包括from、to* @throws IOException*/@Testpublic void search3() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.rangeQuery("age").from(20,false).to(30, false));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}/*** 范围查询, lt:小于,gt:大于* @throws IOException*/@Testpublic void search4() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.rangeQuery("age").lt(30).gt(20));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}模糊查询,支持通配符 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 模糊查询,支持通配符* @throws IOException*/@Testpublic void search5() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.wildcardQuery("name","张三"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}不使用通配符的模糊查询,左右匹配 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 不使用通配符的模糊查询,左右匹配* @throws IOException*/@Testpublic void search6() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.queryStringQuery("张三").field("name"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}多字段模糊查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 多字段模糊查询* @throws IOException*/@Testpublic void search7() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.multiMatchQuery("长", "name", "city"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}多字段模糊查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 堆代码 duidaima.com* 分页搜索* @throws IOException*/@Testpublic void search8() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().from(0).size(2);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}字段排序 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 排序,字段的类型必须是:integer、double、long或者keyword* @throws IOException*/@Testpublic void search9() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().sort("createTime", SortOrder.ASC);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}精确统计筛选文档数 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 精确统计筛选文档数,查询性能有所降低* @throws IOException*/@Testpublic void search10() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().trackTotalHits(true);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}设置源字段过滤返回 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 设置源字段过虑,第一个参数结果集包括哪些字段,第二个参数表示结果集不包括哪些字段* @throws IOException*/@Testpublic void search11() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().fetchSource(new String[]{"name","age","city","createTime"},new String[]{});//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}根据 id 精确匹配 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 根据id精确匹配* @throws IOException*/@Testpublic void search12() throws IOException {String[] ids = new String[]{"1","2"};// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.termsQuery("_id", ids));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}matchAllQuery 搜索全部 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** matchAllQuery搜索全部* @throws IOException*/@Testpublic void search21() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}match 搜索匹配 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** match搜索匹配* @throws IOException*/@Testpublic void search22() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchQuery("name", "张王"));//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}bool组合查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** bool组合查询* @throws IOException*/@Testpublic void search23() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();boolQueryBuilder.must(QueryBuilders.matchQuery("name", "张王"));boolQueryBuilder.must(QueryBuilders.rangeQuery("age").lte(30).gte(20));builder.query(boolQueryBuilder);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}nested 类型嵌套查询 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** nested类型嵌套查询* @throws IOException*/@Testpublic void search24() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件查询BoolQueryBuilder mainBool=new BoolQueryBuilder();mainBool.must(QueryBuilders.matchQuery("name", "赵六"));//nested类型嵌套查询BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();boolQueryBuilder.must(QueryBuilders.matchQuery("products.brand", "A"));boolQueryBuilder.must(QueryBuilders.matchQuery("products.title", "巧克力"));NestedQueryBuilder nested = QueryBuilders.nestedQuery("products",boolQueryBuilder, ScoreMode.None);mainBool.must(nested);builder.query(mainBool);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}多条件查询 + 排序 + 分页 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 多条件查询 + 排序 + 分页* @throws IOException*/@Testpublic void search29() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件搜索BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();boolQueryBuilder.must(QueryBuilders.matchQuery("name", "张王"));boolQueryBuilder.must(QueryBuilders.rangeQuery("age").lte(30).gte(20));builder.query(boolQueryBuilder);//结果集合分页builder.from(0).size(2);//排序builder.sort("createTime",SortOrder.ASC);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}聚合查询-求和 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 聚合查询 sum* @throws IOException*/@Testpublic void search30() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件搜索builder.query(QueryBuilders.matchAllQuery());//聚合查询AggregationBuilder aggregation = AggregationBuilders.sum("sum_age").field("age");builder.aggregation(aggregation);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}聚合查询-求平均值 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 聚合查询 avg* @throws IOException*/@Testpublic void search31() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件搜索builder.query(QueryBuilders.matchAllQuery());//聚合查询AggregationBuilder aggregation = AggregationBuilders.avg("avg_age").field("age");builder.aggregation(aggregation);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}聚合查询-计数 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 聚合查询 count* @throws IOException*/@Testpublic void search32() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件搜索builder.query(QueryBuilders.matchAllQuery());//聚合查询AggregationBuilder aggregation = AggregationBuilders.count("count_age").field("age");builder.aggregation(aggregation);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}聚合查询-分组 codeduidaima.com@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = ElasticSearchApplication.class)public class SearchJunit {@Autowiredprivate RestHighLevelClient client;/*** 聚合查询 分组* @throws IOException*/@Testpublic void search33() throws IOException {// 创建请求SearchSourceBuilder builder = new SearchSourceBuilder();//条件搜索builder.query(QueryBuilders.matchAllQuery());//聚合查询AggregationBuilder aggregation = AggregationBuilders.terms("tag_createTime").field("createTime").subAggregation(AggregationBuilders.count("count_age").field("age")) //计数.subAggregation(AggregationBuilders.sum("sum_age").field("age")) //求和.subAggregation(AggregationBuilders.avg("avg_age").field("age")); //求平均值builder.aggregation(aggregation);//不输出原始数据builder.size(0);//搜索SearchRequest searchRequest = new SearchRequest();searchRequest.indices("cs_index");searchRequest.types("_doc");searchRequest.source(builder);// 执行请求SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);// 解析查询结果System.out.println(response.toString());}}三、小结本文主要围绕 SpringBoot 整合 ElasticSearch 进行各种高级查询的介绍,希望对大家有所帮助!转载自cid:link_0Group/Topic/JAVA/10446
  • [其他] SpringBoot整合ElasticSearch
    版本关系依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>配置server: port: 8082 spring: elasticsearch: rest: uris: 192.168.25.131:9200实体类@Data @AllArgsConstructor @NoArgsConstructor //indexName名字如果是字母那么必须是小写字母 @Document(indexName = "student") public class Student { @Id @Field(store = true, type = FieldType.Keyword) private String sId; @Field(store = true, type = FieldType.Keyword) private String sName; @Field(store = true, type = FieldType.Text, analyzer = "ik_smart") //Text可以分词 ik_smart=粗粒度分词 ik_max_word 为细粒度分词 private String sAddress; @Field(index = false, store = true, type = FieldType.Integer) private Integer sAge; @Field(index = false, store = true, type = FieldType.Date, format = DateFormat.basic_date_time) private Date sCreateTime; @Field(type = FieldType.Keyword) private String[] sCourseList; //数组类型 由数组中第一个非空值决定(这里数组和集合一个意思了) @Field(type = FieldType.Keyword) private List<String> sColorList; //集合类型 由数组中第一个非空值决定 }ElasticsearchRepository接口/** * @author: zhouwenjie * @description: * @create: 2022-05-12 17:37 * ElasticsearchRepository<T, ID> T:实体类泛型,ID:实体类主键类型 **/ public interface StudentMapper extends ElasticsearchRepository<Student, String> { }调用@Test void contextLoads2() { List<String> colorList = new ArrayList<>();//颜色 colorList.add("red"); colorList.add("white"); colorList.add("black"); Student student = new Student("1", "mhh", "济南", 12, new Date(), new String[]{"语文", "数学", "英语"}, colorList); Student save = restTemplate.save(student); System.out.println(save); }
  • [其他] ElasticSearch的使用
    1.创建工程,引入依赖<dependencies> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>5.6.8</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>5.6.8</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>2.9.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.24</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>2.创建索引index@Test //创建索引 public void test1() throws Exception{ // 创建Client连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); //创建名称为blog2的索引 client.admin().indices().prepareCreate("blog2").get(); //释放资源 client.close(); }3.创建映射mapping@Test //创建映射 public void test3() throws Exception{ // 创建Client连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 添加映射 /** * 格式: * "mappings" : { "article" : { "dynamic" : "false", "properties" : { "id" : { "type" : "string" }, "content" : { "type" : "string" }, "author" : { "type" : "string" } } } } */ XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .startObject("article") .startObject("properties") .startObject("id") .field("type", "integer").field("store", "yes") .endObject() .startObject("title") .field("type", "string").field("store", "yes").field("analyzer", "ik_smart") .endObject() .startObject("content") .field("type", "string").field("store", "yes").field("analyzer", "ik_smart") .endObject() .endObject() .endObject() .endObject(); // 创建映射 PutMappingRequest mapping = Requests.putMappingRequest("blog2") .type("article").source(builder); client.admin().indices().putMapping(mapping).get(); //释放资源 client.close(); }4 建立文档document@Test //创建文档(通过XContentBuilder) public void test4() throws Exception{ // 创建Client连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); //创建文档信息 XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .field("id", 1) .field("title", "ElasticSearch是一个基于Lucene的搜索服务器") .field("content", "它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。") .endObject(); // 建立文档对象 /** * 参数一blog1:表示索引对象 * 参数二article:类型 * 参数三1:建立id */ client.prepareIndex("blog2", "article", "1").setSource(builder).get(); //释放资源 client.close(); }5 查询文档操作5.1关键词查询@Test public void testTermQuery() throws Exception{ //1、创建es客户端连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); //2、设置搜索条件 SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(QueryBuilders.termQuery("content", "搜索")).get(); //3、遍历搜索结果数据 SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象 System.out.println("查询结果有:" + hits.getTotalHits() + "条"); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit searchHit = iterator.next(); // 每个查询对象 System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印 System.out.println("title:" + searchHit.getSource().get("title")); } //4、释放资源 client.close(); }5.2 字符串查询@Test public void testStringQuery() throws Exception{ //1、创建es客户端连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); //2、设置搜索条件 SearchResponse searchResponse = client.prepareSearch("blog2") .setTypes("article") .setQuery(QueryBuilders.queryStringQuery("搜索")).get(); //3、遍历搜索结果数据 SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象 System.out.println("查询结果有:" + hits.getTotalHits() + "条"); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit searchHit = iterator.next(); // 每个查询对象 System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印 System.out.println("title:" + searchHit.getSource().get("title")); } //4、释放资源 client.close(); } 5.3 使用文档ID查询文档@Test public void testIdQuery() throws Exception { //client对象为TransportClient对象 SearchResponse response = client.prepareSearch("blog1") .setTypes("article") //设置要查询的id .setQuery(QueryBuilders.idsQuery().addIds("test002")) //执行查询 .get(); //取查询结果 SearchHits searchHits = response.getHits(); //取查询结果总记录数 System.out.println(searchHits.getTotalHits()); Iterator<SearchHit> hitIterator = searchHits.iterator(); while(hitIterator.hasNext()) { SearchHit searchHit = hitIterator.next(); //打印整行数据 System.out.println(searchHit.getSourceAsString()); } }6 查询文档分页操作@Test //分页查询 public void test10() throws Exception{ // 创建Client连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 搜索数据 SearchRequestBuilder searchRequestBuilder = client.prepareSearch("blog2").setTypes("article") .setQuery(QueryBuilders.matchAllQuery());//默认每页10条记录 // 查询第2页数据,每页20条 //setFrom():从第几条开始检索,默认是0。 //setSize():每页最多显示的记录数。 searchRequestBuilder.setFrom(0).setSize(5); SearchResponse searchResponse = searchRequestBuilder.get(); SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象 System.out.println("查询结果有:" + hits.getTotalHits() + "条"); Iterator<SearchHit> iterator = hits.iterator(); while (iterator.hasNext()) { SearchHit searchHit = iterator.next(); // 每个查询对象 System.out.println(searchHit.getSourceAsString()); // 获取字符串格式打印 System.out.println("id:" + searchHit.getSource().get("id")); System.out.println("title:" + searchHit.getSource().get("title")); System.out.println("content:" + searchHit.getSource().get("content")); System.out.println("-----------------------------------------"); } //释放资源 client.close(); }7.查询结果高亮显示@Test //高亮查询 public void test11() throws Exception{ // 创建Client连接对象 Settings settings = Settings.builder().put("cluster.name", "my-elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 搜索数据 SearchRequestBuilder searchRequestBuilder = client .prepareSearch("blog2").setTypes("article") .setQuery(QueryBuilders.termQuery("title", "搜索")); //设置高亮数据 HighlightBuilder hiBuilder=new HighlightBuilder(); hiBuilder.preTags("<font style='color:red'>"); hiBuilder.postTags("</font>"); hiBuilder.field("title"); searchRequestBuilder.highlighter(hiBuilder); //获得查询结果数据 SearchResponse searchResponse = searchRequestBuilder.get(); //获取查询结果集 SearchHits searchHits = searchResponse.getHits(); System.out.println("共搜到:"+searchHits.getTotalHits()+"条结果!"); //遍历结果 for(SearchHit hit:searchHits){ System.out.println("String方式打印文档搜索内容:"); System.out.println(hit.getSourceAsString()); System.out.println("Map方式打印高亮内容"); System.out.println(hit.getHighlightFields()); System.out.println("遍历高亮集合,打印高亮片段:"); Text[] text = hit.getHighlightFields().get("title").getFragments(); for (Text str : text) { System.out.println(str); } } //释放资源 client.close(); }
  • [其他] ElasticSearch的使用
    概述Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。 ElasticSearch的官方地址: cid:link_0Elasticsearch核心概念1 索引 index一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。2 类型 type在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。3 字段Field相当于是数据表的字段,对文档数据根据不同属性进行的分类标识4 映射 mappingmapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。5 文档 document一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。6 接近实时 NRTElasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)7 集群 cluster一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群8 节点 node一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。9 分片和复制 shards&replicas一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因: 1)允许你水平分割/扩展你的内容容量。 2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。复制之所以重要,有两个主要原因: 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制的数量,但是你事后不能改变分片的数量。默认情况下,Elasticsearch中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。设计方案ES 订单数据的同步方案 MySQL数据同步到ES中,大致总结可以分为两种方案:方案1:监听MySQL的Binlog,分析Binlog将数据同步到ES集群中。方案2:直接通过ES API将数据写入到ES集群中。考虑到订单系统ES服务的业务特殊性,对于订单数据的实时性较高,显然监听Binlog的方式相当于异步同步,有可能会产生较大的延时性。且方案1实质上跟方案2类似,但又引入了新的系统,维护成本也增高。所以订单中心ES采用了直接通过ES API写入订单数据的方式,该方式简洁灵活,能够很好的满足订单中心数据同步到ES的需求。由于ES订单数据的同步采用的是在业务中写入的方式,当新建或更新文档发生异常时,如果重试势必会影响业务正常操作的响应时间。所以每次业务操作只更新一次ES,如果发生错误或者异常,在数据库中插入一条补救任务,有Worker任务会实时地扫这些数据,以数据库订单数据为基准来再次更新ES数据。通过此种补偿机制,来保证ES数据与数据库订单数据的最终一致性。IK 分词器和ElasticSearch集成使用IK分词器简介IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为 面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。IK分词器3.0的特性如下:1)采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。2)采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。3)对中英联合支持不是很好,在这方面的处理比较麻烦.需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。4)支持用户词典扩展定义。5)针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。
  • [技术干货] Elasticsearch原理与基本使用
    一、Elasticsearch概述1.1 什么是搜索​ 搜索,就是在任何场景下,找寻想要的信息。通过关键字检索出与此关键字有关的信息。这和查询还不太一样,查询通常是在表格类型的数据中查找,字段的内容的长度往往不大。1.2 使用传统数据库实现搜索​ 传统数据库的情况下,如果要查询某个字段是否包含某些关键字的话,需要使用到like关键字来进行字段匹配,很大概率导致全表扫描,本身来说性能就不算好。如果再加上查询的字段非常长,那么使用like匹配的工作量是很大的,另外如果表的行数也很多,那么性能就更差了。1.3 全文检索与倒排索引​ 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。全文搜索引擎数据库中的数据。而全文检索用到的关键技术就是倒排索引。什么是倒排索引?看看例子就知道了数据库中有如下数据 id 员工描述 1 优秀论文 2 优秀员工称号 3 优秀项目 4 优秀团队 建立倒排索引的步骤: 1、每行切词, 怎么切都可以,看实际需要 1 优秀 论文 2 优秀 员工 称号 3 优秀 项目 4 优秀 团队 2、建立倒排索引 优秀 1,2,3,4 论文 1 员工 2 称号 2 项目 3 团队 4 3、检索 倒排索引意思简单就是指定的词出现在哪些行中,这些行都用唯一id进行标识。 所以这就是为什么倒排索引用到全文检索中,因为可以直接查询到包含相关关键字的内容有哪些。 比如搜索优秀,可以看到优秀这个词在1234中都有出现,然后根据id查询原始数据。​ 有了倒排索引,当我们需要从很多端很长的内容中检索包含指定关键字的内容时,直接根据倒排索引就知道有没有指定关键字了。而如果使用传统数据库,那么必须扫描全部内容,如果数据有1000行,那工作量就很恐怖了。而倒排索引只是查询个关键字而已,无需扫描全部内容。1.4 Lucene和Elasticsearch​ Lucene就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。我们就用java开发的时候,引入lucene jar,然后基于lucene的api进行去进行开发就可以了。但是它只是根据文本做出索引,然后保存下来,但是本身并不提供搜索功能。​ 由于Lucene使用比较复杂,繁琐,所以基于Lucene开发了一个新的项目,也就是Elasticsearch(简称ES)。1.5 ES的特点与适用场景特点:1)可以作为一个大型分布式集群(数百台<a title="服务器" target="_blank" data-cke-saved-href="https://www.yisu.com/" href="https://www.yisu.com/" style="padding:0px; box-sizing:border-box; color:rgb(255, 102, 102); text-decoration:none; outline:none;">服务器</a>)技术,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司; 2)Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES;lucene(全文检索),商用的数据分析软件(也是有的),分布式数据库(mycat); 3)对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分钟部署一下ES,就可以作为生产环境的系统来使用了,数据量不大,操作不是太复杂; 4)数据库的功能面对很多领域是不够用的(事务,还有各种联机事务型的操作);特殊的功能,比如全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理;Elasticsearch作为传统数据库的一个补充,提供了数据库所不能提供的很多功能。适用场景:1)维基百科,类似百度百科,牙膏,牙膏的维基百科,全文检索,高亮,搜索推荐。 2)The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论)+ 社交网络数据(对某某新闻的相关看法),数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜)。 3)Stack Overflow(国外的程序异常讨论论坛),IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案。 4)GitHub(开源代码管理),搜索上千亿行代码。 5)国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(OA,CRM,ERP,等等),数据分析(ES热门的一个使用场景)。1.6 ES中的相关概念近实时两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级。集群clusterES集群可以有多个节点,但是每个节点属于哪个ES集群中是通过配置集群名称来指定的。当然一个集群只有一个节点也是OK的节点node集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群。index--database索引包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。类似于传统数据库中的库的概念type--table 每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。类似于传统数据库中的表的概念。 要注意:es逐渐抛弃掉这个概念了,到6.x版本中,已经只允许一个index只有一个type了。document--行 文档是es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。相当于行field--字段Field是Elasticsearch的最小单位。一个document里面有多个field,每个field就是一个数据字段。 如: product document { "product_id": "1", "product_name": "高露洁牙膏", "product_desc": "高效美白", "category_id": "2", "category_name": "日化用品" 这些就是字段 }mapping--映射约束 数据如何存放到索引对象上,需要有一个映射配置,包括:数据类型、是否存储、是否分词等。所谓映射是对type的存储的一些限制。 例子: 这样就创建了一个名为blog的Index。Type不用单独创建,在创建Mapping 时指定就可以。Mapping用来定义Document中每个字段的类型,即所使用的 analyzer、是否索引等属性。创建Mapping 的代码示例如下: client.indices.putMapping({ index : 'blog', type : 'article', 这里还可以设置type的一些工作属性,比如_source等,后面会讲 body : { article: { properties: { id: { type: 'string', analyzer: 'ik', store: 'yes', }, title: { type: 'string', analyzer: 'ik', store: 'no', }, content: { type: 'string', analyzer: 'ik', store: 'yes', } } } } });1.7 ES读写数据的机制写流程:1、客户端根据提供的es节点,选择一个node作为协调节点,并发送写请求 2、协调节点对写入的document进行路由,将document进行分片。每个分片单独进行写,每个分片默认都是双备份,写在不同的节点上。 3、分片写入时,主备份由协调节点写入,副备份则是从主备份所在节点同步数据过去。 4、当分片都写完后,由协调节点返回写入完成给客户端读流程:读流程就很简单了,如果通过docid来读取,直接根据docid进行hash。判断出该doc存储在哪个节点上,然后到相应节点上读取数据即可。1.8 ES数据存储结构​ 图1.1 ES存储结构首先分为两个区域,一个是索引区域,一个是数据区域。前者用来存储生成的倒排索引,后者用来存储原始的document(可以选择不存,后面有说)。 1)索引对象(index):存储数据的表结构 ,任何搜索数据,存放在索引对象上 。 2)映射(mapping):数据如何存放到索引对象上,需要有一个映射配置, 包括:数据类型、是否存储、是否分词等。 3)文档(document):一条数据记录,存在索引对象上 。es会给每个document生成一个唯一的documentID,用于标识该document。当然也可以手动指定docid 4)文档类型(type):一个索引对象,存放多种类型数据,数据用文档类型进行标识。二、ES部署使用的es版本为:6.6.2下载地址:https://www.elastic.co/products/elasticsearch2.1 单节点部署解压程序到指定目录:tar zxf elasticsearch-6.6.2.tar.gz -C /opt/modules/修改配置文件:cd /opt/modules/elasticsearch-6.6.2/ vim config/elasticsearch.yml 修改如下内容: # ---------------------------------- Cluster ------------------------------------- # 集群名称 cluster.name: my-application # ------------------------------------ Node -------------------------------------- # 节点名称,需要保证全局唯一 node.name: bigdata121 # ----------------------------------- Paths --------------------------------------- # 配置es数据目录,以及日志目录 path.data: /opt/modules/elasticsearch-6.6.2/data path.logs: /opt/modules/elasticsearch-6.6.2/logs # ----------------------------------- Memory ----------------------------------- # 配置es不检查内存限制,内存不够时启动会检查报错 bootstrap.memory_lock: false bootstrap.system_call_filter: false # ---------------------------------- Network ------------------------------------ # 绑定ip network.host: 192.168.50.121 # --------------------------------- Discovery ------------------------------------ # 初始发现节点,用来给新添加的节点进行询问加入集群 discovery.zen.ping.unicast.hosts: ["bigdata121"]修改Linux一些内核参数vim /etc/security/limits.conf 添加如下内容: Es硬性要求打开最小数目最小为65536,进程数最小为4096,否则无法启动 * soft nofile 65536 * hard nofile 131072 * soft nproc 4096 * hard nproc 4096 vim /etc/security/limits.d/20-nproc.conf * soft nproc 1024 #修改为 * soft nproc 4096 这些内核参数需要重启才生效 vim /etc/sysctl.conf 添加下面配置: vm.max_map_count=655360 并执行命令: sysctl -p创建es的数据目录以及日志目录mkdir /opt/modules/elasticsearch-6.6.2/{logs,data} 启动es服务bin/elasticsearch -d -d 表示以后台进程服务的方式启动,不加此选项就以前台进程方式启动测试eses会启动两个对外端口: 9200:restful api的端口 9300:java api端口 可以直接使用curl访问9200端口 curl http://bigdata121:9200 { "name" : "bigdata121", "cluster_name" : "my-application", "cluster_uuid" : "DM6wmLzsQv2xVDkLMBJzOQ", "version" : { "number" : "6.6.2", "build_flavor" : "default", "build_type" : "tar", "build_hash" : "3bd3e59", "build_date" : "2019-03-06T15:16:26.864148Z", "build_snapshot" : false, "lucene_version" : "7.6.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" } 这样就正常了2.2 多节点部署2.2.1 es集群节点类型master node:master 节点主要用于元数据(metadata)的处理,比如索引的新增、删除、分片分配等。data node:data 节点上保存了数据分片。它负责数据相关操作,比如分片的 CRUD,以及搜索和整合操作。这些操作都比较消耗 CPU、内存和 I/O 资源;client node:client 节点起到路由请求的作用,实际上可以看做负载均衡器。那么这三种节点该如何配置,例子:# 配置文件中给出了三种配置高性能集群拓扑结构的模式,如下: # 1. 如果你想让节点从不选举为主节点,只用来存储数据,可作为负载器 # node.master: false # node.data: true # 2. 如果想让节点成为主节点,且不存储任何数据,并保有空闲资源,可作为协调器 # node.master: true # node.data: false # 3. 如果想让节点既不成为主节点,又不成为数据节点,那么可将他作为搜索器,从节点中获取数据,生成搜索结果等 # node.master: false # node.data: false # 4. 节点是数据节点,也是master节点,这是默认配置 # node.master: true # node.data: true2.2.2 es集群常用部署方案1、默认情况下,一个节点是数据节点,也是master节点。对于3-5个节点的小集群来讲,通常让所有节点存储数据和具有获得主节点的资格。你可以将任何请求发送给任何节点,并且由于所有节点都具有集群状态的副本,它们知道如何路由请求。多个master的元数据也会同步,不用担心不一致。要注意,master节点的数量最好最少为3,且为单数 2、当集群节点数量比较大时,那么通常就会将主节点、数据节点分开,专门部署在对应的节点上,然后主节点是多个都可用的,形成HA的结构。要注意,master节点的数量最好最少为3,且为单数实际部署其实和单节点差不多,主要看部署的方案选哪个,master有几个,数据节点有几个,设置下角色即可,这里不多说2.3 安装head插件用qq浏览器或者chrome,直接到应用商店搜索elasticsearch-head,直接安装插件即可​三、java api操作ES3.1 基本操作3.1.1 maven依赖准备<dependencies> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.6.2</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.6.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> 另外需要自己添加一个log4j2的日志格式配置文件,添加到resource目录下 log4j2.xml <?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%m%n"/> </Console> </Appenders> <Loggers> <Root level="INFO"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration> 下面代码中使用 junit进行运行测试,不会用的自己百度3.1.2 创建ES连接操作对象public class ESDemo1 { private TransportClient client; @Before public void getClient() throws UnknownHostException { //1、创建es配置对象 Settings settings = Settings.builder().put("cluster.name", "my-application").build(); //2、连接es集群 client = new PreBuiltTransportClient(settings); //配置es集群地址 client.addTransportAddress(new TransportAddress( InetAddress.getByName("192.168.50.121"), 9300 )); System.out.println(client.toString()); } }3.1.3 索引操作// .get() 表示触发操作 @Test public void createBlog() { //创建索引blog //创建index需要admin用户 client.admin().indices().prepareCreate("blog").get(); client.close(); } //删除索引 @Test public void deleteIndex() { client.admin().indices().prepareDelete("blog").get(); client.close(); }3.1.4 添加doc@Test public void addDocument() { //1、json方式添加document String d = "{\"id\":1, \"name\":\"山海经\"}"; //导入document,并指定源的格式为 json. IndexResponse indexResponse = client.prepareIndex("blog", "article").setSource(d, XContentType.JSON).execute().actionGet(); System.out.println(indexResponse.getId()); client.close(); } @Test public void addDocument2() throws IOException { //2、另外一种方式添加document IndexResponse indexResponse = client.prepareIndex("blog3", "article") .setSource(XContentFactory.jsonBuilder() .startObject() .field("name","静夜思") .field("id",4) .endObject() ).execute().actionGet(); System.out.println(indexResponse.getResult()); client.close(); } @Test public void addDocument3() throws IOException { //3、通过hashmap组织数据 HashMap<String, Object> json = new HashMap<>(); json.put("name","spark从入门到放弃"); json.put("id","6"); IndexResponse indexResponse = client.prepareIndex("blog", "article") .setSource(json).execute().actionGet(); System.out.println(indexResponse.getResult()); client.close(); } @Test public void addMoreDocument() throws IOException { //4、一次请求内部添加多个document BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); bulkRequestBuilder.add( client.prepareIndex("blog2", "comment").setSource( XContentFactory.jsonBuilder() .startObject() .field("name", "山海经") .field("id",1) .field("commentValue","这是一部很好的作品") .endObject()) ); bulkRequestBuilder.add( client.prepareIndex("blog2", "comment").setSource( XContentFactory.jsonBuilder() .startObject() .field("name", "骆驼祥子") .field("id",2) .field("commentValue","这是讲一个人的故事") .endObject()) ); BulkResponse bulkItemResponses = bulkRequestBuilder.get(); System.out.println(bulkItemResponses); client.close(); }要注意的是,从6.x版本开始,一个index中只能有一个type了,如果创建多个type会有以下报错Rejecting mapping update to [blog] as the final mapping would have more than 13.1.5 搜索doc根据docid搜索document //搜索单个document @Test public void getType() { GetResponse documentFields = client.prepareGet().setIndex("blog3").setType("article").setId("2OlH9WwBaToKuF8JhwB5").get(); System.out.println(documentFields.getSourceAsString()); client.close(); } //查询多个doc @Test public void getDocFromMoreIndex() { MultiGetResponse multiGetResponse = client.prepareMultiGet() .add("blog", "article", "1") .add("blog", "article", "2") .get(); //结果打印 for (MultiGetItemResponse itemResponse : multiGetResponse) { System.out.println( itemResponse.getResponse().getSourceAsString()); } client.close(); }3.1.6 更新doc@Test public void updateData() throws IOException { //更新数据方式1:通过 prepareupdate方法 UpdateResponse updateResponse = client.prepareUpdate("blog", "article", "4") .setDoc(XContentFactory.jsonBuilder() .startObject() .field("name", "天黑") .field("id", "5") .endObject() ).get(); System.out.println(updateResponse.getResult()); } @Test public void updateData2() throws IOException, ExecutionException, InterruptedException { //更新数据方式2:通过update方法 UpdateRequest updateRequest = new UpdateRequest().index("blog").type("article").id("4"); updateRequest.doc(XContentFactory.jsonBuilder() .startObject() .field("name", "亚瑟") .field("id", "7") .endObject()); UpdateResponse updateResponse = client.update(updateRequest).get(); System.out.println(updateResponse.getResult()); } @Test public void upsertData() throws IOException, ExecutionException, InterruptedException { //指定doc不存在时就插入,存在就修改 //不存在就插入这个 IndexRequest indexRequest = new IndexRequest("blog","article","6").source( XContentFactory.jsonBuilder().startObject() .field("name","wang") .field("id","10") .endObject() ); //存在就更新这个,注意最后的那个 upsert操作,意思就是不存在就插入上面的 indexrequest UpdateRequest updateRequest = new UpdateRequest().index("blog").type("article").id("6"); updateRequest.doc(XContentFactory.jsonBuilder() .startObject() .field("name", "king") .field("id", "7") .endObject()).upsert(indexRequest); UpdateResponse updateResponse = client.update(updateRequest).get(); System.out.println(updateResponse.getResult()); client.close(); }3.1.7 删除doc@Test public void deleteDocument() { //删除document DeleteResponse deleteResponse = client.prepareDelete("blog", "article", "6").get(); System.out.println(deleteResponse.getResult()); client.close(); }3.2 条件查询doc关键性一个类是 org.elasticsearch.index.query.QueryBuilders;3.2.1 查询指定index所有doc@Test public void matchAll() { //构建全部查询 SearchResponse searchResponse = client.prepareSearch("blog").setQuery(QueryBuilders.matchAllQuery()).get(); //从返回结构中解析doc SearchHits hits = searchResponse.getHits(); for (SearchHit hit:hits){ System.out.println(hit.getSourceAsString()); } client.close(); }3.2.2 全部字段进行全文检索搜索全部字段中包含指定字符的document @Test public void matchSome() { //直接全文检索指定字符 SearchResponse searchResponse = client.prepareSearch("blog3").setQuery(QueryBuilders.queryStringQuery("思")).get(); SearchHits hits = searchResponse.getHits(); for(SearchHit hit:hits) { System.out.println(hit.getId()); System.out.println(); } }3.2.3 通配符字段全文检索@Test public void wildMatch() { //通配符查询,*表示0或者多个字符,?表示单个字符 SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article").setQuery(QueryBuilders.wildcardQuery("name", "wa*")).get(); SearchHits hits = searchResponse.getHits(); for(SearchHit h:hits) { System.out.println(h.getSourceAsString()); } } 这个方法用于匹配某个字段的整个内容,类似like操作3.2.4 对指定字段进行分词搜索@Test public void matchField() { //这是对分词结果进行等值操作的方法,不是对整个字段,而是对字段的分词结果 SearchResponse searchResponse = client.prepareSearch("blog").setQuery(QueryBuilders.termQuery("name", "山")).get(); SearchHits hits = searchResponse.getHits(); for(SearchHit hit:hits) { System.out.println(hit.getSourceAsString()); } client.close(); } 这个方法一定要注意: 比如有一个字段内容如下: 我爱中国 假设分词如下: 我 爱 中国 如果使用 QueryBuilders.termQuery("name", "中") 也就是搜索“中”这个字时,实际上没有结果返回的。因为分词中并没有含有单独的“中”。 所以这个方法是用于完整匹配分词结果中的某个分词的。 由此,可以得出,即便是用整个字段的内容来搜索,这个方法也不会返回任何结果的,因为分词结果不包含。3.2.5对指定字段进行模糊检索@Test public void fuzzy() { // 1 模糊查询 SearchResponse searchResponse = client.prepareSearch("blog").setTypes("article") .setQuery(QueryBuilders.fuzzyQuery("title", "lucene")).get(); // 2 打印查询结果 SearchHits hits = searchResponse.getHits(); // 获取命中次数,查询结果有多少对象 for(SearchHit hit:hits) { System.out.println(hit.getSourceAsString()); } // 3 关闭连接 client.close(); } 这个方法和 termQuery很类似,但是有区别。感兴趣的话可以自己查找资料。这个方法比较少用3.3 映射mapping3.3.1 mapping的定义​ 映射是规定index中的一些属性,以及各自type下的字段的属性(再强调一遍,现在6.x版本一个index下只能有一个type,其实就是变相地去除掉了type)。Elasticsearch映射虽然有idnex和type两层关系,但是实际索引时是以index为基础的。如果同一个index下不同type的字段出现mapping不一致的情况,虽然数据依然可以成功写入并生成各自的mapping,但实际上fielddata中的索引结果却依然是以index内第一个mapping类型来生成的3.3.2 mapping的写法定义mapping时,依旧是使用json格式定义定义。一般格式如下:{ 元数据属性字段,如: _type:是哪个type的mapping,还是那句话,type基本不怎么提了 _index:属于哪个index 。。。。。。。 properties:{ "field1":{ 字段属性字段,如: type:字段数据类型 } "field2":{ 字段属性字段,如: type:字段数据类型 } 。。。。。。。。。 } } 基本格式就是这样,分为两大部分,一个是整个index 的元数据信息,一个是针对具体type中的字段信息。3.3.3 数据类型核心数据类型 字符串:text,keyword 数字:long, integer, short, byte, double, float, half_float, scaled_float 布尔值:boolean 时间:date 二进制:binary 范围:integer_range, float_range, long_range, double_range, date_range 复杂数据类型 数组:array 对象:object 堆叠/嵌套对象: nested 地理:geo_point,geo_point IP: ip 字符个数:token_count(输入一个字符串,保存的是它的长度)3.3.4mapping属性字段元数据字段:_all : 它是文档中所有字段的值整合成的一个大字符串,用空格分割。它进行了索引但没有存储,所以我们只能对他进行搜索不能获取。如果我们没有指定搜索的字段,就默认是在_all字段上进行搜索。 _source :文档信息 包含在文档在创建时的实际主体,它会被存储但不会被索引,用于get或search是返回主体。如果你并不关系数据的主体,只注重数量,那可以将此字段禁用 _routing :路由字段 es会使用下面的计算公式计算数据应保存在哪个分片,索引指定一个路由字段可以自己来控制哪些值放在一起。 shard_num = hash(_routing) % num_primary_shards _meta 自定义的元数据 ,因为元数据是每个文档都会带的,索引如果你想要在每个文档上标注一些信息,就可以使用此属性,自定义一些元数据。 _field_names :保存着非空值得属性名集合,可以通过它查询包含某个字段非空值的文档 _id :主键 _index :索引 _type :类型 _uid :类型和id的组合 uid字段的值可以在查询、聚合、脚本和排序中访问: _parent :父类,可用于关联两个索引字段属性:type 数据类型 改属性用来指定字段的数据类型,一但指点后就不能再修改,如果数据不是以设置的数据类型传入,es会去转换数据,装换不成功则报错。具体可配置的参数,可看前面的数据类型说明。 analyzer 分析器 用于指定索引创建时使用的分析器是什么,即对同一段内容,不同的分析器会用不同的方式分词,最后在倒排索引上的值是不同的。 index 是否索引 索引选项控制字段值是否被索引。它接受true或false,默认为true。没有索引的字段不是可查询的。 store 属性值是否被存储,默认情况下字段是可以被搜索但是内容不存储的,值一般都是保存在_source中。但比如一篇文章你有它的内容和原网址,现在需要对内容进行检索,但查看是跳转到它原网址的,那这时就不需要存储内容了。 fielddata 现场数据 如果你要对一个text类型进行聚合操作,你必须设置这个参数为true。 doc_values 文档数据 建立一个文档对应字段的“正排索引”,其实就是把文档的字段按列存储了,它不会保存分析的字段。方便聚合排序时访问。 format 默认格式 一般用于时间格式的数据,指定默认的数据格式, “yyyy-MM-dd HH:mm:ss” search_analyzer 搜索分析器 指定搜索时使用的分析器,一般不设置在搜索时就会使用创建索引时使用的分析器,如果要自己指定不同的也只要配置即可。 boost 分值 指定字段的相关性评分默认是1.0,数值越大,搜索时排序时使用。也可以直接通过查询时指定分值的方式 coerce 是否转换 在插入数据时,在插入数据类型和映射类型不一致的情况下是否强制转换数据类型。默认是开启的 normalizer 转换器 因为keyword类型的字段是不进行分析的,但是我们又想要将其统一成一个规则,比如都是小写,比如用ASCILL进行编码,其实就是个给keyword用的分析器。可以在setting下的analysis下定义自己的normalizer使用 copy_to 同步复制 在插入值是,会把值一同放到另一个字段中。主要用于自己定义一个类似于_all字段的字端。 dynamic 动态映射控制 该字段是用来控制动态映射的,它有三个值 -true-自动添加映射 -false-新值不索引,不能被搜索,但返回的命中源字段中会存在这个值 -strict-遇到新值抛出异常 enabled 是否启动 这个值是否要用于搜索 ignore_above 忽视上限 一个字符串超过指定长度后就不会索引了 ignore_malformed 忽视错误数据 比如一个文档数据传过来,只用一个字段的数据时不能被存储,ES会抛出异常并且不会存储此数据。我们就可以配置此属性保证数据被存储 include_in_all 是否保存在_all字段中 fields 多字段配置 比如出现标题既要索引,又有不用索引的情景。我们不能对一个字段设置两个类型,又不想再建一个不同类型的相同字段。我们可以使用多字段的方式,在保存数据时,我们只需保存一个字段,ES会默认将数据保存到这个字段下的多字段上。 null_value 空值 假如你插入的数据为空,或者数据中没有这个字段的值。那这个文档的这个字段就不参与搜索了。我们可以通过指定一个显示的空值来让他能够参与搜索 norms 规范 如果一个字段只用于聚合,可以设置为false3.3.5 _all,_sources,store的区别背景: 首先,我们要知道一点,当doc传入es时,es会根据配置给doc的每个字段生成索引,并且会将生成的索引保存到es中。但是至于doc的原始数据是否保存到es中,是可以选择的。这点要先搞清楚,并一定非得把doc的原始数据保存在es中的,es非保存不可的是生成的索引,而不是原始数据 ======================== _all: 这是一个特殊字段,是把所有其它字段中的值,以空格为分隔符组成一个大字符串,然后被分析和索引,但是不存储原始数据,也就是说它能被查询,但不能被取回显示。注意这个字段是可以被索引的。默认情况下,如果要进行全文检索,需要指定在哪个字段上检索,如果不知道在哪个字段上,那么_all就起到作用了。_all能让你在不知道要查找的内容是属于哪个具体字段的情况下进行搜索 ====================== _source: true/false,默认为true 保存的是doc的本来的原数数据,也就是是json格式的doc。他和_all不同,他是json格式的字符串。而且这个字段不会被索引。 当我们执行检索操作时,是到倒排索引中查询,然后获得含有指定关键字的doc的id, 当 _source 设置为 true时 可以根据上面查询到的docid,返回对应id的document的原始数据。 当 _source 设置为 false时 就只能返回对应的document的id,无法回显对应document的原始数据 这种情况下,一般是使用额外的方式来保存document的原始数据的,比如hbase。而es就单纯保存索引而已 ======================= store:true/false,默认为false 这个属性用于指定是否保存document中对应字段的value,这个的概念和上面的source有点类似了,只不过这里store是针对某个field的原始数据,source是针对整个document的原始数据。 当执行想获取一个document的数据时, 1、采用source方式时: 只需产生一次磁盘IO,因为_source存储的时候,直接把整个doc当做一个字段来存储。当我们需要doc中的某个字段时,是先从source读取数据,然后再解析成json,获取到指定字段内容 2、采用store方式时, 因为每个字段都单独存储了,当需要获得整个doc的数据时,就需要单独每个字段进行取值,有多少个字段就产生多少次磁盘IO。 3、store和source混合使用时 如果操作是获取整个doc的数据,那么es会优先从source读取数据。 如果操作是获取某些字段的数据,那么es会优先从store存储中读取数据。因为这样读取的数据量相对较少,无需读取整个doc的数据再解析。 但是注意的是,这两个属性都是单独自己保存数据的,所以如果两个启用的话,相当于数据存储了两次,挺浪费存储空间的,增大了索引的体积3.3.6 api操作mapping创建mapping,要注意,mapping创建之后不能更改@Test public void createMapping() throws Exception { // 1设置mapping,使用jsonbuilder构建mapping XContentBuilder builder = XContentFactory.jsonBuilder() .startObject() .startObject("article") .startObject("properties") .startObject("id1") .field("type", "string") .field("store", "yes") .endObject() .startObject("title2") .field("type", "string") .field("store", "no") .endObject() .startObject("content") .field("type", "string") .field("store", "yes") .endObject() .endObject() .endObject() .endObject(); // 2 添加mapping PutMappingRequest mapping = Requests.putMappingRequest("blog4").type("article").source(builder); client.admin().indices().putMapping(mapping).get(); // 3 关闭资源 client.close(); }查看map@Test public void getIndexMapping() throws ExecutionException, InterruptedException { //构建查看mapping的请求,查看blog3这个index的mapping GetMappingsResponse mappingsResponse = client.admin().indices().getMappings(new GetMappingsRequest().indices("blog3")).get(); //获取mapping ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = mappingsResponse.getMappings(); //迭代打印mapping数据 for (ObjectObjectCursor<String, ImmutableOpenMap<String, MappingMetaData>> mapping : mappings) { if (mapping.value.isEmpty()) { continue; } //最外层的key是index的名称 System.out.println("index key:" + mapping.key); //value包裹的是每个type的mapping,里面以type为key,mapping为value for (ObjectObjectCursor<String, MappingMetaData> mapValue : mapping.value) { System.out.println("type key:" + mapValue.key); System.out.println("type value:" + mapValue.value.sourceAsMap()); } } client.close(); } /* 结果如下: index key:blog3 type key:article type value:{_source={enabled=false}, properties={id={type=long}, name={type=text, fields={keyword={type=keyword, ignore_above=256}}}}} */3.4 spark操作es时的报错在spark.2.1和es6.6项目中混合使用,报错:java.lang.NoSuchMethodError: io.netty.buffer.ByteBuf.retainedSlice(II)Lio/netty/buffer/ByteBuf;这种问题,一般都是使用的某个依赖包的版本问题。使用mvn dependency:tree 看了下,原来spark和es各自依赖的版本不一致,spark使用的是3.x版本,es使用的是4.1.32.Final版本。但是因为spark的依赖在pom.xml中写在前面,迫使es使用的是3.x版本的依赖,导致有些方法不存在,就报错。解决方式很简答,直接指定使用新版本的就好,如下:<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.32.Final</version> </dependency>四、分词器4.1 默认分词器我们知道,建立索引过程中,最重要的一个步骤就是分词,分词的策略有很多,我们看看es默认的中文分词器的效果[root@bigdata121 elasticsearch-6.6.2]# curl -H "Content-Type:application/json" -XGET 'http://bigdata121:9200/_analyze?pretty' -d '{"analyzer":"standard","text":"中华人民共和国"}' { "tokens" : [ { "token" : "中", "start_offset" : 0, "end_offset" : 1, "type" : "<IDEOGRAPHIC>", "position" : 0 }, { "token" : "华", "start_offset" : 1, "end_offset" : 2, "type" : "<IDEOGRAPHIC>", "position" : 1 }, { "token" : "人", "start_offset" : 2, "end_offset" : 3, "type" : "<IDEOGRAPHIC>", "position" : 2 }, { "token" : "民", "start_offset" : 3, "end_offset" : 4, "type" : "<IDEOGRAPHIC>", "position" : 3 }, { "token" : "共", "start_offset" : 4, "end_offset" : 5, "type" : "<IDEOGRAPHIC>", "position" : 4 }, { "token" : "和", "start_offset" : 5, "end_offset" : 6, "type" : "<IDEOGRAPHIC>", "position" : 5 }, { "token" : "国", "start_offset" : 6, "end_offset" : 7, "type" : "<IDEOGRAPHIC>", "position" : 6 } ] }可以看到,标准的中文分词器只是单纯将字分开,其实并不智能,没有词语考虑进去。所以需要更加强大的分词器。常用的有ik分词器4.2 安装ik分词器cd /opt/modules/elasticsearch-6.6.2 执行下面的命令安装,需要联网 bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.6.2/elasticsearch-analysis-ik-6.6.2.zip 注意要根据ES的版本安装对应版本的ik4.3 命令行下IK分词器的使用分两种模式:ik_smart 和 ik_max_word1、 ik_smart 模式,智能解析词语结构 curl -H "Content-Type:application/json" -XGET 'http://bigdata121:9200/_analyze?pretty' -d '{"analyzer":"ik_smart","text":"中华人民共和国"}' { "tokens" : [ { "token" : "中华人民共和国", "start_offset" : 0, "end_offset" : 7, "type" : "CN_WORD", "position" : 0 } ] } 2、ik_max_word 模式,智能解析字和词语 curl -H "Content-Type:application/json" -XGET 'http://192.168.109.133:9200/_analyze?pretty' -d '{"analyzer":"ik_max_word","text":"中华人民共和国"}' { "tokens" : [ { "token" : "中华人民共和国", "start_offset" : 0, "end_offset" : 7, "type" : "CN_WORD", "position" : 0 }, { "token" : "中华人民", "start_offset" : 0, "end_offset" : 4, "type" : "CN_WORD", "position" : 1 }, { "token" : "中华", "start_offset" : 0, "end_offset" : 2, "type" : "CN_WORD", "position" : 2 }, { "token" : "华人", "start_offset" : 1, "end_offset" : 3, "type" : "CN_WORD", "position" : 3 }, { "token" : "人民共和国", "start_offset" : 2, "end_offset" : 7, "type" : "CN_WORD", "position" : 4 }, { "token" : "人民", "start_offset" : 2, "end_offset" : 4, "type" : "CN_WORD", "position" : 5 }, { "token" : "共和国", "start_offset" : 4, "end_offset" : 7, "type" : "CN_WORD", "position" : 6 }, { "token" : "共和", "start_offset" : 4, "end_offset" : 6, "type" : "CN_WORD", "position" : 7 }, { "token" : "国", "start_offset" : 6, "end_offset" : 7, "type" : "CN_CHAR", "position" : 8 } ] }4.4 java api使用ik分词器这里其实和mapping的使用差不多,只是在mapping的字段属性中添加一个 “analyzer” 属性,指定使用的分词器而已。其他都没有区别,这里不重复五、优化5.1 背景ES在数十亿级别的数据如何提高检索效率?​ 这个问题说白了,就是看你有没有实际用过 ES,因为啥?其实 ES 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 5~10s,坑爹了。第一次搜索的时候,是 5~10s,后面反而就快了,可能就几百毫秒。​ 然后你就很懵,每个用户第一次访问都会比较慢,比较卡么?所以你要是没玩儿过 ES,或者就是自己玩玩儿 Demo,被问到这个问题容易懵逼,显示出你对 ES 确实玩的不怎么样?说实话,ES 性能优化是没有银弹的。啥意思呢?就是不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。​ 下面看看几个优化的手段5.2 优化1--filesystemCache5.2.1 基本原理​ 图5.1 ES filesytem cache​ 你往 ES 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去。ES 的搜索引擎严重依赖于底层的 Filesystem Cache,你如果给 Filesystem Cache 更多的内存,尽量让内存可以容纳所有的 IDX Segment File 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。问题:直接读取硬盘数据和从缓存读取数据,性能差距究竟可以有多大?回答:我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1 秒、5 秒、10 秒。但如果是走 Filesystem Cache,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。案例:​ 来看一个真实的案例:某个公司 ES 节点有 3 台机器,每台机器看起来内存很多 64G,总内存就是 64 3 = 192G。每台机器给 ES JVM Heap 是 32G,那么剩下来留给 Filesystem Cache 的就是每台机器才 32G,总共集群里给 Filesystem Cache 的就是 32 3 = 96G 内存。​ 而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 1T 的磁盘容量,ES 数据量是 1T,那么每台机器的数据量是 300G。这样性能会好吗?​ Filesystem Cache 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。5.2.2 优化具体方式​ 首先要知道一点:归根结底,你要让 ES 性能好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半。当然如果内存能容纳全部数据,自然是最好,然而基本生产中没有那么多钱的啦。走内存可以满足秒级以内的查询要求1、去掉写入ES的doc中不必要的字段如果一个doc中有很多字段,但是有些字段压根是没用的(也就是说该字段不会用于搜索),但是读取的时候仍旧会将这些字段都读取,然后缓存到filesytem cache中,占据了大量空间,导致后面的数据只能重新从硬盘中读取。这个时候就要想着取消一些没怎么用的字段了。减小索引的体积。从而节省filesytem cache空间2、采用 ES+HBase架构​ 之前也说到,es可以只存储索引,不存储原始doc数据;或者只存储某些字段的原始数据。通常完整的原始数据都保存在hbase中,然后通过rowkey作为docid导入到es中,最终通过这个rowkey进行唯一性关联。为什么要采用这种架构呢?​ 比如说你现在有一行数据:id,name,age .... 30 个字段。但是你现在搜索,只需要根据 id,name,age 三个字段来搜索。如果你傻乎乎往 ES 里写入一行数据所有的字段,就会导致 90% 的数据是不用来搜索的。但是呢,这些数据硬是占据了 ES 机器上的 Filesystem Cache 的空间,单条数据的数据量越大,就会导致 Filesystem Cahce 能缓存的数据就越少。其实,仅仅写入 ES 中要用来检索的少数几个字段就可以了,比如说就写入 es id,name,age 三个字段。然后你可以把其他的字段数据存在 MySQL/HBase 里,我们一般是建议用 ES + HBase 这么一个架构(官方建议的方案)。​ HBase是列式数据库,其特点是适用于海量数据的在线存储,就是对 HBase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。hbase非常适合这种简单通过key直接获取数据的应用场景。​ 例如:从 ES 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id,然后根据 doc id 到 HBase 里去查询每个 doc id 对应的完整的数据,给查出来,再返回给前端。而写入 ES 的数据最好小于等于,或者是略微大于 ES 的 Filesystem Cache 的内存容量。然后你从 ES 检索可能就花费 20ms,然后再根据 ES 返回的 id 去 HBase 里查询,查 20 条数据,可能也就耗费个 30ms。如果你像原来那么玩儿,1T 数据都放 ES,可能会每次查询都是 5~10s,而现在性能就会很高,每次查询就是 50ms。5.3 优化2--数据预热​ 从概率上来说,大部分的访问量往往集中小部分的数据上,也就是我们所说的数据热点的情况。数据预热通常就是事先将一些可能有大量访问的数据先通过手动访问让它们提前缓存到cache中,然而后面的用户访问这些数据时,就直接走cache查询了,非常快。而且这些数据因为访问量多,所以还需要保证这些热点数据不要被其他非热点数据加载到cache时,被覆盖掉了。这就需要时常手动访问,加载数据到cache中。​ 例子:​ 比如电商,你可以将平时查看最多的一些商品,比如说 iPhone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 Filesystem Cache 里去。​ 总之,就是对于那些你觉得比较热的、经常会有人访问的数据,最好做一个专门的缓存预热子系统。然后对热数据每隔一段时间,就提前访问一下,让数据进入 Filesystem Cache 里面去。这样下次别人访问的时候,性能一定会好很多。5.4 优化3--冷热分离​ 这个也是数据热点的问题。ES 可以做类似于 MySQL 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在 Filesystem OS Cache 里,别让冷数据给冲刷掉。​ 还是来一个例子,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个 Shard。3 台机器放热数据 Index,另外 3 台机器放冷数据 Index。这样的话,你大量的时间是在访问热数据 Index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 Filesystem Cache 里面了,就可以确保热数据的访问性能是很高的。​ 但是对于冷数据而言,是在别的 Index 里的,跟热数据 Index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。5.5 优化4--避免关联查询​ 对于 MySQL,我们经常有一些复杂的关联查询,在 ES 里该怎么玩儿?ES 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 ES 中。搜索的时候,就不需要利用 ES 的搜索语法来完成 Join 之类的关联搜索了。5.6 优化5--document模型设计​ Document 模型设计是非常重要的,很多操作,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。​ ES 能支持的操作就那么多,不要考虑用 ES 做一些它不好操作的事情。如果真的有那种操作,尽量在 Document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。​ 总结一句就是说,ES不适合执行复杂查询操作5.7 优化6--分页性能优化背景: ES 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 Shard 上存储的前 1000 条数据都查到一个协调节点上。如果你有 5 个 Shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。 由于是分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 Shard,每个 Shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你必须得从每个 Shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。 也就是说,你翻页的时候,翻的越深,每个 Shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 ES 做分页的时候,你会发现越翻到后面,就越是慢。 我们之前也是遇到过这个问题,用 ES 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。解决方案:1、不允许深度分页(默认深度分页性能很差)。跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差。 2、类似于 App 里的推荐商品不断下拉出来一页一页的;类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 Scroll API,关于如何使用,大家可以自行上网搜索学习一下。 Scroll是如何做的呢?它会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动,获取下一页、下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。 但是,唯一的一点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,然后去第 120 页,然后又回到第 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,你只能往下拉,一页一页的翻。 使用时需要注意,初始化必须指定 Scroll 参数,告诉 ES 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。 除了用 Scroll API,你也可以用 search_after 来做。search_after 的思想是使用前一页的结果来帮助检索下一页的数据。 显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 Sort 字段。原文链接:https://www.yisu.com/zixun/9202.html
  • [分享交流] 新人报道
    新人报道,我入驻华为云社区啦
  • [技术干货] es6 数组去重
    一行代码实现数组去重(ES6) ES6中新增了Set数据结构,类似于数组,但是 它的成员都是唯一的 ,其构造函数可以接受一个数组作为参数,如: let array = [1, 1, 1, 1, 2, 3, 4, 4, 5, 3];  let set = new Set(array);  console.log(set);  // => Set {1, 2, 3, 4, 5} ES6中Array新增了一个静态方法Array.from,可以把类似数组的对象转换为数组,如通过querySelectAll方法得到HTML DOM Node List,以及ES6中新增的Set和Map等可遍历对象,如: let set = new Set();  set.add(1).add(2).add(3);  let array = Array.from(set);  console.log(array);  // => [1, 2, 3] 于是,现在我们可以用一行代码实现数组去重了:let array = Array.from(new Set([1, 1, 1, 2, 3, 2, 4])); console.log(array); // => [1, 2, 3, 4] 附:ES5实现数组去重var array = [1, '1', 1, 2, 3, 2, 4]; var tmpObj = {}; var result = []; array.forEach(function(a) {   var key = (typeof a) + a;   if (!tmpObj[key]) {     tmpObj[key] = true;     result.push(a);   } }); console.log(result); // => [1, "1", 2, 3, 4]
  • [技术干货] ES6 为字符串
    字符串的遍历器接口ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of循环遍历。for (let codePoint of 'foo') {   console.log(codePoint) }includes(), startsWith(), endsWith()传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。includes():返回布尔值,表示是否找到了参数字符串。startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true 这三个方法都支持第二个参数,表示开始搜索的位置。let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false 上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。模板字符串中嵌入变量,需要将变量名写在${}之中。function authorize(user, action) {   if (!user.hasPrivilege(action)) {     throw new Error(       // 传统写法为       // 'User '       // + user.name       // + ' is not authorized to do '       // + action       // + '.'       `User ${user.name} is not authorized to do ${action}.`);   } } 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。let x = 1; let y = 2; `${x} + ${y} = ${x + y}` // "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}` // "3" 模板字符串之中还能调用函数。function fn() {   return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar 如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。如果模板字符串中的变量没有声明,将报错。// 变量place没有声明 let msg = `Hello, ${place}`; // 报错 由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。`Hello ${'World'}` // "Hello World" 模板字符串甚至还能嵌套。const tmpl = addrs => `   <table>   ${addrs.map(addr => `     <tr><td>${addr.first}</td></tr>     <tr><td>${addr.last}</td></tr>   `).join('')}   </table> `; 上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。const data = [     { first: '', last: 'Bond' },     { first: 'Lars', last: '' }, ]; console.log(tmpl(data)); // // //   //   // //   //   // //  BondLars如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。// 写法一 let str = 'return ' + '`Hello ${name}!`'; let func = new Function('name', str); func('Jack') // "Hello Jack!" // 写法二 let str = '(name) => `Hello ${name}!`'; let func = eval.call(null, str); func('Jack') // "Hello Jack!"
  • [技术干货] 一行代码实现数组去重(ES6)
     ES6中新增了Set数据结构,类似于数组,但是 它的成员都是唯一的 ,其构造函数可以接受一个数组作为参数,如: let array = [1, 1, 1, 1, 2, 3, 4, 4, 5, 3];  let set = new Set(array);  console.log(set);  // => Set {1, 2, 3, 4, 5} ES6中Array新增了一个静态方法Array.from,可以把类似数组的对象转换为数组,如通过querySelectAll方法得到HTML DOM Node List,以及ES6中新增的Set和Map等可遍历对象,如: let set = new Set();  set.add(1).add(2).add(3);  let array = Array.from(set);  console.log(array);  // => [1, 2, 3] 于是,现在我们可以用一行代码实现数组去重了:let array = Array.from(new Set([1, 1, 1, 2, 3, 2, 4])); console.log(array); // => [1, 2, 3, 4] 附:ES5实现数组去重var array = [1, '1', 1, 2, 3, 2, 4]; var tmpObj = {}; var result = []; array.forEach(function(a) {   var key = (typeof a) + a;   if (!tmpObj[key]) {     tmpObj[key] = true;     result.push(a);   } }); console.log(result); // => [1, "1", 2, 3, 4]
  • [技术干货] ES6 为字符串
    字符串的遍历器接口ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of循环遍历。for (let codePoint of 'foo') {   console.log(codePoint) }includes(), startsWith(), endsWith()传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。includes():返回布尔值,表示是否找到了参数字符串。startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true 这三个方法都支持第二个参数,表示开始搜索的位置。let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false 上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。模板字符串中嵌入变量,需要将变量名写在${}之中。function authorize(user, action) {   if (!user.hasPrivilege(action)) {     throw new Error(       // 传统写法为       // 'User '       // + user.name       // + ' is not authorized to do '       // + action       // + '.'       `User ${user.name} is not authorized to do ${action}.`);   } } 大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。let x = 1; let y = 2; `${x} + ${y} = ${x + y}` // "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}` // "3" 模板字符串之中还能调用函数。function fn() {   return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar 如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。如果模板字符串中的变量没有声明,将报错。// 变量place没有声明 let msg = `Hello, ${place}`; // 报错 由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。`Hello ${'World'}` // "Hello World" 模板字符串甚至还能嵌套。const tmpl = addrs => `   <table>   ${addrs.map(addr => `     <tr><td>${addr.first}</td></tr>     <tr><td>${addr.last}</td></tr>   `).join('')}   </table> `; 上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。const data = [     { first: '', last: 'Bond' },     { first: 'Lars', last: '' }, ]; console.log(tmpl(data)); // // //   //   // //   //   // //  BondLars如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。// 写法一 let str = 'return ' + '`Hello ${name}!`'; let func = new Function('name', str); func('Jack') // "Hello Jack!" // 写法二 let str = '(name) => `Hello ${name}!`'; let func = eval.call(null, str); func('Jack') // "Hello Jack!"
总条数:153 到第
上滑加载中