一、ElasticSearch 简介 1、简介 ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多员工能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 语言开发的,并作为 Apache 许可条款下的开放源码发布,是一种流行的企业级搜索引擎。 ElasticSearch 用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
2、特性
分布式的文档存储引擎
分布式的搜索引擎和分析引擎
分布式,支持 PB 级数据
3、使用场景
搜索领域:如百度、谷歌,全文检索等。
门户网站:访问统计、文章点赞、留言评论等。
广告推广:记录员工行为数据、消费趋势、员工群体进行定制推广等。
信息采集:记录应用的埋点数据、访问日志数据等,方便大数据进行分析。
二、ElasticSearch 基础概念 1、ElaticSearch 和 DB 的关系 在 Elasticsearch 中,文档归属于一种类型 type,而这些类型存在于索引 index 中,可以列一些简单的不同点,来类比传统关系型数据库:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch 集群可以包含多个索引 indices,每一个索引可以包含多个类型 types,每一个类型包含多个文档 documents,然后每个文档包含多个字段 Fields。而在 DB 中可以有多个数据库 Databases,每个库中可以有多张表 Tables,没个表中又包含多行 Rows,每行包含多列 Columns。
ES
MySql
字段
列
文档
一行数据
类型(已废弃)
表
索引
数据库
2、索引 索引基本概念(indices) 索引是含义相同属性的文档集合,是 ElasticSearch 的一个逻辑存储,可以理解为关系型数据库中的数据库,ElasticSearch 可以把索引数据存放到一台服务器上,也可以 sharding 后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。
索引类型(index_type) 索引可以定义一个或多个类型,文档必须属于一个类型。在 ElasticSearch 中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。
3、文档( document) 文档是可以被索引的基本数据单位。存储在 ElasticSearch 中的主要实体叫文档 document,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,ElasticSearch 是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。
4、映射 (mapping) ElasticSearch 的 Mapping 非常类似于静态语言中的数据类型:声明一个变量为 int 类型的变量,以后这个变量都只能存储 int 类型的数据。同样的,一个 number 类型的 mapping 字段只能存储 number 类型的数据。 同语言的数据类型相比,Mapping 还有一些其他的含义,Mapping 不仅告诉 ElasticSearch 一个 Field 中是什么类型的值, 它还告诉 ElasticSearch 如何索引数据以及数据是否能被搜索到。 ElaticSearch 默认是动态创建索引和索引类型的 Mapping 的。这就相当于无需定义 Solr 中的 Schema,无需指定各个字段的索引规则就可以索引文件,很方便。但有时方便就代表着不灵活。比如,ElasticSearch 默认一个字段是要做分词的,但有时要搜索匹配整个字段却不行。如有统计工作要记录每个城市出现的次数。对于 name 字段,若记录 new york 文本,ElasticSearch 可能会把它拆分成 new 和 york 这两个词,分别计算这个两个单词的次数,而不是期望的 new york。
三、Spring Data Elasticsearch Spring Data Elasticsearch 是 Spring 提供的一种以 Spring Data 风格来操作数据存储的方式,它可以避免编写大量的样板代码。 Spring Data 的官网:https://spring.io/projects/spring-data
1、常用注解 映射:Spring Data 通过注解来声明字段的映射属性,有下面的三个注解:
@Document
作用在类,标记实体类为文档对象,一般有四个属性
indexName:对应索引库名称
type:对应在索引库中的类型
shards:分片数量,默认 5
replicas:副本数量,默认 1
@Id
作用在成员变量,标记一个字段作为 id 主键
@Field
作用在成员变量,标记为文档的字段,并指定字段映射属性:
type:字段类型,取值是枚举:FieldType
index:是否索引,布尔类型,默认是 true
store:是否存储,布尔类型,默认是 false
analyzer:分词器名称:ik_max_word
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Data @Builder @AllArgsConstructor @NoArgsConstructor @Document(indexName = "article") public class ArticleSearchDTO { @Id private Integer id; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String articleTitle; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String articleContent; @Field(type = FieldType.Integer) private Integer isDelete; @Field(type = FieldType.Integer) private Integer status; }
2、ElasticsearchRestTemplate 用法 在 ElasticsearchTemplate 中,执行查询的大多都是 query 开头,而 query 方法,第一个参数是 Query 的实现类
源码中,NativeSearchQuery 的构造方法中,参数是 QueryBuilder
这个又是什么? 从名字分析,Query 查询,builder 构建,很清楚的分析出来,QueryBuilder 用来构建查询条件,过滤条件。就好比 SQL 语句后面 where name = “张三” 跟这个是一个意思。 分析到这里基本上就差不多了,这里只是说简单使用 ,Spring 中提供了一个类 QueryBuilders ,里面有很多方法来完成各种各样的 QueryBuilder 的构建,字符串型的,Boolean 型的,match,Term 等。
##
四、整合 Elasticsearch 实现博客搜索 1、引入依赖 1 2 3 4 5 6 <!--Elasticsearch相关依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch<artifactId> </dependency>
2、修改 SpringBoot 配置文件 1 2 3 4 spring: elasticsearch: rest: uris: http:
3、搜索实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 @Slf4j @Service("esSearchStrategyImpl") public class EsSearchStrategyImpl implements SearchStrategy { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Override public List<ArticleSearchDTO> searchArticle (String keywords) { if (StringUtils.isBlank(keywords)){ return new ArrayList<>(); } return search(buildQuery(keywords)); } private NativeSearchQueryBuilder buildQuery (String keywords) { NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("articleTitle" , keywords)) .should(QueryBuilders.matchQuery("articleContent" , keywords))) .must(QueryBuilders.termQuery("isDelete" , FALSE)) .must(QueryBuilders.termQuery("status" , PUBLIC.getStatus())); nativeSearchQueryBuilder.withQuery(boolQueryBuilder); return nativeSearchQueryBuilder; } private List<ArticleSearchDTO> search (NativeSearchQueryBuilder nativeSearchQueryBuilder) { HighlightBuilder.Field titleField = new HighlightBuilder.Field("articleTitle" ); titleField.preTags(PRE_TAG); titleField.postTags(POST_TAG); HighlightBuilder.Field contentField = new HighlightBuilder.Field("articleContent" ); contentField.preTags(PRE_TAG); contentField.postTags(POST_TAG); contentField.fragmentSize(200 ); nativeSearchQueryBuilder.withHighlightFields(titleField, contentField); try { SearchHits<ArticleSearchDTO> search = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), ArticleSearchDTO.class); return search.getSearchHits().stream().map(hit -> { ArticleSearchDTO article = hit.getContent(); List<String> titleHighLightList = hit.getHighlightFields().get("articleTitle" ); if (CollectionUtils.isNotEmpty(titleHighLightList)) { article.setArticleTitle(titleHighLightList.get(0 )); } List<String> contentHighLightList = hit.getHighlightFields().get("articleContent" ); if (CollectionUtils.isNotEmpty(contentHighLightList)) { article.setArticleContent(contentHighLightList.get(contentHighLightList.size() - 1 )); } return article; }).collect(Collectors.toList()); } catch (Exception e) { log.error(e.getMessage()); } return new ArrayList<>(); } }
4、使用 Maxwell 同步数据库与 Elasticsearch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Component @RabbitListener(queues = MAXWELL_QUEUE) public class MaxWellConsumer { @Autowired private ElasticsearchMapper elasticsearchMapper; @RabbitHandler public void process (byte [] data) { MaxwellDataDTO maxwellDataDTO = JSON.parseObject(new String(data), MaxwellDataDTO.class); Article article = JSON.parseObject(JSON.toJSONString(maxwellDataDTO.getData()), Article.class); switch (maxwellDataDTO.getType()) { case "insert" : case "update" : elasticsearchMapper.save(BeanCopyUtils.copyObject(article, ArticleSearchDTO.class)); break ; case "delete" : elasticsearchMapper.deleteById(article.getId()); break ; default : break ; } } }
添加 ElasticsearchMapper 接口用于操作 Elasticsearch 继承 ElasticsearchRepository 接口,这样就拥有了一些基本的 Elasticsearch 数据操作方法,同时定义了一个衍生查询方法。
1 2 3 4 @Repository public interface ElasticsearchMapper extends ElasticsearchRepository <ArticleSearchDTO ,Integer > {}