本文将介绍Linux7环境下如何安装ElasticSearch、ElasticSearch常见启动异常解决方法、SpringBoot2.x整合ElasticSearch。
elasticsearch
下文简称 es
是一个解决大数据搜索(TB/PB级别)的框架。index
、type
、document
的理解:mysql | database | table | record |
---|---|---|---|
elasticsearch | index | type | document |
全文检索,结构化检索,数据统计、分析,接近实时处理,分布式搜索(可部署数百台服务器),处理PB级别的数据
,搜索纠错,自动完成
日志搜索,数据聚合,数据监控,报表统计分析
维基百科,Stack Overflow,GitHub
elasticsearch
6.2.x版本基于Lucene
7.x,更快,性能进一步提升,对应的序列化组件,升级到Jackson 2.8
官方文档
es5.6
):官方地址shell
命令:wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.8.tar.gz
/export/server
(我的linux用来存放服务器应用的目录)下由于 es
是由java开发的,es5.6
依赖 jdk1.8
,下载 jdk1.8
之后再 /etc/profile
中配置java环境变量
# java env
JAVA_HOME=/export/software/jdk1.8.0_161
CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar
PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
export JAVA_HOME CLASSPATH PATH
/export/software
是我存放软件的目录,我将下载的jdk1.8
解压到了该目录(jdk1.8.0_161
)
配置 es
变量,方便启动 es
,/etc/profile
# es env
ES_HOME=/export/server/elasticsearch-5.6.8
PATH=$PATH:$ES_HOME/bin
修改 /export/server/elasticsearch-5.6.8/config/elasticsearch.yml
中的 Network
部分:
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: 0.0.0.0 #绑定可连接当前es服务的IP白名单,0.0.0.0表示任何主机都可访问
#
# Set a custom port for HTTP:
#
http.port: 9200 #es服务http端口
#
# For more information, consult the network module documentation.
如果的你主机被公网访问,那么为了安全考虑不建议你设置
network.host
为0.0.0.0
经过上面一些列的配置后就可以启动 es
了,但是不能以 root
用户启动,否则会报错(es
防止黑客入侵可获取 root
权限)如下:
[2018-07-22T13:30:17,864][WARN ][o.e.b.ElasticsearchUncaughtExceptionHandler] [] uncaught exception in thread [main] org.elasticsearch.bootstrap.StartupException:java.lang.RuntimeException: can not run elasticsearch as root
...
你可以通过 useradd -m 用户名
的命令创建一个用户(如 zaw
):
useradd -m zaw
并给 zaw
用户设置密码:
passwd zaw #回车后输入密码
给 zaw
用户赋予 es
目录所有权限
chmod 777 -R /export/server/elasticsearch-5.6.8
切换到 zaw
用户:
[root@pinyoyougou-docker config]# su zaw
[zaw@pinyoyougou-docker config]$
启动 es
(elasticsearch
是 es/bin
下的一个命令):
[zaw@pinyoyougou-docker root]$ elasticsearch
[2018-07-22T13:44:37,924][INFO ][o.e.n.Node ] [] initializing ...
[2018-07-22T13:44:39,037][INFO ][o.e.e.NodeEnvironment ] [ISgfHbl] using [1] data paths, mounts [[/ (r ootfs)]], net usable_space [12.5gb], net total_space [16.9gb], spins? [unknown], types [rootfs]
[2018-07-22T13:44:39,038][INFO ][o.e.e.NodeEnvironment ] [ISgfHbl] heap size [1.9gb], compressed ordin ary object pointers [true]
[2018-07-22T13:44:39,048][INFO ][o.e.n.Node ] node name [ISgfHbl] derived from node ID [ISg fHblQQqyenLY8JrqXzw]; set [node.name] to override
[2018-07-22T13:44:39,049][INFO ][o.e.n.Node ] version[5.6.8], pid[3839], build[688ecce/2018 -02-16T16:46:30.010Z], OS[Linux/3.10.0-514.el7.x86_64/amd64], JVM[Oracle Corporation/Java HotSpot(TM) 64- Bit Server VM/1.8.0_161/25.161-b12]
[2018-07-22T13:44:39,049][INFO ][o.e.n.Node ] JVM arguments [-Xms2g, -Xmx2g, -XX:+UseConcMa rkSweepGC, -XX:CMSInitiatingOccupancyFraction=75, -XX:+UseCMSInitiatingOccupancyOnly, -XX:+AlwaysPreTouch , -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -Djdk.io.permissionsUseCanon icalPath=true, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCap acityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Dlog4j.skipJansi=true, - XX:+HeapDumpOnOutOfMemoryError, -Des.path.home=/export/server/elasticsearch-5.6.8]
[2018-07-22T13:44:45,319][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [aggs-matrix-stats]
[2018-07-22T13:44:45,335][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [ingest-common]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [lang-expression]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [lang-groovy]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [lang-mustache]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [lang-painless]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [parent-join]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [percolator]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [reindex]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [transport-netty3]
[2018-07-22T13:44:45,336][INFO ][o.e.p.PluginsService ] [ISgfHbl] loaded module [transport-netty4]
[2018-07-22T13:44:45,337][INFO ][o.e.p.PluginsService ] [ISgfHbl] no plugins loaded
[2018-07-22T13:44:55,510][INFO ][o.e.d.DiscoveryModule ] [ISgfHbl] using discovery type [zen]
[2018-07-22T13:44:57,353][INFO ][o.e.n.Node ] initialized
[2018-07-22T13:44:57,353][INFO ][o.e.n.Node ] [ISgfHbl] starting ...
[2018-07-22T13:45:08,276][INFO ][o.e.t.TransportService ] [ISgfHbl] publish_address {192.168.25.135:930 0}, bound_addresses {[::]:9300}
[2018-07-22T13:45:08,297][INFO ][o.e.b.BootstrapChecks ] [ISgfHbl] bound or publishing to a non-loopba ck address, enforcing bootstrap checks
[2018-07-22T13:45:09,937][INFO ][o.e.m.j.JvmGcMonitorService] [ISgfHbl] [gc][young][12][8] duration [967m s], collections [1]/[1.5s], total [967ms]/[4.2s], memory [78.8mb]->[66mb]/[1.9gb], all_pools {[young] [48 .6mb]->[33.2mb]/[66.5mb]}{[survivor] [8.3mb]->[8.3mb]/[8.3mb]}{[old] [21.8mb]->[24.5mb]/[1.9gb]}
[2018-07-22T13:45:09,939][WARN ][o.e.m.j.JvmGcMonitorService] [ISgfHbl] [gc][12] overhead, spent [967ms] collecting in the last [1.5s]
[2018-07-22T13:45:11,470][INFO ][o.e.c.s.ClusterService ] [ISgfHbl] new_master {ISgfHbl}{ISgfHblQQqyenL Y8JrqXzw}{aWtcpw25Qo-qBczOAtnnCw}{192.168.25.135}{192.168.25.135:9300}, reason: zen-disco-elected-as-mast er ([0] nodes joined)
[2018-07-22T13:45:11,792][INFO ][o.e.h.n.Netty4HttpServerTransport] [ISgfHbl] publish_address {192.168.25 .135:9200}, bound_addresses {[::]:9200}
[2018-07-22T13:45:11,793][INFO ][o.e.n.Node ] [ISgfHbl] started
使用 elasticsearch -d
(daemonize)或 elasticsearch &
可在后台启动 elasticsearch
从而避免关闭终端也会关闭 es
max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
原因
启动 es
的用户拥有的可创建文件描述的权限太低,至少需要65536。
解决办法
切换到root
用户
[zaw@pinyoyougou-docker config]$ su
密码: #root用户密码
修改/etc/security/limits.conf
,在文件尾添加如下内容:
zaw hard nofile 65536
其中
zaw
是你创建的用来启动es
的用户
切换到 zaw
用户启动 es
[root@pinyoyougou-docker ~]# su zaw
[zaw@pinyoyougou-docker root]$ elasticsearch
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
解决办法
切换到 root
用户修改/etc/sysctl.conf
,在文件末尾添加:
vm.max_map_count=655360
执行 sysctl -p
使更改生效,再切换到 zaw
启动 es
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000085330000, 2060255232, 0) failed; error='Cannot allocate memory' (errno=12)
需增加服务器内存,或者在 es/config/jvm.options
中修改如下两处:
# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space
-Xms128M
-Xmx128M
max number of threads [1024] for user [work] likely too low, increase to at least [2048]
使用 root
修改 /etc/security/limits.d/
下以 -nproc.conf
结尾的文件:
* soft nproc 2048
root soft nproc unlimited
Exception in thread "main" java.nio.file.AccessDeniedException: /usr/local/software/temp/elasticsearch-6.2.2/config/jvm.options
当前用户权限不够,切换到 root
,赋予普通用户操作 es
目录所有权
chmod 777 -R /export/server/elasticsearch-5.6.8
访问 192.168.25.135:9200
若响应结果如下则 es
部署成功
{
"name" : "ISgfHbl",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "8XJxpaVvRkytn7SQ4AYBaQ",
"version" : {
"number" : "5.6.8",
"build_hash" : "688ecce",
"build_date" : "2018-02-16T16:46:30.010Z",
"build_snapshot" : false,
"lucene_version" : "6.6.1"
},
"tagline" : "You Know, for Search"
}
192.168.25.135
是我部署es
的虚拟机IP如果访问失败,那你需要看一下
es
服务所在主机的防火墙是否开放9200
端口
如果使用的是虚拟机,你可以通过 service iptables stop
(centos6.x)或 systemctl stop firewalld
(centos7.x)直接关闭防火墙。
es5.6
http操作es快速入门:官方文档
关于 PostMan的使用可以参考 SpringBoot2.x(二)SpringBoot接口Http这篇文章。下面将根据官方文档提供接口测试
es
的使用
192.168.25.135:9200/_cat/health?v
(GET提交)epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1532244920 15:35:20 elasticsearch green 1 1 0 0 0 0 0 0 - 100.0%
当前就一个
es
,下文将省略es
IP和端口(192.168.25.135:9200
)
/_cat/nodes?v
(GET)ip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
192.168.25.135 3 92 0 0.00 0.01 0.05 mdi * ISgfHbl
/_cat/indices?v
(GET)health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
当前还未创建 index
,因此只显示表头信息
/customer?pretty
(PUT){
"acknowledged": true,
"shards_acknowledged": true,
"index": "customer"
}
常见异常原因:
- 要使用PUT提交方式
- 如果你使用的是PostMan,清空当前标签页的
Headers
和Body
,本提交只需设置提交方式和提交链接。我就是因为之前在当前标签页设置了请求体,然后换链接成/customer?pretty
的时候没有清空它导致报错困扰了我好久
请求结果如下:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "unknown setting [index.password] please check that any required plugins are installed, or check the breaking changes documentation for removed settings"
}
],
"type": "illegal_argument_exception",
"reason": "unknown setting [index.password] please check that any required plugins are installed, or check the breaking changes documentation for removed settings"
},
"status": 400
}
这时我们再次查看 indices
:/_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open customer xN-iWj0iQDSzKFelIQ7H7g 5 1 0 0 955b 955b
更多通过http对es中index的增删改查操作可参阅 官方文档
官方文档
Spring Boot Version (x) | Spring Data Elasticsearch Version (y) | Elasticsearch Version (z) |
---|---|---|
x <= 1.3.5 | y <= 1.3.4 | z <= 1.7.2* |
x >= 1.4.x | 2.0.0 <=y < 5.0.0** | 2.0.0 <= z < 5.0.0** |
spring data elasticsearch | elasticsearch |
---|---|
3.1.x | 6.2.2 |
3.0.x | 5.5.0 |
2.1.x | 2.4.0 |
2.0.x | 2.2.0 |
1.3.x | 1.5.2 |
这里SpringBoot使用 2.3
,es
使用 5.6
引入 starter-data-elasticsearch
依赖
org.springframework.boot
spring-boot-starter-data-elasticsearch
添加SpringBoot整合es配置属性(application.properties
)
# ELASTICSEARCH (ElasticsearchProperties)
# es集群名称(本例只有一个节点),任意.
spring.data.elasticsearch.cluster-name=elasticsearch
# es节点地址,默认9300,可在es/config/elasticsearch.yml中修改
spring.data.elasticsearch.cluster-nodes=192.168.25.135:9300
# 是否开启es仓库
spring.data.elasticsearch.repositories.enabled=true
你需要在你的 pojo
类上添加 @Document
注解,同时指明 indexName
(相当于数据库名),type
(相当于表名)
package top.zhenganwen.springbootesdemo.pojo;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Document;
import java.io.Serializable;
/**
* Article class
*
* @author zhenganwen
* @date 2018/7/23
*/
@Data
@Document(indexName = "blog", type = "article")
public class Article implements Serializable{
private Long id;
private String title;
private String content;
private String summary;
private int pv;
private String author;
}
与传统的DAO编写不同的是,你只需创建一个接口 extends ElasticsearchRepository
,其中 T
是该DAO操作的 pojo
类,ID
是作为主键的属性类型(必须是可序列化的)。你还需要在该接口上添加 @Repository
(或 @Component
注解),Spring在扫描该组件时会根据你传入的 T
动态创建一个实现该接口的 bean
并注册到容器中。
ElasticsearchRepository
是 Spring Data ElasticSearch
为我们封装好的可进行 crud
、分页、排序的接口。
package top.zhenganwen.springbootesdemo.repository;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import top.zhenganwen.springbootesdemo.pojo.Article;
/**
* ArticleEsRepository class
* 实现Article在es中的crud、分页、排序、高亮关键字
*
* @author zhenganwen
* @date 2018/7/23
*/
@Repository
public interface ArticleEsRepository extends ElasticsearchRepository {
}
package top.zhenganwen.springbootesdemo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import top.zhenganwen.springbootesdemo.pojo.Article;
import top.zhenganwen.springbootesdemo.repository.ArticleEsRepository;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootEsDemoApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private ArticleEsRepository articleEsRepository;
@Test
public void testSave() {
Article article = new Article();
article.setAuthor("Alice");
article.setContent("spring boot data es");
article.setId(1l);
article.setPv(100);
article.setSummary("spring boot es");
articleEsRepository.save(article);
}
}
如果你的DAO接口在 extends ElasticsearchRepository
时没有指定 T
或 ID
没有实现 Serializable
,在加载Spring容器时会报错,因为它无法根据你传入的泛型动态创建DAO实现类。
如果测试显示绿条,可以使用PostMan查看索引列表 /_cat/indices?v
:
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open customer xN-iWj0iQDSzKFelIQ7H7g 5 1 0 0 955b 955b
yellow open blog aA45XC7vSOyURbl-Z8twXw 5 1 1 0 5.7kb 5.7kb
查看blog
的结构/blog
:
{
"blog": {
"aliases": {},
"mappings": {
"article": {
"properties": {
"author": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"content": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "long"
},
"pv": {
"type": "long"
},
"summary": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
},
"settings": {
"index": {
"refresh_interval": "1s",
"number_of_shards": "5",
"provided_name": "blog",
"creation_date": "1532326112557",
"store": {
"type": "fs"
},
"number_of_replicas": "1",
"uuid": "aA45XC7vSOyURbl-Z8twXw",
"version": {
"created": "5060899"
}
}
}
}
}
按主键查记录(/blog/article/1
):
{
"_index": "blog",
"_type": "article",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"id": 1,
"title": null,
"content": "spring boot data es",
"summary": "spring boot es",
"pv": 100,
"author": "Alice"
}
}
使用Spring Data ElasticSearch
查询记录的关键接口是 QueryBuilders
,其中封装了各种 丰富的crud
查询条件,这里使用它做一个关键字查询的实例:
@RequestMapping("queryForTitle")
public Object queryForTitle(String content) {
QueryBuilder queryBuilder = QueryBuilders.matchQuery("content", "spring");
Iterable articles = articleEsRepository.search(queryBuilder);
return articles;
}
请求http://localhost:8080/article/queryForTitle?content=spring
响应结果如下:
{
"content": [
{
"id": 1,
"title": null,
"content": "spring boot data es",
"summary": "spring boot es",
"pv": 100,
"author": "Alice"
}
],
"pageable": {
"sort": {
"sorted": false,
"unsorted": true
},
"offset": 0,
"pageSize": 1,
"pageNumber": 0,
"paged": true,
"unpaged": false
},
"facets": [],
"aggregations": null,
"scrollId": null,
"totalElements": 1,
"totalPages": 1,
"number": 0,
"size": 1,
"sort": {
"sorted": false,
"unsorted": true
},
"first": true,
"numberOfElements": 1,
"last": true
}
根据响应结果的 json 结构,前端可以按需取值。
由于 es
查询不是本文重点,大家可以参考官网学习查询API:官方文档
更多资源搜索请到白玉搜一搜