一、ElasticSearch简介
1.1 ElasticSearch vs Lucene的关系
ElasticSearch vs Lucene的关系,简单⼀句话就是,成品与半成品的关系。
(1)Lucene专注于搜索底层的建设,而ElasticSearch专注于企业应用。
(2)Lucene是单节点的API,ElasticSearch是分布式的—为集群而⽣。
(3)Lucene需要⼆次开发,才能使用。不能像百度或谷歌⼀样,它只是提供⼀个接⼝需要被实现才能使用, ElasticSearch直接拿来用。
1.2 ElasticSearch特性
安装管理便便
大规模分布式
多租户支持
高可用性
操作持久化
友好的RESTful API
二、ElasticSearch逻辑结构
集群–>index(索引)–>types(类型)–>document(文档)–>field(字段)
//index3 索引名,_doc 类型(默认为_doc),102 文档id名, "book_id" 字段名
POST index3/_doc/102
{
"book_id":102,
"book_name":"C++程序设计",
"book_author":"谭浩强",
"book_price":22.22,
"book_desc":"C++程序设计中的名著"
}
- 索引(index)
索引是ElasticSearch存放数据的地方,可以理解为关系型数据库中的⼀个数据库
索引的名字必须是全部⼩写,不能以下划线开头,不能包含逗号
- 类型(type)
类型用于区分同⼀个索引下不同的数据类型,相当于关系型数据库中的表。
es 6.0 开始不推荐⼀个index下多个type的模式,并且会在 7.0 中完全移除。在7.0 的index下是⽆法创建多个 type
- 文档(documents)
文档是ElasticSearch中存储的实体,类比关系型数据库,每个⽂档相当于数据库表中的⼀行数据。
索引的名字必须是全部⼩写,不能以下划线开头,不能包含逗号
- 字段(fields)
⽂档由字段组成,相当于关系数据库中列的属性,不同的是ES的不同文档可以具有不同的字段集合。 - 节点与集群
⼀个集群是由⼀个或多个节点组成的集合,集群上的节点都具备存储数据,并提供跨节点的索引和搜索功能。
集群通过⼀个唯⼀的名称作为标识,节点通过设置集群名称就可以加⼊相应的集群,当然这需要节点所在的网络能够发现集群。所以要注意在同⼀个网络中,不同环境、服务的集群的名称不能重复。
三、ElasticSearch安装(Linux)
3.1 Elastic 和 Elasticsearch
Elastic官⽹:https://www.elastic.co/cn/
Elasticsearch官⽹:https://www.elastic.co/cn/products/elasticsearch
Elastic有⼀条完整的产品线及解决方案:Elasticsearch、Logstash、Kibana等,这三个就是大家常说的ELK技术栈。
3.2 Linux下安装ES
出于安全考虑,elasticsearch默认不允许以root账号运⾏
- 创建用户设置密码
[root@localhost ~]# useradd es
[root@localhost ~]# passwd es
Changing password for user es.
New password:
Retype new password:
[root@localhost ~]# chmod 777 /usr/local 【授予es⽤户/usr/local⽬录 可读可写可执⾏权限】
[root@localhost ~]# su - es
[es@localhost ~]$
- 检查JDK版本(需要JDK1.8+)
[es@localhost ~]# java -version
openjdk version "1.8.0_222-ea"
OpenJDK Runtime Environment (build 1.8.0_222-ea-b03)
OpenJDK 64-Bit Server VM (build 25.222-b03, mixed mode)
- 将ES的压缩包上传⾄ /usr/local 目录并解压
[es@localhost local]$ tar -zxvf elasticsearch-7.6.1-linux-x86_64.tar.gz
- 进入elasticsearch-7.6.1中添加data 文件
[es@localhost local]# cd elasticsearch-7.6.1
[es@theo elasticsearch-7.6.1]#mkdir data
[es@theo elasticsearch-7.6.1]# ls
bin config data jdk lib LICENSE.txt log smodules NOTICE.txt plugins README.asciidoc
- 查看配置文件
[es@theo elasticsearch-7.6.1]# cd config
[es@localhost config]# ls
elasticsearch.yml jvm.options data log4j2.properties role_mapping.yml roles.yml users
users_roles
- 修改 jvm.options(修改ElasticSearch占用的内存)
Elasticsearch基于Lucene的,而Lucene底层是java实现,因此我们需要配置jvm参数
[es@localhost config]# vim jvm.options
# 默认配置如下
# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space
-Xms1g
-Xmx1g
#修改成
-Xms256m
-Xmx256m
- 修改 elasticsearch.yml
- 下面是做的单机安装的修改,如果要做集群,只需要在这个配置⽂件中添加其它节点信息即可。
# (修改集群节点信息)(当前是单节点配置)
# ---------------------------------- Cluster -----------------------------------17
cluster.name: my-application
# ------------------------------------ Node ------------------------------------23
node.name: node-1
# --------------------------------- Discovery ----------------------------------72
cluster.initial_master_nodes: ["node-1"]
# (修改数据⽂件和⽇志⽂件存储⽬录路径)
# ---------------------------- Paths ------------------------------
path.data: /usr/local/elasticsearch-7.6.1/data
path.logs: /usr/local/elasticsearch-7.6.1/logs
# (修改绑定的ip,默认只允许本机访问,修改为0.0.0.0后则可以远程访问)
# ---------------------------- Network ------------------------------
# 默认只允许本机访问,修改为0.0.0.0后则可以远程访问
network.host: 0.0.0.0
- 进入elasticsearch/bin目录运行
[es@localhost elasticsearch-7.6.1]# cd /usr/local/elasticsearch-7.6.1/bin
[es@localhost elasticsearch-7.6.1]# ./elasticsearch
3.3 启动错误问题总结
-
错误1:内核过低
我们使用的是centos6,其linux内核版本为2.6。而Elasticsearch的插件要求至少3.5以上版本。不过没关系,我们禁用这个插件即可。
修改elasticsearch.yml文件,在最下面添加如下配置bootstrap.system_call_filter: false
然后重启
-
错误2:⽂件权限不足
我们用的是es用户,⽽不是root,所以文件权限不⾜。
⾸先⽤root⽤户登录,然后修改配置⽂件vim /etc/security/limits.conf
添加下面的内容:
* soft nofile 65536 * hard nofile 131072 * soft nproc 4096 * hard nproc 4096
-
错误3:线程数不够
[1]: max number of threads [1024] for user [es] is too low, increase to at least[4096]
这是线程数不够
继续修改配置vim /etc/security/limits.d/20-nproc.conf
修改下⾯的内容:
soft nproc 1024 # 改为 soft nproc 4096
-
错误4:进程虚拟内存(要在root用户下操作)
[3]: max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least[262144]
vm.max_map_count:限制⼀个进程可以拥有的VMA(虚拟内存区域)的数量
继续修改配置⽂件, vim /etc/sysctl.conf 添加下⾯内容:vm.max_map_count=655360
修改完成之后在终端执行重启
##然后执⾏命令 sysctl -p
-
错误5:未设置节点
the default discovery settings are unsuitable for production use; at least one of [discovery.seed_ho...]
修改elasticsearch.yml
cluster.name: my-application node.name: node-1 cluster.initial_master_nodes: ["node-1"]
四、安装Kibana
Kibana是⼀个基于Node.js的Elasticsearch索引库数据统计工具,可以利用Elasticsearch的聚合功能,生成各种图表,如柱形图,线状图,饼图等。
而且还提供了操作Elasticsearch索引数据的控制台,并且提供了⼀定的API提示,⾮常有利于我们学习Elasticsearch的语法。
-
安装
kibana版本与elasticsearch保持⼀致,也是7.6.1解压到特定目录即可tar -zxvf kibana-7.6.1-linux-x86_64.tar.gz
-
配置
进⼊安装目录下的config⽬录,修改kibana.yml文件:server.port: 5601 server.host: "0.0.0.0
-
运行
进⼊安装目录下的bin目录启动./kibana
发现kibana的监听端⼝是5601
我们访问:http://47.96.11.185:5601
五、安装IK分词器(Linux)
5.1 安装
- 在本地将下载的IK分词器压缩包解压到当前文件夹
- 将解压后的文件夹上传到云主机plugins文件夹中
- 重启es
[es@theo bin]$ ./elasticsearch
5.2 测试
- 按ik_smart方式将“千锋武汉”分词,结果为“千” “峰” “武汉”
5.2 配置自定义词库(3种方式)
- 在elasticsearch-analysis-ik-7.6.1/plugins/ik/config⽬录中定义词典⽂件(.dic)
- 在词典⽂件中定义⾃定义词汇
- elasticsearch-analysis-ik-7.6.1/plugins/ik/config/IKAnalyzer.cfg.xml加载⾃定义词典⽂件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--⽤户可以在这⾥配置⾃⼰的扩展字典 -->
<entry key="ext_dict">mywords.dic</entry>
<!--⽤户可以在这⾥配置⾃⼰的扩展停⽌词字典-->
<entry key="ext_stopwords"></entry>
<!--⽤户可以在这⾥配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--⽤户可以在这⾥配置远程扩展停⽌词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
六、ES基本操作
ES是基于RESTful实现访问
不同操作需要使用不同的请求方式
基于REST的基本访问规范
6.1 数据类型
es中⼀个document表示⼀条记录,记录中field值的存储是有类型的
-
string
text 可分词
keyword 不能分词 -
Numeric datatypes
long , integer , short , byte , double , float , half_float , scaled_float -
Date datatype
data — 日期的存储时以 long 类型存储的毫秒数 -
Boolean datatype
boolean — true | false | “true” | “false” -
Binary datatype
binary 基于base64编码的字符串 -
Range datatypes
integer_range , float_range , long_range , double_range , date_range
6.2 创建
//创建Index索引并指定field类型
PUT index4
{
"mappings": {
//设置存储在哪个分⽚上(可以不设置该字段,默认1)
"settings": {
"number_of_shards": 2
}
//设置索引属性
"properties": {
"bookId":{
"type": "long"
},
"bookName":{
"type": "text"
},
"author":{
"type": "keyword"
},
"time":{
"type": "date"
}
}
}
}
6.3 添加
POST index4/_doc/1
{
"bookId":10001,
"bookName":"Java程序设计",
"author":"张三",
"time":234567890
}
6.4 修改
//使⽤新增操作post的请求覆盖原记录
POST index1/_doc/103
{
"book_id":103,
"book_name":"Python王者归来",
"book_author":"杰哥",
"book_price":33.22,
"book_desc":"Python从⼊⻔到放弃"
}
//使⽤_update修改
POST index1/_doc/103/_update
{
"book_id":103,
"book_name":"Python王者归来",
"book_author":"杰哥",
"book_price":33.22,
"book_desc":"Python从⼊⻔到放弃"
}
6.5 删除
DELETE index1/_doc/103
6.6 查询
# 查询索引信息
GET index4
# 查询索引的mappings信息
GET index4/_mappings
# 查询索引的属性设置
GET index4/_settings
- term和terms
用于对keyword字段进行精确匹配,搜索之前不会对关键字进行分词
//查询field类型keyword的author字段是“张三”的
GET /index3/_search
{
"query": {
"term": {
"author": "张三"
}
}
}
//查询field类型keyword的author字段是“张三”或者“李四”的
GET /index3/_search
{
"query": {
"terms": {
"author": ["张三","李四"]
}
}
}
- match查询(重点)
match查询表示对text字段进行部分匹配(模糊查询),搜索之前会对关键词进行分词
GET /index4/_search
{
"query": {
"match": {
"bookName": "Java程序"
}
}
}
//match_all 表示查询全部内容,不指定任何条件
GET /index4/_search
{
"query": {
"match_all": {}
}
}
//multi_match 在多个字段中匹配同同时还有关键字的
GET /index4/_search
{
"query": {
"multi_match": {
"query": "Java",
"fields": ["bookName","author"]
}
}
}
- 高亮显示(重点)
对匹配的关键词进行特殊样式的标记
GET /index3/_search
{
"query": {
"match": {
"bookName": "Java"
}
},
//对查询出来的bookName字段的值进行红色字体显示
"highlight": {
"fields": {
"bookName": {}
},
"pre_tags": "<label style='color:red'>",
"post_tags": "</label>"
}
}
- 根据id查询
//根据⼀个id查询⼀个document
GET /index4/_doc/1
//根据多个id查询多个document ==> select * from ... where id in [1,2,3]
GET /index4/_search
{
"query":{
"ids":{
"values":["1","2","3"]
}
}
}
- 其他查询
//prefix查询,根据指定字段的前缀值进⾏查询
GET /index4/_search
{
"query": {
"prefix": {
"author": {
"value": "张"
}
}
}
}
//fuzzy查询,模糊查询,输⼊⼤概的内容es检索相关的数据
GET /index4/_search
{
"query": {
"fuzzy": {
"bookName": {
"value": "jav"
}
}
}
}
//wildcard查询:正则匹配
GET /index4/_search
{
"query": {
"wildcard": {
"author": {
"value": "张*"
}
}
}
}
//range查询,根据范围进⾏查询
GET /index4/_search
{
"query": {
"range" : {
"bookId" : {
"gt" : 10001,
"lte" : 10003
}
}
}
}
//分⻚查询(查询从第一天数据开始每页20条 显示"bookId","bookName"字段信息)
GET /index4/_search
{
"query": {
"match_all": {}
},
"_source": ["bookId","bookName"],
"from": 0,
"size": 20
}
- 复合查询—bool
复合查询——多条件查询
- should ==> or 满足其一
- must ==> and 同时满足
- must_not ==> not 不是
GET /index4/_search
{
"query": {
"bool":{
"must_not": [
{
"match": {
"bookName": "Java"
}
},
{
"match": {
"author": "张三"
}
}
]
}
}
}
- 结果过滤—filter
filter——根据条件进⾏查询,不计算分数,会对经常被过滤的数据进⾏缓存
GET /index3/_search
{
"query": {
"bool":{
"filter": [
{
"match": {
"bookName": "Java"
}
},
{
"match": {
"author": "张三"
}
}
]
}
}
}
七、SpringBoot整合ES
官⽅参考地址 https://www.elastic.co/guide/en/elasticsearch/client/index.html
7.1 添加es的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
7.2 添加配置
spring:
elasticsearch:
rest:
uris: http://47.96.11.185:9200
- 如果是SSM项目则添加配置类
@Bean
public RestHighLevelClient getRestHighLevelClient(){
//添加es所在Linux的公网ip和端口号及协议
HttpHost httpHost = new HttpHost("47.96.11.185", 9200, "http");
RestClientBuilder restClientBuilder = RestClient.builder(httpHost);
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClientBuilder);
return restHighLevelClient;
}
7.3 使用操作
@SpringBootTest
class Esdemo3ApplicationTests {
//注入restHighLevelClient对象
@Resource
private RestHighLevelClient restHighLevelClient;
/**
* 在es中创建索引
*/
@Test
public void testCreateIndex() throws IOException {
CreateIndexRequest createIndexRequest = new CreateIndexRequest("index4");
CreateIndexResponse createIndexResponse =
restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
//b 为判断是否添加成功
boolean b = createIndexResponse.isAcknowledged);
}
/**
* 删除索引
*/
@Test
public void testDeleteIndex() throws IOException {
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("index4");
AcknowledgedResponse deleteIndexRes =
restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
//b 为判断是否删除成功
boolean b = deleteIndexRes.isAcknowledged);
}
/**
* 添加⽂档:将数据存⼊es
*/
@Test
public void testCreateDocument() throws IOException {
Book book = new Book(10005,"平凡的世界","路遥",new Date().getTime());
//将book对象转换为json字符串格式存入es
ObjectMapper objectMapper = new ObjectMapper();
String jsonStr = objectMapper.writeValueAsString(book);
IndexRequest request = new IndexRequest("index3");
//设置文档id
request.id("10005");
request.source(jsonStr, XContentType.JSON);
IndexResponse indexResponse = restHighLevelClient.index(request,RequestOptions.DEFAULT);
System.out.println(indexResponse);
}
/**
* 搜索
*/
@Test
public void testSearch() throws IOException {
SearchRequest searchRequest = new SearchRequest("index3");
//设置搜索条件内容searchSourceBuilder
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//查询显示分页条件
searchSourceBuilder.from(0);
searchSourceBuilder.size(10);
//查询全部字段
// searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//查询部分字段
searchSourceBuilder.query(QueryBuilders.matchQuery("bookName","Java"));
//设置高亮字段条件highlightBuilder
HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("bookName");
highlightBuilder.preTags("<label style='color:red'>");
highlightBuilder.postTags("</label>");
searchSourceBuilder.highlighter(highlightBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResp = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
//查询到结果hits 可使用迭代器遍历
SearchHits hits = searchResp.getHits();
//查询数据封装
Iterator<SearchHit> iterator = hits.iterator();
List<Product> products = new ArrayList<>();
while(iterator.hasNext()){
SearchHit searchHit = iterator.next();
String str = searchHit.getSourceAsString();
//将json字符串转换为对象
Product product = objectMapper.readValue(str, Product.class);
//获取高亮字段(是一个数组)
HighlightField highlightField = searchHit.getHighlightFields().get("productName");
if(highlightField != null){
String s = Arrays.toString(highlightField.fragments());
//将高亮字替换原属性
product.setProductName(s);
}
products.add(product);
}
}
}