Elasticsearch安装、原理解析、整合SpringBoot

ElasticSearch

ElasticSearch概念

Elasticsearch是Elastic Stack核心的分布式搜索和分析引擎。

什么是Elastic Stack

Elastic Stack,就是ElasticSearch + LogStash + Kibana

Logstash用于收集,聚合和丰富数据并将其存储在Elasticsearch中。

Kibana提供了一套可视化界面,可以交互式的浏览数据,以及管理和监视堆栈。

ElasticSearch是一个分布式,高性能、高可用、可伸缩的搜索和分析系统。

Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。Elasticsearch主要用于云计算中,能够达到实时搜索,稳定,可靠,快速,并且安装使用方便。官方客户端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和许多其他语言中都是可用的。

Elasticsearch的使用场景

网上商场,搜索商品.

配合logStash和kibana进行日志分析

为什么要使用Elasticsearch

假设用关系型数据库做搜索,当用户在搜索框输入“一名程序员”时,数据库通常只能把这四个字去进行全部匹配。可是在文本中,可能会出现“他是一名合格的程序员”,这时候就没有结果了,但是ES就可以实现检索,而且速度极快。

Elasticsearch基本概念

Elasticsearch和关系型数据库概念对应关系(方便理解ES的架构)

近实时(NRT)

ES是一个近实时的搜索引擎(平台),代表着从添加数据到能被搜索到只有很少的延迟。(大约是1s)

文档

Elasticsearch是面向文档的。可以把文档理解为关系型数据库中的一条记录,文档会被序列化成json格式,保存在Elasticsearch中。同样json对象由字段组成,每一个字段都有自己的类型(字符串,数值,布尔,二进制,日期范围类型)。当我们创建文档时,如果不指定类型,Elasticsearch会帮我们自动匹配类型。每个文档都一个ID(_id),你可以自己指定,也可以让Elasticsearch自动生成。json格式,支持数组/嵌套,在一个索引里面,你可以存储任意多的文档。

具体字段类型和属性的对应关系请参考官网:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/mapping-types.html

索引

索引是具有某种相似特性的文档集合。例如,您可以拥有客户数据的索引、产品目录的索引以及订单数据的索引。索引的名称必须全部是小写字母。在单个集群中,您可以定义任意多个索引。

类型

一个索引可以有多个类型。例如一个索引下可以有文章类型,也可以有用户类型,也可以有评论类型。

 

从6.0开始,type已经被逐渐废弃。在7.0之前,一个index可以设置多个types。7.0开始一个索引只能创建一个type(默认是_doc,索引创建以后会自动创建)

节点

节点是一个Elasticsearch实例,本质上就是一个java进程。

节点的类型主要分为如下几种:

master eligible节点

每个节点启动后,默认就是master eligible节点,可以通过node.master: false 禁止,

master eligible可以参加选举成为master节点,当第一个节点启动后,它会将自己选为master节点。

data节点

可以保存数据的节点。负责保存分片数据。

Coordinating(协调)节点

负责接收客户端请求,当请求发送到某个节点的时候,这个节点就成为了协调节点,将请求发送到合适的节点,最终把结果汇集到一起,返回给客户端。每个节点默认都起到了协调节点的职责。

分片和分片副本

一个索引可能会存储大量数据,这些数据可能超过单个节点的硬件限制。例如,十亿个文档的单个索引占用了1TB的磁盘空间,可能不适合单个节点的磁盘,或者可能太慢而无法单独满足来自单个节点的搜索请求。

为了解决此问题,Elasticsearch提供了将索引细分为多个碎片的功能。创建索引时,只需定义所需的分片数量即可。每个分片本身就是一个功能齐全且独立的“索引”,可以托管在群集中的任何节点上。

分片很重要,它可能分布在多个节点上。

在随时可能发生故障的网络环境中,分片副本非常有用,为了防止某节点因某种原因脱机。导致数据查询不到,Elasticsearch允许将索引分片的一个或多个副本制作为所谓的副本分片(简称副本)。如果分片/节点发生故障,分片副本可提供高可用性(相当于一个主从)。

重要的是要注意:副本碎片永远不会与从其复制原始碎片的节点分配在同一节点上。

例如:node1和node2的集群,如果分片1在node1节点,那么分片1的副本就不可能分布在node1节点,因为如果这样设计的话就没意义了,如果node1挂了,那么分片1 和它的副本都不可用了。

创建索引后,可以随时动态更改副本数,但不能更改分片数,因为如果修改了分片数的话会导致分片上的数据错乱。

Elasticsearch写数据原理

 

写请求发送到集群节点,比如发送到了node1,这个节点内部会去根据ID计算hash,计算这个ID到底属于哪个主分片(ID通过hash以后对主分片的数量取余),比如这个时候计算出来属于主分片2,这个时候node1节点会把请求转发到node2节点的主分片2,进行写入数据,当主分片2写入数据成功以后,会把请求并行转发到对应的两个副本R2-1和R2-2,写入成功以后返回到主分片,然后返回到node1,最后由node1返回到客户端,报告写入成功,如果写入副本的时候失败了,会把写入失败的分片移除(防止读取数据的时候从这个分片中读不到数据或者读到脏数据)。

以上写入都是写入到内存中,写入之前会先转换为段(这就是近实时中说到的,有1S的延迟时间),同时伴随有一个日志文件的写入,当写入到内存以后过五分钟以后,通过定时任务,才把数据写到磁盘上,日志文件的存在是为了解决定时任务5分钟期间内存出现问题导致读取不到内存数据的问题,如果内存出现问题,直接读取日志文件,写到磁盘。

官网参考:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/docs-replication.html

Elasticsearch读数据原理

Elasticsearch查询分为两个阶段: Query(查)和 Fetch(取),假设我们进行分页查询从 from到 from + size 的文档信息,下面是查询流程:

from:代表前面忽略多少数据,size:代表查询数据的条数

Query 阶段

 

1、客户端发送一个search请求到node2上,node节点此时就是协调节点。

2、接着node2以协调节点的身份广播这个search请求到索引里面每一个主分片或者副分片上(不为主副),每个分片执行查询并进行排序,返回from+size个排序后的文档 ID给协调节点node2。

3、然后node2负责将所有分片查询返回的数据给合并到一个全局的排序的列表。

Fetch 阶段

1、协调节点node2从全局排序列表中,选取 from 到 from + size 个文档的 ID,并发送一个批量的查询请求到相关的分片上(即发送批量文档ID,根据ID查询文档)。

2、协调节点node2将查询到的结果集返回到客户端上。

官网参考:https://www.elastic.co/guide/en/elasticsearch/reference/6.0/docs-replication.html

倒排索引

倒排索引就是为什么ES查询如此快的原因

官网地址:https://www.elastic.co/guide/en/elasticsearch/guide/current/inverted-index.html

原理自行参考:通过分词到内存中找ID,再通过ID到磁盘上查找数据

https://blog.csdn.net/jiaojiao521765146514/article/details/83750548

https://www.cnblogs.com/dreamroute/p/8484457.html

Elasticsearch安装(linux)

下载安装包

下载elasticsearch安装包,版本7.9.1(目前最新版本)

下载地址:https://www.elastic.co/cn/downloads/elasticsearch

 

 

安装elasticsearch

特别注意:

elasticsearch和kibana的安装路径不能有空格,要不然kibana启动会报如下错误

 

把安装包上传到服务器的安装目录并解压

 

注意:

elasticsearch是不允许使用root用户启动的

在6.xx之前,可以通过root用户启动。但是后来发现黑客可以通过elasticsearch获取root用户密码,所以为了安全性,在6版本之后就不能通过root启动elasticsearch

新建操作用户

groupadd elasticsearch
useradd elasticsearch -g elasticsearch
cd /usr/local/src/elasticsearch
chown -R elasticsearch: elasticsearch elasticsearch-7.9.1

修改JVM参数 

如果机器内存足够的话也可以默认,默认1G

vi config/jvm.options

修改如下内容: 

-Xms512m
-Xmx512m

修改ES相关配置 

vi config/elasticsearch.yml

修改如下内容: 

#集群名称, 默认名称为elasticsearch
cluster.name=elasticsearch
#节点名称
node.name=node-1
#允许访问的IP
network.host: 0.0.0.0
#管理管端口
http.port: 9200
#允许成为主节点的节点名称
cluster.initial_master_nodes: ["node-1"]
#数据保存路径
path.data: /usr/local/src/elasticsearch/elasticsearch-7.9.1/data
#日志路径
path.logs: /usr/local/src/elasticsearch/elasticsearch-7.9.1/logs 

修改sysctl.conf

vi /etc/sysctl.conf

写入如下内容: 

vm.max_map_count=655360

使配置生效

sysctl -p

vm最大虚拟内存,max_map_count[65530]太低,至少增加到[262144]注:如果不修改,启动会报如下错误:

max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

修改limits.conf 

vi /etc/security/limits.conf

写入如下内容

* soft nofile 65536
* hard nofile 131072
* soft nproc 2048
* hard nproc 4096

说明:

* 所有用户

nofile - 打开文件的最大数目

noproc - 进程的最大数目

soft 指的是当前系统生效的设置值

hard 表明系统中所能设定的最大值

注:如果不修改,启动会报如下错误:

最大文件描述符[4096]对于elasticsearch进程可能太低,至少增加到[65536]

descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]

修改90-nproc.conf 

vi /etc/security/limits.d/90-nproc.conf

写入如下内容:

* soft nproc 4096

注:如果不修改,启动会报如下错误:

用户的最大线程数[2048]过低,增加到至少[4096]

max number of threads [2048] for user [tongtech] is too low, increase to at least [4096]

启动 

cd /usr/local/src/elasticsearch/elasticsearch-7.9.1/bin
#以后台方式运行
./elasticsearch -d

防火墙设置

需要把ES相关端口添加白名单

#开启9200端口
firewall-cmd --zone=public --add-port=9200/tcp --permanent
#开启9300端口
firewall-cmd --zone=public --add-port=9300/tcp --permanent
#重启防火墙
systemctl restart firewalld

验证 

浏览器访问:http://ip:9200/

Elasticsearch重启

ps -ef | grep elastic
kill -9 PID

Kibana安装(linux)

下载安装包

下载kibana安装包,版本7.9.1(需要和ES版本一致

下载地址:https://www.elastic.co/cn/downloads/kibana

 

安装Kibana

把安装包上传到服务器的安装目录并解压

 

修改配置文件

vi /usr/local/src/elasticsearch/kibana-7.9.1-linux-x86_64/config/kibana.yml

 修改如下配置

#管理端端口
server.port: 5601
#允许访问的IP
server.host: "0.0.0.0"
#管理端语言,默认未英文,修改为中文
i18n.locale: "zh-CN"
#ES地址
elasticsearch.hosts: ["http://localhost:9200"]

启动kibana

cd /usr/local/src/elasticsearch/kibana-7.9.1-linux-x86_64/bin
nohup ./kibana --allow-root &

防火墙设置 

把kibana端口设置为白名单

#开启5601端口
firewall-cmd --zone=public --add-port=5601/tcp --permanent
#重启防火墙
systemctl restart firewalld

访问kibana 

浏览器访问:http://ip:5601/

出现如下界面表示启动成功

 

Kibana重启

netstat -apn|grep 5601
kill -9 PID

 

Elasticsearch用户设置 

完成Elasticsearch和kibana的安装以后发现管理端可以直接访问数据,这样是不安全的,下面就是Elasticsearch用户相关介绍

用户新增

Elasticsearch内部已经内置了一些用户,只需要设置这些用户的密码,并修改配置文件即可。

修改配置文件

修改elasticsearch.yml文件加入如下配置启用节点上的Elasticsearch安全功能

xpack.security.enabled: true
xpack.license.self_generated.type: basic
xpack.security.transport.ssl.enabled: true

重启ES,访问ES,这个时候需要用户名密码访问,如下截图: 

 

设置用户密码

ES bin目录执行命令,依次设置用户密码

./elasticsearch-setup-passwords interactive

用户登录

 

Kibana配置

这个时候访问kibana发现也需要登录

需要修改kibana的配置文件:kibana.yml

加入以下配置:

#ES的登录用户名
elasticsearch.username: "elastic"
# ES的登录用户名密码,需要对应
elasticsearch.password: "123456"

重启kibana,访问kibana 

 

分词器安装

Elasticsearch内部已经内置了一些分词器,无需进一步配置即可在任何索引中使用

官网地址:

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html

IK分词器

下载ik分词器安装包,版本7.9.1(需要和ES的版本对应)

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

 

 

                                                                       

安装

安装包解压,在ES的plugins目录下创建一个ik文件夹,把解压后的内容放到该文件夹即可。

验证

重启ES,执行如下命令:

POST /_analyze
{
  "analyzer": "ik_max_word",  

  "text": "一名程序员"
}

 


  

 

Elasticsearch整合Springboot

版本对应关系

由于Elasticsearch版本为7.9.1,所以Springboot版本必须至少为2.3.x,此处采用目前官网最新版本:2.3.4.RELEASE

导入依赖

<parent>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-parent</artifactId>

       <version>2.3.4.RELEASE</version>

       <relativePath/>

</parent>

 

<!--elasticsearch包-->

<dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-data-elasticsearch</artifactId>

</dependency>

 

<!--单元测试-->

<dependency>

       <groupId>org.springframework.boot</groupId>

       <artifactId>spring-boot-starter-test</artifactId>

       <scope>test</scope>

       <exclusions>

              <exclusion>

                     <groupId>org.junit.vintage</groupId>

                     <artifactId>junit-vintage-engine</artifactId>

              </exclusion>

       </exclusions>

</dependency>

配置类 

配置类里面配置了连接ES的连接信息,相当于配置了一个数据源

import org.elasticsearch.client.RestHighLevelClient;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.elasticsearch.client.ClientConfiguration;

import org.springframework.data.elasticsearch.client.RestClients;

import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;

 

@Configuration

public class RestClientConfig extends AbstractElasticsearchConfiguration {

 

    @Override

    @Bean

    public RestHighLevelClient elasticsearchClient() {

        final ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo("10.0.2.102:9200")

                .withBasicAuth("elastic", "123456").build();

        return RestClients.create(clientConfiguration).rest();

    }

}

创建实体类 

实体类上加上Document注解,指定索引名称和主分片副分片数量,type可以不用指定了,默认一个索引只有一个类型,属性加上对应的注解,具体属性和字段对应关系文档章节已经说明,请自行参考。

analyzer = "ik_max_word"是指定IK分词器,说明name字段分词的时候需要根据IK分词器去分词,不采用ES内置的分词器,也可以用ES内置的分词器。

 

测试

注入elasticsearchRestTemplate

@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;

索引创建 

/**
 * 创建索引
 */
@Test
public void createIndex() throws Exception {
    IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Item.class);
    boolean exists = indexOperations.exists();
    System.out.println("索引是否已经存在:"+exists);
    if(!exists){
    indexOperations.create();
}
}

数据新增

/**

 * 测试新增数据

 */

@Test

void save() {

       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

       String str = simpleDateFormat.format(new Date());

       Goods goods = new Goods(1093l, "", "测试数据" + str, 37400, 20, 100,

                     "https://m.360buyimg.com/mobilecms/s720x720_jfs/t1/10441/9/5525/162976/5c177debEaf815b43/3aa7d4dc182cc4d9.jpg!q70.jpg.webp",

                     "https://m.360buyimg.com/mobilecms/s720x720_jfs/t1/10441/9/5525/162976/5c177debEaf815b43/3aa7d4dc182cc4d9.jpg!q70.jpg.webp",

                     10, str, str, "10000243333000", 558, "测试", "", "", 1, 1, 1, 5L);

       /*方法1*/

       Goods save = elasticsearchRestTemplate.save(goods);

       System.out.println(save.toString());

 

       /*方法2*/

       //        IndexQuery indexQuery = new IndexQueryBuilder()

       //                .withId(goods.getId()+"")

       //                .withObject(goods)

       //                .build();

       //        String documentId = elasticsearchRestTemplate.index(indexQuery,IndexCoordinates.of("goods"));

       //        System.out.println(documentId);

}

数据查询详情 

/**

 * 测试通过ID查询

 */

@Test

void searchById() {

       Goods goods = elasticsearchRestTemplate.get("1092", Goods.class, IndexCoordinates.of("goods"));

       System.out.println(goods.toString());

}

查询的时候如果是根据ID查询,那么这个ID指的是文档id(_id),而且查询返回的实体里面的id也是文档id(分页查询返回结果中的实体id也是文档id),所以最好让文档id和业务数据的id保持一致,如果不一致,请注意实体id和ES文档id类型兼容,因为ES默认生成的文档id是字符串,如果业务id为long类型的话查询会报错。注意:

数据修改

/**

 * 测试修改数据

 */

@Test

void update() {

       Map<String, Object> map = new HashMap<>();

       map.put("name", "测试数据修改");

       UpdateQuery updateQuery = UpdateQuery.builder("1093").withDocument(Document.from(map)).build();

       UpdateResponse updateResponse = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("goods"));

       System.out.println(updateResponse.getResult());

}

分页查询 

/**

 * 分页查询

 */

@Test

void getByMatch() {

       MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "测试");

       NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

       //id排序

       FieldSortBuilder sortBuilder = SortBuilders.fieldSort("id").order(SortOrder.ASC);

       //分页

       Pageable pageable = PageRequest.of(0, 10);

       nativeSearchQueryBuilder.withFilter(matchQueryBuilder).withSort(sortBuilder).withPageable(pageable);

       NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();

       SearchHits<Goods> goods = elasticsearchRestTemplate

                     .search(nativeSearchQuery, Goods.class, IndexCoordinates.of("goods"));

       goods.getSearchHits().forEach(info -> {

              Goods good = info.getContent();

              System.out.println(good.toString());

       });

}
posted @ 2020-09-22 13:40  *闯  阅读(386)  评论(0编辑  收藏  举报