ElasticSearch
1.Lucene 和 ElasticSearch
ElasticSearch是基于Lucene做了一些封装和增强
2.概述
简称es,是一个开源的高扩展的分布式全文检索引擎,可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。也使用Java开发并使用Lucene作为核心来实现所有索引和搜索的功能,目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
3.es和solr的差别
- 都是基于Lucene使用Java开发的Apache开源搜索引擎
- 对已有数据查询solr更快
- 实时建立索引的时候,solr会产生io阻塞,查询性能较差,es占有明显优势
- 数据量大量增加es几乎不受影响,solr不行
- es开箱即用,solr安装更复杂
- solr利用
Zookeeper
进行分布式管理,es自身带有分布式协调管理功能 - solr支持更多格式的数据,es仅支持json格式
- solr官方提供的功能更多,es更注重核心功能,高级功能由第三方插件提供
- solr查询快,更新索引时慢(插入删除)
- solr比较成熟,es开发维护着较少,更新快
4.安装
声明:
jdk1.8+
安装es客户端,界面工具
ELK三剑客,解压即用。(web项目需要前端环境如node.js py等)
熟悉目录
bin # 启动文件
config # 配置文件
log4j2 # 日志配置文件
jvm.options # Java虚拟机配置
elasticsearch.yml # es配置文件 默认9200port
jdk # Java环境
liv #相关jar包
modules # 功能模块
plugins # 插件
logs # 日志和
启动
- Windows下双击
elasticsearch.bat
- 访问localhost:9200
访问测试
You Know, for Search
可视化界面 - 当作数据展示工具
elasticsearch-head下载,然后按照前端项目,进行依赖下载npm i
+启动npm run start
- 启动后存在跨域问题
修改配置**/config/elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: "*"
启动访问localhost:9100
了解ELK
ELK 是ElasticSearch、Logstash、Kibana三大开源框架首字母大写简称。也叫做Elastic Stack,es上述已经介绍,是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。Logstatsh是ELK的中央数据流引擎,用于从不同目标收集的不同格式数据,经过过滤后支持输出到不同目的地。Kibana可以将es的数据通过友好的页面展示出来。
安装Kibana
- 下载解压
- 启动
/bin/Kibana.bat
- 访问默认端口
localhost:5601
- 左侧螺丝刀图标开发者工具
- 汉化->
config/kibana.yml
添加i18n.locale: "zh-CN"
5.es核心概念
与关系型数据库的对比
Relational DB | Elasticsearch |
---|---|
库(database) | 索引(indices) |
表(tables) | types |
行(rows) | documents |
字段(columns) | fields |
物理设计:es在后台把每个索引划分成多个切片,每个分片可以在集群中的不同服务器间迁移
文档
es是面向文档的,索引和搜索数据的最小单位是文档,文档有几个重要属性
- 自我包含,一篇文档同时包含字段和值
- 层次型的,文档可以包含文档
- 灵活的结构,文档不依赖预先定义的模式
类型
类型是文档的逻辑容器,类型中对于字段的定义称为映射。es会自动地将新字段加入映射,但是这个字段不确定类型,es会自动推断。
索引
索引是映射类型的容器,es的索引是一个非常大的文档集合,索引存储了映射类型的字段和其他设置。然后他们被存储到了各个分片上了。
物理结构
一个集群至少有一个节点,一个节点就是一个es进程,节点可以有多个索引。
如果创建索引,索引默认会由5个分片构成,称为primary shard主分片,每个主分片有一个副本replica shard复制分片
实际上,一个分片是一个Lucene索引,一个包含倒排索引的文件目录
倒排索引
es使用的是一种称为倒排索引的结构,采用Lucene倒排索引作为底层,这种结构适用于全文搜索文档中所有不重复的列表构成。(关键词到文档的映射关系)
6.IK分词器
概念
分词:把一段中文或者别的划分成一个个的关键字,搜索的时候会通过分词进行匹配,默认分词会将中文拆分成一个一个的字,这不符合要求,需要安装中文分词器ik解决。
ik提供了两个分词算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度切分
安装
- GitHub上查找下载
- 解压
- 放到es的
plugins
目录下 - 重启es,出现
loaded plugin [analysis-ik]
日志打印 - 或者通过
elasticsearch-plugin
命令查看plugin list
ik分词器增加自定义的分词
ik/config
下添加name.dic
字典文件
雨丘
秋雨
注入配置 IKAnalyzer.cfg.xml
中添加<entry key="ext_dict">name.dic</entry>
重启生效
7.Rest风格说明
基本Rest命令说明
method | url地址 | 描述 |
---|---|---|
PUT | ip:9200/索引名称/类型名称/文档id | 创建文档 指定文档id |
POST | ip:9200/索引名称/类型名称 | 创建文档 随机文档id |
POST | ip:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | ip:9200/索引名称/类型名称/文档id | 删除文档 |
GET | ip:9200/索引名称/类型名称/文档id | 通过id查询文档 |
POST | ip:9200/索引名称/类型名称/_search | 查询所有数据 |
数据类型
- 字符串:text、 keyword
- 数值:long、 integer、 short、 byte、 double、 float、 scaled float
- 日期:date
- 布尔:boolean
- 二进制:binary
- ...
索引操作基本测试
-
创建一个索引 (这里同时插入了数据)
PUT /test1/type1/1 { "name": "yuqiu", "age": 18 }
-
指定类型 (这里只建立了索引)
PUT /test2 { "mappings": { "properties": { "name": { "type": "text" }, "age": { "type": "long" }, "birthday": { "type": "date" } } } }
-
获取索引信息
GET test2
-
GET _cat/
获得es的当前状态信息 -
修改索引
PUT /test1/type1/1 { "name": "yuqiu2004", "age": 18 }
以上方式版本号会(修改次数)增加
POST /test3/_doc/1/_update { "doc": { "name": "example text"f } }
-
删除 通过路径判断删除索引还是文档
DELETE /test3/_doc/1
文档操作基本测试
-
创建数据
PUT /yuqiu/user/1 { "name": "yuqiu", "age": 23, "desc": "aaaaaaaaaaaaaaaaaaaaaa", "tags": ["qw", "dd", "qq"] }
-
获取数据
GET /yuqiu/user/1
-
更新数据 同上索引操作中PUT
-
POST方式修改只会修改指定的字段,而PUT方式会将未指定的置空
复杂操作搜索
- 查询结果中有一个_score字段 表示匹配度或权重 匹配度越高权重越高
构造查询参数:
GET yuqiu/user/_search
{
"query": {
"match": {
"name": "test" // 匹配名字 多个条件使用空格隔开就行
}
},
"_source": ["name", "desc"], // 结果过滤
"sort": [ // 排序指定字段
"age": {
"order": "asc"
}
],
"from": 0, // 分页参数 从零开始
"size": 20,
}
布尔查询
must
指定多个需要同时满足的条件(相当于and)should
指定满足任一即可的条件(相当于or)must_not
指定不能满足的条件filter
进行过滤
GET yuqiu/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "yuqiu"
}
},
{
"match": {
"age": 12
}
}
]
}
}
}
GET yuqiu/user/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "yuqiu"
}
},
{
"match": {
"age": 12
}
}
]
}
}
}
GET yuqiu/user/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"age": 3
}
}
]
}
}
}
GET yuqiu/user/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "yuqiu"
}
}
],
"filter": {
"range": {
"age": {
"gt": 10 // 查询大于10的
}
}
}
}
}
}
精确查询
term
查询是直接通过倒排索引指定的词条进程精确查询的,term
只能查询单个词,terms
查询多个词
- term 直接精确查询 不会对搜索词进行分词拆解
- match 使用分词器解析 如果要根据短语搜索使用
match_phrase
keyword类型:不会进行分词拆分
高亮查询
GET yuqiu/user/_search
{
"query": {
"match": {
"name": "yuqiu"
}
},
"hignlight": {
"pre_tags": "<p ...>", // 指定包围的标签
"post_tags": "</p>",
"fields": {
"name": {}
}
}
}
8.集成springboot(Java Rest API)
此版本在es8.x后被弃用 官方推荐Elasticsearch Java API Client
-
导入maven依赖
<dependency> <groupId>org.sprintframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
-
(可选)自定义修改es依赖版本
<properties> <java.version>1.8</java.version> <elasticsearch.version>7.6.1</elasticsearch.version><!--保证和服务端版本一致--> </properties>
-
配置bean对象
@Configuration public class ESConfig{ @Bean public RestHighLevelClient restHighLevelClient(){ RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpPost("127.0.0.1", 9200, "http") ) ); return client; } }
-
创建索引api测试
@Test void createHotelIndex() throws IOException { CreateIndexRequest request = new CreateIndexRequest("hotel"); client.indices().create(request, RequestOptions.DEFAULT); }
-
查询索引api
@Test void getHotelIndex() throws IOException { GetIndexRequest request = new GetIndexRequest("hotel"); boolean exists = client.indices().exists(request, RequestOptions.DEFAULT); System.out.println(exists); }
-
删除索引api
@Test void deleteHotelIndex() throws IOException { DeleteIndexRequest request = new DeleteIndexRequest("hotel"); AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT); System.out.println(delete.isAcknowledged()) }
-
创建文档
@Test void testAddDocument() throws IOException { User user = new User("testName", 3); IndexRequest request = new IndexRequest("hotel"); request.id("1"); request.timeout(TimeValue.timeValuleSeconds(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()); }
-
文档增删改查
// 测试文档是否存在 @Test void testIsExists() throws IOException { GetRequest getRequest = new GetRequest("yuqiu", "1"); getRequest.fetchSourceContext(new FetchSourceContext(false)); getRequest.storedFields("_none_");// 不返回source上下文 boolean e = client.exists(getRequest, RequestOptions.DEFAULT); System.out.println(e); } // 获取文档的信息 @Test void testGetDocument() throws IOException { GetRequest getRequest = new GetRequest("yuqiu", "1"); GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); System.out.println(getResponse.getSourceAsString()); System.out.println(getResponse); } // 更新文档的信息 @Test void testUpdateDocument() throws IOException { UpdateRequest updateRequest = new UpdateRequest("yuqiu", "1"); updateRequest.timeout("1s"); User user = new User("testName2", 18); updateRequest.doc(JSON.toJSONString(user), XContentType.JSON); UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT); System.out.println(updateResponse.status()); } // 删除文档 @Test void testDeleteDocument() throws IOException { DeleteRequest deleteRequest = new DeleteRequest("yuqiu", "3"); request.timeout("1s"); DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT); System.out.println(deleteResponse.status()); } // 批量插入数据 @Test void testBulkRequest() throws IOException { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.timeout("10s"); ArrayList<User> list = new ArrayList<User>(); list.add(new User("demoName", 17)); list.add(new User("demoName", 17)); list.add(new User("demoName", 17)); list.add(new User("demoName", 17)); list.add(new User("demoName", 17)); // ... for (int i = 0; i < list.size(); i++) { bulkRequest.add( new IndexRequest("yuqiu") .id(""+i+1) .source(JSON.toJSONString(list.get(i)), XContentType.JSON)); } BulkResponse res = client.bulk(bulkRequest, RequestOptions.DEFAULT); System.out.println(bulkResponse.hasFailures());// 查看是否失败 } // 查询 @Test void testSearch() { SearchRequest searchRequest = new SearchRequest("yuqiu"); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); var param = QueryBuilders.matchAllQuery()...// 构造查询条件 sourceBuilder.query(param); sourceBuilder.timeout("60s"); searchRequest.source(sourceBuilder); SearchResponse res = client.search(searchRequest, RequestOptions.DEFAULT); System.out.println(res.getHits()); }