赞
踩
Elasticsearch
是一个开源的分布式、RESTful
风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene
。
Lucene
可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java
并将 Lucene
直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene
非常复杂。
为了解决Lucene
使用时的繁复性,于是Elasticsearch
便应运而生。它使用 Java
编写,内部采用 Lucene
做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene
做了一层封装,它提供了一套简单一致的 RESTful API
来帮助我们实现存储和检索。
当然,Elasticsearch
不仅仅是 Lucene
,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:
由于Elasticsearch
的功能强大和使用简单,维基百科、卫报、Stack Overflow、GitHub等都纷纷采用它来做搜索。现在,Elasticsearch已成为全文搜索领域的主流软件之一。
docker pull elastics
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 elasticsearch
9300与9200区别
9300端口: ES节点之间通讯使用
9200端口: ES节点 和 外部 通讯使用9300是TCP协议端口号,ES集群之间通讯端口号
9200端口号,暴露ES RESTful接口端口号
注意: 若是开发环境最好给他分配内存,因为,ES
默认启动占用内存是2g,反正我是舍不得。
在浏览器中输入网址“http://ip:9200/”,如果看到以下信息就说明你的电脑已成功安装Elasticsearch:
Kibana
是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。
docker pull kibana:7.6.1
注: 版本对应
安装完成以后需要启动kibana容器,使用--link
连接到elasticsearch容器,命令如下:
docker run --name kibana --link=elasticsearch:test -p 5601:5601 -d kibana:7.6.1
启动以后可以打开浏览器输入http://ip:5601
就可以打开kibana的界面了。
Elasticsearch 数据管理的顶层单位就叫做 Index(索引),相当于关系型数据库里的数据库的概念。另外,每个Index的名字必须是小写。
Index里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。Document 使用 JSON 格式表示。同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
Document 可以分组,比如employee这个 Index 里面,可以按部门分组,也可以按职级分组。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document,类似关系型数据库中的数据表。
不同的 Type 应该有相似的结构(Schema),性质完全不同的数据(比如 products 和 logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。
文档元数据为_index, _type, _id, 这三者可以唯一表示一个文档,_index表示文档在哪存放,_type表示文档的对象类别,_id为文档的唯一标识。
每个Document都类似一个JSON结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个 Document,可以类比关系型数据库数据表中的字段。
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。在全文搜索的世界中,存在着几个庞大的帝国,也就是主流工具,主要有:
该索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。Elasticsearch能够实现快速、高效的搜索功能,正是基于倒排索引原理。
Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)
在 Elasticsearch 中,文档(Document)归属于一种类型(Type),而这些类型存在于索引(Index)中,下图展示了Elasticsearch与传统关系型数据库的类比:
Relational DB | Elasticsearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | types |
行(rows) | documents |
字段(columns) | fifields |
Elasticsearch提供了多种交互使用方式,包括Java API
和RESTful API
。所有其他语言可以使用RESTful API 通过端口 9200 和 Elasticsearch 进行通信,你可以用你最喜爱的 web 客户端访问 Elasticsearch 。甚至,你还可以使用 curl 命令来和 Elasticsearch 交互。
一个Elasticsearch请求和任何 HTTP 请求一样,都由若干相同的部件组成:
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
返回的数据格式为JSON,因为Elasticsearch中的文档以JSON格式储存。其中,被 < > 标记的部件:
部件 | 说明 |
---|---|
VERB | 适当的 HTTP 方法 或 谓词 : GET、 POST、 PUT、 HEAD 或者 DELETE。 |
PROTOCOL | http 或者 https(如果你在 Elasticsearch 前面有一个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。 |
PORT | 运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。 |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | 一个 JSON 格式的请求体 (如果请求需要的话) |
对于HTTP方法,它们的具体作用为:
methodurl地址 | url地址 | 描述 |
---|---|---|
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档id |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
HEAD | 请求获取对象的基础信息 | |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
Elasticsearch是把restful玩到了极限
我们以下面的数据为例,来展示Elasticsearch的用法。 以下全部的操作都在Kibana
中完成,创建的index
为conference
, type
为event
首先创建index为conference, 创建type为event, 插入id为1的第一条数据,
PUT /conference/event/1
{
"host": "Dave Nolan",
"title": "real-time Elasticsearch",
"description": "we will discuss using Elasticsratch to index data in real time",
"attendees": ["Dave", "Shay", "John", "Harry"],
"date": "2013-02-18T18:30",
"reviews": 3
}
PUT /conference/event/2
{
"host": "Andy",
"title": "Moving Hadoop to the mainstream",
"description": "Come hear about how Hadoop is moving to the main stram",
"attendees": ["Andy", "Matt", "Bill", "Clint"],
"date": "2013-07-24T18:30",
"reviews": 1
}
PUT /conference/event/3
{
"host": "Andy",
"title": "Big Data and the cloud at Microsoft",
"description": "Disussion abour the Microsoft Azure cloud and HDInsight",
"attendees": ["Andy", "Michael", "Ben", "David"],
"date": "2013-07-31T18:30",
"reviews": 1
}
PUT /conference/event/4
{
"host": "Mik",
"title": "Logging and Elasticsearch",
"description": "Get a deep dive for what Elastisearch is and how it can be used for logging with Logstash as well as Kibaana",
"attendees": ["Shay", "Rashid", "Erik", "Grant"],
"date": "2013-04-24T18:30",
"reviews": 3
}
PUT /conference/event/5
{
"host": "Dave",
"title": "Elasticsearch at Rangespan and Exonar",
"description": "Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch",
"attendees": ["Dave", "Andrew", "David", "Clint"],
"date": "2013-06-24T18:30",
"reviews": 3
}
在上面的命令中,路径/conference/event/1
表示文档的index
为conference
,type
为event
, id
为1. 类似于上面的操作,依次插入剩余的4条数据,完成插入
比如我们想要删除conference
中event
里面id
为5的数据,只需运行下面命令即可:
DELETE /conference/event/5
返回结果如下:
{
"_index" : "conference",
"_type" : "event",
"_id" : "5",
"_version" : 2,
"result" : "deleted",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
表示该文档已成功删除。如果想删除整个event
类型,可输入命令:
DELETE /conference/event
如果想删除整个conference索引,可输入命令:
DELETE /conference
修改数据的命令为POST
, 比如我们想要将conference
中event
里面id
为4
的文档的作者改为Bob,那么需要运行命令如下:
POST /conference/event/4/_update
{
"doc": {"host": "xiaoming"}
}
返回的信息如下:(表示修改数据成功)
{
"_index" : "conference",
"_type" : "event",
"_id" : "4",
"_version" : 3,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 10,
"_primary_term" : 1
}
查询结果解释
- took - 耗费多少毫秒
- time_out - 是否超时
- _shards - 数据分片情况
- hits.total - 查询结果的数量
- hits.max_score - document 对于一个 search 的相关度匹配分数,越相关分数越高
- hits.hits - 匹配 document 的详细数据
语法
GET /conference/event/_search
GET /conference/event/_search
搜索attendees包含Dave的名字,而且按照reviews降序排序:
GET /conference/event/_search?q=attendees:Dave&sort=reviews:desc
结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "conference",
"_type" : "event",
"_id" : "3",
"_score" : null,
"_source" : {
"host" : "Andy",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch",
"attendees" : [
"Dave",
"Andrew",
"David",
"Clint"
],
"date" : "2013-06-24T18:30",
"reviews" : 3
},
"sort" : [
3
]
},
{
"_index" : "conference",
"_type" : "event",
"_id" : "2",
"_score" : null,
"_source" : {
"host" : "Dave Nolan",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch",
"attendees" : [
"Dave",
"Andrew",
"David",
"Clint"
],
"date" : "2013-06-24T18:30",
"reviews" : 2
},
"sort" : [
2
]
},
{
"_index" : "conference",
"_type" : "event",
"_id" : "1",
"_score" : null,
"_source" : {
"host" : "Dave",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "Representatives from Rangespan and Exonar will come and discuss how they use Elasticsearch",
"attendees" : [
"Dave",
"Andrew",
"David",
"Clint"
],
"date" : "2013-06-24T18:30",
"reviews" : 1
},
"sort" : [
1
]
}
]
}
}
适用于临时的在命令行使用一些工具,比如curl,快速的发出请求,来检索想要的信息;但是如果查询请求很复杂,是很难去构建的,在生产环境中,几乎很少使用query string search
。
GET /conference/event/_search?q=attendees:Dave
GET /conference/event/_search?q=+attendees:Dave
属性attendees包含Dave
GET /conference/event/_search?q=-attendees:Dave
属性attendees不包含Dave
GET /conference/event/_search?q=attendees:Dave&sort=reviews:desc
查询attendees包含Dave,同时按照reviews降序排序
GET /conference/event/_search?q=Dave
直接可以搜索所有的field,任意一个field包含指定的关键字就可以搜索出来。
DSL:Domain Specified Language,特定领域的语言
http request body
:请求体,可以用json的格式来构建查询语法,比较方便,可以构建各种复杂的语法,比query string search
肯定强大多了
GET /conference/event/_search
{
"query": { "match_all": {} }
}
查询attendees包含Dave,同时按照reviews降序排序
GET /conference/event/_search
{
"query": {
"match": {
"attendees":"Dave"
}
},
"sort": [
{
"reviews": {
"order": "desc"
}
}
]
}
查询所有,在分页
GET /conference/event/_search
{
"query": {
"match_all": {}
},
"from": 2,
"size": 3
}
查询字段attendees包含Dave关键字,在分页
GET /conference/event/_search
{
"query": {
"match": {
"attendees": "Dave"
}
},
"from": 2,
"size": 3
}
GET /conference/event/_search
{
"query": { "match_all": {} },
"_source": ["attendees", "host"]
}
搜索attendees属性包含 Clint ,而且reviews大于等于2
GET /conference/event/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"attendees": "Clint"
}
}
],
"filter": {
"range": {
"reviews": {
"gte": 2
}
}
}
}
}
}
全文检索 - 会将输入的关键字拆解,去倒排索引里面一一匹配,只要匹配上任意一个拆解后的单词,就可以作为结果返回 ,返回的结果包含了两个文档,放在数组
hits
中。让我们对这个结果做一些分析,第一个文档的description
里面含有“using Elasticsearch
”,这个能匹配“use Elasticsearch
”是因为Elasticsearch
含有内置的词干提取算法,之后两个文档按_score
进行排序,_score
字段表示文档的相似度(默认的相似度算法为BM25)。
GET /conference/event/_search
{
"query": {
"match": {
"description": "and Elasticsearch"
}
}
}
结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 3,
"relation" : "eq"
},
"max_score" : 1.1001158,
"hits" : [
{
"_index" : "conference",
"_type" : "event",
"_id" : "4",
"_score" : 1.1001158,
"_source" : {
"host" : "xiaoming",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "how they use Elasticsearch",
"attendees" : [
"Andy",
"Michael",
"Ben",
"David"
],
"date" : "2013-06-24T18:30",
"reviews" : 4
}
},
{
"_index" : "conference",
"_type" : "event",
"_id" : "3",
"_score" : 0.822573,
"_source" : {
"host" : "Andy",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "and discuss",
"attendees" : [
"Dave",
"Andrew",
"David",
"Clint"
],
"date" : "2013-06-24T18:30",
"reviews" : 3
}
},
{
"_index" : "conference",
"_type" : "event",
"_id" : "2",
"_score" : 0.6333549,
"_source" : {
"host" : "Dave Nolan",
"title" : "Elasticsearch at Rangespan and Exonar",
"description" : "and Exonar will come",
"attendees" : [
"Dave",
"Andrew",
"David",
"Clint"
],
"date" : "2013-06-24T18:30",
"reviews" : 2
}
}
]
}
}
当 and Elasticsearch 作为查询条件时会被拆分成 and、Elasticsearch 两个查询条件,但是查询结果的 _score 会体现跟原始查询条件的匹配度
短语搜索 - 跟全文检索相反,搜索关键字必须在指定的字段文本中完全匹配
GET /conference/event/_search
{
"query": {
"match_phrase": {
"description": "Elasticsearch"
}
}
}
高亮搜索 - 被匹配的关键字将会在查询结果中高亮标注(默认*)*
GET /conference/event/_search { "query": { "match": { "description": "and" } }, "highlight": { "fields": { "description":{} } } }
其中TransportClient
和RestClient
是Elasticsearch
原生的api。TransportClient
可以支持2.x,5.x版本,TransportClient
将会在Elasticsearch 7.0
弃用并在8.0中完成删除,替而代之,我们使用JavaHigh LevelRESTClient
,它使用HTTP请求而不是Java序列化请求。Jest
是Java
社区开发的,是Elasticsearch
的Java Http Rest客户端;Spring Data Elasticsearch
是spring集成的Elasticsearch开发包。
TransportClient将会在后面的版本中弃用,因此不推荐后续使用;而Jest由于是社区维护,所以更新有一定延迟,目前最新版对接ES6.3.1,近一个月只有四个issue,说明整体活跃度较低,因此也不推荐使用;Spring Data Elasticsearch主要是与Spring生态对接,可以在web系统中整合到Spring中使用。目前比较推荐使用官方的高阶、低阶RestClient,官方维护,比较值得信赖。主要说rest高级客户端连接,也是官网推荐的连接方式
引入模块需要的依赖
<!--SpringBoot默认使用SpringData ElasticSearch模块进行操作-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<!--jest连接方式 start-->
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>6.3.1</version>
</dependency>
<!--jest end-->
rest连接客户端
spring:
elasticsearch:
rest:
uris: http://172.16.0.192:9200
添加我们的rest高级客户端连接地址
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient restClient = new RestHighLevelClient(
RestClient.builder(
new HttpHost("172.16.0.192", 9200, "http")));
return restClient;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
public class User {
private String name;
private Integer age;
}
@Autowired
RestHighLevelClient restHighLevelClient;
//索引的创建
@Test
void testCreateIndex() throws IOException {
// 1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest(ESconst.ES_INDEX);
//2.客户端执行请求
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
//获取索引
@Test
void testExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest(ESconst.ES_INDEX);
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
//删除索引
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest(ESconst.ES_INDEX);
//删除
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
// 测试添加文档
@Test
void testAddDocument() throws IOException {
User user = new User("三月三", 19);
//创建请求
IndexRequest request = new IndexRequest(ESconst.ES_INDEX);
//规则 put /index/_doc/1
request.id("2");
request.timeout(TimeValue.timeValueSeconds(1));
request.timeout("1s");
//将我们的数据放入请求
request.source(JSON.toJSONString(user), XContentType.JSON);
//客户端发送请求,获取响应结果
IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
//对应我们命令返回的状态
System.out.println(indexResponse.status());
}
// 获取文档,判断是否存在 get /index/doc/1
@Test
void testIsExist() throws IOException {
GetRequest request = new GetRequest(ESconst.ES_INDEX, "1");
// 不获取返回的 _source 的上下文了
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none_");
boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
//获取文档信息
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest(ESconst.ES_INDEX, "1");
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}
//更新文档的信息
@Test
void testUpdateDocument() throws IOException {
UpdateRequest request = new UpdateRequest(ESconst.ES_INDEX, "1");
request.timeout("1s");
User user = new User("狂神说java", 18);
request.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = restHighLevelClient.update(request, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
//删除文档的信息
@Test
void testDeleteRequest() throws IOException {
DeleteRequest request = new DeleteRequest(ESconst.ES_INDEX, "1");
request.timeout("1s");
DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
System.out.println(response.status());
}
// 特殊的,真的项目一般都会批量插入数据!
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("zysheep1", 1));
userList.add(new User("zysheep2", 2));
userList.add(new User("zysheep3", 3));
userList.add(new User("zysheep4", 5));
userList.add(new User("zysheep5", 6));
userList.add(new User("zysheep7", 7));
for (int i = 0; i < userList.size(); i++) {
//批量跟新删除
bulkRequest.add(
new IndexRequest(ESconst.ES_INDEX)
.id("" + (i + 1)).source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures());
}
/**
* 查询
* SearchRequest 搜索请求
* SearchSourceBuilder 条件构造
* HighlightBuilder 构建高亮
* TermQueryBuilder 精确查询
* MatchAllQueryBuilder匹配所有
* QueryBuilder 对应我们刚才看到的命令!
* 查询条件,我们可以使用 QueryBuilders 工具来实现
* QueryBuilders.termQuery 精确
* QueryBuilders.matchAllQuery() 匹配所有
*/
@Test
void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest(ESconst.ES_INDEX);
//构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.highlighter();
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "zysheep1");
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
System.out.println("=================================");
for (SearchHit hit : searchResponse.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。