Elassticsearch
什么是全文搜索
我想找简历:只要出现java 、 go 原本的需求就是只要简历中出现了 go 分布式 elasticsearch 都是我要的
我们生活中的数据总体分为两种:结构化数据和非结构化数据。
- **结构化数据:**指具有固定格式或有限长度的数据,如数据库,元数据等。
- **非结构化数据:**指不定长或无固定格式的数据,如邮件,word文档等。
非结构化数据另一种叫法叫全文数据。
按照数据的分类,搜索也分为两种:
- 对结构化数据的搜索:如对数据库的搜索,用SQL语句。再如对元数据的搜索,如利用windows搜索对文件名,类型,修改时间进行搜索等。
- 对非结构化数据的搜索:如利用windows的搜索也可以搜索文件内容,Linux下的grep命令,再如用Google和百度可以搜索大量内容数据。
对非结构化数据也即对全文数据的搜索主要有两种方法:
一种是顺序扫描法(Serial Scanning):所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。假如有一个80G硬盘,如果想在上面找到一个内容包含某字符串的文件,可能需要几个小时的时间。Linux下的grep命令也是这一种方式。这是一种比较原始的方法,但对于小数据量的文件,这种方法还是最直接,最方便的。但是对于大量的文件,这种方法的速度就很慢。
另一种是全文检索(Full-text Search):即先建立索引,再对索引进行搜索。索引是从非结构化数据中提取出之后重新组织的信息。
什么是elasticsearch
Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene™ 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:
- 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
- 实时分析的分布式搜索引擎。
- 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。
ES的适用场景
- 维基百科
- The Guardian、新闻
- Stack Overflow
- Github
- 电商网站、检索商品
- 日志数据分析、logstash采集日志、ES进行复杂的数据分析(ELK)
- 商品价格监控网站、用户设定价格阈值
- BI系统、商业智能、ES执行数据分析和挖掘
ES特点
- 可以作为一个大型的分布式集群(数百台服务器)技术,处理PB级数据,服务大公司,可以运行在单机上,服务小公司。
- ES不是什么新技术,主要是将全文检索、数据分析以及分布式技术合并在一起,才形成了独一无二的ES.lucene(全文检索)、商用的数据分析软件、分布式数据库 (mycat)
- 对用户而言,是开箱即用,非常简单,作为中小型的应用,直接3分钟部署ES,就可以作为生产环境的系统使用,数据量不大,操作不是很复杂。
- 数据库的功能面对很多领域是不够用的(事务,还有各种联机事务的操作):特殊的功能,比如全文检索、同义词处理、相关度排名、复杂数据分析、海量数据近实时处理;ES作为传统数据库的一个补充,提供了数据库所不能提供的很多功能。
elasticsearch安装
1. 关闭并禁用防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
systemctl status firewalld.service
2. 通过docker安装elasticsearch
#新建es的config配置文件夹
mkdir -p /data/elasticsearch/config
#新建es的data目录
mkdir -p /data/elasticsearch/data
#新建es的plugins目录
mkdir -p /data/elasticsearch/plugins
#给目录设置权限
chmod 777 -R /data/elasticsearch
#写入配置到elasticsearch.yml中, 下面的 > 表示覆盖的方式写入, >>表示追加的方式写入,但是要确保http.host: 0.0.0.0不能被写入多次
echo "http.host: 0.0.0.0" >> /data/elasticsearch/config/elasticsearch.yml
#安装es
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms128m -Xmx256m" \
-v /data/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/elasticsearch/data:/usr/share/elasticsearch/data \
-v /data/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.10.1
3. 通过docker安装 kibana
docker run -d --name kibana -e ELASTICSEARCH_HOSTS="http://192.168.0.104:9200" -p 5601:5601 kibana:7.10.1
新建数据
查看索引
GET _cat/indices
通过put+id新建数据
在customer下保存id为1的数据, 这里id是必须的
PUT /account/_doc/1
{
"name":"bobby",
"age":18,
"company":{
"name":"xiaoxi",
"address":"beijing"
}
}
同一个请求发送多次,下面的信息会产生变化
"_version" : 11,
"result" : "updated", #这里第一次是created,后续都是updated
"_seq_no" : 10, #版本号
#### 发送post不带id新建数据
POST user/_doc/
{
"name":"likezhu"
}
POST user/_doc/2
{
"name":"likezhu"
}
如果post带id就和put一样的操作了, put是不允许不带id的
post + _create
没有就创建,有就报错
POST user/_create/1
{
"name":"likezhu"
}
查看index
GET _cat/indices //查看所有索引
GET /account //查看index的基本信息
获取数据
获取数据
GET user/_doc/1
{
"_index" : "user", //所属
"_type" : "_doc",
"_id" : "1",
"_version" : 11, #版本号
"_seq_no" : 10, #并发控制,用于乐观锁
"_primary_term" : 1, #同_seq_no, 主分片重新分配, 如重启,就会变化
"found" : true,
"_source" : {
"name" : "bobby"
}
}
只返回source的值
GET user/_source/1
搜索数据
Elasticsearch有两种查询方式
11. URI带有查询条件(轻量查询)
查询能力有限,不是所有的查询都可以使用此方式
12. 请求体中带有查询条件(复杂查询)
查询条件以JSON格式表现,作为查询请求的请求体,适合复杂的查询
通过url查询数据
请求参数位于_search端点之后,参数之间使用&分割,例如:
GET /_search?pretty&q=title:azure&explain=true&from=1&size=10&sort=title:asc&fields:user,title,content
搜索API的最基础的形式是没有指定任何查询的空搜索,它简单地返回集群中所有索引下的所有文档。
通过request body查询数据
GET account/_search
{
"query": {
"match_all": {}
}
}
更新数据
想要给已有的数据新增字段
POST users/_doc/1
{
"age":18
}
此时会发现,已有的数据的name字段没有了,只有age字段
此时我们需要使用
POST user/_update/1
{
"doc": {
"age":18
}
}
删除数据
删除数据和索引
DELETE users/_doc/1
DELETE users
批量插入和批量查询
批量操作(bulk)
用法
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
字段说明:
{
"account_number" : 6, //银行账户
"balance" : 5686, // 余额
"firstname" : "Hattie",
"lastname" : "Bond",
"age" : 36,
"gender" : "M",
"address" : "671 Bristol Street",
"employer" : "Netagy", //所在公司
"email" : "hattiebond@netagy.com",
"city" : "Dante", //城市
"state" : "TN" //国家
}
批量获取(mget)
GET /_mget
{
"docs": [
{
"_index": "my-index-000001",
"_id": "1"
},
{
"_index": "my-index-000001",
"_id": "2"
}
]
}
全文查询 - 分词
match查询(匹配查询)
match:模糊匹配,需要指定字段名,但是输入会进行分词,比如”hello world”会进行拆分为hello和world,然后匹配,如果字段中包含hello或者world,或者都包含的结果都会被查询出来,也就是说match是一个部分匹配的模糊查询。查询条件相对来说比较宽松。
GET user/_search
{
"query": {
"match": {
"address": "street"
}
}
}
match_phrase查询 短语查询
match_phase:会对输入做分词,但是需要结果中也包含所有的分词,而且顺序要求一样。以”hello world”为例,要求结果中必须包含hello和world,而且还要求他们是连着的,顺序也是固定的,hello that word不满足,world hello也不满足条件。
GET user/_search
{
"query": {
"match_phrase": {
"address": "Madison street"
}
}
}
multi_match查询
multi_match查询提供了一个简便的方法用来对多个字段执行相同的查询,即对指定的多个字段进行match查询
POST resume/_doc/12
{
"title": "后端工程师",
"desc": "多年go语言开发经验, 熟悉go的基本语法, 熟悉常用的go语言库",
"want_learn":"python语言"
}
POST resume/_doc/13
{
"title": "go工程师",
"desc": "多年开发经验",
"want_learn":"java语言"
}
POST resume/_doc/14
{
"title": "后端工程师",
"desc": "多年开发经验",
"want_learn":"rust语言"
}
GET account/_search
{
"query": {
"multi_match": {
"query": "go",
"fields": ["title", "desc"]
}
}
}
query_string查询
query_string:和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
GET user/_search
{
"query":{
"query_string": {
"default_field": "address",
"query": "Madison AND street"
}}}
2. match all查询
GET user/_search
{
"query": {
"match_all": {}
}
}
term 级别查询
term查询
term: 这种查询和match在有些时候是等价的,比如我们查询单个的词hello,那么会和match查询结果一样,但是如果查询”hello world”,结果就相差很大,因为这个输入不会进行分词,就是说查询的时候,是查询字段分词结果中是否有”hello world”的字样,而不是查询字段中包含”hello world”的字样,elasticsearch会对字段内容进行分词,“hello world”会被分成hello和world,不存在”hello world”,因此这里的查询结果会为空。这也是term查询和match的区别。
GET user/_search
{
"query": {
"term": {
"address": "madison street"
}
}
}
range查询 - 范围查询
GET user/_search
{
"query":{
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
}
exists查询
GET user/_search
{
"query": {
"exists": {
"field": "school"
}
}
}
fuzzy模糊查询
GET user/_search
{
"query": {
"match": {
"address": {
"query": "Midison streat",
"fuzziness": 1
}
}
}
}
GET /_search
{
"query": {
"fuzzy": {
"user.id": {
"value": "ki"
}
}
}
}
复合查询
Elasticsearch bool查询对应Lucene BooleanQuery, 格式如下
{
"query":{
"bool":{
"must":[
],
"should":[
],
"must_not":[
],
"filter":[
],
}
}
}
must: 必须匹配,查询上下文,加分
should: 应该匹配,查询上下文,加分
must_not: 必须不匹配,过滤上下文,过滤
filter: 必须匹配,过滤上下文,过滤
bool查询采用了一种匹配越多越好的方法,因此每个匹配的must或should子句的分数将被加在一起,以提供每个文档的最终得分
GET user/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"state": "tn"
}
},
{
"range": {
"age": {
"gte": 20,
"lte": 30
}
}
}
],
"must_not": [
{
"term": {
"gender": "m"
}
}
],
"should": [
{
"match": {
"firstname": "Decker"
}
}
],
"filter": [
{
"range": {
"age": {
"gte": 25,
"lte": 30
}
}
}
]
}
}
}
go使用gin框架代码实现es(示例):
package main
import (
"context"
"fmt"
"strconv"
"encoding/json"
"github.com/olivere/elastic/v7" // 请确保导入适合你的Elasticsearch版本
"your_project/model" // 请根据实际项目路径修改
)
var (
esClient *elastic.Client // Elasticsearch客户端
err error // 错误处理
ctx context.Context // 上下文
)
// 初始化函数,用于单例模式的Elasticsearch客户端实例化
func init() {
esClient, err = elastic.NewClient(
elastic.SetURL("http://localhost:9200"), // 设置Elasticsearch的URL
elastic.SetSniff(false)) // 关闭节点嗅探
if err != nil {
// 处理错误
panic(err) // 如果创建客户端失败,则程序崩溃
}
// 初始化全局变量ctx为背景上下文
ctx = context.Background()
}
// 创建索引函数
func CreateIndex(indexName string) error {
// 判断索引是否存在
exists, err := esClient.IndexExists(indexName).Do(ctx)
if err != nil {
// 处理错误
fmt.Println("Error checking index existence:", err)
return err
}
if !exists {
// 如果索引不存在,创建一个新的索引
createIndex, err := esClient.CreateIndex(indexName).BodyString(mapping).Do(ctx)
if err != nil {
// 处理错误
panic(err) // 创建索引失败则程序崩溃
}
fmt.Println("Index created:", createIndex)
}
return nil
}
// 从MySQL到Elasticsearch的批量导入函数
func MySqlToEsBulk(indexName string) {
videos := []model.Video{} // 定义一个切片来存储视频数据
model.DB.Model(&model.Video{}).Offset(0).Limit(100).Find(&videos) // 从数据库查询视频数据
// 创建一个bulk请求
bulkRequest := esClient.Bulk()
for _, v := range videos {
// 为每个视频创建一个bulk请求动作
req := elastic.NewBulkIndexRequest().Index(indexName).Id(strconv.Itoa(int(v.Id))).Doc(v)
bulkRequest = bulkRequest.Add(req) // 添加请求到bulk请求中
}
// 执行bulk请求
_, err := bulkRequest.Do(ctx)
if err != nil {
fmt.Println("Failed to execute bulk request:", err)
}
fmt.Println("Successfully executed bulk request")
}
// 添加文档到Elasticsearch的函数
func AddDoc() {
// 创建一个视频实例
tweet1 := model.Video{
Id: 8888888,
Title: "间谍过家家",
SubTitle: "安尼亚",
ChannelId: 1,
Status: 0,
EpisodesCount: 32,
IsEnd: 0,
IsHot: 0,
RegionId: 0,
TypeId: 0,
EpisodesUpdateTime: 0,
Comment: 0,
UserId: 0,
IsRecommend: 0,
}
// 将视频实例索引到Elasticsearch
put1, err := esClient.Index().
Index("videos"). // 指定索引名
Id("8888888"). // 指定文档ID
BodyJson(tweet1). // 使用JSON格式的文档体
Do(ctx)
if err != nil {
// 处理错误
panic(err) // 索引文档失败则程序崩溃
}
fmt.Printf("Indexed video %s to index %s\n", put1.Id, put1.Index)
}
// 在Elasticsearch中执行搜索的函数
func EsSearch(indexName string, termQuery *elastic.TermQuery, offset, limit int) (int64, []model.Video, error) {
// 执行搜索请求
searchResult, err := esClient.Search().
Index(indexName). // 指定索引名
Query(termQuery). // 添加查询条件
From(offset).Size(limit). // 设置分页
Pretty(true). // 使输出更美观
Highlight(elastic.NewHighlight().Field("title").PreTags("<span style='color:red;'>").PostTags("</span>")). // 高亮标题
Do(ctx) // 执行搜索请求
if err != nil {
return 0, nil, err // 返回错误
}
// 处理搜索结果
var results []model.Video
if searchResult.Hits.TotalHits.Value > 0 {
for _, hit := range searchResult.Hits.Hits {
var searchData model.Video
// 高亮标红展示
if hit.Highlight != nil && hit.Highlight["title"] != nil {
searchData.HighTitle = hit.Highlight["title"][0]
}
err := json.Unmarshal(hit.Source, &searchData) // 反序列化搜索结果
if err != nil {
return 0, nil, err // 返回错误
}
results = append(results, searchData) // 添加到结果集中
}
} else {
return 0, nil, nil // 如果没有结果,返回
}
return searchResult.Hits.TotalHits.Value, results, nil // 返回总命中数和结果集
}
入口函数
package main
import (
"gin_video/router"
"gin_video/server"
)
func main() {
//检测索引和创建索引
//server.CreateIndex("videos")
//server.MySqlToEsBulk("videos")
//server.AddDoc()
r := router.InitRouter()
r.Run(":8888") // 监听并在 0.0.0.0:8080 上启动服务
}
控制器
func SearchVideo(c *gin.Context) {
title := c.Query("title")
offset, limit := Page(c)
termQuery := elastic.NewTermQuery("title", title)
nums, videos, _ := server.EsSearch("videos", termQuery, offset, limit)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": map[string]interface{}{
"total_count": nums,
"video": videos,
},
"msg": "success",
})
}