2020年9月

在导入数据之前,我这里准备了一个book 表,在表中存储了一万条数据。

select * from books limit 1 \G;
*************************** 1. row ***************************
book_id: 220
book_name:  Java项目实战教程+Java程序设计与项目实训教程
book_intr:
book_price1: 72.00
book_price2: 72.50
book_author: 姜华刘闯
book_press: 清华大学出版社
book_date:  2012-09-01
book_kind_str: PHP
book_kind: 2
dd_id: 1328395676
1 row in set (0.001 sec)

我们现在想办法,将这些数据导入到 es 中,在这里我们是用 grom 来完成数据的读取,我们先导入 50 条数据。导入数据 之前我们应该先创建 mapping。

Untitled

先简单说一下 key 和 keyword 的区别,如果需要全文检索那么就用 text ,如果是要精准匹配那么就使用 keyword。

在上一节中我们说过id 这个属性,一般来说我们可以灌入自己的id,不适用 es 自动生成,要不然取数据的时候会比较麻烦。

插入数据的步骤

利用 grom 获取数据

定义db 工具类

package db

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "log"
)

var db *gorm.DB

func init() {
    var err error
    db, err = gorm.Open("mysql",
        "root:123123@tcp(localhost:3306)/books?charset=utf8mb4&parseTime=True&loc=Local")
    if err != nil {
        log.Fatal(err)
    }
    db.SingularTable(true)
    db.DB().SetMaxIdleConns(5)
    db.DB().SetMaxOpenConns(10)

}
func  GetDB() *gorm.DB {
    return db
}

定义 model

package model

type Books struct {
    BookID     int     `gorm:"column:book_id;AUTO_INCREMENT;PRIMARY_KEY"`
    BookName   string  `gorm:"column:book_name;type:varchar(50)"`
    BookIntr   string  `gorm:"column:book_intr;type:text"`
    BookPrice1 float64 `gorm:"column:book_price1;type:decimal"`
    BookPrice2 float64 `gorm:"column:book_price2;type:decimal"`
    BookAuthor string  `gorm:"column:book_author;type:varchar(50)"`
    BookPress  string  `gorm:"column:book_press;type:varchar(50)"`
    BookDate   string  `gorm:"column:book_date;type:varchar(50)"`
    BookKind   int     `gorm:"column:book_kind;type:int"`
}
type BookList []*Books

获取数据

package main

import (
    "es/db"
    "es/model"
    "fmt"
)

func main() {
    page := 1
    pageSize := 50
    for {
        bookList := model.BookList{}
        dbConn := db.GetDB().Order("book_id desc").Limit(pageSize).Offset((page - 1) * page).Find(&bookList)
        if dbConn.Error != nil || len(bookList) == 0 {
            break
        }
        fmt.Println(bookList[1])
        break
    }
}

# 
# &{19551  C语言程序设计教程(第2版) 导语_点评_推荐词 27.2 32  湘潭大学出版社  8}

在 main 函数中我们用分页的方式获取数据,每一次获取 50 条。

es 插入数据

我们先将 esclient 抽出一个单独的包。

package es

import (
    "github.com/olivere/elastic/v7"
)

func GetEsClient() (*elastic.Client, error) {
    client, err := elastic.NewClient(
        elastic.SetURL("http://192.168.124.16:9200/"),
        elastic.SetSniff(false),
    )
    if err != nil {
        return nil, err
    }
    return client, nil
}

在之前插入我们使用的是写死的 json 字符串,在这里我们使用模型。

package main

import (
    "context"
    "es/db"
    "es/es"
    "es/model"
    "fmt"
    "log"
    "strconv"
)

func main() {
    page := 1
    pageSize := 50
    client, err := es.GetEsClient()
    if err != nil {
        log.Fatal(err)
    }
    for {
        bookList := model.BookList{}
        dbConn := db.GetDB().Order("book_id desc").Limit(pageSize).Offset((page - 1) * page).Find(&bookList)
        if dbConn.Error != nil || len(bookList) == 0 {
            break
        }
        fmt.Println(bookList[1])

        for _, book := range bookList {
            resp, err := client.Index().Index("books").Id(strconv.Itoa(book.BookID)).BodyJson(book).Do(context.Background())
            if err != nil {
                fmt.Println(err)
            } else {
                fmt.Println(resp)
            }
        }

        break
    }
}

接下来我们执行代码,后查看统计:

curl http://192.168.124.16:9200/books/_count
{"count":50,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0}}

我们可以看到在 es 中已经有 50 条数据了。

$ curl http://192.168.124.16:9200/books/_search | python -m json.tool
{
    "_shards": {
        "failed": 0,
        "skipped": 0,
        "successful": 1,
        "total": 1
    },
    "hits": {
        "hits": [
            {
                "_id": "19552",
                "_index": "books",
                "_score": 1.0,
                "_source": {
                    "BookAuthor": "\u848b\u6e05\u660e",
                    "BookDate": " 2008-10-01",
                    "BookID": 19552,
                    "BookIntr": "\u5bfc\u8bed_\u70b9\u8bc4_\u63a8\u8350\u8bcd",
                    "BookKind": 8,
                    "BookName": " C\u8bed\u8a00\u7a0b\u5e8f\u8bbe\u8ba1\u5b9e\u9a8c\u6307\u5bfc\u4e0e\u4e60\u9898\u89e3\u7b54",
                    "BookPress": "\u4eba\u6c11\u90ae\u7535\u51fa\u7248\u793e",
                    "BookPrice1": 19,
                    "BookPrice2": 19
                },
                "_type": "_doc"
            },
....
}

/books/_search 如果没有接参数的话会默认显示 10 条数据。

在上一篇博文中,我们通过 curl 请求项 es 中添加了一条索引,提交数据的本质也是发送 http 请求。不过我们使用 curl 发送请求的时候没有提示,接下来我们使用 kibana 来提交数据。

目前大多数开发者应该都听说过 elk,e 就是es,其中的 k,就是 kibana ,一款为 es 设计的可视化工具,我们可以使用 kibana 来操作 es。

安装 kibana

docker pull blacktop/kibana:7.4

docker tag blacktop/kibnana:7.4 kb:7.4

版本必须和 es 保持一致。

启动 kibana

docker run --init -d --name kb -e elasticsearch.hosts="http://192.168.124.16:9200" \
 -p 5601:5601 kb:74

当启动成功后,我们访问 http://192.168.124.16:5601,点击 consolse。

console

在 kibana 中输入请求是有提示的:

代码提示

我们点击 play 按钮就会发送请求到 es 中。

play_button

这样我们就获得了在上节中设置的mapping。

用 GoSDK 向 ES 中插入第一条数据

接下来我们新增一个数据。

package main

import (
    "context"
    "fmt"
    "github.com/olivere/elastic/v7"
    "log"
)

func main() {
    client, err := elastic.NewClient(
        elastic.SetURL("http://192.168.124.16:9200/"),
        elastic.SetSniff(false),
    )
    if err != nil {
        log.Fatal(err)
    }
    ctx := context.Background()
    json := `{"news_title": "test1", "news_type":"php", "news_status":1}`
    data, err := client.Index().Index("news").Id("101").BodyString(json).Do(ctx)
    if err != nil {
        log.Fatal(data)
    }

    fmt.Println(data)
}
  • 在 es 中有 id 的概念,我们可以指定也可以自动生成,在这里我们指定了 id 为 101。
  • 我们用 BodyString 传入一个写死的 json 字符串,在真是项目中肯定是不会这么写!

接下来我们回到 kibana 中查看数据。

get

在 kibana 中可以查到我们的数据,证明写入成功了,其中 _source 存储的就是具体的值。

如果我们想要删除,只需要将 get 改为 DELETE 就可以了。

delete

successful:1 表示删除成功了,再去查询会发现 found: false,表示没有查询到数据。

get2

快速部署

这里为了方便直接使用 Docker,官方镜像是 CentOS 镜像,不够美丽,因此我们使用了第三方的镜像,体积比较小,适合我们在学习过程中快速部署。

docker pull blacktop/elasticsearch:7.4

下载好后修改 tag,不然太长了

docker tag blacktop/elasticsearch:7.4 es:74

运行 ES。

docker run  -d --name es -p 9200:9200 es:74

如果我们是在虚拟机中使用 docker 那么内存尽量要大了一点,至少

有 4G 的内存。

执行 curl 看一下容器是否启动成功:

$ curl [http://192.168.124.16:9200/](http://192.168.124.16:9200/)
{
"name" : "832e35c81ff4",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "tfwkn9ZKQNO0PD0iNfqdwg",
"version" : {
"number" : "7.4.2",
"build_flavor" : "oss",
"build_type" : "tar",
"build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
"build_date" : "2019-10-28T20:40:44.881551Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}

GolangSDK

Go 的 SDK我们也选择第三方的库,因为目前官方的库还没有第三方实用。

go get github.com/olivere/elastic/v7

mapping

mapping 映射类似于在数据库中定义表结构,表里里有哪些字段、字段是什么类型。没有mapping 也能够创建索引,但是正规的方式是自己来创建 mapping,而不是动态映射。

例如现在一个 MySQL 中有一个news 表,其中存储的是新闻,表结构如下:

字段 name备注
news_id新闻 id
news_title新闻标题
news_type新闻类型(目前为中文)
news_status0 代表下架 1 正常 2 不可修改

在向 es 中导入的时候,我们可以不创建这个表结构,直接向 es中写入数据,es 可以进行类型推断,但是有的时候会推测错误,一般来说我们得先创建表结构。

同时向 es 写入数据有两种模式:

  1. 直接调用 es 的 api
  2. 通过 logstat 导入数据

一般我们不会选择第一种,而是直接选择使用 logstat 导入。

在 es 中字段一旦增加就不能删除了,只能新增,所以在最开始我们尽量就设计好,要不更改的时候会比较麻烦。

我们现在来创建 mapping,es 为我们提供了接口,我们使用 curl 发送请求创建接口

$ curl --location --request PUT 'http://192.168.124.16:9200/news' \
> --header 'Content-Type: application/json' \
> --data-raw '{
>     "mappings" : {
>         "properties" : {
>             "news_title": {
>                 "type": "text"
>             },
>             "news_type": {
>                 "type" : "keyword"
>             },
>             "news_status": {
>                 "type": "byte"
>             }
>         }
>     }
> }'
{"acknowledged":true,"shards_acknowledged":true,"index":"news"}

/news 是我们的索引名称,—data-raw 中就是我们要创建的数据。

创建完成后我们通过 get 方式可以查看 mapping。

curl --location --request GET 'http://192.168.124.16:9200/news/_mapping'
{"news":{"mappings":{"properties":{"news_status":{"type":"byte"},"news_title":{"type":"text"},"news_type":{"type":"keyword"}}}}}%

Go 查看 Mapping

接下来,我们用 golang 实现获取 mapping,代码其实非常简单:

package main

import (
    "context"
    "fmt"
    "github.com/olivere/elastic/v7"
    "log"
)

func main() {
    client, err := elastic.NewClient(
        elastic.SetURL("http://192.168.124.16:9200/"),
        elastic.SetSniff(false),
    )
    if err != nil {
        log.Fatal(err)
    }
    ctx := context.Background()
    mapping, err := client.GetMapping().Index("news").Do(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(mapping)
}

# 代码运行结果
# map[news:map[mappings:map[properties:map[news_status:map[type:byte] news_title:map[type:text] news_type:map[type:keyword]]]]]

这里需要注意的是 elastic.SetSniff(false) ,当我们使用 elastic 的时候会自动转换地址,不过获取的地址由于我们使用的是 docker,这就导致获取IP 地址的时候其实得到的是容器的内容地址,我们是否无法访问的。