写在前面
本文看下es数据建模相关的内容。
1:什么是数据建模
数据建模是对真实数据的一种抽象,最终映射为计算机形式的表现。其包括如下三个阶段:
1:概念模型
2:逻辑模型
3:数据模型
2:es数据建模的过程
es的数据建模其实就是确定各个字段都需要如何进行设置,什么类型?是否需要搜索?等,具体需要考虑的问题如下:
注意以上四个要素没有先后的顺序,而只是需要综合考虑的因素。
2.1:数据类型
为了选择合适的数据类型,我们来看下每种数据类型的特点。
2.1.1:text
默认会被分词器分词,可以搜索,但是不支持聚合和排序,如果想要支持的话需要显式将fielddata设置为true。
2.1.2:keyword
不会被分词,所以一般用在不需要分词时使用,如主键id,邮箱,手机号,身份证号等。支持聚合,搜索和排序,以及用于查询时精确匹配方式的过滤。
2.1.3:多字段类型
当希望一个数据按照多种的数据类型来存储,从而满足诸如按照不同的分词器进行分词,按照不同的查询条件进行查询(如存储为int则可以按照range来查询),但又不希望设置多个字段时使用。默认的在es中如果时字段时text类型的,则会默认添加一个名称为keyword的keyword类型的字段,当然实际工作中我们我们不需要这个默认行为则可以通过显式mapping来自己定义。
2.1.4:数值类型
数值类型是一种结构化数据,数值类型应该尽量设置能够满足存储要求的最小类型,如可以设置为byte,就不要设置为long。
2.1.5:枚举类型
枚举类型是一种结构化数据,建议设置为keyword,以获得更好的性能。
2.1.6:日期,布尔,地理信息等
设置为对应的类型即可。
2.2:搜索,聚合,排序
- 搜索
是否需要被搜索,也是对字段进行建模时要考虑的一个重要因素,因为搜索功能需要分词,以及创建对应的倒排索引数据结构,所以需要额外的存储消耗,以及构建对应数据结构的性能消耗。 - 聚合,排序
聚合和排序功能需要依赖于doc_values,和fielddata,需要简历对应的数据结构来满足聚合和排序功能,因此也会有对应的存储成本,和对应数据结构的维护成本。
对于这三个因素可从以下方面进行考虑:
1:如果是同时不需要搜索,聚合和排序,则可考虑设置enable=false,不存储_source(还需要注意不存储_source的话将无法reindex和更新)
2:如果是不需要搜索,则可以设置index:false
3:如果不需要聚合和排序,则可以设置doc_values和fielddata为false
4:如果是更新频繁,聚合频繁,则可考虑设置keyword类型的eager_global_ordinals为true,可以利用缓存来提高性能。
2.3:额外存储字段值
如果希望额外存储字段值,则可以设置store:true,一般结合enbled:false使用。
enabled:false一般应用在一些指标数据的存储上,这些数据不需要reindex,更新。此时如果还希望查看某些字段的话则可以设置store为true。
但是实际的应用中不建议直接设置enabled:false,而是考虑使用高压缩的存储方式来减少存储的开销。
2.4:数据建模优化实例
假定我们要对如下的数据进行建模:
如下是默认生成的mapping:
其中cover_url
被自动映射为text类型,并增加keyword类型的子字段,如下:
# Index 一本书的信息
PUT books/_doc/1
{
"title": "Mastering ElasticSearch 5.0",
"description": "Master the searching, indexing, and aggregation features in ElasticSearch Improve users’ search experience with Elasticsearch’s functionalities and develop your own Elasticsearch plugins",
"author": "Bharvi Dixit",
"public_date": "2017",
"cover_url": "https://images-na.ssl-images-amazon.com/images/I/51OeaMFxcML.jpg"
}
#查询自动创建的Mapping
GET books/_mapping
假定根据实际的业务需求,cover url不需要支持搜索,只需要支持聚合即可,此时我们就可以将其显式的设置为keyword,并将index设置为false,如下:
DELETE books
#优化字段类型
PUT books
{
"mappings": {
"properties": {
"author": {
"type": "keyword"
},
"cover_url": {
"type": "keyword",
"index": false
},
"description": {
"type": "text"
},
"public_date": {
"type": "date"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 100
}
}
}
}
}
}
GET books/_mapping
此时cover_url因为设置了index:false就不支持搜索了:
#Cover URL index 设置成false,无法对该字段进行搜索
POST books/_search
{
"query": {
"term": {
"cover_url": {
"value": "https://images-na.ssl-images-amazon.com/images/I/51OeaMFxcML.jpg"
}
}
}
}
但依然是支持聚合的:
#Cover URL index 设置成false,依然支持聚合分析
POST books/_search
{
"aggs": {
"cover": {
"terms": {
"field": "cover_url",
"size": 10
}
}
}
}
假定需求发生变更,要求将文章的内容存储在content字段中,并且不需要做更新和reindex。
考虑到content内容比较大,所以如果放在_source中返回的话会占用比较多的网络带宽资源,并且数据查询到额速度也会降低,所以为了解决这个问题,我们可以考虑如下的两种方案:
1:source filtering不返回数据,特别是content
2:设置enabled:false,并设置字段store:true
其中对于1:source_fitering 只是在返回给客户端时不返回,在汇总数据时还是返回的,如下图:
所以对于这个需求我们需要通过2设置enabled:false来解决。
如下在mapping中显式设置enabled:false:
DELETE books
#新增 Content字段。数据量很大。选择将Source 关闭
PUT books
{
"mappings": {
"_source": {
"enabled": false
},
"properties": {
"author": {
"type": "keyword",
"store": true
},
"cover_url": {
"type": "keyword",
"index": false,
"store": true
},
"description": {
"type": "text",
"store": true
},
"content": {
"type": "text",
"store": true
},
"public_date": {
"type": "date",
"store": true
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 100
}
},
"store": true
}
}
}
}
插入数据:
# Index 一本书的信息,包含Content
PUT books/_doc/1
{
"title": "Mastering ElasticSearch 5.0",
"description": "Master the searching, indexing, and aggregation features in ElasticSearch Improve users’ search experience with Elasticsearch’s functionalities and develop your own Elasticsearch plugins",
"content": "The content of the book......Indexing data, aggregation, searching. something else. something in the way............",
"author": "Bharvi Dixit",
"public_date": "2017",
"cover_url": "https://images-na.ssl-images-amazon.com/images/I/51OeaMFxcML.jpg"
}
#查询结果中,Source不包含数据
POST books/_search
{}
但依然可以查询和高亮,因为store:true所以会存储字段的原始值(但是enabled:false所以原始文档是不存储的,即_source是没有信息的)
:
#搜索,通过store 字段显示数据,同时高亮显示 conent的内容
POST books/_search
{
"stored_fields": [
"title",
"author",
"public_date"
],
"query": {
"match": {
"content": "searching"
}
},
"highlight": {
"fields": {
"content": {}
}
}
}
3:数据建模最佳实践
3.1:最佳实践一,如何处理关联关系
比如一个学生选择了多门课程,如果是在mysql等关系型数据库中就是1:n的关联关系。
按照如下的情况考虑:
1:如果是不需要通过关联的信息进行查询则优先考虑反范式化设计的存储方式,即存储到一个文档中
问题:如果是查询的话,会存在查询不准的问题,此时需要考虑其他方式
2:如果是对关联关系需要精确查询的话,则考虑使用nest存储方式。
问题:更新关联关系的数据都要更新整个文档,更新效率低
3:如果是需要更新的话,则考虑使用child/parent
需要额外的维护关联关系,读取的性能比较差
需要注意:kibana目前不支持针对nest,child/parent数据的报表展示。如果希望使用kibana进行报表展示的话需要做出取舍。
3.2:最佳实践二,避免过多字段
出现字段过多的情况一般都是因为将dynamic mapping的值设置为true了。此时每次文档中有新增字段都会在mapping中增加字段信息。如果是mapping信息中字段过多的话会导致如下的问题:
1:mapping庞大,字段多,不好维护
2:mapping信息需要维护在cluster state中,而cluster state需要同步到所有节点中,所以集群压力会变大
因此我可以考虑将dynamic mapping设置为stric来避免这个问题,但这就要求我们在建模阶段就考虑清楚都需要那些字段,避免后期引起不必要的麻烦。除了设置dynamic mapping为strict外,还可以考虑通过设置index.mapping.total_fields.limits来限制字段最大个数,默认为1000。
dynamic还有另外两个值,false,此时文档会写入_source,但不索引,生成mapping。strict,此时写入报错,生产上建议设置为strict。
针对这种情况,来看个例子,首先来创建一个文档:
DELETE cookie_service
PUT cookie_service/_doc/1
{
"url":"www.google.com",
"cookies":{
"username":"tom",
"age":32
}
}
查看mapping:
再来创建一个文档:
PUT cookie_service/_doc/2
{
"url":"www.amazon.com",
"cookies":{
"login":"2019-01-01",
"email":"xyz@abc.com"
}
}
再来看mapping:
那么,如果实际场景中就是可能有多种不同的值该怎么办呢?可以考虑使用类似于竖表 的方式来处理这种属性不确定的情况。
首先定义mapping:
DELETE cookie_service
#使用 Nested 对象,增加key/value
PUT cookie_service
{
"mappings": {
"properties": {
"cookies": {
"type": "nested",
"properties": {
"name": {
"type": "keyword"
},
"dateValue": {
"type": "date"
},
"keywordValue": {
"type": "keyword"
},
"IntValue": {
"type": "integer"
}
}
},
"url": {
"type": "keyword"
}
}
}
}
其中name存储属性的名称,具体的值存储到对应类型的字段上去,比如date类型则存储到dateValue字段中,int类型的属性值则存储到intValue字段中。
我们来索引几个文档:
##写入数据,使用key和合适类型的value字段
PUT cookie_service/_doc/1
{
"url": "www.google.com",
"cookies": [
{
"name": "username",
"keywordValue": "tom"
},
{
"name": "age",
"intValue": 32
}
]
}
PUT cookie_service/_doc/2
{
"url": "www.amazon.com",
"cookies": [
{
"name": "login",
"dateValue": "2019-01-01"
},
{
"name": "email",
"IntValue": 32
}
]
}
接着查看数据:
可以正常查询,并且此时mapping信息也不会有任何的改变。
3.3:最佳实践三,避免正则查询
正则查询可能会引起性能问题,在生产环境中要避免。一般使用空间换时间的方案来解决,来看一个实际的例子。
比如现在有es的版本号信息存储在字段verison中,如7.1.0
,现在要查询小版本号为1的所有文档,则可能使用如下的查询:
POST /esinfo/_search
{
"query": {
"regexp": {
"name": "*.1.*"
}
}
}
那么,通过空间换时间的思想该如何解决这个问题呢?可以将主版本号,小版本号,bug修复版本号作为子字段的形式分为3个字段来存储,如下:
DELETE softwares
# 优化,使用inner object
PUT softwares/
{
"mappings": {
"_meta": {
"software_version_mapping": "1.1"
},
"properties": {
"version": {
"properties": {
"display_name": {
"type": "keyword"
},
"hot_fix": {
"type": "byte"
},
"marjor": {
"type": "byte"
},
"minor": {
"type": "byte"
}
}
}
}
}
}
模拟索引一个文档:
#通过 Inner Object 写入多个文档
PUT softwares/_doc/1
{
"version":{
"display_name":"7.1.0",
"marjor":7,
"minor":1,
"hot_fix":0
}
}
PUT softwares/_doc/2
{
"version":{
"display_name":"7.2.0",
"marjor":7,
"minor":2,
"hot_fix":0
}
}
查询:
# 通过 bool 查询,
POST softwares/_search
{
"query": {
"bool": {
"filter": [
{
"match": {
"version.marjor": 7
}
},
{
"match": {
"version.minor": 2
}
}
]
}
}
}
以上使用了filter查询,可以有效的利用到缓存,提高查询效率。
3.4:最佳实践四,避免空值聚合不准问题
如果是值为null,可能会会导致聚合不准的问题,如下:
结果为5,很明显是错误的。
可以通过设置null_value来解决这个问题:
DELETE ratings
PUT ratings
{
"mappings": {
"properties": {
"rating": {
"type": "float",
"null_value": 1.0
}
}
}
}
PUT ratings/_doc/1
{
"rating":5
}
PUT ratings/_doc/2
{
"rating":null
}
POST ratings/_search
{
"size": 0,
"aggs": {
"avg": {
"avg": {
"field": "rating"
}
}
}
}
此时结果为3,就符合业务上的要求了。文章来源:https://www.toymoban.com/news/detail-850915.html
写在后面
参考文章列表
source filtering 。文章来源地址https://www.toymoban.com/news/detail-850915.html
到了这里,关于ElasticSearch之数据建模的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!