1、文档分析

  • 将一块文本分成适合于倒排索引的独立的 词条

  • 将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里:

  1. 字符过滤器:首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 & 转化成 and。

  2. 分词器:其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。

  3. Token 过滤器:最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

1.1、内置分析器

Elasticsearch 还附带了可以直接使用的预包装的分析器。接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:

"Set the shape to semi-transparent by calling set_trans(5)"
"Set the shape to semi-transparent by calling set_trans(5)"

标准分析器

标准分析器是 Elasticsearch 默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。它会产生:

set, the, shape, to, semi, transparent, by, calling, set_trans, 5

简单分析器

简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生:

set, the, shape, to, semi, transparent, by, calling, set, trans

空格分析器

空格分析器在空格的地方划分文本。它会产生:

Set, the, shape, to, semi-transparent, by, calling, set_trans(5)

语言分析器

特定语言分析器可用于很多语言。它们可以考虑指定语言的特点。例如, 英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的词干 。

set, shape, semi, transpar, call, set_tran, 5

注意看 transparent、 calling 和 set_trans 已经变为词根格式

1.2、IK分词器

是这样的,当我们使用默认的分词器来解析中文的时候会发现,将我们的中文逐个拆解,但是事实上我们分词是是希望他根据词组来分词,我们这里采用 IK 中文分词器,下载地址为:Releases · medcl/elasticsearch-analysis-ik · GitHub

将解压后的后的文件夹放入 ES 根目录下的 plugins 目录下,重启 ES 即可使用。我们这次加入新的查询参数”analyzer”:”ik_max_word”

{ "text": "测试单词", "analyzer": "ik_max_word"}ik_max_word:会将文本做最细粒度的拆分ik_smart:会将文本做最粗粒度的拆分

分词结果

{ "tokens": [ { "token": "测试", "start_offset": 0, "end_offset": 2, "type": "CN_WORD", "position": 0 }, { "token": "单词", "start_offset": 2, "end_offset": 4, "type": "CN_WORD", "position": 1 } ]}

ES 中也可以进行扩展词汇,首先查询”弗雷尔卓德”那么时候会将这个词逐个拆分,但是事实上这是没有什么意义的,我们希望他是一个完整的词组,所以我们要自定义词组。

首先进入 ES 根目录中的 plugins 文件夹下的 ik 文件夹,进入 config 目录,创建 custom.dic文件,写入弗雷尔卓德。同时打开 IKAnalyzer.cfg.xml 文件,将新建的 custom.dic 配置其中,重启 ES 服务器。

{ "text": "弗雷尔卓德", "analyzer": "ik_max_word"}{ "tokens": [ { "token": "弗雷尔卓德", "start_offset": 0, "end_offset": 5, "type": "CN_WORD", "position": 0 } ]}

1.3、自定义分析器

虽然 Elasticsearch 带有一些现成的分析器,然而在分析器上 Elasticsearch 真正的强大之处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单元过滤器来创建自定义的分析器。在 分析与分析器 我们说过,一个 分析器 就是在一个包里面组合了三种函数的一个包装器, 三种函数按照顺序被执行:

字符过滤器

字符过滤器 用来 整理 一个尚未被分词的字符串。例如,如果我们的文本是 HTML 格式的,它会包含像

或者这样的 HTML 标签,这些标签是我们不想索引的。我们可以使用 html 清除 字符过滤器 来移除掉所有的 HTML 标签,并且像把 Á 转换为相对应的 Unicode 字符 Á 这样,转换 HTML 实体。一个分析器可能有 0 个或者多个字符过滤器。

分词器

一个分析器必须有一个唯一的分词器。 分词器把字符串分解成单个词条或者词汇单元。 标准分析器里使用的标准分词器 把一个字符串根据单词边界分解成单个词条,并且移除掉大部分的标点符号,然而还有其他不同行为的分词器存在。

例如, 关键词 分词器 完整地输出 接收到的同样的字符串,并不做任何分词。 空格 分词器 只根据空格分割文本 。 正则 分词器 根据匹配正则表达式来分割文本 。

词单元过滤器

经过分词,作为结果的词单元流 会按照指定的顺序通过指定的词单元过滤器 。词单元过滤器可以修改、添加或者移除词单元。我们已经提到过 lowercase 和 stop 词过滤器 ,但是在Elasticsearch 里面还有很多可供选择的词单元过滤器。词干过滤器把单词遏制为词干。 ascii_folding 过滤器移除变音符,把一个像 “très” 这样的词转换为 “tres” 。ngram 和 edge_ngram 词单元过滤器 可以产生 适合用于部分匹配或者自动补全的词单元。接下来,我们看看如何创建自定义的分析器:

# PUT http: //localhost:9200/my_index{ "settings": { "analysis": { "char_filter": { "&_to_and": { "type": "mapping", "mappings": [ "&=> and " ] } }, "filter": { "my_stopwords": { "type": "stop", "stopwords": [ "the", "a" ] } }, "analyzer": { "my_analyzer": { "type": "custom", "char_filter": [ "html_strip", "&_to_and" ], "tokenizer": "standard", "filter": [ "lowercase", "my_stopwords" ] } } } }}

2、Kibana

Kibana 是一个免费且开放的用户界面,能够让你对 Elasticsearch 数据进行可视化,并让你在 Elastic Stack 中进行导航。你可以进行各种操作,从跟踪查询负载,到理解请求如何流经你的整个应用,都能轻松完成。

Kibana 7.17.16 | Elastic

修改 config/kibana.yml 文件

# 默认端口server.port: 5601# ES 服务器的地址elasticsearch.hosts: ["http://localhost:9200"]# 索引名kibana.index: ".kibana"# 支持中文i18n.locale: "zh-CN"

Windows 环境下执行 bin/kibana.bat 文件,启动成功后访问 http://localhost:5601。

3、框架集成

3.1、Spring Data 框架介绍

Spring Data 是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持 map-reduce 框架和云计算数据服务。 Spring Data 可以极大的简化 JPA(Elasticsearch„)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了 CRUD 外,还包括如分页、排序等一些常用的功能。

Spring Data Elasticsearch 基于 spring data API 简化 Elasticsearch 操作,将原始操作Elasticsearch 的客户端 API 进行封装 。Spring Data 为 Elasticsearch 项目提供集成搜索引擎。Spring Data Elasticsearch POJO 的关键功能区域为中心的模型与 Elastichsearch 交互文档和轻松地编写一个存储索引库数据访问层。

配置类

package com.songzhishu.es.config;​import lombok.Data;import org.apache.http.HttpHost;import org.elasticsearch.client.RestClient;import org.elasticsearch.client.RestClientBuilder;import org.elasticsearch.client.RestHighLevelClient;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;​/** * @BelongsProject: elaticsearch-7-spring-data * @BelongsPackage: com.songzhishu.es.config * @Author: 斗痘侠 * @CreateTime: 2024-02-0513:13 * @Description: es配置类 * @Version: 1.0 */@ConfigurationProperties(prefix = "elasticsearch")@Configuration@Datapublic class ElasticsearchConfig extends AbstractElasticsearchConfiguration {​ private String host; private int port;​ /** * 配置es连接 * @return */ @Override public RestHighLevelClient elasticsearchClient() { // 连接es RestClientBuilder builder = RestClient.builder(new HttpHost(host, port)); // 高级客户端对象 RestHighLevelClient client = new RestHighLevelClient(builder); return client; }}

接口

package com.songzhishu.es.dao;​import com.songzhishu.es.pojo.Product;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;import org.springframework.stereotype.Repository;​/** * @BelongsProject: elaticsearch-7-spring-data * @BelongsPackage: com.songzhishu.es.dao */@Repositorypublic interface ProductDao extends ElasticsearchRepository {​}

实体

package com.songzhishu.es.pojo;​import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;import org.springframework.data.annotation.Id;import org.springframework.data.elasticsearch.annotations.Document;import org.springframework.data.elasticsearch.annotations.Field;import org.springframework.data.elasticsearch.annotations.FieldType;​/** * @BelongsProject: elaticsearch-7-spring-data * @BelongsPackage: com.songzhishu.es.pojo * @Author: 斗痘侠 * @CreateTime: 2024-02-0513:10 * @Description: 商品实体类 * @Version: 1.0 */@Data@NoArgsConstructor@AllArgsConstructor@ToString@Document(indexName = "product", shards = 3, replicas = 1)//索引名字,分片数,副本数public class Product {​ @Id private Long id;//商品id​ @Field(type = FieldType.Text, analyzer = "ik_max_word")//分词器 ik_max_word 最大化分词 private String title;//商品标题​ @Field(type = FieldType.Keyword)//不分词 private String category;//商品分类​ @Field(type = FieldType.Double)//浮点型 private Double price;//商品价格 @Field(type = FieldType.Keyword,index = false)//不分词 不索引(就是不能搜索) private String images;//商品图片​}

测试类

package com.songzhishu.es.test;​import com.songzhishu.es.dao.ProductDao;import com.songzhishu.es.pojo.Product;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Sort;import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;​import javax.annotation.Resource;import java.util.ArrayList;import java.util.List;​​/** * @BelongsProject: elaticsearch-7-spring-data * @BelongsPackage: com.songzhishu.es.test * @Author: 斗痘侠 * @CreateTime: 2024-02-0513:25 * @Description: 测试类 * @Version: 1.0 */@SpringBootTestpublic class SpringDataESIndexTest {​ @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate;​ @Resource private ProductDao productDao;​ @Test public void createIndex() { // 创建索引 会根据Product类的@Document注解信息来创建 如果已经存在就会失败 System.out.println("创建索引"); }​ @Test public void deleteIndex() { // 删除索引 boolean b = elasticsearchRestTemplate.indexOps(Product.class).delete(); System.out.println("删除索引" + b); }​ /** * 测试文档操作-创建文档 */ @Test public void testDocument() { // 创建文档 Product product = new Product(); product.setId(8L); product.setTitle("一加手机1"); product.setCategory("手机"); product.setPrice(2599.00); product.setImages("http://www.baidu.com"); productDao.save(product); System.out.println("创建文档"); }​ /** * 测试文档操作-删除文档 */ @Test public void testDeleteDocument() { // 删除文档 productDao.deleteById(8L); System.out.println("删除文档"); }​ /** * 测试文档操作-修改文档 */ @Test public void testUpdateDocument() { // 修改文档 Product product = new Product(); product.setId(1L); product.setTitle("华为手机"); product.setCategory("手机"); product.setPrice(2999.00); product.setImages("http://www.baidu.com"); productDao.save(product); System.out.println("修改文档"); }​ /** * 测试文档操作-查询文档 */ @Test public void testQueryDocument() { // 查询文档 Product product = productDao.findById(1L).get(); System.out.println(product); }​ /** * 测试文档操作-查询所有 */ @Test public void testQueryAll() { // 查询所有 Iterable products = productDao.findAll(); products.forEach(System.out::println); }​ /** * 测试文档操作-批量添加 */ @Test public void testAddAll() { // 批量添加 List list = new ArrayList(); for (int i = 8; i < 18; i++) { Product product = new Product((long) i, "oppo手机" + i, "手机", 4999.00, "http://www.baidu.com"); list.add(product); } productDao.saveAll(list); System.out.println("批量添加"); }​ /** * 测试文档操作-批量删除 */ @Test public void testDeleteAll() { // 批量删除 productDao.deleteAll(); System.out.println("批量删除"); }​ /** * 测试文档操作-分页查询 */ @Test public void testQueryPage() { // 分页查询 设置分页参数 int page = 0; // 页码 int size = 5; // 每页显示的条数​ Sort sort = Sort.by(Sort.Direction.DESC, "id");//排序 按照id降序​ PageRequest request = PageRequest.of(page, size, sort); Iterable products = productDao.findAll(request);​ products.forEach(System.out::println); }​​}
​​
package com.songzhishu.es.test;​import com.songzhishu.es.dao.ProductDao;import com.songzhishu.es.pojo.Product;import org.elasticsearch.cluster.metadata.AliasAction;import org.elasticsearch.index.query.QueryBuilders;​import org.elasticsearch.index.query.TermQueryBuilder;import org.elasticsearch.search.aggregations.AggregationBuilders;import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;import org.elasticsearch.search.sort.SortBuilders;import org.elasticsearch.search.sort.SortOrder;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.domain.PageRequest;import org.springframework.data.elasticsearch.core.AggregationsContainer;import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;import org.springframework.data.elasticsearch.core.SearchHits;​import org.springframework.data.elasticsearch.core.clients.elasticsearch7.ElasticsearchAggregations;import org.springframework.data.elasticsearch.core.query.*;​​import javax.annotation.Resource;​/** * @BelongsProject: elaticsearch-7-spring-data * @BelongsPackage: com.songzhishu.es.test * @Author: 斗痘侠 * @CreateTime: 2024-02-0514:11 * @Description: 测试类 查询 * @Version: 1.0 */@SpringBootTestpublic class SpringDataESSearchTest {​ @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate;​ @Resource private ProductDao productDao;​ /** * 测试查询-term查询 */ @Test public void testTermQuery() { /*NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();​ //term查询 queryBuilder.withQuery(QueryBuilders.termQuery("title", "手机"));​ SearchHits search = elasticsearchRestTemplate.search(queryBuilder.build(), Product.class);​ search.forEach(productSearchHit -> { System.out.println(productSearchHit.getContent()); });*/​ CriteriaQuery query = new CriteriaQuery(new Criteria("title").is("手机"));​ SearchHits search = elasticsearchRestTemplate.search(query, Product.class); search.forEach(productSearchHit -> { System.out.println(productSearchHit.getContent()); });​​ }​​ /** * 测试查询-分页查询 */ @Test public void testPageQuery() {​ NativeSearchQuery query = new NativeSearchQueryBuilder() .withPageable(PageRequest.of(0, 5)) .withQuery(QueryBuilders.termQuery("title", "手机")) .build();​ SearchHits search = elasticsearchRestTemplate.search(query, Product.class);​ search.forEach(productSearchHit -> { System.out.println(productSearchHit.getContent()); }); }​ /** * 测试查询-排序查询 */ @Test public void testSortQuery() {​ NativeSearchQuery query = new NativeSearchQueryBuilder() .withPageable(PageRequest.of(0, 5)) .withQuery(QueryBuilders.termQuery("title", "手机")) .withSorts(SortBuilders.fieldSort("price").order(SortOrder.DESC)) .build();​ SearchHits search = elasticsearchRestTemplate.search(query, Product.class);​ search.forEach(productSearchHit -> { System.out.println(productSearchHit.getContent()); }); }​​ /** * 测试查询-聚合查询 */​ @Test public void testAggQuery() {​ NativeSearchQuery query = new NativeSearchQueryBuilder() .addAggregation(AggregationBuilders.terms("categoryAgg").field("category")) .build();​ SearchHits search = elasticsearchRestTemplate.search(query, Product.class);​ ElasticsearchAggregations aggregations = (ElasticsearchAggregations) search.getAggregations();​​​​ }​ /** * 测试查询-高亮查询 */ @Test public void testHighLightQuery() {​ NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.termQuery("title", "手机")) .withHighlightFields(new HighlightBuilder.Field("title").preTags("").postTags("")) .build();​ SearchHits search = elasticsearchRestTemplate.search(query, Product.class);​ search.forEach(productSearchHit -> { System.out.println(productSearchHit.getHighlightField("title")); }); }​}

4、配置项

参数名参数值说明
cluster.nameelasticsearch配置 ES 的集群名称,默认值是 ES,建议改成与所存数据相关的名称,ES 会自动发现在同一网段下的集群名称相同的节点。
node.namenode-1集群中的节点名,在同一个集群中不能重复。节点的名称一旦设置,就不能再改变了。当然可以设 置 成 服 务 器 的 主 机 名 称 , 例 如node.name:${HOSTNAME}。
node-mastertrue指定该节点是否有资格被选举成为 Master 节点,默认是 True,如果被设置为 True,则只是有资格成为Master 节点,具体能否成为 Master 节点,需要通过选举产生。
node-datatrue指定该节点是否存储索引数据,默认为 True。数据的增、删、改、查都是在 Data 节点完成的。
index.number_of_shards1设置都索引分片个数,默认是 1 片。也可以在创建索引时设置该值,具体设置为多大都值要根据数据量的大小来定。如果数据量不大,则设置成 1 时效率最高
index.number_of_replicas1设置默认的索引副本个数,默认为 1 个。副本数越多,集群的可用性越好,但是写索引时需要同步的数据越多。
transport.tcp.compresstrue设置在节点间传输数据时是否压缩,默认为 False,不压缩
discovery.zen.minimum_master_nodes1设置在选举 Master 节点时需要参与的最少的候选主节点数,默认为 1。如果使用默认值,则当网络不稳定时有可能会出现脑裂。合理的数值为 (master_eligible_nodes/2)+1 ,其中master_eligible_nodes 表示集群中的候选主节点数
discovery.zen.ping.timeout3s设置在集群中自动发现其他节点时 Ping 连接的超时时间,默认为 3 秒。在较差的网络环境下需要设置得大一点,防止因误判该节点的存活状态而导致分片的转移

5、新版api

   org.elasticsearch.plugin x-pack-sql-jdbc 7.17.16 ​  co.elastic.clients elasticsearch-java 7.17.16 ​  com.fasterxml.jackson.core jackson-databind 2.16.0 ​  jakarta.json jakarta.json-api 2.1.2 ​ 

5.1、普通操作

5.1.1、索引
// 创建索引CreateIndexRequest request = new CreateIndexRequest.Builder().index("myindex").build();final CreateIndexResponse createIndexResponse = client.indices().create(request);​System.out.println("创建索引成功:" + createIndexResponse.acknowledged());​// 查询索引GetIndexRequest getIndexRequest = new GetIndexRequest.Builder().index("myindex").build();final GetIndexResponse getIndexResponse = client.indices().get(getIndexRequest);​System.out.println("索引查询成功:" + getIndexResponse.result());​// 删除索引DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest.Builder().index("myindex").build();​final DeleteIndexResponse delete = client.indices().delete(deleteIndexRequest);final boolean acknowledged = delete.acknowledged();​System.out.println("删除索引成功:" + acknowledged);
5.1.2、文档
// 创建文档IndexRequest indexRequest = new IndexRequest.Builder() .index("myindex") .id(user.getId().toString()) .document(user) .build();final IndexResponse index = client.index(indexRequest);System.out.println("文档操作结果:" + index.result());​// 批量创建文档final List operations = new ArrayList();for ( int i= 1;i <= 5; i++ ) { final CreateOperation.Builder builder = new CreateOperation.Builder(); builder.index("myindex");builder.id("200" + i); builder.document(new User(2000 + i, 30 + i * 10, "zhangsan" + i, "beijing", 1000 + i*1000));​final CreateOperation objectCreateOperation = builder.build();​final BulkOperation bulk = new BulkOperation.Builder().create(objectCreateOperation).build(); operations.add(bulk);}​BulkRequest bulkRequest = new BulkRequest.Builder().operations(operations).build();final BulkResponse bulkResponse = client.bulk(bulkRequest);System.out.println("数据操作成功:" + bulkResponse);// 删除文档DeleteRequest deleteRequest = new DeleteRequest.Builder().index("myindex").id("1001").build();client.delete(deleteRequest);
5.1.3、文档搜索
final SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder().index("myindex1");​MatchQuery matchQuery = new MatchQuery.Builder().field("city").query(FieldValue.of("beijing")).build();​Query query = new Query.Builder().match(matchQuery).build();​searchRequestBuilder.query(query);​SearchRequest searchRequest = searchRequestBuilder.build();​final SearchResponse search = client.search(searchRequest, Object.class);​System.out.println(search);

5.2、函数操作

5.2.1、索引操作
// 创建索引final Boolean acknowledged = client.indices().create(p -> p.index("")).acknowledged();System.out.println("创建索引成功");​// 获取索引System.out.println( client.indices().get( req -> req.index("myindex1")).result());​​// 删除索引client.indices().delete( reqbuilder -> reqbuilder.index("myindex")).acknowledged();
5.2.2、文档操作
// 创建文档System.out.println( client.index( req -> req.index("myindex").id(user.getId().toString()) .document(user) ).result());​​// 批量创建文档client.bulk( req -> {users.forEach( u -> { req.operations( b -> { b.create( d -> d.id(u.getId().toString()).index("myindex").document(u) ); return b; } ); } ); return req; });​​// 删除文档client.delete( req -> req.index("myindex").id("1001"));
5.2.3、文档查询
client.search( req -> { req.query( q -> q.match( m -> m.field("city").query("beijing") ) );return req; } , Object.class);

Copyright © maxssl.com 版权所有 浙ICP备2022011180号