Go项目优化——使用Elasticsearch搜索引擎

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: Go项目优化——使用Elasticsearch搜索引擎

案例:

http准备

util/http.go

用于向es服务器发送json格式的Put和Post请求

package util
import (
  "errors"
  "github.com/astaxie/beego/httplib"
  "github.com/bitly/go-simplejson"
  "io"
  "time"
)
// HttpPutJson
// @Title HttpPutJson
// @Description 用于向es服务器发送put请求(新建索引or添加文档)
func HttpPutJson(url, body string) error {
  resp, err := httplib.Put(url).
    Header("Content-Type", "application/json").
    SetTimeout(10*time.Second, 10*time.Second).
    Body(body).
    Response()
  if err == nil {
    defer resp.Body.Close()
    // 不正常的响应状态码
    if resp.StatusCode >= 300 || resp.StatusCode < 200 {
      // es会将错误信息写在body里 打印错误信息
      bodyErr, _ := io.ReadAll(resp.Body)
      body = string(bodyErr)
      err = errors.New(resp.Status + ";" + body)
    }
  }
  return err
}
// HttpPostJson
// @Title HttpPostJson
// @Description 用于向es服务器请求数据,查询数据
// @Param url string
// @Param body string 条件
// @Return *simplejson.Json es服务器返回的信息
func HttpPostJson(url, body string) (*simplejson.Json, error) {
  resp, err := httplib.Post(url).
    Header("Content-Type", "application/json").
    SetTimeout(10*time.Second, 10*time.Second).
    Body(body).
    Response()
  var sj *simplejson.Json
  if err == nil {
    defer resp.Body.Close()
    // 不正常的响应状态码
    if resp.StatusCode >= 300 || resp.StatusCode < 200 {
      bodyErr, _ := io.ReadAll(resp.Body)
      body = string(bodyErr)
      err = errors.New(resp.Status + ";" + body)
    } else {
      bodyBytes, _ := io.ReadAll(resp.Body)
      sj, err = simplejson.NewJson(bodyBytes)
    }
  }
  return sj, err
}

案例(新增):

建立索引+添加文档

发布图书的时候为图书和章节文档内容建立索引。

models/elasticSearch.go

package models
import (
  "es.study/util"
  "fmt"
  "github.com/PuerkitoBio/goquery"
  "github.com/astaxie/beego/logs"
  "strconv"
  "strings"
)
var (
  // (应写在配置文件里)搜索引擎配置,后面要加'/'
  elasticHost = "http://localhost:9200/"
)
// ElasticBuildIndex
// localhost:9200/index/_doc/doc_id
// index: 索引 对应sql里的表
// _doc: 文档类型,ES 7.0 以后的版本 已经废弃文档类型了,一个 index 中只有一个默认的 type,即 _doc。
// @Title ElasticBuildIndex
// @Description  指定id的图书增加索引
// @Author hyy 2022-10-14 21:06:27
// @Param bookId int 图书 id
func ElasticBuildIndex(bookId int) {
  // func(m *Book) Select(filed string, value interface{}, cols ...string)
  // SELECT [cols...] FROM books WHERE filed=value;
  book, _ := NewBook().Select("book_id", bookId, "book_id", "book_name", "description")
  addBookToIndex(book.BookId, book.BookName, book.Description)
  // index document
  var documents []Document
  fields := []string{"document_id", "book_id", "document_name", "release"}
  GetOrm("r").QueryTable(TNDoucments()).Filter("book_id", bookId).All(documents, fields...)
  if len(documents) > 0 {
    for _, document := range documents {
      // release: 已发布的章节
      addDocumentToIndex(document.DocumentId, document.BookId, flatHtml(document.Release))
    }
  }
}
// addBookToIndex
// @Title addBookToIndex
// @Description 向图书索引(相当于图书表)中,添加图书
// @Author hyy 2022-10-14 21:07:38
// @Param bookId int 图书id
// @Param bookName string 图书名
// @Param description string 图书描述
func addBookToIndex(bookId int, bookName, description string) {
  queryJson := `
    {
      "book_id":%v,
      "book_name":"%v",
      "description":"%v"
    }
  `
  // ElasticSearch API
  host := elasticHost
  api := host + "mbooks/_doc/" + strconv.Itoa(bookId)
  // 发起请求:
  queryJson = fmt.Sprintf(queryJson, bookId, bookName, description)
  err := util.HttpPutJson(api, queryJson)
  if err != nil {
    logs.Debug(err)
  }
}
// addDocumentToIndex
// @Title addDocumentToIndex
// @Description  向章节文档索引(相当于章节文档表)中,添加章节文档
// @Author hyy 2022-10-14 21:09:09
// @Param documentId int 文档id
// @Param bookId int 所属图书id
// @Param release string 文档发布内容
func addDocumentToIndex(documentId, bookId int, release string) {
  queryJson := `
    {
      "document:_id":%v,
      "book_id":%v,
      "release":"%v"
    }
  `
  // ElasticSearch API
  host := elasticHost
  api := host + "mdocument/_doc/" + strconv.Itoa(documentId)
  // 发起请求:
  queryJson = fmt.Sprintf(queryJson, documentId, bookId, release)
  err := util.HttpPutJson(api, queryJson)
  if err != nil {
    logs.Debug(err)
  }
}
// flatHtml
// 剔除章节里的html标签,取出文本
func flatHtml(htmlStr string) string {
  htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
  htmlStr = strings.Replace(htmlStr, "\"", "", -1)
  gq, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
  // 如果不为空,说明没有
  if err != nil {
    return htmlStr
  }
  return gq.Text()
}

在发布图书的时候,调用ElasticBuildIndex(bookId)接口,将图书信息以及章节内容添加到es中。

案例(查询):

搜索图书:

package models
import (
  "es.study/util"
  "fmt"
  "github.com/PuerkitoBio/goquery"
  "github.com/astaxie/beego/logs"
  "strconv"
  "strings"
)
var (
  // (应写在配置文件里)搜索引擎配置,后面要加'/'
  elasticHost = "http://localhost:9200/"
)
// ... 新增索引or添加文档
// ElasticSearchBook
// localhost:9200/index/_doc/_search
// index: 索引 对应sql里的表
// _doc: 文档类型,ES 7.0 以后的版本 已经废弃文档类型了,一个 index 中只有一个默认的 type,即 _doc。
// @Title ElasticSearchBook
// @Description 根据关键字搜索图书,获取图书的id
// @Author hyy 2022-10-14 20:49:37
// @Param kw string 关键字
// @Param pageSize int 页大小
// @Param page int 页码(可选)
// @Return []int bookId的数组
// @Return int 书的总数
// @Return error 错误
func ElasticSearchBook(kw string, pageSize, page int) ([]int, int, error) {
  var bookIds []int
  count := 0
  if page > 0 {
    // 第一页对应搜索引擎里的第0页
    page = page - 1
  } else {
    page = 0
  }
  queryJson := `
    {
      "query":{
        "multi_match":{
          "query":"%v",
          "fields":["bookName","description"]
        }
      },
      "_source":["book_id"],
      "size":%v,
      "from":%v
    }
  `
  // elasticSearch api
  host := elasticHost
  api := host + "mbook/_doc/_search"
  queryJson = fmt.Sprintf(queryJson, kw, pageSize, page)
  sj, err := util.HttpPostJson(api, queryJson)
  if err == nil {
    count = sj.GetPath("hits", "total").MustInt()
    resultArray := sj.GetPath("hits", "hits").MustArray()
    for _, result := range resultArray {
      if eachMap, ok := result.(map[string]interface{}); ok {
        id, _ := strconv.Atoi(eachMap["_id"].(string))
        bookIds = append(bookIds, id)
      }
    }
  }
  return bookIds, count, err
}
// ElasticSearchDocument
// @Title ElasticSearchDocument
// @Description 根据关键字搜索章节文档,返回章节文档的id,
// 该函数提供两种搜索:
//  1. 搜所有图书的章节文档
//  2. 搜某一本图书的章节文档,所以有个可选参数bookId
//
// @Author hyy 2022-10-14 20:56:54
// @Param kw string 关键字
// @Param pageSize int 页大小
// @Param page int 页码
// @Param bookId ...int 图书id(可选)
// @Return []int 章节文档的id数组
// @Return int 总数
// @Return error 错误
func ElasticSearchDocument(kw string, pageSize, page int, bookId ...int) ([]int, int, error) {
  var documentIds []int
  count := 0
  if page > 0 {
    // 第一页对应搜索引擎里的第0页
    page = page - 1
  } else {
    page = 0
  }
  queryJson := `
    {
      "query":{
        "match":{
          "release":"%v",
        } 
      },
      "_source":["document_id"],
      "size":%v,
      "from":%v
    }
  `
  queryJson = fmt.Sprintf(queryJson, kw, pageSize, page)
  if len(bookId) > 0 && bookId[0] > 0 {
    queryJson = `
      {
        "query":{
          "bool":{
            "filter":[{
              "term":{
                "book_id":%v
              }
            }],
            "must":{
              "multi_match":{
                "query":"%v",
                "fields":["release"]
              }
            }
          } 
        },
        "_source":["document_id"],
        "size":%v,
        "from":%v
      }
    `
    queryJson = fmt.Sprintf(queryJson, kw, pageSize, page)
  }
  // elasticSearch api
  host := elasticHost
  api := host + "mdocument/_doc/_search"
  sj, err := util.HttpPostJson(api, queryJson)
  if err==nil{
    count =  sj.GetPath("hits","total").MustInt()
    resultArray := sj.GetPath("hits", "hits").MustArray()
    for _, result := range resultArray {
      if eachMap, ok := result.(map[string]interface{}); ok {
        id, _ := strconv.Atoi(eachMap["_id"].(string))
        documentIds = append(documentIds, id)
      }
    }
  }
  return documentIds, count, err
}

关于sj.GetPath("hits","hits")原因如下:

es查询到多个结果的时候,返回结果如下:

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
———>"hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
———————>"hits": [ // 原始数据
            {
                "_index": "shopping",
                "_type": "_doc",
                "_id": "TYu9pn8BfWqG58AR7Mzw",
                "_score": 1.0,
                "_source": {
                    "title": "小米手机",
                    "category": "小米",
                    "images": "http://xxx.com/xm.jpg",
                    "price": 3999.00
                }
            },
            ...
        ]
    }
}

结果:

优化前:

优化后:

性能的具体提升使用ab自行进行压力测试

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
2月前
|
机器学习/深度学习 搜索推荐 关系型数据库
号称Elasticsearch 10倍性能搜索引擎到底有多强悍
号称Elasticsearch 10倍性能搜索引擎到底有多强悍
53 0
|
1月前
|
存储 算法 编译器
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
掌握Go语言:探索Go语言递归函数的高级奥秘,优化性能、实现并发、解决算法难题(28)
|
8天前
|
Go 开发者
Golang深入浅出之-Go语言项目构建工具:Makefile与go build
【4月更文挑战第27天】本文探讨了Go语言项目的构建方法,包括`go build`基本命令行工具和更灵活的`Makefile`自动化脚本。`go build`适合简单项目,能直接编译Go源码,但依赖管理可能混乱。通过设置`GOOS`和`GOARCH`可进行跨平台编译。`Makefile`适用于复杂构建流程,能定义多步骤任务,但编写较复杂。在选择构建方式时,应根据项目需求权衡,从`go build`起步,逐渐过渡到Makefile以实现更高效自动化。
25 2
|
8天前
|
存储 搜索推荐 Java
Java远程连接本地开源分布式搜索引擎ElasticSearch
Java远程连接本地开源分布式搜索引擎ElasticSearch
|
9天前
|
监控 安全 Go
【Go语言专栏】Go语言中的并发性能分析与优化
【4月更文挑战第30天】Go语言以其卓越的并发性能和简洁语法著称,通过goroutines和channels实现并发。并发性能分析旨在解决竞态条件、死锁和资源争用等问题,以提升多核环境下的程序效率。使用pprof等工具可检测性能瓶颈,优化策略包括减少锁范围、使用无锁数据结构、控制goroutines数量、应用worker pool和优化channel使用。理解并发模型和合理利用并发原语是编写高效并发代码的关键。
|
11天前
|
运维 关系型数据库 MySQL
Serverless 应用引擎产品使用之在阿里函数计算中,部署Go项目可以区分环境如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
15 0
|
26天前
|
监控 搜索推荐 安全
面经:Elasticsearch全文搜索引擎原理与实战
【4月更文挑战第10天】本文是关于Elasticsearch面试准备的博客,重点讨论了四个核心主题:Elasticsearch的分布式架构和数据模型、CRUD操作与查询DSL、集群管理与性能优化,以及安全与插件扩展。文中通过代码示例介绍了如何进行文档操作、查询以及集群管理,并强调理解Elasticsearch的底层原理和优化策略对面试和实际工作的重要性。
32 6
|
29天前
|
监控 数据可视化 搜索推荐
初识Elasticsearch:打造高效全文搜索与数据分析引擎
【4月更文挑战第7天】Elasticsearch,一款由Elastic公司开发的分布式搜索引擎,以其全文搜索和数据分析能力在全球范围内广泛应用。它基于Apache Lucene,支持JSON,适用于日志分析、监控等领域。Elasticsearch的亮点包括:精准快速的全文搜索,通过倒排索引和分析器实现;强大的数据分析与实时响应能力,提供丰富聚合功能;弹性扩展和高可用性,适应水平扩展和故障恢复;以及完善的生态系统,与Kibana、Logstash等工具集成,支持多种编程语言。作为大数据处理的重要工具,Elasticsearch在企业级搜索和数据分析中扮演关键角色。
25 1
|
2月前
|
数据采集 消息中间件 搜索推荐
搜索引擎 _ Elasticsearch(二)
搜索引擎 _ Elasticsearch
29 0
|
2月前
|
分布式计算 搜索推荐 Java
搜索引擎 _ Elasticsearch(一)
搜索引擎 _ Elasticsearch
38 0

热门文章

最新文章