Elasticsearch搜索服务学习之十二——数据建模(五)——嵌套关系和父子关系结合建模示例

高新技术,ElasticSearch

2017-09-04

173

0

目录


还记得我们在建模的第一篇文章——关联关系提到的问题吗?

业务场景

考虑这样一个例子:我们正在开发一个酒店预订系统,设计的重要信息包括酒店基本信息、酒店的房型信息、房型的价格信息,在关系型数据库中,很好处理这几个之间的关系,然后,我们需要在ES中除了按照酒店的基本信息进行全文检索外,还需要按照价格范围进行酒店过滤。实体间的关系如下图:

 

在这样一个比较复杂的关系中,我们怎么设计我们的数据模型呢?

在实际的业务中,酒店的数量是非常庞大的,而每一家酒店的房型数量基本是固定的,修改的频率很低,而房型的价格随着时间的推移,波动的频率很高,变化很大。

同时,在搜索酒店的时候,会查询开始时间到结束时间段内酒店的最低价格,而现在的业务暂时不用关心房型信息,房型是从关系数据库查询的,但是可能以后会改为从es查询。

综上,业务上看有如下特点:

  • 酒店信息:数量大,但是修改的频率低,一旦酒店信息入库,除非上下架,否则酒店信息很少变动;
  • 房型信息:每家酒店的房型数量基本固定,可能会出现房型新增、上下架房型,但是频率低;
  • 房价信息:每家酒店的每个房型,在不同的时间价格变化频繁,甚至每天的每个时段价格都不一样,同时数量也是非常庞大。

数据建模

综上,上边的业务模型基本上初步可以判断是一个祖孙关系,顶层是酒店,中间是房型,底层是房价。现在的关系看来是这样:

但是,由于房型信息的业务特性,而且我们知道祖孙关系,层级越多,性能越低。而中间层的房型信息相对于单家酒店来说,其数量一般是固定的,而且修改频率不高,似乎更好的办法是将房型设计为嵌套对象,从而大大提升酒店查询的性能。

综上考虑,我们能否将酒店和房价设置为父子关系,而将酒店和房型设置为嵌套关系呢?

答案是可以得。在数据库中,价格表除了存储roomId(房型id),还冗余了hotelId(酒店id),因此价格跟酒店也存在关联关系。但是要解决一个问题:在查询价格的时候,需要考虑房型的状态,例如房型是否可用?是否支持钟点房?这怎么办呢?

这里为了解决这个问题,我们需要在价格数据上冗余存储房型的一些属性,以便按照该属性对价格进行过滤,例如前边提到的房型是否可用。设计上尽量将这部分属性简化,例如:设计为int型,存储时耗费资源较少,查询效率较高。

最终,整个关系如下:

映射关系

上边的模型映射关系如下:

PUT /hotel
{
"mappings": {
 "data": {
   "properties": {
     "name": {
       "type": "text",
       "analyzer": "ik_max_word",
       "fields": {
         "pinyin": {
           "type": "text",
           "analyzer": "pinyin"
         },
         "keyword": {
           "type": "keyword",
           "ignore_above": 256
         }
       }
     },
     "rooms": {
       "type": "nested", // 定义为嵌套对象
       "properties": {
         "name": {
           "type": "text",
           "analyzer": "ik_max_word",
           "fields": {
             "pinyin": {
               "type": "text",
               "analyzer": "pinyin"
             },
             "keyword": {
               "type": "keyword",
               "ignore_above": 256
             }
           }
         }
       }
     }
   }
 },
 "price": {
   "_parent": { // 定义为子对象
     "type": "data"
   }
 }
}
}

上边的映射只保留了重要的字段。

在索引price时,需要指定hotelId,使其建立关联关系,同时保证hotel与price在同一分片中。

PUT /hotel/price/价格id?parent=酒店id

查询某个酒店的价格:

GET hotel/price/_search
{
  "query": {
    "term": {
      "hotelId": {
        "value": "10039"
      }
    }
  }
}

按照价格查询酒店信息:

GET /hotel/data/_search
{
  "from" : 0,
  "size" : 10,
  "query" : {
    "bool" : {
      "must" : [
        {
          "term" : { // 酒店所在的省份
            "provinceCode" : {
              "value" : "110000",
              "boost" : 1.0
            }
          }
        },
        {
          "term" : { // 酒店的状态
            "statusCode" : {
              "value" : "01",
              "boost" : 1.0
            }
          }
        }
      ],
      "filter" : [ // 按照价格进行过滤,不参与评分
        {
          "has_child" : {
            "query" : {
              "bool" : {
                "must" : [
                  {
                    "range" : {
                      "activeTime" : { // 价格的有效时间
                        "from" : 1502899200000,
                        "to" : 1502985600000,
                        "include_lower" : true,
                        "include_upper" : false,
                        "boost" : 1.0
                      }
                    }
                  }
                ],
                "disable_coord" : false,
                "adjust_pure_negative" : true,
                "boost" : 1.0
              }
            },
            "type" : "price", // 指定查询的索引类型为price
            "score_mode" : "none", // 不设置评分模式
            "min_children" : 0,
            "max_children" : 2147483647,
            "ignore_unmapped" : false,
            "boost" : 1.0
          }
        }
      ],
      "disable_coord" : false,
      "adjust_pure_negative" : true,
      "boost" : 1.0
    }
  },
  "_source" : {
    "includes" : [ ],
    "excludes" : [  // 业务上不需要房型数据,这里设置不返回
      "rooms",
      "nightUsers"
    ]
  },
  "sort" : [  // 按照坐标位置的距离远近进行排序
    {
      "_geo_distance" : {
        "coordinate" : [
          {
            "lat" : 30.615153,
            "lon" : 104.070815
          }
        ],
        "unit" : "m", // 距离单位为米
        "distance_type" : "arc",
        "order" : "asc", // 升序排列
        "validation_method" : "STRICT"
      }
    }
  ]
}

待解决问题

在搜索酒店时,如果能够实现既按照条件查询酒店,又对价格进行聚合查询,以返回符合条件的最低价格?即一次查询返回酒店信息,又返回酒店的最低价格信息?

总结

本文记录了嵌套关系和父子关系结合使用的例子,实际上建模的时候考虑的问题很多,同时还要结合实际业务需要。可能这种方式并不是最优的,也可能不是和你的业务,有更好的思路请留言探讨。

相关阅读

Elasticsearch搜索服务学习之十一——数据建模(一)——关联关系

Elasticsearch搜索服务学习之十二——数据建模(二)——嵌套对象

Elasticsearch搜索服务学习之十二——数据建模(三)——父子关系模型

Elasticsearch搜索服务学习之十二——数据建模(四)——祖孙关系模型


前一篇:Elasticsearch搜索服务学习之十二——数据建模(四)——祖孙关系模型
后一篇:CDN导致GET请求始终无法获取参数

belonk

轻轻地我走了,正如我轻轻地来,我挥一挥衣袖,不带走一片云彩