es基础

索引#

Copy Highlighter-hljs
// 1、向目标url传递 JSON形式的参数 建立hotel索引 使用-d发送带参数的请求(默认是post提交)
curl -H "Content-Type:application/json" -XPUT 127.0.0.1:9200/hotel -d
{
"mappings":{
"properties":{
"title":{
"type":"text" //text文本类型 对关键词进行模糊搜索
},
"city":{
"type":"keyword" // keyword普通关键词类型 可以进行相等判断
},
"price":{
"type":"double" // 双精度浮点型 对大小进行比较的判断
}
}
}
}
Copy Highlighter-hljs
// 创建一条 ID 为 001 的文档
POST /hotel/_doc/001
{
"title":"青岛再来酒店",
"city":"青岛",
"price":588.78
}
// 根据ID 搜索文档
GET /hotel/_doc/001
// 根据一般字段搜索文档
GET /hotel/_search
{
"query":{
"term":{
"price":{ // 根据价格搜索文档
"value":578.78
}
}
}
}
// 根据文本字段搜索文档
GET /hotel/_search
{
"query":{
"match":{ // 根据title字段搜索
"title":"再来"
}
}
}
Copy Highlighter-hljs
// 2、索引操作
// 创建索引
PUT /hotel
{
"setting":{
"number_of_shards":15 // 指定主分片个数
"number_of_replicas":2 // 指定副分片个数
},
"mappings":{
"properties":{
...
}
}
}
// 删除索引
DELETE /hotel
// 关闭索引
// 在有些场景下,某个索引暂时不使用,但后期可以会使用,使用是指数据写入和数据搜索。
POST /hotel/_close
// 打开索引
POST /hotel/_open
// 索引别名
// 别名是指给一个或者多个索引定义另外一个名称,使索引别名和索引之间可以建立某种逻辑关系
// 如果要搜索 1月 2月 3月的日志,可以给这三个月的索引建立相同的别名
POST /_aliases
{
"actions":[
{
"add":{ // 为索引january_log 建立别名 last_three_month
"index":"january_log",
"alias":"last_three_month"
// 设置january_log 为索引别名 last_three_month 的数据写入对象
"is_write_index":true
}
},
{
"add":{ // 为索引february_log 建立别名 last_three_month
"index":"february_log",
"alias":"last_three_month"
}
},
{
"add":{ // 为索引march_log 建立别名 last_three_month
"index":"march_log",
"alias":"last_three_month"
}
}
]
}
// 请求搜素索引 last_three_month的数据 uid 001 的用户记录时,ES会分别转发到january_log february_log march_log 3个索引中
GET /last_three_month/_search
{
"query":{
"term":{
"uid":"001"
}
}
}
// 使用别名表示索引之间的替代关系,主要用于更改索引参数优化,eg:hotel_1索引创建后,主分片不能更改,别名hotel。
// 此时可以创建hotel_2,除主分片个数和hotel_1不同,其他设置和hotel_1相同,
// 当hotel_2的索引数据准备好后,删除hotel_1索引的别名,同时,设置hotel_2的别名为hotel。
Copy Highlighter-hljs
//3、映射操作
//关系数据库中叫表结构,在ES中叫映射。
// 查看映射
GET /hotel/_mapping
// 映射中的字段类型不可以修改,但是字段可以扩展。最常见的扩展方式是增加字段和为object(对象)类型的数据新增属性。
POST /hotel/_mapping
{
"properties":{
"tag":{
"type":"keyword"
}
}
}

数据类型#

Copy Highlighter-hljs
// 基本的数据类型
// keyword类型
// 该类型是不进行切分的字符串类型。"不进行切分"指的是:在索引时,对keyword类型的数据不进行切分,直接构建倒排索引。
// keyword类型数据一般用于比较字符串是否相等,不对数据进行部分匹配,因此查询这种类型数据时使用term查询。
// keyword类型一般用于对文档的过滤、排序和聚合。
// text类型
// 该类型是可进行切分的字符串类型。"可切分"指的是:在索引时,可按照相应的切词算法对文本内容进行切分,然后构建倒排索引。
// 在搜索时,对该类型的查询字符串按照用户的切词算法进行切分,然后对切分后的部分匹配打分。
// term搜索用于搜索值和文档对应的字段是否完全相等,对于text类型字段,使用term搜索不到数据。
// match搜索的是text类型
// 数值类型
// ES支持的数值类型有long integer short byte double float half_float scaled_float unsigned_long
// 对于数值型数,一般使用term搜素或者range范围搜索。
// 布尔类型
// 使用boolean定义,用于业务中的二值表示。其值可以是true或false,或者是字符串形式的"true"和"false",使用term查询。
// 日期类型
// ES中日期类型名称为date。ES中存储的日期是标准的UTC格式。
// 日期类型搜索一般使用range查询。
// 日期类型默认不支持yyyy-MM-dd HH:mm:ss格式,如果经常使用这种格式,可以在索引的mapping中设置日期字段的format属性的自定义格式。
PUT /hotel
{
"mappings":{
"properties":{
"create_time":{ // 指定日期型字段的格式 此时写入的数据格式也要是这种
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss"
}
}
}
}
// 复杂的数据类型
// 数组类型
// ES数组没有定义方式,使用方式是开箱即用的,即无须事先声明,在写入时把数据用中括号[]括起来,由ES对该字段完成定义。
// 如果事先已经定义了字段类型,在写入数据时以数组形式写入,ES也会将该类型转为数组。
// 为hotel索引增加一个标签字段
PUT /hotel/_mapping
{
"properties":{
"tag":{
"type":"keyword"
}
}
}
// 写入数据
POST /hotel/_doc/001
{
...
"tag":["车位","免费WIFI"]
}
// 查看写入的数据
{
...
"_source":{
....
"tag":[ // tag字段自动转换为字符串数组类型
"车位","免费WIFI"
]
}
}
// 注:数组类型的字段适用于元素类型的搜索方式,也就是数组元素适用于什么搜索,数组字段就适用于什么搜索。
// 上面数组元素类型是keyword,tag字段可以适用于term搜索
Copy Highlighter-hljs
// 对象类型
// 和数组类型一样,对象类型也不用事先定义,在写入文档的时候ES会自动识别并转换为对象类型。
// 实际业务中,一个文档需要包含其他内部对象。例如:酒店搜索需求中,用户希望酒店信息中包含评论数据,分为好评数和差评数。
// 为了支持这种类型 ES使用使用对象类型。
POST /hotel/_doc/001
{
...
"comment_info":{
"properties":{
"favourable_comment":199,
"negative_comment":68
}
}
}
//执行后,索引hotel增加了一个字段 comment_info,它有两个属性,分别为favourable_comment和negative_comment
// 查询mapping
{
...
"comment_info":{ // 评论数据
"properties":{
"properties":{
"properties":{
"favourable_comment":{ // 好评数据 类型long
"type":"long"
},
"negative_comment":{ // 差评数据 类型 long
"type":"long"
}
}
}
}
}
}
// 根据对象类型中的属性进行搜索,可以直接使用"."操作符进行指向。
GET /hotel/_search
{
"query":{
"range":{ // 使用range搜索对象类型的数据
"comment_info.properties.favourable_commnet":{
"gte":200
}
}
}
}
// 地理类型
// 该类型的定义需要在mapping中指定目标字段的数据类型为 geo_point类型。
PUT /hotel
{
"mappings":{
"properties":{
...
"location":{
"type":"geo_point"
}
}
}
}
POST /hotel/_doc/001
{
...
"location":{ // 写入geo_point类型数据,lat 纬度,lon 经度
"lat":40.12134
"lon":116.78965
}
}
// 动态映射
// 当字段没有定义时,ES可以根据写入的数据自动定义改字段的类型,这中机制叫做动态映射。
// 数组类型和对象类型都不需要提前定义,ES根据写入的数据自动创建mapping中对应的字段并指定类型。
// 多字段
// 针对同一个字段,有时需要不同的数据类型,表现在为了不同的目的以不同的方式索引相同的字段。
// 例如:在搜索中,既希望按照用户姓名进行搜索,又希望按照姓氏进行排列。
// 在mapping中将姓名字段先定义为text类型和keyword类型,其中,keyword类型的字段叫做子字段,这样ES在建立索引时会将姓名字段建立两份索引,即text类型的索引和keyword类型的索引。
PUT /hotel_order
{
"mappings":{
"properties":{
"user_name":{ // 定义user_name字段类型为text
"type":"text",
"fields":{ // 定义user_name 多字段
"user_name_keyword":{
// 定义user_name字段的子字段为user_name_keyword,并定义其类型为keyword
"type":"keyword"
}
}
}
}
}
}
// 搜索
GET /hotel_order/_search
{
"query":{
"match":{ // match搜索text类型的字段
"user_name":"join"
}
},
"sort":{ // 排序使用子字段
"user_name.user_name_keyword":"asc"
}
}

文档操作#

Copy Highlighter-hljs
// 4、文档操作
// 单条写入文档
POST /hotel/_doc/001
{
...
}
// 批量写入文档
POST /_bulk
{"index":{"_index":"hotel"}} // 指定批量请求的索引
{"title":"aa","city":"北京","price":556.00} // 写入的数据
{"index":{"_index":"hotel"}} // 指定批量请求的索引
{...} // 写入的数据
// 实际使用过程中需要批量写入的文档比较多时,一般使用linux系统中的curl命令进行数据的批量写入。
// curl命令支持上传文件,可以将批量写入的JOSN数据保存到文件中,然后使用curl命令进行提交。
curl -s -XPOST '127.0.0.1:9200/_bulk?pretty' --data-binary "@bulk_doc.json"
其中bulk_doc.json是文件名称。
// 更新单条文档
POST /hotel/_update/001
{ // 在url中指定文档 _id
"doc"{
...
}
}
// 批量更新文档
POST /_bulk
{"update":{"_index":"hotel","_id":"001"}} // 指定批量更新的索引和文档 _id
{"doc":{"title":"aaa","city":"北京","price":556.00}} // 设定更新的文档内容
{"update":{"_index":"hotel","_id":"002"}}
{"doc":{"title":"aaa","city":"北京","price":557.00}}
// 根据条件更新文档
// 使用 _update_by_query功能
POST /${index_name}/_update_by_query
{
"query":{
// 条件更新的 查询条件
},
"script":{
// 提交更新的具体更新脚本代码
}
}
POST /hotel/_update_by_query
{
"query":{ // 更新文档的查询条件:城市为北京
"term":{
"city":{
"value":"北京"
}
}
},
"script":{ // 条件更新的更新脚本,将城市改为上海
"source":"ctx._source['city'] = '上海'",
"lang":"painless"
}
}
// 删除单条文档
DELETE /hotel/_doc/001
// 批量删除文档
POST /_bulk
// 批量删除文档,指定文档_id
{"delete":{"_index":"hotel","_id":"001"}}
{"delete":{"_index":"hotel","_id":"002"}}
//根据条件删除文档
POST /hotel/_delete_by_query
{
"query":{ // 删除文档的查询条件:城市为北京
"term":{
"city":{
"value":"北京"
}
}
}
}
Copy Highlighter-hljs
// 5、搜索辅助功能
// 指定返回的字段
// ES中,通过 _source 子句设定返回结果的字段。_source指向一个JSON数组,数组中的元素是返回的字段名称。
GET /hotel/_search
{
"_source":["title","city"], // 设定只返回 title 和city 字段
...
}
// 结果计数
// 对搜索结果进行计数。ES 提供了 _count API功能。
GET /hotel/_count
{
"query":{
"term":{
"city":{
"value":"北京"
}
}
}
}
// 结果分页
// 通过设置 from 和size 来定义搜索位置和每页显示的文档数量
// from表示查询结果的起始下标,默认值为0,size 表示从起始下标开始返回的文档个数,默认值为10
GET /hotel/_search
{
"from":0,
"size":20,
"query":{
"term":{
"city":{
"value":"北京"
}
}
}
}
// 默认情况下,用户最多可以取得10000个文档,即from 为0时,size参数最大为10000,请求大于该值,ES报错。
// 更改默认值
PUT /hotel/_settings
{
"index":{ // 设定搜索返回的文档个数
"max_result_window":20000
}
}
// 性能分析
// 开启profile功能只需要在一个正常的搜索请求的DSL中添加 "profile":"true"即可。
// 也可以在kibana -> dev tools -> search profiler中查看
GET /hotel/_search
{
"profile":"true", // 打开性能剖析开关
"query":{
"match":{
"title":"金子"
}
}
}
// 评分分析
// 有时我们需要知道某个文档具体的打分详情,以便于对搜索DSL问题展开排序。
// ES 提供 explain 功能来帮助使用者查询搜索时的匹配详情。
GET /hotel/_explain/_002
{
"query":{
"match":{
"title":"金子"
}
}
}

搜索#

Copy Highlighter-hljs
// 6、搜索匹配功能
// ES提供了很多搜索匹配功能,既有进行完全匹配的term搜索,也有范围匹配的range搜索
// 既有进行分词匹配的match搜索,也有按照前缀匹配的suggest搜索。
// match_all 查询所有文档
// 相当于关系数据库中 select * from table_name
// 使用ES中的match_all 查询可以完成类似的功能,ES不对文档进行打分计算,默认情况下每个文档为1.0的得分。
GET /hotel/_search
{
"_source":["title","price"],
"query":{
"match_all":{ // 查询所有文档
"boost":2 // 通过boost设定得分值,设置所有文档的分值为2.0
0
}
}
}
// term 查询
// 用于精确查询,查询待查字段和查询值是否完全匹配。字段数据类型可以是数值型、布尔型、日期型、数组型及关键字等。
GET /hotel/_search
{
"query":{
"term":{
"price":{
"value":"500"
}
}
}
}
// terms查询
// 它是term查询的扩展形式,用于查询一个或多个值与待查字段是否完全匹配。
GET /hotel/_search
{
"query":{
"terms":{
"city":["北京","天津"] // 搜索城市北京或者天津的文档
}
}
}
// range 查询
// 用于范围查询,一般是对数值型和日期型数据的查询。
// gt 大于 lt 小于 gte 大于或等于 lte 小于或等于
GET /hotel/_search
{
"query":{
"range":{
"price":{
"get":300,
"lte":500
}
}
}
}
// 使用range查询时,查询值必须符合该字段在mappings中设置的规范。
// 例如:在酒店索引中,price字段是double类型,则range应该使用数值型或数组类型的字符串形式,不能使用其他形式。
// 和term查询类似,查询日期型的字段时,徐璈准信该字段在mappings中定义的格式
GET /hotel/_search
{
"query":{
"range":{
"create_time":{
"gte":"20230506120000"
"lte":"20230507120000"
}
}
}
}
// exists 查询
// 在某些场景下,我们希望找到某个字段不为空的文档,则可以用exists搜索。字段不为空的条件有:
// 1.值存在且不是null 2.值不是空数组 3. 值是数组,但不是[null]
GET /hotel/_search
{
"query":{
"exists":{ // 查询tag字段不为空的文档
"field":"tag"
}
}
}
// 布尔查询
// 复合搜索是一种在一个搜索语句中包含一种或多种搜索子句的搜索。
// 布尔查询是常用的复合查询,它把多个子查询组合成一个布尔表达式。
// must (相当于逻辑与) 必须匹配该查询条件
// should(相当于逻辑或) 可以匹配该查询条件
// must not(相当于逻辑非) 必须不匹配该查询条件
// filter(过滤查询) 必须匹配过滤条件,不进行打分计算
// must 查询,命中的文档必须匹配该子查询的结果,并且ES会将该子查询与文档的匹配程度值加入总得分里。
// must搜索包含一个数组,可以把其他的term级别的查询及布尔查询放入其中。
GET /hotel/_search
{
"query":{
"bool":{
"must":[ // must 查询,数组内可封装各类子查询
{
"term":{
"city":{
"value":"北京"
}
}
},
{ // 第二个子查询
"range":{
"price":{
"gte":300,
"lte":500
}
}
}
]
}
}
}
// should 查询
// 命中的文档可以匹配该查询的一个或多个子查询的结果,并且ES会将该查询与文档的匹配程度加入总得分里。
// should 包含一个数组,可以把其他额term级别的查询及布尔查询放入其中。
GET /hotel/_search
{
"query":{
"bool":{
"should":[ // should查询,数组内可封装各类子查询
{
"term":{
"city":{
"value":"北京"
}
}
},
{
"term":{
"city":{
"value":"天津"
}
}
}
]
}
}
}
// must not查询
// 命中的文档不能匹配该查询中的一个或多个子查询的结果,ES会将该查询与文档的匹配程度加入总得分里。
// must not 查询包含一个数组,可以把其他term级别的查询及布尔查询放入其中。
GET /hotel/_search
{
"query":{
"bool":{
"must_not":[
{
"term":{
"city":{
"value":"北京"
}
}
},
{
"term":{
"city":{
"value":"天津"
}
}
}
]
}
}
}
// filter查询
// filter 查询关注的是查询条件和文档是否匹配,不进行相关的打分计算,但是会对匹配结果进行缓存。
// 其他布尔查询关注的是查询条件和文档的匹配程度,并按照匹配程度进行打分。
GET /hotel/_search
{
"query":{
"bool":{
"fitlter":[ // filter查询,数组内可封装各类子查询
{
"term":{ // 城市为北京
"city":"北京"
}
},
{
"term":{ // 满房状态为否
"full_room":false
}
}
]
}
}
}
// Constant Score 查询
// 如果不想让检索词频率 TF(Term Frequency) 对搜索结果排序有影响,只想过滤某个文本字段是否包含某个词,
// 可以使用 Constant Score 将查询语句包装起来。
GET /hotel/_search
{
"_source":["amenities"],
"query":{
"constant_score":{ // 满足条件即打分为1
"boost":2.0,
"filter":{
"match":{ // 查询设施中包含 "停车场" 的文档
"amenities":"停车场"
}
}
}
}
}
// 使用 Constant Score搜索时,不论搜索词在文档中出现多少次,这些文档的得分都是一样的。
// 参数boost 可以控制命中文档的得分,默认值为1.0
// Function Score查询
// 使用ES进行搜索时,命中的文档默认按照相关度进行排序。有些场景下用户需要敢于该"相关度",此时使用Fuction Score查询。
// 使用时,用户必须定义一个查询以及一个或多个函数,这些函数为每个文档计算一个新分数。
GET /hotel/_search
{
"_source":["title","city"],
"query":{
"function_score":{
"query":{
"term":{
"city":{
"value":"北京"
}
}
},
"functions":[ // 定义函数
{ // 此处只定义了一个函数:随机函数
"random_score":{}
}
]
}
}
}
// 全文搜索
// 全文搜索首先对查询词进行分析,然后根据查询词的分词结果构建查询。
// 这里的全文指的是文本类型数据(text类型)
// 机构号搜索一般用于精确匹配,而全文搜索用于部分匹配。
// match查询
// 只要分词中的一个或多个在文档中存在即可。
GET /hotel/_search
{
"_source":["title"],
"query":{
"match":{
"title":"hello酒店"
}
}
}
// 或者
GET /hotel/_search
{
"_source":["title"],
"query":{
"match":{
"title":{
"query":"hello酒店"
"operator":"and"
}
}
}
}
// 注:match搜索可以设置operator参数,该参数决定文档按照分词后的词集合进行"与"还是"或"匹配。
// 有时使用"与"操作过于严苛,使用"或"操作又过于宽松。
// 这时可以使用minimum_should_match参数,该参数叫作最小匹配参数,其值为一个数值,意义为可以匹配上的词的个数。
// 一般情况下将其设置为一个百分数。
GET /hotel/_search
{
"_source":["title"],
"query":{
"match":{
"title":{ // match搜索条件
"query":"酒店",
"operator":"or",
"minimum_should_match":"80%" // 设置最小匹配度80%
}
}
}
}
// muti_match查询
// 有时用户需要在多个字段中查询关键词,除了使用布尔查询封装多个match查询之外,可替代的方案是使用multi_match.
// 在multi_match 的query子句中组织数据匹配规则,并在fields子句中指定需要搜索的字段列表。
GET /hotel/_search
{
"_source":["title","amenities"],
"query":{
"multi_match":{
"query":"假日", // 匹配关键字为 假日
"fields":[ // 设置匹配的字段为title和amenities
"title",
"amenities"
]
}
}
}
// match_phrase查询
// 用于匹配短语,搜索确切的短语或邻近的词语。
// 设置match_phrase查询的slop参数,用来调节匹配词之间的距离阀值。
GET /hotel/_search
{
"query":{
"match_phrase":{
"title":"文雅酒店",
"slop":2 // 将"文雅"和"酒店"之间的最大匹配距离设置为2
}
}
}
// 基于地理位置查询
// ES提供了基于地理位置的搜索功能。它主要支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询。
// 另一种是地理形状查询(geo_shape),即支持点、线、圆形和多边形查询等。
// geo_point字段的查询有3种,分别为geo_distance查询、geo_bounding_box查询和geo_polygon
// geo_distance查询需要用户指定一个坐标点,在指定距离该点的范围后,ES即可查询到相应的文档。
// 查询天安门【经纬度为[116.4039,39.915143]】5km范围内的酒店:
GET /hotel/_search
{
"_source":[
"title","city","location"
],
"query":{
"geo_distance":{
"distance":"5km", // 设置距离范围为5km
"location":{ // 设置中心点经纬度
"lat":"39.915143", // 设置纬度
"lon":"116.4039" // 设置经度
}
}
}
}
// geo_shape查询提供的是矩形搜索,需要用户给出左上角的顶点地理坐标和右下角的顶点地理坐标。
// 假设国贸商圈为一个矩形,其左上角顶点经纬度为[116.457044,393922821],右下角顶点的经纬度为
// [116.479466,39.907104],则在国贸商圈内搜索酒店的DSL为:
GET /hotel/_search
{
"query":{
"geo_bouding_box":{
"location":{
"top_left":{ //设置左上角的顶点坐标
"lat":"393922821",
"lon":"116.457044"
},
"bottom_right":{ //设置右下角的顶点坐标
"lat":"39.907104",
"lon":"116.479466"
}
}
}
}
}
// geo_polygon 比geo_shape提供的地理范围功能更加灵活,它支持多边形内的文档搜索,
// 使用该查询需要提供多边形所有顶点的地理坐标。
// 假设北京地坛公园商圈的地形为三角形,三个顶点的经纬度分别为
// [116.417088,39.959829]、[116.432035,39.960272]、[116.421399,39.965802]
GET /hotel/_search
{
"query":{
"geo_polygon":{
"location":{
"points":[
{ // 设置三角形的第 1 个顶点坐标
"lat":"39.959829",
"lon":"116.417088"
},
{ // 设置三角形的第 2 个顶点坐标
"lat":"39.960272",
"lon":"116.432035"
},
{ // 设置三角形的第 3 个顶点坐标
"lat":"39.965802",
"lon":"116.421399"
}
]
}
}
}
}

搜索建议#

Copy Highlighter-hljs
// 搜索建议
// 即在用户输入搜索关键词的过程中系统进行自动补全。
// 在搜索时,用户每输入一个字符,前端就需要向后端发送一次查询请求对匹配项进行查询,
// 因此这种场景对后端相应速度的要求比较高。
// ES通过ES的搜索建议查询Completion Suggester实现。
// 使用 Completion Suggester 其对应的字段类型需要定义为 completion类型
// 酒店搜索建议的索引:
PUT /hotel_sug
{
"mappings":{
"properties":{
"query_word":{ // 定义query_word字段,类型为completion
"type":"completion"
}
}
}
}
POST /_bulk
{
{"index":{"_index":"hotel_sug","_id":"001"}}
{"query_word":"如家酒店"}
{"index":{"_index":"hotel_sug","_id":"002"}}
{"query_word":"如家精选酒店"}
...
}
// 假如搜索"如家"关键字,需要ES给出前缀为该词的酒店查询词,DSL如下:
GET /hotet_sug/_search
{
"suggest":{
"hotel_zh_sug":{ // 定义搜索建议名称
"prefix":"如家", // 设置搜索建议的前缀
"completion":{ // 设置搜索建议对应的字段名称
"field":"query_word"
}
}
}
}
// 搜索建议的结果不是封装在hits中,而是单独封装在suggest中。
// 在suggest.hotel_zh_sug.options中可以看到每一个候选集的文档信息。

排序#

Copy Highlighter-hljs
// 按字段排序
// 使用sort子句对字段值进行排序时,需要指定排序的字段,设置order参数为 asc或desc。
GET /hotel/_search
{
"_source":["title","price"],
"query":{
"match":{
"title":"酒店"
}
},
"sort":{
{
"price":{
"order":"desc"
}
}
}
}
// 按地理距离排序
// 使用时,需要在sort内部指定排序名称为geo_distance,并制定目的地坐标。
// 还可以指定排序结果sort子句中的距离的计量单位,默认值为km即千米。
// 在进行距离计算时,系统默认使用的算法arc,该算法的特点是计算精准但是耗时较长,用户可以使用
// distance_type参数选择另一种计算速度快单经度略差的算法,名称为plane。
// eg:使用geo_distance 查询天安门5km范围内的酒店,并按照距离由近及远进行排序:
GET /hotel/_search
{
"_source":["title","city","location"],
"query":{
"geo_distance":{
"distance":"5km", // 设置地理范围5km
"location":{ // 设置中心点坐标
"lat":"39.915143",
"lon":"116.4039"
}
}
},
"sort":[
{
"_geo_distance":{ // 设置排序的中心点坐标
"location":{
"lat":"39.915143",
"lon":"116.4039"
},
"order":"asc", // 按距离由近到远进行排序
"unit":"km", // 排序所使用的距离的计量单位
"distance_type":"plane" // 排序所使用的距离计算算法
}
}
]
}

文本的索引建立过程#

Copy Highlighter-hljs
// 文本的索引建立过程
// ES 使用一种称为 "倒排索引"的数据结构。
// 倒排索引中的所有词语存储在词典中,每个词语又指向包含它的文档信息列表。
// 字符过滤器时分析器处理文本数据的第一道工序,它接收原始的字符流,对原始字符流中的字符进行添加、删除或者
// 转换操作,进而改变原始的字符流。比如:去除html标签
// 分词过滤器接收分词器的处理结果,并可以将切分好的词语进行加工和修改,进而对分词结果进行规范化、统一化和
// 优化处理。
// ES 内置分词过滤器:Lower Case过滤器将所有字母转换为小写形式
// Stop Token过滤器将停用词从分词结果中移除,同义词分词过滤器 为分词结果添加同义词。
// 分析器其实是字符串过滤器、分词器和分词过滤器的组合体,可以在索引建立时和搜索时指定使用这些分析器。
// DSL中可以直接使用参数 analyzer来指定分析器的名称进行测试
POST _analyze
{
"analyzer":"standard", // 指定分析器名称为standard
"text":"The letter tokenizer is not configurable." // 待分析文本
}
POST /hotel/_analyze
{
"filed":"title", // 使用酒店索引的title字段对应的分析器分析文本
"text":"北京假日大酒店"
}
// 指定分析器:一种方式是在索引的settings 参数中设置当前索引的所有文本字段的分析器
// 另一种方式是在索引的mappings参数中设置当前字段的分析器
// 索引时使用分析器
PUT /hotel
{
"settings":{
"analysis":{
"analyzer":{ // 指定所有text字段索引时使用 simple 分析器
"default":{
"type":"simple"
}
}
}
}
}
// 搜索时使用分析器
PUT /hotel
{
"mappings":{
"properties":{
"title":{
"type":"text",
"analyzer":"whitespace", // 索引时使用whitespace分析器
"search_analyzer":"whitespace" // 搜索时使用whitespace分析器
}
}
}
}
// 如果指定的搜索分析器和索引时的分析器不一致,则ES在搜索时可能出现有不符合预期的匹配情况。
// IK分词器用户添加分词
// 在IK分词器的安装目录下的config子目录中创建文件my.dict,在其中添加分词即可,多个词语添加,每个词语为单独一行。
// 添加完成后修改IK分析器的配置文件,路径为config/IKAnalyzer.cfg.xml,将新建的字典文件加入ext_dict选项中。
// <entry key="ext_dict">my.dict</entry> 配置完重启ES。
// 建立索引时使用同义词
// ES 内置的分词过滤器中,有一种分词过滤器叫作 synonyms,它是定义同义词的分词过滤器。
// eg:使用IK分析器和synonym分词过滤器一定定义索引的DSL:
PUT /hotel
{
"settings":{
"analysis":{
"filter":{ // 定义分词过滤器
"ik_synonyms_filter":{
"type":"synonym",
"synonyms":[ // 在分词过滤器中定义近义词
"北京、首都",
"天津、天津卫",
"假日、度假"
]
}
},
"analyzer":{ // 自定义分析器
"ik_analyzer_synonyms":{
"tokenizer":"ik_max_word", // 指定分词器
"filter":[ // 指定分词过滤器
"lowercase",
"ik_synonyms_filter"
]
}
}
}
},
"mappings":{
"properties":{
"title":{
"type":"text",
"analyzer":"ik_analyzer_synonyms" // 指定索引时使用自定义的分析器
}
}
}
}
// 查询时使用同义词
// ES内置的分词过滤器中还有个分词过滤器叫作synonym_graph,它是一种支持查询时用户自定义同义词的分词过滤器。
// 使用IK分析器和synonym_graph分词过滤器一定定义索引的DSL:
PUT /hotel
{
"settings":{
"analysis":{
"filter":{ // 定义分词过滤器
"ik_synonyms_graph_filter":{
"type":"synonym_graph",
// "synonyms_path":"mydict/synonyms.dict" // 指定同义词文件及其路径
"synonyms":[ // 在分词过滤器中定义近义词
"北京,首都"
"天津,天津卫"
"假日,度假"
]
}
}
"analyzer":{ // 自定义分析器
"ik_analyzer_synonyms_graph":{
"tokenizer":"ik_max_word", // 指定分词器
"filter":[ // 指定分词过滤器
"lowercase",
"ik_synonyms_graph_filter"
]
}
}
}
},
"mappings":{
"properties":{
"title":{
"type":"text",
"analyzer":"ik_max_word",
// 指定查询时使用自定义的分析器
"search_analyzer":"ik_analyzer_synonyms_graph"
}
}
}
}
// 同义词比较多时,ES支持用户将同义词放在文件中,文件的位置必须是在${ES_HOME}/config目录及其子目录下,
// 注意该文件必须存在ES集群中的每一个节点上。
// 创建索引时,在settings中指定同义词的文件及其路径。
// 拼音搜索
// 在{ES_HOME}/plugins/下创建名称为pinyin-analysis的子目录,下载elasticsearch-analysis-pinyin-7.10.2.zip
// 并解压其目录下。
// PUT /hotel
{
"settings":{
"analysis":{
"analyzer":{ // 自定义分析器
"ik_pinyin_analyzer":{
"tokenizer":"ik_max_word", // 设置分词器为ik_max_word
"filter":["pinyin_filter"] // 设置分词过滤器为pinyin_filter
}
},
"filter":{ // 定义分词过滤器
"pinyin_filter":{
"type":"pinyin", // 封装pinyin分词过滤器
"keep_first_letter":true, // 设置保留拼音的首字母
"keep_full_pinyin":false, // 设置保留拼音的全拼
"keep_none_chinese":true, // 设置不保留中文
}
}
}
},
"mappings":{
"properties":{
"title":{
"type":"text",
"analyzer":"ik_pinyin_analyzer" // 设置使用自定义分析器
}
}
}
}

高亮显示#

Copy Highlighter-hljs
// 高亮显示
// 在ES中通过设置DSL的highlight 参数可以对搜索的字段高亮显示。
GET /hotel/_search
{
"query":{
"match":{
"title":"酒店"
}
},
"highlight":{ // 设置高亮字段
"fields":{
"title":{}
}
}
}
// 进行高亮显示的标记标签默认使用 <em></em>
// 修改默认标签
{
....
"highlight":{
"fields":{
"title":{ // 设置默认使用标签<high></high> 标记匹配词语
"pre_tags":["<high>"],
"post_tags":["</high>"]
}
}
}
}
// 高亮显示的搜索策略
// plain策略是精准度比较高的策略,在处理大量文档和大文本的索引进行多字段高亮显示搜索时耗费的资源比较严重。
// unified策略 ES高亮默认使用的是改策略。
// fvh(fast vector highlighter)搜索策略是为了弥补上述两种策略在大文本索引高亮显示搜索时的速度低问题。
// fvh 是基于向量的高亮显示搜索策略。更适合在文档中包含大字段的情况(如超过1MB)下使用,
// 如果计算机的I/O性能更好(如使用SSD),则fvh策略在速度上的优势更加明显。
{
....
"highlight":{
"fields":{
"title":{ // 设置使用plain匹配策略
"type":"plain"
}
}
}
}
// 拼写纠错
// 在ES中进行纠错匹配时使用fuzzy-match搜索,该搜素使用编辑距离和倒排索引相结合的形式完成纠错。

聚合#

Copy Highlighter-hljs
// ES 不仅可以使用聚合功能对文档进行计数,还可以计算文档字段的平均值、最大值和最小值等。
// ES 还提供了同聚合的功能,以便于对多维度数据进行聚合。
PUT /hotel
{
"settings":{
"number_of_shards":1
},
"mappings":{
"properties":{
"title":{
"type":"text"
},
"city":{
"type":"keyword"
},
"price":{
"type":"double"
},
"create_time":{
"type":"date"
},
"full_room":{
"type":"boolean"
},
"location":{
"type":"geo_point"
},
"tags":{
"type":"keyword"
},
"comment_info":{
"properties":{
"favourable_comment":{
"type":"integer"
},
"negative_comment":{
"type":"integer"
}
}
}
}
}
}
Copy Highlighter-hljs
// ES 聚合请求的地址也是索引的搜索地址,可以使用aggs 子句封装聚合请求。
// 使用avg子句进行平均值的聚合时,可以在avg子句中指定聚合的字段。
// 默认情况下,查询将匹配所有文档,如果不需要返回匹配的文档信息,最好将返回的文档个数设置为0。
// 这样结果看起来更整洁,又可以提高查询速度。
// 下面DSL查询所有酒店的平均价格并且不返回匹配的文档信息
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{ // 聚合名称
"avg":{
"field":"price" // 计数文档的平均价格
}
}
}
}
// 搜索结果的aggregations子句中存储着聚合结果。
// 如果聚合的指标字段不是ES的基本类型,例如object类型,则可以使用点运算进行引用。
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"avg":{
// 使用点运算符引用object类型字段数据
"field":"comment_info.favourable_comment"
}
}
}
}
// 与平均值类似,最大值、最小值及加和值分别使用max、min、sum子句进行聚合。
// 空值处理
// ES聚合查询提供的value_count聚合,该聚合用于统计字段非空值的个数。
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"value_count":{
"field":"price" // 统计price字段非空值的个数
}
}
}
}
// 需要注意的是,如果判断的字段是数组类型,则value_count统计的是改字段 数组中 非空元素个数的总和,
// 而不是数组的个数总和。
// 如果需要以空值字段的数据作为聚合指标对其进行聚合,可以在指标统计中通过missing参数指定填充值
// 对空值进行填充。
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"sum":{
"field":"price",
"missing":100 // 计算加和值时将price字段中的空值用100代替
}
}
}
}
Copy Highlighter-hljs
// 聚合指标是指符合条件的文档字段的聚合,有时还需要根据某些维度进行聚合。
// 例如在搜索酒店时,按照城市、是否满房、标签和创建时间等维度统计酒店的平均价格。这些字段统称为"桶"。
// 在同一维度内有一个或多个桶。例如城市桶有"北京""天津"等。
// 桶 在概念上类似于mysql中的分组(group by)
// 单维度桶聚合
// 最简单的桶聚合是单维度桶聚合,指的是按照一个维度对文档进行分组聚合。
// 在桶聚合时,聚合的桶也需要匹配,匹配的方式有terms、filter、ranges等。
// terms 聚合是按照字段的实际完整值进行匹配和分组的,它使用的维度字段必须是keyword、bool、keyword数组等
// 适合精确匹配的的数据类型,因此不能对text字段直接使用terms聚合,如果对text字段有terms聚合的需求,则
// 需要在创建索引时为该字段增加多字段功能。
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"terms":{ // 按照城市聚合
"field":"city"
}
}
}
}
// 查询结果
{
...
"aggregations":{
"my_agg":{ //单维度聚合的名称
"doc_count_error_upper_bound":0, // 可能被遗漏的文档数量的最大值
"sum_other_doc_count":0, // 除了返回给用户的文档外剩下的文档总数
"buckets":[ // 聚合桶
{
"key":"北京",
"doc_count":3 // 该聚合桶下的文档数量
},
{
"key":"天津",
"doc_count":2
}
]
}
}
}
// 因为ES支持多桶聚合,所以每个桶聚合需要定义一个名字,此处定义了一个桶聚合,名为my_agg.
// 此处聚合统计各个城市的酒店的文档个数。
// 聚合字段为bool型时,key值为1和0,ES提供key_as_string桶识别字段,它是原始值的字符串形式。
// ranges 匹配的是数值字段,表示按照数值范围进行分组。
// 用户可以在ranges中添加分组,每个分组用from和to表示分组的起止数值。注意该分组包含起始值不包含终止值。
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"field":"price",
"ranges":[ // 多个范围桶
{
"to":200 // 不指定from,默认from为0
},
{
"from":200,
"to":500
},
{
"from":500 // 不指定to,默认to为该字段的最大值
}
]
}
}
}
// 按照城市维度进行聚合,统计各个城市酒店价格:
GET /hotel/_search
{
"size":0,
"my_agg":{ // 单维度聚合名称
"terms":{ // 定义单维度桶
"field":"city"
},
"aggs":{ // 用于封装单维度桶下的聚合指标
"my_sum":{ // 聚合指标名称
"sum":{ // 对price字段进行加和
"field":"price",
"missing":200
}
}
}
}
}
// 多维度桶嵌套聚合
// 例如搜索酒店,统计各个城市的满房和非满房状态下酒店平均价格。
GET /hotel/_search
{
"size":0,
"aggs":{
"group_city":{ // 多维度桶名称
"terms":{
"field":"city"
},
"aggs":{ // 单维度桶
"group_full_room":{
"terms":{
"field":"full_room"
},
"aggs":{ // 聚合指标
"my_sum":{
"avg":{
"field":"price",
"missing":200
}
}
}
}
}
}
}
}
// ES 支持嵌套桶聚合,进行嵌套时,可以使用aggs子句进行子桶的继续嵌套,指标放在最里面的子桶内。
// 搜素结果
{
...
"aggregations":{
"group_city":{ // 多维度聚合名称
"doc_count_error_upper_bound":0,
"sum_other_doc_count":0,
"buckets":[ // 第一层桶聚合列表
{
"key":"北京", // 第一层桶的key
"doc_count":3,
"group_full_room":{ // 第二层桶聚合
"doc_count_error_upper_bound":0,
"sum_other_doc_count":0,
"buckets":[ // 单维度聚合列表
{
"key":1,
"key_as_string":"true",
"doc_count":2,
"my_sum":{
"value":378.0 // 聚合指标
}
},
{
"key":0,
"key_as_string":"false",
"doc_count":1,
"my_sum":{
"value":200.0
}
}
]
}
},
{
...
}
]
}
}
}
// 第一层先按照城市分组分为北京、天津,第二层在北京、天津桶下继续分桶,分为满房、非满房桶,
// 对应的聚合指标即价格的加和值存储在内部的my_sum字段中。
// 地理距离聚合
// 按照地理距离进行聚合是一个非常实用的功能,例如搜索酒店时,查看2km,5km范围内的酒店个数。
// 用户可以使用geo_distance 聚合进行地理距离聚合。通过field参数来设置距离计算的字段,
// 可以在origin子句中设定距离的原点,通过unit设置距离的单位,可以选择 mi(米) 和 km(千米)。
// ranges子句用来对距离进行阶段性的分组
GET /hotel/_search
{
"size":0,
"aggs":{
"my_agg":{
"geo_distance":{
"field":"location",
"origin":{ // 指定聚合的中心点经纬度
"lat":39.915143,
"lon":116.4039
},
"unit":"km", // 指定聚合时的距离计量单位
"ranges":[ // 指定每一个聚合桶的距离范围
{
"to":3
},
{
"from":3,
"to":10
},
{
"from":10
}
]
},
"aggs":{ // 指定聚合指标
"my_min":{ // 聚合指标名称
"min":{ // 计算每个桶内price字段的最小值
"field":"price",
"missing":100
}
}
}
}
}
}

聚合方式#

Copy Highlighter-hljs
// ES支持灵活的聚合方式,它不仅支持聚合和查询相结合,还支持在聚合后的结果中进行过滤筛选。
// 直接聚合
// 直接聚合指的是聚合时的DSL没有query子句,是直接对索引内的所有文档进行聚合。
// 前面的示例都属于直接聚合。
// 先查询在聚合
// 与直接聚合相对应,这种查询方式需要增加quer子句,参加聚合的文档必须匹配query查询。
GET /hotel/_search
{
"size":0,
"query":{ // 指定查询query逻辑
"term":{
"city":{
"value":"北京"
}
}
},
"aggs":{ // 指定聚合逻辑
"my_agg":{
"avg":{
"field":"price"
}
}
}
}
// 前过滤器
// 有时需要对聚合条件进一步地过滤,但是又不能影响当前的查询条件。
// 例如用户进行酒店搜索时,搜索条件是天津的酒店,单聚合时需要将非满房的酒店平均价格进行聚合。
GET /hotel/_search
{
"size":0,
"query":{ // 指定查询的query逻辑
"term":{
"city":{
"value":"天津"
}
}
},
"aggs":{
"my_agg":{
"filter":{ // 指定过滤器逻辑
"term":{
"full_room":false
}
},
"aggs":{ // 指定聚合逻辑
"my_avg":{
"avg":{
"field":"price"
}
}
}
}
}
}
// 我们使用es 都会先查询、过滤后再进行聚合,但有时也需要再聚合后再过滤。
// 后过滤器
// 有些场景,需要根据条件进行数据查询,但是聚合的结果集不受影响。
// 例如搜索酒店,用户查询"假日",此时应该展现标题中带假日的酒店。
// 但是在该页面,如果还希望给用户呈现出全国各个城市的酒店平均价格,
// 这时可以使用 ES 提供的后过滤器功能。
GET /hotel/_search
{
"size":0,
"query":{
"match":{
"title":"假日"
}
},
"post_filter":{
"term":{
"city":"北京"
}
},
"aggs":{
"my_agg":{
"avg":{
"field":"price",
"missing":200
}
}
}
}

聚合排序#

Copy Highlighter-hljs
// ES 提供 sort子句进行自定义排序,可以按照聚合后的文档计数的大小进行排序。
// 可以按照聚合后的某个指标进行排序,可以按照每个组的名称进行排序。
// 按文档计数排序
// 聚合排序时,业务需求可能有按照每个组聚合后的文档数量进行排序的场景。
// 此时可以使用_count 来引用每组聚合的文档计数进行排序。
GET /hotel/_search
{
"size":0,
"aggs":{
"group_city":{
"terms":{
"field":"city",
"order":{
"_count":"asc" // 按照文档计数进行升序排列
}
},
"aggs":{
"my_avg":{
"avg":{
"field":"price", // 使用价格平均值作为聚合指标
"missing":200
}
}
}
}
}
}
// 按聚合指标排序
// 在聚合排序时,业务需求可能有按照每个组聚合后的指标值进行排序的场景。
// 此时可以使用指标的聚合名称来引用每组聚合的文档计数。
// 例如按照城市的酒店平均价格进行聚合,并按照聚合后的平均价格进行升序排列:
GET /hotel/_search
{
"size":0,
"aggs":{
"group_city":{
"terms":{
"field":"city",
"order":{ // 按照聚合指标进行升序排列
"my_avg":"asc"
}
},
"aggs":{
"my_avg":{ // 定义聚合指标
"avg":{
"field":"price",
"missing":200
}
}
}
}
}
}
// 按分组key排序
// 在聚合排序时,业务需求可能有按照每个分组的组名称排序的场景。
// 此时可以使用 _key 来引用分组名称。
// 例如按照城市酒店平均价格进行聚合,并按照聚合后的分支名称进行升序排列:
GET /hotel/_search
{
"size":0,
"aggs":{
"group_city":{
"terms":{
"field":"city",
"order":{ // 按照分组key 的自然顺序升序排列
"_key":"asc"
}
},
"aggs":{
"my_avg":{
"avg":{ // 定义聚合指标
"field":"price",
"missing":200
}
}
}
}
}
}

聚合分页#

Copy Highlighter-hljs
// ES 支持同时返回查询结果和聚合结果,前面介绍聚合查询时,查询结果和聚合结果各自封装在不同的子句中。
// 当聚合结果和查询结果封装在一起时,还需要考虑对结果分页的问题。前面介绍的聚合查询就不能解决这些问题了。
// ES 提供了 Top hits聚合和 Collapse 聚合可以满足上述需求,但它们的分页方案是不同的。
// Top hits聚合
// Top hits绝活指的是聚合时在每个分组内部按照某个规则选出前N个文档进行展示。
// 例如搜索金都时,如果希望按照城市分组,每组按照匹配分数降序展示3条文档数据
GET /hotel/_search
{
"size":0,
"query":{
"match":{
"title":"金都"
}
},
"aggs":{
"group_city":{ // 按照城市进行桶聚合
"terms":{
"field":"city"
},
"aggs":{
"my_avg":{
"top_hits":{ // 指定返回每个桶的前3个文档
"size":3
}
}
}
}
}
}
// 返回结果
{
...
"aggregations":{
"group_city":{
"doc_count_error_upper_bound":0,
"sum_other_doc_count":0,
"buckets":[ // 每个桶聚合返回前3个文档
{
"key":"北京",
"doc_count":2,
"my_avg":{
....
}
}
...
]
}
}
}
// Top hits聚合不能完成自动分页,可以一次性获取聚合结果并将其存放在内存中或者
// Redis中,然后自行实现翻页逻辑,完成翻页。
// Collapse 聚合
// 当在索引中有大量数据命中时,Top hits聚合存在效率问题,并且需要用户自行排序。
// ES推出了Collapse聚合,即用户可以在collapse子句中指定分组字段,匹配query的结果按照
// 该字段进行分组,并在每个分组中按照得分高低战死组内的文档。
// 当用户在query子句外指定 from和size时,将作用在Collapse聚合之后,即此时的
// 分页是作用在分组之后的。
GET /hotel/_search
{
"from":0, // 指定分页的起始位置
"size":5, // 指定每页返回的数量
"query":{ // 指定查询的query逻辑
"match":{
"title":"金都"
}
},
"collapse":{ // 指定按照城市进行Collapse聚合
"field":"city"
}
}
// Collapse聚合的结果是封装在hit中的。

Canal简介#

Copy Highlighter-hljs
// Binlog日志用于记录MySQL Master的所有数据表中更新的操作,包括新增、修改和删除数据,
// 它是以二进制形式存储的数据操作日志,所以叫做Binlog。
// Binlog日志用于同步关系型数据库中的主从数据,然后对其进行重放,从而达到与Master数据同步的目的。
// Canal是阿里巴巴开源的监听Binlog日志的组件,它实现了MYSQL Slave 与MYSQL Master之间的通信协议,
// 将自己伪装成 Mysql Slave,从而获取和解析 Binlog日志。
// Canal 提供了很多对接方案,用可选择配置Kafka、HBase、ES等存储组件。
// 搜索建议所使用的查询词来源于搜索系统中的用户历史搜索行为日志,因此需要一段时间的业务积累。
// 用户搜索日志存储在Hadoop中,从Hadoop中获取用户搜索日志后,需要对搜索关键字进行统计,
// 如统计每个搜索关键字的搜索次数,统计的数值将作为suggest匹配后的排序权重,最后
// 由初始化程序将处理的结果存入ES中。
// 在将数据存储ES中时,需要满足根据拼音首字母和拼音全拼匹配查询词的场景,因此需要借助HanLP
// 工具得到查询词对应的拼音首字母和拼音全拼,之后将查询词、拼音首字母和拼音全拼写入索引中。
// 为满足搜索建议的匹配逻辑,在ES中建立搜索建议索引时需要考虑3个比较重要的字段:
// 一是提示词字段,该字段为原始的用户搜索的中文字段,
// 二是提示词的拼音全拼字段,三是提示词的拼音首字母字段。
// 最后,客户端发起搜索建议请求,搜索建议服务基于查询词、拼音搜字母和拼音全拼到搜索建议服务
// 基于查询词、拼音搜字母和拼音全拼到搜索建议索引中进行匹配,并对不同匹配赋予一定的排序权重
// 后进行排序,最后将匹配结果添加到最终的列表中。
PUT /hotel_suggest
{
"settings":{
"number_of_shards":3, // 指定主分片个数
"number_of_replicas":2 // 指定副分片个数
},
"mappings":{
"properties":{
"id":{ // 定义ID字段的类型为keyword
"type":"keyword"
},
"chinese":{ // 定义chinese字段的类型为completion
"type":"completion"
},
"full_pinyin":{ // 定义full_pinyin字段类型为 completion
"type":"completion"
},
"head_pinyin":{ // 定义head_pinyin字段的类型为completion
"type":"completion"
}
}
}
}
// chinese/full_pinyin/head_pinyin 注意只有将这三个字段类型设置为completion,
// 该字段才能作为 suggest查询字段使用。
GET /hotel/_search
{
"query":{
"match_all":{}
}
}
// 返回结果
{
...
"hits":[
{
"_index":"hotel_suggest",
"_type":"doc",
"_id":"5",
"_score":1.0,
"_source":{
"chinese":{
"input":"如家酒店", // 搜索建议文本
"weight":76 // 搜索建议权重
},
"head_pinyin":{
"input":"rjjd",
"weight":76
},
"id":5,
"full_pinyin":{
"input":"rujiajiudian",
"weight":76
}
}
}
...
]
}
// 最后返回匹配文档的chinese字段的值。

elasticsearch API及其应用#

通过各种语言的API(java、go、PHP)可以做所有的操作(发送query,完成检索并返回结果集合),其本质是各个语言的客户端封装底层的HTTP请求,调用Elasticsearch 的 restful接口。

Copy Highlighter-hljs
{
...
"hightlight":{
"pre_tags":["<strong>"],
"post_tags":["</strong>"],
"fields":{
"content":{
"fragment_size":10, // 规定高亮搜索词所在上下文长度
"number_of_fragments":3 // 规定显示多少条高亮的结果
}
}
}
}

Logstash#

logstash 是数据收集 --> 数据过滤 --> 输出 的框架,主要包含三个部分 input、filter、output

logstash:7.6.2(版本与ES版本保持一致,6.x及以上版本支持pipeline)

安装:

1、安装JDK1.8(版本对应需1.8)

2、解压:tar -zxvf logstash-7.6.2.tar.gz

3、input插件是:logstash-input-jdbc,output插件是logstash-output-elasticsearch;这两个属于常见插件,logstash已经集成了,无须额外安装。

查看logstash已按照插件的命令,在按照目录bin下面执行:./logstash-plugin list;

4、使用logstash-input-jdbc输入插件,需要连接mysql驱动的jar包,将jar包放入指定目录(上传mysql驱动jar包mysql-connector-java-5.1.47.jar),然后在配置文件的jdbc_driver_library配置上对应的路径接口。

一般需要创建一个含有定义输入输出等内容的配置文件,可以在文件内写入数据来源和目的地、是否对日志数据进行某种格式转换等,并在启动logstash时指定拟采用的配置文件名称。

Copy Highlighter-hljs
// 在logstash的配置文件中设置数据来源和目的地
input { # 数据来源
stdin{} # stdin 是标准输入文件
}
ooutput { # 目的地
stdout{} # stdout 是标准输出文件
}

在linux中使用面的命令启动bin目录下的配置文件(参数-f的作用是指定logstash配置文件)

Copy Highlighter-hljs
$ ./logstash -f ./conf.conf
# 测试配置文件是否正确:
$ ./logstash -f ./conf.conf -t

input:处理输入的日志数据#

处理基于file方式输入的日志信息:

通过file方式,可以从指定的文件中读取数据并输入到logstash 中。基于这一特性,可以监控某些程序的日志文件(要求这些日志文件的格式是以行来组织的)。

Copy Highlighter-hljs
// logstash 配置文件格式,基于file方式读取指定文件
input {
file {
// 可选项,默认是plain,可通过这个参数设置编码方式
codec=>...
// 可选项,指定logstash隔多久检查被监听的路径下是否有新文件,默认为15s
discover_interval=>...
// 可选项,不想被监听的文件可在这里指定
exclude=>...
// 必须项,指定需要处理的文件路径
path=>...
// 可选项,如果不想用默认的$HOME/.sincedb*,可在这里指定其他位置的配置文件
sincedb_path=>...
// 可选项,指定logstash 隔多久写一次 sincedb文件,默认为15s
sincedb_write_interval=>...
// 可选项,取值范围为["beginnig","end"],指定logstash从什么位置开始读取文件数据,默认为end。
// 如果导入原数据,将其改成beginning,logstash 就从头开始读取
start_position=>...
// 可选项,指定logstash隔多久检查一次被监听文件状态是否有更新,默认为1s
stat_interval=>...
tags=>... // 可选项,
type=> ... //可选项,
// 其他选项,略
}
}
Copy Highlighter-hljs
input {
file {
path=>"/home/test/test.txt"
start_position=>"beginning"
sincedb_path=>"home/test/logstash/my.txt"
}
}
output {
stdout{}
elasticsearch { // 通过HTTP的方式将数据传输到elasticsearch中
hosts=>["localhost"]// 指定数字形式的hosts列表
}
}

output:输出日志数据#

将处理后的日志输出到elasticsearch 中:

通过设置logstash 的output部分中 elasticsearch 部分,可将 elasticsearch作为处理日志的接收端。这种方式可以将收集到的日志通过HTTP接口存放到elasticsearch中。

Copy Highlighter-hljs
// logstash 配置文件中的output 部分的格式
output {
elasticsearch {
// 编解码器,可选项,默认为 plain
codec=>...
// 字符串,可选项
document_id=>...
// 数值,可选项,默认为500
flush_size=>...
// 数组,可选项,elasticsearch服务器的主机列表,默认为["127.0.0.1"]
hosts=>...
// 数值,可选项,默认为1
idle_flush_time=>...
// 字符串,可选项,默认为 "logstash-%{+YYYY.MM.dd}"
index=>...
// 布尔值,可选项,默认为true
manage_template=>...
// 密码,可选项
password=>...
// 有效的文件路径,可选项
template=>...
// 字符串,可选项,默认为 logstash
template_name=>...
// 布尔值,可选项,默认为false
template_overwrite=>...
// 字符串,可选项
user=>...
}
}
Copy Highlighter-hljs
input {
stdin{ }
}
output {
elasticsearch {
hosts=>["localhost"]
}
stdout {}
}

然后,依次启动 elasticsearch、logstash。

Logstash 同步mysql数据#

批量同步配置:

Copy Highlighter-hljs
input {
stdin {
}
jdbc {
# mysql 数据库链接,shop为数据库名
jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/rebuild?characterEncoding=UTF-8&useSSL=false"
# 用户名和密码
jdbc_user => "root"
jdbc_password => "root"
# 驱动
jdbc_driver_library => "E:/2setsoft/1dev/logstash-7.8.0/mysqletc/mysql-connector-java-5.1.7-bin.jar"
# 驱动类名
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
parameters => {"number" => "200"}
# 执行的sql 文件路径+名称
#statement_filepath => "E:/2setsoft/1dev/logstash-7.8.0/mysqletc/user.sql"
statement => "SELECT * FROM `hhyp_article` WHERE delete_time = 0"
# 是否将字段名转换为小写,默认true(如果有数据序列化、反序列化需求,建议改为false);
lowercase_column_names => false
# Value can be any of: fatal,error,warn,info,debug,默认info;
sql_log_level => warn
# 是否记录上次执行结果,true表示会将上次执行结果的tracking_column字段的值保存到last_run_metadata_path指定的文件中;
record_last_run => true
# 需要记录查询结果某字段的值时,此字段为true,否则默认tracking_column为timestamp的值;
use_column_value => true
# 需要记录的字段,用于增量同步,需是数据库字段
tracking_column => "ModifyTime"
# Value can be any of: numeric,timestamp,Default value is "numeric"
tracking_column_type => timestamp # record_last_run上次数据存放位置;
last_run_metadata_path => "E:/2setsoft/1dev/logstash-7.8.0/mysqletc/last_id.txt"
# 是否清除last_run_metadata_path的记录,需要增量同步时此字段必须为false
clean_run => false
# 设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 索引类型
type => "article"
}
jdbc {
# mysql 数据库链接,shop为数据库名
jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/rebuild?characterEncoding=UTF-8&useSSL=false"
# 用户名和密码
jdbc_user => "root"
jdbc_password => "root"
# 驱动
jdbc_driver_library => "E:/2setsoft/1dev/logstash-7.8.0/mysqletc/mysql-connector-java-5.1.7-bin.jar"
# 驱动类名
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
parameters => {"number" => "200"}
# 执行的sql 文件路径+名称
#statement_filepath => "E:/2setsoft/1dev/logstash-7.8.0/mysqletc/goods_class.sql"
statement => "SELECT * FROM `hhyp_article_cate` WHERE delete_time = 0"
# 是否将字段名转换为小写,默认true(如果有数据序列化、反序列化需求,建议改为false);
lowercase_column_names => false
# Value can be any of: fatal,error,warn,info,debug,默认info;
sql_log_level => warn
# 设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 索引类型
type => "article_cate"
}
}
filter {
json {
source => "message"
remove_field => ["message"]
}
}
output { if[type] == "article" {
elasticsearch {
hosts => ["127.0.0.1:9200"]
index => "hhyp_article"
document_id => "%{id}"
}
}
if[type] == "article_cate" {
elasticsearch {
hosts => ["127.0.0.1:9200"]
index => "hhyp_article_cate"
document_id => "%{id}"
}
}
stdout {
codec => json_lines
}
}

注意事项:
1.索引type。
ES从6版本后一个索引下不能有多个type,所以在多张表同步时,最好一张表建立一个索引。所以在每个jdbc里type指定了一个名称。可以拿该名称在output时做判断。

2.logstash_jdbc_last_run储存位置。
logstash_jdbc_last_run是一个用来记录最后一次进行同步的时间节点,为了下一次增量同步的条件。如果不在配置中显式设置,默认是在系统盘的用户目录下。如果是windows,比如是我的是C:\Users\Administrator.logstash_jdbc_last_run ,设置方式在上面代码里也有说明,如果自行设置需要在指定目录下创建一个TXT文件,执行完毕后也会储存最后同步的时间节点。

捕捉错误方式:
1.命令面板查看。
这种方式是在命令通过bin\logstash -f mysqletc\mysql.conf启动时抛出的错误,在滚动的面板中主要查看ERROR行列。

2.日志查看。
日志位置,logstash根目录>logs。里面不光有启动时抛出的错误日志还有运行的慢日志,可以解压一个带plain的日志,打开文件就可以查看到运行过程中的日志信息了,如下就是错误位置的提示语。

错误解决方法:
1.'reader' unacceptable code point ' ' (0x0) special characters are not allowed
这个错误是某个文件中存在特殊字符,而在启动命令后,主要读取的除了conf配置文件和SQL执行指令就是logstash_jdbc_last_run了。排除了配置和SQL指令和之前一摸一样,所以问题就是默认在C:\Users\Administrator\下的.logstash_jdbc_last_run了,所以只需要删掉再重新启动就可以了。

2.<LogStash::ConfigurationError: Must set either :statement or :statement_filepath. Only one may be set at a time.>
这个错误就很明显了,statement和statement_filepath这两个配置项只能有一个。因为都是用于执行SQL指令的,一个是放在独立的文件中,用于繁杂的查询指令,而另一个是放在同步配置文件中。所以只需删掉或注释掉其中一项就可以了。

修改删除#

依靠logstash的input-jdbc-plugin插件实现自动的增量更新,只能增量添加数据而不能修改或者删除数据。

我们根据input-jdbc-plugin这个插件的配置实现修改和删除。

更新:

用一个更新时间的字段来作为每次logstash增量更新的 tracking column,这样logstash每次增量更新就会根据上一次的最后的更新时间来作为标记。

索引的document id必须是主键,这样在每次增量更新的时候,才会只是增加数据,之前ID相同的数据就会被覆盖,从而达到update的效果。

删除:

实现删除主要是通过fitler给数据加上状态,然后output里面根据状态,做出不同的响应。

Copy Highlighter-hljs
input {
jdbc{
...
tracking_column => "update_time"
tracking_column_type => "timestamp"
...
}
}
filter {
# deleted、disable=1为删除或禁用的数据,这里判断然后给加上 delete 标识
# action 相当于 @metadata 对象的一个属性,应该还可以加上其他的,比如 [@metadata][test]
if [deleted] == 1 or [disable] == 1 {
mutate{ add_field => { "[@metadata][action]"=>"delete" }}
} else {
mutate{ add_field => { "[@metadata][action]]"=>"index" }}
}
# 状态字段使用完成以后,可以移除,避免同步到ES中
mutate {
remove_field => ["deleted","disabled"]
}
}
output {
elasticsearch {
# action指定 实现删除
action => "%{[@metadata][action]}"
hosts=>[]
index=>"index名字,也可以使用变量"
document_id => ""
}
}
posted @   caibaotimes  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2021-01-19 sublime 光标选中多行
2021-01-19 mysql删除重复记录并且只保留一条
点击右上角即可分享
微信分享提示
CONTENTS