ElasticSearch
Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。除了全文搜索功能,还包括以下作用:
- 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
- 实时分析的分布式搜索引擎。
- 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
一. Elasticsearch的应用
- 记录和日志分析
Logstash采集日志,ES进行复杂的数据分析(ELK技术,Elasticsearch+Logstash+Kibana) - 全文检索
比如Github用它检索上亿行代码 ,维基百科的全文搜索和高亮的实现。 - 文档数据库
主要抢占 Mongo 的市场,它在读写性能上优于 Mongo ,同时也支持地理位置查询,还方便地理位置和文本混合查询 - 监控
用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅。
二. Elasticsearch的安装和使用
- 直接下载安装包解压即用,此处省略
- 基于docker-compose安装,请参考——安装教程
三. Elasticsearch关键概念
Elasticsearch的文件存储,Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式。
关系数据库和es的对比
关系数据库 | Elasticsearch |
---|---|
数据库 | 索引(index) |
表 | 类型(types) |
行 | 文档(documents) |
属性 | 字段(field) |
一个 Elasticsearch 集群可以包含多个索引(数据库),其中包含了很多类型(表)。es7开始一个索引只能包含一个type(默认类型为_doc),es8将取消type。类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互,可以使用Java API,也可以直接使用HTTP的Restful API方式。Elasticsearch 将一个索引划分为多个切片,这些切片可以在集群的不同服务器间迁移。Elasticsearch最关键的就是提供强大的检索能力。Elasticsearch索引的精髓:“一切设计都是为了提高搜索的性能”。为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新。往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还默默的为这些字段建立索引–倒排索引,因为Elasticsearch最核心功能是搜索。
四. 倒排索引(反向索引)
倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。比如在查找某个关键词的时候,会先在单词词典中查找到该关键字所在的倒排文件,然后在倒排文件中查找该单词出现在哪些文档中。
- 单词词典
搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
- 哈希加链表
比如在解析一个新文档的时候,对于某个在文档中出现的单词T,首先利用哈希函数获得其哈希值,之后根据哈希值,就找到了对应的冲突链表。如果冲突链表里已经存在这个单词,说明单词在之前解析的文档里已经出现过。如果在冲突链表里没有发现这个单词,说明该单词是首次碰到,则将其加入冲突链表里。通过这种方式,当文档集合内所有文档解析完毕时,相应的词典结构也就建立起来了。 - 树形结构
用B树或B+树可以实现,此处略。
- 倒排列表
倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。
比如ES里已经存了5个文档
以下是根据五个文档建立的倒排文件
比如拿谷歌关键词来说,其在文档1出现了1次,文档2出现了1次,文档3出现了2次,文档4出现了1次,文档5出现了1次。如果将出现的次数作为权值的话,那么首先会搜索出来文档3。
建立倒排文件的过程也很简单,比如新添加一个文档6的内容是“谷歌创始人是拉斯”,会将其分词分为谷歌、创始人、拉斯。那么会分别在倒排文件的谷歌行、创始人行、拉斯行各添加一项纪录{6:1}
五. ik分词器
ik分词器的安装
- 下载压缩包,必须和es的版本一致
下载地址 - 将压缩包解压到plugins目录(数据卷映射时已创建)的ik目录(需要新创建)下
- 重新启动es即可
自定义词典
打开ik目录下的config目录,创建自定义字典,自定义词典的文件为文件名.dic
,然后将自己要定义的词写入自定义的词典中即可。然后打开IKAnalyzer.cfg.xml文件,配置刚刚编写的个人字典的位置。
词典的使用
ik分词器提供了两个分词算法:ik_smart(最少切分)和ik_max_word(最细粒度划分)
- ik_max_word:分词结果为人民代表大会、人民代表、人民、代表大会、代表、大会
GET _analyze
{
"analyzer": "ik_max_word",
"text": "人民代表大会"
}
- ik_smart:分词结果只有人民代表大会
GET _analyze
{
"analyzer": "ik_smart",
"text": "人民代表大会"
}
六. Elasticsearch基本命令
- 创建索引。
索引名为test1,并且约定了该索引的结构,包括了三个字段,并约定了字段的类型
PUT /test1
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
},
"birth": {
"type": "date"
}
}
}
}
- 查看索引信息:
GET /test1
- 查看所有的索引:
GET _cat/indices?v
- 插入数据
此处的类型写了默认类型_doc
PUT /test1/_doc/2
{
"name":"cb",
"age":21,
"birth":"1996-03-03"
}
- 查看数据:
GET /test1/_doc/2
- 修改数据
POST /test1/_doc/2/_update
{
"doc":{
"name":"ls"
}
}
- 删除数据:
DELETE /test1/_doc/2
- 删除索引:
DELETE /test1
- 条件搜索:
GET /test1/_doc/_search?q=name:"zhang"
七. 花样查询
- match单条件查询
GET /test1/_doc/_search
{
"query":{
"match":{
"name":"mazi"
}
},
"_source":["name","age"],
"sort":[
{
"age":{
"order":"desc"
},
"birth":{
"order":"asc"
}
}
],
"from":0,
"size":1
}
参数介绍
参数 | 说明 |
---|---|
query | 查询的方式,这里的match只能单字段查询 |
_source | 规定要查询的字段 |
sort | 定义查询结果排序的方式 |
from | 从结果的第几条开始显示 |
size | 显示的条数 |
查询结果中有一项是hits
,通过该项,可以看出查找出来的信息。比如查询出的总条数、查询出的最大分值score,查询出的具体信息。
- bool多条件查询
GET /test1/_doc/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"name":"mazi"
}
},
{
"match":{
"age":23
}
}
],
"filter":{
"range":{
"age":{
"lt":40,
"gt":10
}
}
}
}
}
}
此处的must表示且,两个条件都符合才会被查出来,除了must之外,还有should表示或者,must_not表示非。filter是查询过滤器,此处用到了范围查询,只有年龄大于10岁并且小于40岁的记录才会被查出来
-
term精确查询
term既可以实现但条件也可以实现多条件。term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词。而match查询会先对搜索词进行分词,分词完毕后再逐个对分词结果进行匹配,因此相比于term的精确搜索,match是分词匹配搜索 -
高亮查询
GET /test1/_search
{
"query":{
"match": {
"name": "mazi"
}
},
"highlight": {
"pre_tags": "<p style='color:red'>",
"post_tags": "</p>",
"fields": {
"name": {}
}
}
}
查询出来符合查询条件的内容会被<p style='color:red'></p>
包裹
八. SpringBoot操作ElasticSearch
(1)搭建环境
- 创建SpringBoot项目
- 引入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 配置RestHighLevelClient,用来操作es
@Configuration
public class EsConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client=new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.189.135",9200,"http")));
return client;
}
}
- 测试类注入RestHighLevelClient
@Autowired
private RestHighLevelClient restHighLevelClient;
(2)对索引的操作
关键就是做什么就创建什么样的请求。
- 创建索引
@Test //创建索引
void test1() throws IOException {
CreateIndexRequest request=new CreateIndexRequest("test6");
CreateIndexResponse response=restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response);
}
- 判断索引是否存在
@Test //判断索引是否存在
void test2() throws IOException {
GetIndexRequest request=new GetIndexRequest("test6");
boolean flag=restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(flag);
}
- 删除索引
@Test //删除索引
void test3() throws IOException {
DeleteIndexRequest request=new DeleteIndexRequest("test6");
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
(3)对文档的操作
- 添加文档
@Test //添加文档
void test4() throws IOException {
//创建要被存储的对象
User user=new User("tom",20,"111@qq.com");
//创建请求
IndexRequest request=new IndexRequest("test6");
//设置文档编号
request.id("2");
//将对象用FastJson转化为json字符串
request.source(JSON.toJSONString(user),XContentType.JSON);
//执行请求
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(response.status());
}
- 查看文档是否存在
void test5() throws IOException {
GetRequest request=new GetRequest("test6","2");
boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
- 查看某条记录
void test6() throws IOException {
GetRequest request=new GetRequest("test6","2");
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
System.out.println(response.getSourceAsString());
}
- 更新文档
@Test //更新文档
void test7() throws IOException {
UpdateRequest request=new UpdateRequest("test6","2");
User user=new User("tony",21,"222@qq.com");
request.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
System.out.println(response.status());
}
- 删除文档
(4)查询操作
- 条件查询
@Test //条件查询
void test9() throws IOException {
SearchRequest request=new SearchRequest("test6");
//构建查询条件
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
//定义查询类型
QueryBuilder queryBuilder= QueryBuilders.termQuery("name","user1");
sourceBuilder.query(queryBuilder)
.timeout(new TimeValue(60,TimeUnit.SECONDS));
request.source(sourceBuilder);
//执行查询请求
SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = search.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
}
(5)批处理操作
- 批量添加文档
@Test
void test10() throws IOException {
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("10s");
for (int i=1;i<=10;i++){
bulkRequest.add(new IndexRequest("test6")
.id(String.valueOf(i))
.source(JSON.toJSONString(new User("user"+i,i+10,"email"+i)),XContentType.JSON));
}
BulkResponse responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(responses.status());
}