一、引言
1.1 海量数据
在海量数据中执行搜索功能时,如果使用MySQL,效率太低。
1.2 全文检索
在海量数据中执行搜索功能时,如果使用MySQL,效率太低。
1.3 高亮显示
将搜索关键字,以红色的字体展示。
二、ES概述
2.1 ES的介绍
ES是一个使用Java语言并且基于Lucene编写的搜索引擎框架,他提供了分布式的全文搜索功能,提供了一个统一的基于RESTful风格的WEB接口,官方客户端也对多种语言都提供了相应的API。
Lucene:Lucene本身就是一个搜索引擎的底层。
分布式:ES主要是为了突出他的横向扩展能力。
全文检索:将一段词语进行分词,并且将分出的单个词语统一的放到一个分词库中,在搜索时,根据关键字去分词库中检索,找到匹配的内容。(倒排索引)
RESTful风格的WEB接口:操作ES很简单,只需要发送一个HTTP请求,并且根据请求方式的不同,携带参数的同,执行相应的功能。
应用广泛:Github.com,WIKI,Gold Man用ES每天维护将近10TB的数据。
2.2 ES的由来
ES回忆时光 |
---|
2.3 ES和Solr
Solr在查询死数据时,速度相对ES更快一些。但是数据如果是实时改变的,Solr的查询速度会降低很多,ES的查询的效率基本没有变化。
Solr搭建基于需要依赖Zookeeper来帮助管理。ES本身就支持集群的搭建,不需要第三方的介入。
最开始Solr的社区可以说是非常火爆,针对国内的文档并不是很多。在ES出现之后,ES的社区火爆程度直线上升,ES的文档非常健全。
ES对现在云计算和大数据支持的特别好。
2.4 倒排索引
将存放的数据,以一定的方式进行分词,并且将分词的内容存放到一个单独的分词库中。
当用户去查询数据时,会将用户的查询关键字进行分词。
然后去分词库中匹配内容,最终得到数据的id标识。
根据id标识去存放数据的位置拉取到指定的数据。
检索的时候 先将检索的内容分词,然后去分词库匹配 拿到匹配数据的索引 再根据索引去数据存储的位置 拿到匹配的数据
elk elasticsearch (存储分析检索数据) + logstash (采集数据) + kibana (展示数据的图形化界面)
倒排索引 |
---|
2.5基本概念
2.5.1 Index (索引)
- 动词 ,相当于Mysql 中的insert
- 名词, 相当于Mysql 中的 Database
2.5.2 Type (类型)
在Index(索引)中 ,可以定义一个或多个类型。
类似Mysql 中的table ,每一种类型的数据放在一起
2.5.3 Document (文档)
保存在某个索引(index)下,某种类型(type)的一个数据(Document) ,文档是json 格式的,Document 就像是Mysql 中某个table里面的内容。
三、 ElasticSearch安装
3.1 安装ES&Kibana 不建议
1).下载镜像文件
docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2
2).创建挂载目录
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
3).启动容器
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx1024m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
#如果 启动elasticsearch 失败, 查看 日期 docker logs 容器id 查看 某容器的日志
#docker logs 容器id
# 需要 给 elasticsearch 目录授权
#chmod -R 777 /mydata/elasticsearch/ 给整个目录授权 可读可写可执行
#在启动容器 应该就好了
4).启动kibana
docker run --name kibana -e ELASTICSEARCH_URL=http://192.168.5.202:9200 -p 5601:5601\
-d kibana:7.4.2
测试访问
- http://192.168.5.202:9200 如访问不到 查看日志
- http://192.168.5.202:5601 如访问不到 查看日志 看容器内 kibana.yml 中 ip 地址是否正确
3.1.1 使用docker-compose的方式安装(推荐)
- mkdir -p /mydata/elasticsearch/config
- mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
version: "3.1"
services:
elasticsearch:
image: daocloud.io/library/elasticsearch:7.4.2
restart: always
container_name: elasticsearch
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
volumes:
- /opt/docker_elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
- /opt/docker_elasticsearch/data:/usr/share/elasticsearch/data
- /opt/docker_elasticsearch/plugins:/usr/share/elasticsearch/plugins
ports:
- 9200:9200
- 9300:9300
kibana:
image: daocloud.io/library/kibana:7.4.2
restart: always
container_name: kibana
ports:
- 5601:5601
environment:
- elasticsearch_url=http://192.168.40.102:9200
depends_on:
- elasticsearch
9300 端口 为 es 集群间组件的通信端口,9200 端口为浏览器访问的http 协议resetful 端口
记得 给文件夹授权 授权之后 再docker-compose up
chmod 777 config
chmod 777 data
如果访问 http://192.168.40.102:9200 失败,查看日志是否vm内存配置过小elasticsearch启动时遇到的错误。
问题翻译过来就是:elasticsearch用户拥有的内存权限太小,至少需要262144;
解决办法1:在 /etc/sysctl.conf文件最后添加一行
vm.max_map_count=262144 把宿主机内存配大一些
解决办法2:启动时 指定内存 咱们的安装方法 就是启动时 指定内存
3.2 安装IK分词器
ik 分词器 就是 elasticsearch 的一个插件
进去到ES容器内部,跳转到bin目录下,执行bin目录下的脚本文件:
./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
重启ES的容器,让IK分词器生效。
注意:
如果在线安装太慢的话,可以先下载下来压缩包然后手动安装
把下载的压缩包解压到挂载目录 plugins 下的 ik 文件夹里,
cd /mydata/elasticsearch/plugins
mkdir ik
cd ik 先把压缩包放在 ik 目录中
unzip elasticsearch-analysis-ik-7.4.2.zip
解压完之后 可以把压缩包删掉:rm -rf *.zip
进入容器内部 bin 目录下 执行elasticsearch-plugin list 查看插件列表有没有 ik
ik_max_word 是ik 分词器的 一种分词方式还有别的分词方式。
校验IK分词器 |
---|
四、 ElasticSearch基本操作
4.1 ES的结构 --- 操作es 之前 先了解es 的结构
4.1.1 索引Index,分片和备份
ES的服务中,可以创建多个索引。
每一个索引默认被分成5片存储。
每一个分片都会存在至少一个备份分片。
备份分片默认不会帮助检索数据,当ES检索压力特别大的时候,备份分片才会帮助检索数据。
备份的分片必须放在不同的服务器中。
理解:
索引index是es中最大的数据存储单位 ,和mysql 的区别是 一个索引(index)中可以存海量(几亿条)数据 ,如果我们要在几亿条数据中检索出几条想要的数据 效率会很低 所以 es 提供了 一种对索引进行分片的机制 ,ES 天然支持集群,在集群服务器中 ES 把一个索引进行分片 放在不同的服务器上 如下图 例如 有一亿条数据 分成两个分片 每个分片上有5000万条数据 这样做的好处 一是 提高查询速度 二是 提高数据的存储量,另外 为了保证数据的安全 每个主分片会有备份分片 主分片和备份分片在不同的服务器上 , 比如 主分片2 挂掉了 在 ES服务1 上面 还有 主分片2的备份分片 ,这样在一定程度上保证了数据的安全性 避免数据的丢失。但是,如果当前集群中只有一台es服务器,那么,这台服务器上 放的都是主分片,没有备份分片,什么时候扩展了集群中的另一台服务器才会存放备份分片。
索引分片备份 |
---|
4.1.2 类型 Type
一个索引下,可以创建多个类型。
es7 版本 不推荐使用type ,但是还是能用,到后面的es 版本 就不能再使用type 了
类型 |
---|
4.1.3 文档 Doc
一个类型下,可以有多个文档。这个文档就类似于MySQL表中的多行数据。
文档 |
---|
4.1.4 属性 Field
一个文档中,可以包含多个属性。类似于MySQL表中的一行数据存在多个列。
属性 |
---|
4.2 操作ES的RESTful语法
GET请求:
http://ip:port/_cat/nodes
:查看所有节点 在kibana 中使用 GET _cat/nodes
http://ip:port/_cat/health
:查看es 健康状况
http://ip:port/_cat/master
:查看主节点
http://ip:port/_cat/indices
:查看所有索引 相当于 show databases;
http://ip:port/index
:查询索引信息 GET book 相当于查看 数据库表结构
http://ip:port/index/type/doc_id
:查询指定的文档信息注意 咱们用的是 es 7 直接使用type 的 话 会给出警告信息 ,咱们使用 _doc 代替 type
比如 查询指定文档信息 GET book/_doc/1 查询 book 索引中 id 为1 的文档信息
GET book/_doc/2
注意 咱们用的是 es 7 直接使用type 的 话 会给出警告信息 ,咱们使用 _doc 代替 type
比如 查询指定文档信息 GET book/_doc/1 查询 book 索引中 id 为1 的文档信息
GET book/_doc/2
POST请求:
http://ip:port/index/type/doc_id
# 指定文档id的添加操作
# 如果索引还未创建 还可以创建索引
POST book/_doc/2
{
"name":"西游记",
"author":"吴承恩"
}
http://ip:port/index/type/_search:查询文档,可以在请求体中添加json字符串来代表查询条件。
# 查询操作
POST book/_search
{
"query":{
"match": {
"name": "西游记"
}
}
}
http://ip:port/index/type/doc_id/_update:修改文档,在请求体中指定json字符串代表修改的具体信息 注意 带 _update 的 修改 json 格式 里需要加 doc 对比本文档下面的案例说明。
POST book/_update/1 # 修改操作
{
"doc":{
"name":"大奉打更2人",
"author":"xxxxx2"
}
}
PUT请求:
http://ip:port/index:创建一个索引,也可以在请求体中指定索引的信息,类型,结构。
PUT book2 # 创建一个叫 book2 的索引 执行第二次会报错
# 添加或修改文档 第一次是添加(同样 索引不存在 也会创建索引) 后面再执行是修改
PUT book3/_doc/1
{
"name":"java"
}
http://ip:port/index/type/_mappings:代表创建索引时,指定索引文档存储的属性的信息
DELETE请求:
http://ip:port/index:删除索引
- DELETE book2 # 删除book2 索引
http://ip:port/index/type/doc_id:删除指定的文档
- DELETE book/_doc/2 删除索引book中 id 为2 的文档
4.3 索引的操作
4.3.1 创建一个索引
语法如下先创建一个最简单的先不指定他的结构化数据。
# 创建一个索引
PUT /person
{
"settings": {
"number_of_shards": 5, # 分片 数5
"number_of_replicas": 1 # 备份
}
}
4.3.2 查看索引信息
语法如下:去management 中查看索引信息
Primaries 意思是分片
Replicas 意思是备份
Health健康状态黄色表示不太健康,因为现在es 集群中只有一台服务器备份分片没有地方存放,所以是黄色的健康状态,如果集群中有多台服务器,备份分片就可以存储在别的服务器上,避免这台服务器挂掉数据丢失问题。
点索引的名字可以查看索引的详细信息。
# 查看索引信息
GET /person
查看信息 |
---|
4.3.3 删除索引
语法如下
# 删除索引
DELETE /person
4.4 ES中Field可以指定的类型
字符串类型:
text:一把被用于全文检索。 将当前Field进行分词。
keyword:当前Field不会被分词。
数值类型:
scaled_float:根据一个long和scaled来表达一个浮点型,long-345,scaled-100 -> 3.45
half_float:精度比float小一半。
float:3.402823e+38 ~ 1.401298e-45(e+38表示是乘以10的38次方,e-45表示乘以10的负45次方),占用4个字节
double:1.797693e+308~ 4.9000000e-324 (e+308表示是乘以10的308次方,e-324表示乘以10的负324次方)占用8个字节
byte:取值范围为-128~127(-2的7次方到2的7次方-1),占用1个字节
short:取值范围为-32768~32767(-2的15次方到2的15次方-1),占用2个字节
integer:取值范围为-2147483648~2147483647(-2的31次方到2的31次方-1),占用4个字节
long:取值范围为-9223372036854774808~922337203685477480(-2的63次方到2的63次方-1),占用8个字节
时间类型:
- date类型,针对时间类型指定具体的格式
布尔类型:
- boolean类型,表达true和false
二进制类型:
- binary类型暂时支持Base64 encode string
范围类型:
- long_range:赋值时,无需指定具体的内容,只需要存储一个范围即可,指定gt,lt,gte,lte
- integer_range:同上
- double_range:同上
- float_range:同上
- date_range:同上
- ip_range:同上
经纬度类型:
- geo_point:用来存储经纬度的
ip类型:
- ip:可以存储IPV4或者IPV6
其他的数据类型参考官网:Field datatypes | Elasticsearch Guide [7.6] | Elastic
4.5 创建索引并指定数据结构
语法如下
# 创建索引,指定数据结构
PUT /book
{
"settings": {
# 分片数
"number_of_shards": 5,
# 备份数
"number_of_replicas": 1
},
# 指定数据结构
"mappings": {
# 类型 Type es 7 可以把这个删了
"novel": {
# 文档存储的Field
"properties": {
# Field属性名
"name": {
# 类型
"type": "text",
# 指定分词器 # 在对这个属性做分词的时候 使用 ik分词器
"analyzer": "ik_max_word",
# 指定当前Field可以被作为查询的条件 如果为false 则不能作为查询条件
"index": true ,
# 当前field是否需要额外存储 一般设置为false 即可 不需要额外存储
"store": false
},
"author": {
# keyword 也算是字符串类型
"type": "keyword"
},
"count": {
"type": "long"
},
"on-sale": {
"type": "date",
# 时间类型的格式化方式
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"descr": {
"type": "text",
# 在对这个属性做分词的时候 使用 ik分词器
"analyzer": "ik_max_word"
}
}
}
}
}
4.6 文档的操作
文档在ES服务中的唯一标识,
_index
,_type
,_id
三个内容为组合,锁定一个文档,操作是添加还是修改。
4.6.1 新建文档
自动生成_id
# 添加文档,自动生成id 不推荐这种自动生成的id
POST /book/_doc
{
"name": "盘龙",
"author": "我吃西红柿",
"count": 100000,
"on-sale": "2000-01-01",
"descr": "山重水复疑无路,柳暗花明又一村"
}
手动指定_id
# 添加文档,手动指定id 推荐使用
PUT /book/_doc/1
{
"name": "红楼梦",
"author": "曹雪芹",
"count": 10000000,
"on-sale": "1985-01-01",
"descr": "一个是阆苑仙葩,一个是美玉无瑕"
}
4.6.2 修改文档
覆盖式修改
# 修改文档 覆盖式修改 如果没有指定某个属性 这个属性会被覆盖掉 覆盖没了
PUT /book/novel/1
{
"name": "红楼梦",
"author": "曹雪芹",
"count": 4353453,
"on-sale": "1985-01-01",
"descr": "一个是阆苑仙葩,一个是美玉无瑕"
}
doc修改方式
# 修改文档,基于doc方式 不会覆盖之前的内容 指定哪一个属性 修改哪一个属性
POST /book/novel/1/_update # 7 之前的写法
{
"doc": {
# 指定上需要修改的field和对应的值
"count": "1234565"
}
}
# 现在 都这样写
POST book/_update/1
{
"doc":{
"name": "斗破苍穹"
}
}
4.6.3 删除文档
根据id删除
# 根据id删除文档
DELETE book/_doc/1 # 删除id 为1 的文档
4.6.4 补充
在kibana 可视化界面中可以看到 创建的索引信息
五、Java操作ElasticSearch
5.1 Java连接ES
创建springboot工程
导入依赖
这里注意 springboot版本 默认了es 的 一些版本,需要咱们自己统一定义 要不然会有版本冲突 所以 在pom 文件中 统一 es 版本
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
<dependencies>
<!-- elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.4.2</version>
</dependency>
<!-- elasticsearch 高阶API-->
<dependency>
<!--导入 es 的 高阶 api 来 操作 es
要进行配置
如果使用spsringdata 操作es 配置会比较简单 只需要在配置文件指定es 的地址就好了
我们是自己配的 所以自己对es 做配置
-->
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
<!-- 3. junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 4. lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
创建配置类,测试连接ES
package com.ymk.esdemo.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchConfig {
//RequestOptions 这个类 主要封装了 访问 ES 的 一些头信息 一些 设置信息
public static final RequestOptions COMMON_OPTIONS;
static {
// 请求设置项
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
// builder.addHeader("Authorization","Bearer"+TOKEN);
// builder.setHttpAsyncResponseConsumerFactory(new HttpAsyncResponseConsumerFactory
// .HeapBufferedResponseConsumerFactory(30*1024*1024*1024));
COMMON_OPTIONS = builder.build();
}
@Bean
public RestHighLevelClient esRestClient(){
RestClientBuilder builder = null;
builder = RestClient.builder(new HttpHost("192.168.5.205",9200,"http"));
RestHighLevelClient client = new RestHighLevelClient(builder);
//RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.56.10", 9200, "http")));
return client;
}
}
// 测试 能不能得到 RestHighLevelClient
@SpringBootTest
class EsdemoApplicationTests {
@Resource
private RestHighLevelClient client;
@Test
void contextLoads() {
System.out.println(client);
}
}
5.2 Java操作索引
5.2.1 创建索引
代码如下
/**
* 索引的创建
*
* */
/**
* {
* "properties":{
* "name":{
* "type":"text"
* },
* "age":{
* "type":"integer"
* },
* "birthday":{
* "type":"date",
* "format":"yyyy-MM-dd"
* }
* }
*
* }
*
* */
@Test
public void demo1() throws IOException {
// 创建索引
String index = "person";
//1. 准备关于索引的settings
Settings.Builder settings = Settings.builder()
.put("number_of_shards", 3)
.put("number_of_replicas", 1);
//2. 准备关于索引的结构mappings
XContentBuilder mappings = JsonXContent.contentBuilder()
.startObject() // 和 endObject 成对出现
.startObject("properties")
.startObject("name")
.field("type","text")
.endObject()
.startObject("age")
.field("type","integer")
.endObject()
.startObject("birthday")
.field("type","date")
.field("format","yyyy-MM-dd")
.endObject()
.endObject()
.endObject();
//3. 将settings和mappings封装到一个Request对象
// 不同的操作 准备的request 对象不一样 与下文对比
CreateIndexRequest request = new CreateIndexRequest(index)
.settings(settings)
.mapping(mappings);// 如果是6版本 还需要在这个方法指定type //.mapping(type,mappings)
//4. 通过client对象去连接ES并执行创建索引
// 通过client 对象 把上面准备的 request 对象 发到es执行
CreateIndexResponse resp = client.indices().create(request, RequestOptions.DEFAULT);
//5. 输出
System.out.println("resp:" + resp.toString());
}
5.2.2 检查索引是否存在
代码如下
/**
* 判断 索引是否存在
* */
@Test
public void demo2() throws IOException {
//1. 准备request对象
String index = "person";
GetIndexRequest request = new GetIndexRequest(index);
request.indices();
//2. 通过client去操作
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
//3. 输出
System.out.println(exists);
}
5.2.3 删除索引
代码如下
@Test
public void delete() throws IOException {
//1. 准备request对象
DeleteIndexRequest request = new DeleteIndexRequest();
request.indices(index);
//2. 通过client对象执行
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
//3. 获取返回结果
System.out.println(delete.isAcknowledged());
}
5.3 Java操作文档
5.3.1 添加文档操作
代码如下
添加fastjson 依赖, jackson 也可以 用法大致一样
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
// 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Person {
private Integer id;
private String name;
private Integer age;
@JSONField(format = "yyyy-MM-dd") // fastjson 转换对象时 对日期类型字段的 格式转换
private Date birthday;
}
// 测试方法
/**
* 添加文档
*
* */
@Test
public void demo4() throws IOException {
String index = "person";
//1. 准备一个json数据
Person person = new Person(1,"张三"