当前 ElasticSearch
已经到了 8.0+
,新版本都有很多新特性,性能和功能都有大幅提升,建议使用较高版本,这里采用 7.12.1
版本 👼
es 版本
安装 ElasticSearch
脚本如下:
1
2
3
4
5
6
7
docker run -d \
--name elasticsearch \
-e "discovery.type=single-node" \
--privileged \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
常用命令说明:
-e "cluster.name=es-docker-cluster"
:设置集群名称
-e "http.host=0.0.0.0"
:监听的地址,可以外网访问
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小
-e "discovery.type=single-node"
:非集群模式
-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定elasticsearch
的数据目录
-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定elasticsearch
的日志目录
-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定elasticsearch
的插件目录
--privileged
:授予逻辑卷访问权
-p 9200:9200
:端口映射配置
docker 安装 es
安装完成后,在浏览器中输入:http://172.27.46.132/:9200/
即可看到 elasticsearch
的响应结果:
es 响应结果
我们可以基于 http
请求操作 ElasticSearch
,但基于 http
操作比较麻烦,可以采用 Kibana
实现可视化操作。
Kibana
是一个免费且开放的用户界面,能够对 Elasticsearch
数据进行可视化,并在 Elastic Stack
中进行导航,可以进行各种操作,从跟踪查询负载,到理解请求如何流经的整个应用,都能轻松完成。Kibana
让能够自由地选择如何呈现自己的数据。
使用 Docker
安装 Kibana
非常简单,但是执行命令需要注意 Kibana
操作的 ElasticSearch
地址,因为 Kibana
是需要连接 ElasticSearch
进行操作的,所以需要 es 和 Kibana
在同一网段,安装 Kibana
命令如下:
1
2
3
4
5
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://172.27.46.132:9200 \
-p 5601:5601 \
kibana:7.12.1
命令说明:
-e ELASTICSEARCH_HOSTS=http://172.27.46.132:9200"
:设置 elasticsearch
的地址,因为 kibana
已经与elasticsearch
在一个网络,因此可以用容器名直接访问 elasticsearch
,也可以写 IP 地址实现访问。
-p 5601:5601
:端口映射配置
安装的时候如果没有镜像,会下载镜像,效果如下:
安装 Kibana
安装成功后,可以访问 http://172.27.46.132:5601/
效果如下:
Kibana 效果
可以点击 Add data
,添加示例数据,如下图,随意选一个即可,不选其实也是可以的。
添加数据
Kibana
支持中文配置,可以把 Kibana
配置成中文版,切换中文操作如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#进入容器
docker exec -it kibana /bin/bash
#进入配置文件目录
cd /usr/share/kibana/config
#编辑文件kibana.yml
vi kibana.yml
#在最后一行添加如下配置
i18n.locale: zh-CN
#保存退出
exit
#并重启容器
docker restart kibana
等待 Kibana
容器启动,再次访问 http://172.27.46.132:5601/
效果如下:
中文效果
打开 Kibana
,点击开发工具,操作如下:
开发工具
在左边输入如下内容,用于查询分词:
1
2
3
4
5
GET _analyze
{
"analyzer": "standard",
"text": "php 是世界上最好的语言"
}
效果如下:
分词效果
使用 standard
对 php 是世界上最好的语言
进行分词。
分词
:提取一句话或者一篇文章中的词语。
我们在使用 ElasticSearch
的时候,默认用 standard
分词器,但 standard
分词器使用的是按空格分词,这种分词操作方法不符合中文分词标准,我们需要额外安装中文分词器。
IK Analyzer
是一个开源的,基于 java 语言开发的轻量级的中文分词工具包。可以直接在 ElasticSearch
中集成 IK 分词器,IK
分词器集成ElasticSearch
下载地址:elasticsearch-analysis-ik/releases 。IK 可以跟 es 的版本一致,我们 es 的版本是 7.12.1,IK 的版本也是用 7.12.1。
IK 分词器
下载安装包 elasticsearch-analysis-ik/releases/tag/v7.12.1 后,,需要将 elasticsearch-analysis-ik-7.12.1.zip
拷贝到 elasticSearch
容器里的的 /usr/share/elasticsearch/plugins
目录中解压即可。
cp elasticsearch-analysis-ik-7.12.1.zip
解压文件成功后,然后重启 es 容器。
IK
分词器包含两种模式:
ik_smart
:最少切分
ik_max_word
:最细切分
前面使用默认的standard
分词器,对中文分词非常难用,安装IK分词器后,我们可以使用IK分词器测试,测试代码如下:
1
2
3
4
5
GET _analyze
{
"analyzer": "ik_max_word",
"text": "php 是世界上最好的语言"
}
测试效果如下:
效果
可以发现对中文的分词效果是比较不错的,但也存在一些不足,比如 最好的
我们希望它是一个词,而 上
我们希望它不被识别一个词,这种需求可以同过自定义词典解决。
IK 分词器支持自定义词典,包括自定义分词,也包含自定义停用分词,操作起来非常简单。接下来实现一下自定义词典和停用词典。
自定义词典,需要先创建自己的词典,再引用自己的词典即可。
创建词典
在 es 容器目录 /usr/share/elasticsearch/plugins/ik/config
中创建自己的词典,例如叫 custome.dic
,在文件中添加自定义的词语,操作如下:
创建词典
我们把自定义的词典 最好的
添加到了 custome.dic
中了,这就是创建词典,如果有多个自定义词,需要换行加入,这里一定要注意中文分词设置编码格式为 UTF-8
。
引用词典
修改 config/IKAnalyzer.cfg.xml
引用自己创建的 custome.dic
词典,配置如下:
引用自定义字典
再使用 Kibana
效果如下:
效果
自定义停用词典和自定义词典一样,需要先创建自己的词典,再引用自己的词典即可。
创建词典
在 config
中创建自己的停用词典,例如叫 stop.dic
,在文件中添加自定义的停用词语,操作如下:
停用词汇
引用词典
修改 config/IKAnalyzer.cfg.xml
引用自己创建的 stop.dic
停用词典,配置如下:
停用词典配置
修改完成后,重启 elasticsearch
容器。
再使用 Kibana
测试效果如下:
停用分词效果
可以发现,不再有 世界
,的
,是
,上
,语言
的分词了,说明停用分词也生效了。
现在有个需求是向 mysql 中插入文章,但是同时需要向 es 中写入文章,通过 es 来分词,优化搜索,通过 es 搜索时,可以返回 mysql 中对应的 id。
1
2
3
4
5
6
7
8
CREATE TABLE ` article `
(
` id ` int ( 10 ) unsigned NOT NULL AUTO_INCREMENT primary key ,
` title ` varchar ( 200 ) NOT NULL comment '文章标题' ,
` content ` text COMMENT '文章内容' ,
` create_time ` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ,
` update_time ` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '文章表' ;
golang 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/elastic/go-elasticsearch/v7/esapi"
_ "github.com/go-sql-driver/mysql"
"log"
"strings"
)
func main () {
// 初始化 MySQL 连接
db , err := sql . Open ( "mysql" , "root:123456@tcp(localhost:3306)/wb-test" )
if err != nil {
log . Fatal ( err )
}
defer db . Close ()
// 初始化 Elasticsearch 连接
esConfig := elasticsearch . Config {
Addresses : [] string { "http://localhost:9200" },
}
esClient , err := elasticsearch . NewClient ( esConfig )
if err != nil {
log . Fatal ( err )
}
// 插入文章到 MySQL
articleTitle := "蝶恋花"
articleContent := "花褪残红注青杏小。燕子飞时,绿水人家绕。枝上柳绵注吹又少。天涯何处无芳草。墙里秋千墙外道。墙外行人,墙里佳人笑。笑渐不闻声渐悄。多情却被无情恼。"
_ , err = db . Exec ( "INSERT INTO article (title, content) VALUES (?, ?)" , articleTitle , articleContent )
if err != nil {
log . Fatal ( err )
}
// 获取插入文章的 MySQL ID
var articleID int
err = db . QueryRow ( "SELECT LAST_INSERT_ID()" ). Scan ( & articleID )
if err != nil {
log . Fatal ( err )
}
// 在 Elasticsearch 中写入文章
esIndexName := "articles" // Elasticsearch 索引名
docID := fmt . Sprintf ( "%d" , articleID )
//esDoc := map[string]interface{}{
// "title": articleTitle,
// "content": articleContent,
// "mysql_id": articleID, // 将 MySQL ID 存储在 Elasticsearch 中
//}
// 创建 Elasticsearch 文档
esRequest := esapi . IndexRequest {
Index : esIndexName ,
DocumentID : docID ,
Body : strings . NewReader ( fmt . Sprintf ( `{
"title": "%s",
"content": "%s",
"mysql_id": %d
}` , articleTitle , articleContent , articleID )),
Refresh : "true" ,
}
res , err := esRequest . Do ( context . Background (), esClient )
if err != nil {
log . Fatal ( err )
}
defer res . Body . Close ()
// 从 Elasticsearch 中查询文章,包括 MySQL ID
//esQuery := map[string]interface{}{
// "query": map[string]interface{}{
// "match": map[string]interface{}{
// "title": "示例文章标题",
// },
// },
//}
esSearchRequest := esapi . SearchRequest {
Index : [] string { esIndexName },
Body : strings . NewReader ( fmt . Sprintf ( `{
"query": {
"match": {
"title": "示例文章标题"
}
}
}` )),
}
res , err = esSearchRequest . Do ( context . Background (), esClient )
if err != nil {
log . Fatal ( err )
}
defer res . Body . Close ()
// 处理 Elasticsearch 查询结果
if res . IsError () {
log . Fatalf ( "Error: %s" , res . Status ())
}
// 解析查询结果
var response map [ string ] interface {}
if err := json . NewDecoder ( res . Body ). Decode ( & response ); err != nil {
log . Fatalf ( "Error parsing the response body: %s" , err )
}
hits := response [ "hits" ].( map [ string ] interface {})[ "hits" ].([] interface {})
for _ , hit := range hits {
source := hit .( map [ string ] interface {})[ "_source" ].( map [ string ] interface {})
title := source [ "title" ].( string )
content := source [ "content" ].( string )
mysqlID := source [ "mysql_id" ].( float64 ) // 从 Elasticsearch 中获取 MySQL ID
fmt . Printf ( "标题: %s内容: %sMySQL ID: %d" , title , content , int ( mysqlID ))
}
}
通过 kibana 查看到 es 现在有 2 条数据:
结果数据
在 kibana 中通过查询接口搜索 content 内容包含 “绕” 的,展示效果如下:
搜索结构
查询 content 含有 “11111” 或 title 中含有 “悯” 的数据结果如下:
查询结果