使用ElasticSearch
参考文献:https://blog.csdn.net/yb546822612/article/details/103196486
一、搜索引擎简介
1、Elasticsearch简介
Elasticsearch是一个实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。
它用于全文搜索、结构化搜索、分析以及将这三者混合使用
维基百科使用Elasticsearch提供全文搜索并高亮关键字,以及输入实时搜索(search-asyou-type)和搜索纠错(did-you-mean)等搜索建议功能。
英国卫报使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。 StackOverflow结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。
Github使用Elasticsearch检索1300亿行的代码。
但是Elasticsearch不仅用于大型企业,它还让像DataDog以及Klout这样的创业公司将最初的想法变成可扩展的解决方案。Elasticsearch可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据 。
Elasticsearch是一个基于Apache Lucene?的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
但是,Lucene只是一个库。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的 RESTful API 来隐藏Lucene的复杂性,从而让全文搜索变得简单。
2、Solr简介
Solr是一个独立的企业级搜索应用服务器
Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化
Solr可以独立运行,运行在Jetty、Tomcat等这些Servlet容器中,Solr 索引的实现方法很简单,用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr根据xml文档添加、删除、更新索引 。Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr 返回Xml、json等格式的查询结果进行解析,组织页面布局。Solr不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
solr是基于lucene开发企业级搜索服务器,实际上就是封装了lucene。
Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果。
3、Lucene简介
Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。
Lucene是一个全文检索引擎的架构。那什么是全文搜索引擎?
全文搜索引擎是名副其实的搜索引擎,国外具代表性的有Google、Fast/AllTheWeb、AltaVista、Inktomi、Teoma、WiseNut等,国内著名的有百度(Baidu)。它们都是通过从互联网上提取的各个网站的信息(以网页文字为主)而建立的数据库中,检索与用户查询条件匹配的相关记录,然后按一定的排列顺序将结果返回给用户,因此他们是真正的搜索引擎。
从搜索结果来源的角度,全文搜索引擎又可细分为两种,一种是拥有自己的检索程序(Indexer),俗称“蜘蛛”(Spider)程序或“机器人”(Robot)程序,并自建网页数据库,搜索结果直接从自身的数据库中调用,如上面提到的7家引擎;另一种则是租用其他引擎的数据库,并按自定的格式排列搜索结果,如Lycos引擎。
4、Elasticsearch和Solr比较
5、ElasticSearch vs Solr 总结
- Es基本是开箱即用,非常简单。Solr安装略复杂一丢丢
- Solr利用Zookeeper进行分布式管理,而ElasticSearch自身带有分布式协调管理功能。
- Solr支持更多格式的数据,比如Json、xml、csv,而ElasticSearch仅支持Json文件格式。
- Solr官方提供的功能更多,而ElasticSearch本身就注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要Kibana友好支撑
- Solr查询快,但是更新索引慢(即插入删除慢),用于电商等查询多的应用;
- ES建立索引快(即查询慢),即实时性查询快,用于facebook新浪等搜索。
- Solr是传统搜索应用的有力解决方案,但ElasticSearch更适用于新兴的实时搜索应用
- Solr比较成熟,有一个更大,更成熟的用户、开发和贡献社区,而ElasticSearch相对开发维护者较少,更新太快,学习使用成本较高。
二、ElasticSearch安装
最低需求:jdk1.8 保证jdk环境正常
官网下载地址:https://www.elastic.co/cn/downloads/elasticsearch
华为镜像站下载地址:https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D 【下载速度快】
1、下载解压
文件目录:
bin:可执行文件
config:配置文件
elasticsearch.yml:ElasticSearch配置文件
jvm.options:配置虚拟机 、
lib:jar依赖包
modules:功能模块
logs:日志
plugins:插件
2、启动服务
进入bin目录执行elasticsearch.bat 可执行文件,等待服务启动完成,访问localhost:9200端口
测试访问:
一个ElasticSearch也算是一个集群,cluster_name:集群名称 和 uuid
3、安装可视化工具
需要Nodejs环境
方便观察ElasticSearch中的数据信息
下载地址:https://github.com/mobz/elasticsearch-head
下载后解压文件目录,进入到解压后的文件目录
npm install
npm run start
启动成功如图:
配置跨域:
默认情况下,可视化面板无法连接到9200端口
配置跨域进行解决
-
关闭ElasticSearch(关闭运行ElasticSearch的命令窗口) -
打开ElasticSearch的配置文件目录 -
修改elasticsearch.yml 文件,添加以下代码
http.cors.enabled: true
http.cors.allow-origin: "*"
-
重启ElasticSearch
测试访问
4、安装Kibana
需要有Nodejs环境
安装Kibana为了方便后面的测试,当然也可以使用前面安装的可视化工具进行测试,但是它没有json格式化,非常的麻烦
常用测试工具:Postman、curl、谷歌浏览器插件
这里就使用Kibana,安装Kibana之后ElasticSearch会自动添加Kibana的索引
下载地址:https://www.elastic.co/cn/downloads/kibana#ga-release
-
下载解压后进入bin目录,执行kibana.bat 文件 -
访问5601端口 -
-
找到工具选项 之后的所有工作都在这里进行
修改Kibana语言
Kibana提供了对中文的支持,需要修改Kibana配置文件即可
进入Kibana安装目录下的conf目录,修改kibana.xml文件
i18n.locale: "zc-CN"
重启项目!汉化完成
三、ES核心概念
- 索引
- 字段类型(mapping)
- 文档(documents)
- 分片(倒排索引)
elasticsearch是面向文档,关系行数据库和elasticsearch客观的对比!一切都是JSON!
Relational DB | Elasticsearch |
---|
数据库(database) | 索引 | 表(tables) | types | 行(rows) | documents | 字段(columns) | fields |
一个elasticsearch就是一个集群,默认集群名称就是elasticsearch
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表)
每个类型下又包含多个文档(行),每个文档中又包含多个字段(列)
索引
物理设计: elasticsearch在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器之间迁移
逻辑设计:
一个索引中,包含多个文档,比如说文档1,文档2。当我们索引一篇文档时,可以通过这样的一个顺序找到它:索引 > 类型 > 文档id,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是一个字符串。
文档
就是我们的一条条数据
ElasticSearch是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,ElasticSearch中,文档有几个重要属性:
- 自我包含,一篇文档同时包含字段和对应的值,也就是同时包含 key:value
- 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这么来的
- 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据中,要提前设置好字段才能使用,在ElasticSearch中,对于字段是非常灵活的,有时候,我们可以忽略改字段,或者动态的添加一个新字段。
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串,也可以是整形。
因为ElasticSearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在ElasticSearch中,类型有时候也称为映射类型。
类型
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name映射为字符串类型。
我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么ElasticSearch是怎么做的呢?
ElasticSearch会自动的将新字段加入映射,但是这个字段不确定它是什么类型,ElasticSearch就开始猜,如果这个值是18,那么ElasticSearch会认为它是整形。
但是ElasticSearch不一定都会猜对,所以最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了。先定义好字段,后使用
索引
就是数据库
索引是映射类型的容器,ElasticSearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。
物理设计:节点和分片 如何工作
一个集群至少有一个节点,而一个节点就是一个ElasticSearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5分片(primary shard,又称主分片) 构成的,没一个主分片会有一个副本(replica shard,又称为复制分片)
上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于丢失。
实际上,一个分片是一个Lucene索引,一个包含 倒排索引的文件目录,倒排索引的结构使得ElasticSearch在不扫描全部文档的情况下,就能告诉i哪些文档包含特定的关键字。
倒排索引
ElasticSearch使用的是一种称为倒排索引的结构,采用Lucene倒排索引作为底层。这种结构适用于快速的全文搜素,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。
例如:
如果要搜索含有python标签的文章,那相对于查找所有原始数据而言,查找倒排索引后的数据将会快很多。只需要查看标签这一栏,直接获取相关文章id即可
过滤掉无关数据,提高效率!
-
ElasticSearch的索引和Lucene的索引对比 在ElasticSearch中,索引这个词被频繁使用,这就是术语的使用。在ElasticSearch中,索引被分成多个分片,每份分片是一个Lucene的索引。 所以一个ElasticSearch索引是由多个Lucene索引组成。
四、IK分词器
简单来说就是把一句话或一段话进行合理拆分
比如说:我爱中国 ,使用分词器后可以分为:我、我爱、爱、中国
分词器根据输入的一段文字进行合理分词,然后再通过分词后的关键词进行搜索文档内容。
默认情况下分词只对英文有效,所以我们需要安装ik分词器进行支持中文分词
一、下载安装IK分词器
IK提供了两个分词算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分
下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases
-
下载之后放入ElasticSearch安装目录下的插件目录即可(Plugins) 需要新建文件夹,将文件解压到文件夹中。 文件夹名称建议使用ik -
重启ElasticSearch,可以看到ik分词器已经被加载了 如果不确定是否加载了ik分词器插件,可以通过bin目录下执行cmd命令窗口,elasticsearch-plugin list 命令进行查询插件列表
? 3.启动Kibana,进行查询测试 也可以使用其他的工具 例如:Postman等测试工具
ik_smart最少切分
ik_max_word最细粒度划分,也就是说将一段文字尽可能拆分成多段的,直到没有分词的可能性
自定义字典:
当我们发现某些词不能被拆分,但是还是被ik分词器进行了拆分,例如:你好我叫123 被分为:你好、我、叫、1、2、3
如何让分词器不去拆分某些词呢。这个时候我们就需要向分词器的字典中添加词组
例:
向ik分词器字典中添加词组
打开ik分词器配置文件夹(config) , 编辑IKAnalyzer.cfg.xml 文件 *.dic文件都是它的词典
新建我们自己的词典 文件xxx.dic,新建完成后,将文件名.dir加入 key为ext_dict标签内
文件内应该写入想要组词的文字,这里就写撸森了,因为需要让ik分词器识别它为一个词
重启 ElasticSearch!!!
再次测试一波!
添加词典前 和 添加词典后
以后需要添加词组的话只需要在自定义.dic字典文件中添加一个词组即可!
RestFul风格
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制。
基本Rest命令说明:
method | url地址 | 描述 |
---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) | POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) | POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 | DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 | GET | localhost:9200/索引名称/类型名称/文档id | 通过文档id查询文档 | POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
字段类型
默认情况下不指定类型,ElasticSearch将会自动配置字段类型
类型 | |
---|
字符串类型 | text、keyword | 数值类型 | long、integer、short、byte、double、float、half float、scaled float | 日期类型 | date | 布尔类型 | boolean | 二进制类型 | binary |
创建索引时建立类型规则
PUT /test2
{
"mappings": {
"properties": {
"name":{
"type": "text"
},
"age":{
"type": "integer"
},
"time":{
"type": "date"
}
}
}
}
可以通过GET命令进行获得指定 索引、以及文档信息 例:GET /test1
扩展命令
通过GET /_cat 可以获取ES的信息
索引基本操作
创建索引
PUT /索引名/类型名/文档id
{请求体}
?
更新数据
方式1:直接 进行覆盖文档
方式2:使用 POST /索引/类型/文档id/_update进行更新
方式1:
方式2:
删除操作
通过DELETE命令进行删除 索引、文档
例:DELETE /test1、DELETE /test1/_doc/1
根据你的请求来判断是删除索引还是删除文档
文档基本操作
基本操作
-
添加数据 PUT /xiaoge/user/1
{
"name":"张三",
"age":20,
"desc":"你好啊",
"tags":["直男","技术宅"]
}
-
获取数据 -
更新数据 使用PUT 或者 POST /xxx/xx/id/_update (传递参数需要在doc对象内传递) 【推荐使用update】
简单的条件查询,可以根据默认的映射规则,产生基本查询!
复杂操作搜索 (排序、分页、高亮、模糊查询、精准查询)
#精确匹配
GET /xiaoge/user/_search
{
"query":{
"match": {
"name": "张三"
}
}
}
指定显示某个字段信息
排序操作
分页操作
布尔值查询
must(and),所有条件都需要符合
should(or)满足一个即可
must_not (不需要的)
过滤数据 filter ,可以取区间值,也可以单判断
匹配多个条件
精确查询
term 查询是直接通过倒排索引指定的词条进行精确查询, 查询的时候可以通过字段.keyword进行直接直接查询
关于分词
- term:直接查询精确的
- match:会先通过分词器解析 (先分析,后查询)
两个类型 text keyword
- text:会被分词器拆分
- keyword : 不会被分词器进行拆分
精确查询多个值
高亮查询
GET /xiaoge/user/_search
{
"query":{
"match": {
"name": "张三"
}
},
"highlight":{
"fields": {
"name": {}
}
}
}
默认情况下是em标签进行包裹,如何自定义标签呢?通过pre_tags 和 post_tags 设置前缀和后缀标签内容
GET /xiaoge/user/_search
{
"query":{
"match": {
"name": "张三"
}
},
"highlight":{
"pre_tags": "<p style='color:red'>",
"post_tags": "</p>",
"fields": {
"name": {}
}
}
}
集成SpringBoot
高版本文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
-
添加依赖 注意:由于SpringBoot默认整合版本不是我们需要的版本,因此需要修改依赖的版本信息为当前elasticsearch对应的版本。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.15.1</elasticsearch.version>
</properties>
-
初始化 @Configuration
public class ElasticSeachConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")
));
return client;
}
}
索引API操作
-
创建索引
CreateIndexRequest request = new CreateIndexRequest("xiaoge_index");
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
-
获取索引是否存在
GetIndexRequest request = new GetIndexRequest("xiaoge_index");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
-
删除索引
DeleteIndexRequest request = new DeleteIndexRequest("xiaoge_index");
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());
文档API操作
-
添加文档
User user = new User();
user.setName("小王");
user.setAge(15);
IndexRequest request = new IndexRequest("xiaoge_index");
request.id("1");
request.timeout("1s");
request.source(JSON.toJSONString(user), XContentType.JSON);
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
-
获取文档是否存在 GetRequest getRequest = new GetRequest("xiaoge_index", "1");
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
-
获取文档信息 GetRequest getRequest = new GetRequest("xiaoge_index", "1");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
-
更新文档内容
UpdateRequest updateRequest = new UpdateRequest("xiaoge_index","1");
updateRequest.timeout("1s");
updateRequest.doc(JSON.toJSONString(new User("小白",12)),XContentType.JSON);
UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(update);
-
删除文档
DeleteRequest deleteRequest = new DeleteRequest("xiaoge_index", "1");
deleteRequest.timeout("1s");
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse);
-
批量插入数据 批量更新、删除操作都可以在bulkRequest.add方法里面创建不同请求进行操作!
BulkRequest bulkRequest = new BulkRequest();
ArrayList<User> users = new ArrayList<>();
users.add(new User("小明",12));
users.add(new User("小白",20));
users.add(new User("小黑",20));
users.add(new User("小桥",15));
users.add(new User("小灰",10));
for(int i=0;i<users.size();i++){
bulkRequest.add(
new IndexRequest("xiaoge_index")
.id(""+(i+1))
.source(JSON.toJSONString(users.get(i)),XContentType.JSON)
);
}
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures());
-
根据条件查询文档信息 通过QueryBuilders可以快速创建查询条件!
SearchRequest searchRequest = new SearchRequest("xiaoge_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder queryAll = QueryBuilders.matchAllQuery();
sourceBuilder.from(1);
sourceBuilder.size(5);
sourceBuilder.query(queryAll);
sourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
highlightBuilder.preTags("<li style='color:red;font-weight:bold'>");
highlightBuilder.postTags("</li>");
sourceBuilder.highlighter(highlightBuilder);
searchRequest.source(sourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = search.getHits();
for (SearchHit hit : hits) {
System.out.println(hit);
}
高亮查询关键代码: SearchRequest searchRequest = new SearchRequest("goods_jd");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from((index-1)*size);
sourceBuilder.size(size);
if(keyword!=null && keyword.trim().length()>0){
sourceBuilder.query(QueryBuilders.matchPhraseQuery("title",keyword));
}else{
sourceBuilder.query(QueryBuilders.matchAllQuery());
}
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
ArrayList<Map<String,Object>> list = new ArrayList<>();
for (SearchHit hit : hits.getHits()) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField title = highlightFields.get("title");
if(title!=null){
Text[] fragments = title.getFragments();
String n_title = "";
for (Text fragment : fragments) {
n_title += fragment;
}
sourceAsMap.put("title",n_title);
}
list.add(sourceAsMap);
}
return list;
这里暂时还没有使用IK分词器
爬坑
参考文献
狂神说Java
|