vlambda博客
学习文章列表

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

Chapter 5. Analytics Using Elasticsearch

多年来,数字世界见证了数据的巨大爆炸。随着存储空间成本的迅速下降,世界变得越来越小,以及通信技术的兴起,可以存储和处理的数据量猛增。由于人类几乎不可能手动理解这么多数据,因此产生了对数据分析的需求。

随着 PB 级的数据存储在他们的数据库中,用户提出了一些用例,他们可以在这些用例中处理他们的数据并带来一些很棒的观察结果和强大的洞察力。这些信息稍后可以转化为洞察力,在此基础上我们可以为商业和管理世界采取行动。这种特殊的技术在各个领域都有广泛的应用。零售商可以利用这种洞察力来了解客户的偏好、趋势和使用模式,从而改进他们的服务。在金融领域,数据分析可用于监控金融新闻、博客和人们对公司的一般情绪,以预测其股票活动。在移动电话领域,分析可以通过跟踪用户的使用水平来了解用户的后付费充值行为,并且这些信息可以通过在适当的时间为用户提供适当的折扣来推动用户进入下一个使用水平。在政治领域,数据分析技术可用于监控社交媒体和新闻,以了解人们对政党的总体情绪。

因此,我们有各种各样的分析用例。 Elasticsearch 在分析方面的特别之处在于它能够处理大量数据集,并且能够同时对其应用分析。在本章中,我们将以机票预订作为我们的用例/应用程序,看看 Elasticsearch 如何融入其中,以帮助管理层更好地了解他们的客户,从而建立更好的业务前景。

A flight ticket analytics scenario


航班预订 代理机构可以发现数据分析对于改善其业务非常重要。每张预订的机票都包含以下有关乘客和航班的信息:

  • 预约时间

  • 航班起飞时间

  • 航班到达时间

  • 飞机起飞的机场

  • 飞机到达的机场

  • 乘客是男是女

  • 他/她访问的目的

  • 乘客是经济舱还是商务舱

因此,有了这个 信息,我们可以用 JSON 对文档进行建模,如下所示:

{
    "departure" : {  
      "time" : "2014-10-10 01:01:01",
      "airport" : "Kochi",
    },
    "arrival" : {  
      "time" : "2014-10-10 01:01:01",
      "airport" : "Bangalore",
    }.
    "passengerDetails" : {
      "name" : "Damodar Das",
      "purposeOfVisit" : "WorkRelated",
      "sex" : "Male"
    },
    "ticketType" : "Business",
    "timeOfBooking" : "2014-09-09 09:09:01"
}

Index creation and mapping

Elasticsearch 是无模式的,这意味着您不必声明模式并索引 文档。相反,在索引文档时,它会识别每个字段的类型并创建映射。这里,字段的类型是指 Elasticsearch 应该如何将数据项视为字符串、数字、日期或其他内容。虽然 Elasticsearch 在索引数据时会猜测每个字段的类型,但它的自动检测映射在许多场景中可能对我们来说还不够。为了利用与日期相关的操作,例如日期范围查询和日期聚合,只需将这些字段定义为字段类型,Elasticsearch 将其理解为日期字段类型并知道其日期值。

因此,根据我们的要求,我们可以如下定义我们的模式:

#!/bin/bash

if [ -z $1 ] ; then
    echo "Please enter hostname"
    exit
fi

hostname=$1

curl -X PUT "http://$hostname:9200/planeticketing" -d '{
        "index": {
            "number_of_shards": 2,
            "number_of_replicas": 1
        }
    }'

curl -X PUT "http://$hostname:9200/planeticketing/ticket/_mapping" -d '{
    "ticket" : {
    "properties" : {
        "ticketType" : { "type" : "string" , "index" : "not_analyzed" },
        "timeOfBooking" : { "type" : "date", "format" : "YYYY-MM-dd HH:mm:ss"},
        "departure" : {
            "properties" : {
                "airport" : { "type" : "string" },
                "time" : { "type" : "date", "format" : "YYYY-MM-dd HH:mm:ss"}
            }
        },
        "arrival" : {
            "properties" : {
                "airport" : { "type" : "string" },
                "time" : { "type" : "date", "format" : "YYYY-MM-dd HH:mm:ss"}
            }
        },
        "passengerDetails" : {
            "properties" : {
                "name" : { "type" : "string" },
                "purposeOfVisit" : { "type" : "string", "index" : "not_analyzed"  },
                "sex" : { "type" : "string", "index" : "not_analyzed" }
            }
        }
    }}
}'

首先,我们 创建了一个名为planeticketing 的索引,并为其分配了两个分片和一个副本。副本确保即使一次 机器在网络中出现故障,集群也会应对这种情况并确保搜索集群仍然可供搜索,无需任何停机时间。

接下来,我们定义了模式。请注意,对于大多数具有字符串值的字段,例如 sex purposeOfVisit 等,我们添加not_analyzed 字段类型定义。将索引属性定义为 not_analzed 可确保搜索完全匹配的字段适用于该字段。这确保了这些字段没有被标记或小写以进行索引,并确保在聚合时,我们可以聚合原始/实际值而不是分解的标记,这会给我们带来奇怪的结果。

not_analyzed 的索引配置将确保字符串在到达反向索引之前没有被标记。使用它,我们可以获得完全匹配,并且还可以获得实际字符串而不是标记化字符串的聚合。您还应该注意,我们还可以配置一个字段,以便当我们将索引值设置为 no 时该字段不可搜索。在这种情况下,字符串中的任何标记都不会转到反向索引。

我们对所有日期时间字段采用了时间格式 YYYY-MM-dd HH:mm:ss。需要将此信息提供给 Elasticsearch,以便它理解 JSON 中给出的日期字符串(它如何解释并以日期格式存储它)。

A case study on analytics requirements

该机构认为,随着时间的推移,它需要监控预订门票的分布情况。它提供了一组它感兴趣的分析报告。可视化总是有助于在单一视图中掌握一个想法,该公司认为可视化 这些值会给它们带来比原始值更好的价值。

因此,该公司对周报提出了以下分析,我们将在本章中进一步讨论:

  • 乘客男女分布。

  • 订票趋势。

  • 航班起飞和到达机场的相关性。

  • 门票类型与时间的相关性。

  • 最喜欢的订票时间。

  • 大多数首选工作日出行。

  • 旅客到访目的、票种与性别的相关性。

Male and female distribution of passengers

首先,让我们从一个简单的问题开始。管理层希望看到男女乘客的比例。我们可以在 passengerDetails.sex 字段上运行术语聚合。这将返回男性乘客和女性乘客的数量。

Elasticsearch 查询如下:

{ 
  "query": { 
    "range": { 
      "timeOfBooking": { 
        "gte": "2014-09-01 00:00:00",   // => We are applying analytics on one month worth data
        "lt": "2014-10-01 00:00:00" 
      } 
    } 
  }, 
  "aggregations": { 
    "maleToFemale": { 
      "terms": { 
        "field": "passengerDetails.sex"  // => Apply aggregation on sex field
      } 
    } 
  } 
} 

以下是上述查询的响应:

  "aggregations" : { 
    "maleToFemale" : { 
      "buckets" : [ 
          {  "key" : "Male",  "doc_count" : 3  }, 
          { "key" : "Female",   "doc_count" : 1  }
      ] 
      } 
    } 
} 

最后,让我们使用饼图可视化响应:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

Note

您应该注意,这里的聚合范围在查询中。这意味着聚合仅发生在与查询匹配的文档上。

推论:据观察,与女性 乘客相比,更多的男性乘客更喜欢乘飞机旅行。这个比例是 1:4,较低的比例是指女性乘客,较高的比例是指男性乘客。

Time-based patterns or trends in booking tickets

问题:管理层想要研究大多数旅行者预订机票的可能时间。根据这些统计数据,他们将在高峰时段添加新服务器或额外支持,以简化预订过程。此外,他们希望将假期与预订时间相关联,以检查他们是否可以为旅行者推出任何额外的优惠。

如何解决:Elasticsearch 提供了日期直方图聚合,我们可以使用它在给定粒度上聚合日期。这种粒度可以是一小时、一分钟、一月甚至几秒。 Elasticsearch 还支持其他格式,例如 1.5 天、3.5 个月等。

Elasticsearch 查询如下:

 { 
  "query": { 
    "range": { 
      "timeOfBooking": { 
        "gte": "2014-09-01 00:00:00", 
        "lt": "2014-10-01 00:00:00" 
      } 
    } 
  }, 
  "aggregations": { 
    "ticketTrends": { 
      "date_histogram": { 
        "field": "timeOfBooking", 
        "interval": "day" 
      } 
    } 
  } 
}

下面的 是前面查询的响应:

  "aggregations" : { 
    "ticketTrends" : { 
      "buckets" : [ { 
          "key_as_string" : "2014-09-09 00:00:00",  
          "key" : 1410220800000, 
          "doc_count" : 3 
      }, { 
          "key_as_string" : "2014-09-21 00:00:00", 
          "key" : 1411257600000, 
          "doc_count" : 1 
      } ] 
    } 
  } 

分析推论:我们收到了不同的小时数和每小时对应的文档数。对于每个小时,我们将小时开始的纪元作为关键字段,将日期字符串表示作为 key_as_string 字段。

x 轴为时间的普通条形图是表示此信息的最佳方式,如下图所示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

Hottest arrival and departure points

问题:管理层希望研究机场的航班起飞和到达趋势,以了解最大起飞和到达次数发生在哪里。根据数据,他们希望推出折扣等优惠,并希望在该航线上增加更多航班。

如何解决:Elasticsearch 中聚合的美妙之处在于您可以将聚合嵌套在另一个聚合中。这意味着您可以将术语聚合嵌套在另一个术语聚合或任何其他聚合类型中。这可以增加聚合嵌套的级别数并使其更深。在这里,我们有一个两级聚合,第一级作为航班起飞城市的术语聚合。在第二层,我们将到达城市标记为术语聚合。这种组合为我们提供了每个出发城市的顶级出发城市和顶级到达城市的列表。

这是 Elasticsearch 查询:

{ 
  "query": { 
    "range": { 
      "timeOfBooking": { 
        "gte": "2014-09-01 00:00:00", 
        "lt": "2014-10-01 00:00:00" 
      } 
    } 
  }, 
  "aggregations": { 
    "departure": { 
      "terms": { 
        "field": "departure.airport" 
      }, 
        "aggregations": { 
            "arrival": { 
                "terms": { 
                    "field": "arrival.airport" 
                  } 
              } 
          } 
      } 
  } 
} 

下面的 是前面查询的响应:

  "aggregations" : { 
    "departure" : { 
      "buckets" : [ { 
        "key" : "kochi", 
        "doc_count" : 3, 
            "arrival" : { 
                "buckets" : [ { 
                    "key" : "delhi", 
                    "doc_count" : 2 
                  }, { 
                    "key" : "bangalore", 
                    "doc_count" : 1 
                  } ] 
              } 
      }, { 
        "key" : "banglore", 
        "doc_count" : 1, 
            "arrival" : { 
                "buckets" : [ { 
                    "key" : "delhi", 
                    "doc_count" : 1 
                } ] 
            }     
      } ] 
    } 
  } 
} 

以下是上述响应的可视化表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

分析推断:我们收到了预订机票的热门城市以及每个城市连接到的热门城市。我们使用树形图巧妙地表示了这些信息。

The correlation of ticket type with time

问题:目前,根据计划中的旅行社提供的服务,有两种机票:商务舱和经济舱。在商务舱中,乘客得到了更多的关注,并为他/她提供了美味的食物甚至饮料。然而,管理层指出,固定数量的商务舱和经济舱座位并没有得到很好的回报。有时,经济舱座位已满,但商务舱座位仍然空无一人。同样,如果他们将这些座位分配给经济舱而不是商务舱,他们将从航班中获得更好的收入。由于可以在商务舱和经济舱之间动态分配座位,管理层有兴趣了解每月的座位占用趋势。

根据这一趋势,他们可以预设每个班级的座位数量,从而获得更好的收入。

如何解决:我们可以使用 Elasticsearch 的嵌套聚合能力来解决这个问题。 timeOfBooking 字段上具有日期直方图聚合的两级聚合将构成聚合的基本级别。在日期聚合之上的 ticketType 字段的术语聚合将为我们提供所需的结果。

是 Elasticsearch 查询:

{
  "query": {
    "range": {
      "timeOfBooking": {
        "gte": "2014-09-01 00:00:00",
        "lt": "2014-10-01 00:00:00"
      }
    }
  },
  "aggregations": {
    "ticketTrends": {
      "date_histogram": {
        "field": "timeOfBooking",
        "interval": "day"
      },
      "aggs": {
        "ticketType": {
          "terms": {
            "field": "ticketType"
          }
        }
      }
    }
  }
}

以下是上述查询的响应:

  "aggregations" : {
    "ticketTrends" : {
      "buckets" : [ {
        "key_as_string" : "2014-09-09 00:00:00",
        "key" : 1410220800000,
        "doc_count" : 3,
        "ticketType" : {
          "buckets" : [ {
            "key" : "Ecnomical",
            "doc_count" : 2
          }, {
            "key" : "Business",
            "doc_count" : 1
          } ]
        }
      }, {
        "key_as_string" : "2014-09-13 00:00:00",
        "key" : 1410566400000,
        "doc_count" : 1,
        "ticketType" : {
          "buckets" : [ {
            "key" : "Business",
            "doc_count" : 1
          } ]
        }
    } , { …. } , {…..} ,       
    }
  }
}

图表为您提供了前面响应的可视化表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

分析推论: 可以得出,月初和月中,公务舱客票很多,但后来下降了,优先选择经济舱。

Distribution of the travel duration

问题:一家外部机票预订公司来找管理层,声称如果降低短期旅行的价格,它可以从中获得微薄的利润,但更重要的是,它会让乘客的旅途更加舒适,从而让他们更想乘飞机旅行。他们的理论是,如果有人开始乘坐飞机旅行,他们会觉得乘坐普通火车或公共汽车会感到不舒服。因此,即使降低短途旅行的关税可能不会被证明是非常有利可图的,但它可能有助于吸引更多乘客,以便他们使用飞机进行日常旅行。看到这个提议的明确优势,管理层要求分析团队提供旅行时间的统计数据。管理层需要了解是否更喜欢较短的旅行时间,如果不是,幅度有多大。

如何解决:聚合的灵活性在于它对脚本的钩子。 Elasticsearch 提供对各种语言的脚本支持,例如 Groovy、JavaScript 和 Python。

Note

MVEL 已从 Elasticsearch 1.4.0 中弃用并删除。由于其沙盒功能和更快的执行速度,Groovy 是 Elasticsearch 的默认和首选语言。

Elasticsearch 为此类自定义脚本公开了各种数据挂钩,其中可以引用索引文档中的字段值,并且还可以引用其他属性,例如术语的逆文档频率提供。在这里,我们将使用这个脚本挂钩来访问到达和离开日期字段。减去这些值将给出旅行的持续时间。但请记住,与大多数其他应用程序和语言一样,Elasticsearch 将日期存储为纪元。这意味着在访问日期值时,您实际上将获得该时间的纪元表示。因此,在减去这些日期/时间字段时,我们应该得到一个时代的飞行持续时间。

Note

这是计算它的数学公式(在代码中):

def timeDiff = doc['arrival.time'].value -  doc['departure.time'].value ;
return  Math.round( timeDiff/(1000 * 100 * 60 ))  

我们稍后会找到这个时代表示的相应小时。然后聚合该值以找到每个持续时间的频率。

Elasticsearch 请求如下:

{
  "query": {
    "range": {
      "timeOfBooking": {
        "gte": "2014-09-01 00:00:00",
        "lt": "2014-10-01 00:00:00"
      }
    }
  },
  "aggregations": {
    "ticketTrends": {
      "terms": {
        "script": "def timeDiff = doc['arrival.time'].value -  doc['departure.time'].value ; Math.round( timeDiff/(1000 * 100 * 60 ))  ",
        "lang": "groovy"
      }
    }
  }
}

下面是前面查询的响应:

  "aggregations" : {
    "ticketTrends" : {
      "buckets" : [ {
            "key" : "2",
            "doc_count" : 1
      }, {
            "key" : "1",
            "doc_count" : 2
      }, {
            "key" : "3",
            "doc_count" : 4
    }, {
            "key" : "10,
            "doc_count" : 10
      },  {
            "key" : "21",
            "doc_count" : 12
      } ]
    }
  }

下图为您提供了上述响应的直观表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

分析推论:我们注意到飞机很少用于短途旅行。人们在短途旅行中更喜欢便宜的交通方式。

The most preferred or hottest hour for booking tickets

问题:服务器维护团队通知管理层,有时服务器上的负载 过高,有时负载很小。为了在峰值负载时更好地为流量提供服务,它需要更多的机器。管理层认为,如果能追踪大部分订票的高峰时段,就可以更关注那个时间段的订票趋势。管理层还希望验证服务器维护团队的说法,如果证明正确,则添加更多机器来处理负载。

因此,管理层已经向我们询问有关网站流量每小时分布的数据。

如何解决它:我们使用提供给我们的脚本挂钩将日期转换为其特定时间。聚合这个值可以让我们将一天中的时间分配到当天的预订数量。

Note

用于获取日期的代码如下:

def mydate = new Date(doc['timeOfBooking'].value);
return mydate.format('HH')",

以下是 Elasticsearch 请求:

{
  "query": {
    "range": {
      "timeOfBooking": {
        "gte": "2014-09-01 00:00:00",
        "lt": "2014-10-01 00:00:00"
      }
    }
  },
  "aggregations": {
    "ticketTrends": {
      "terms": {
        "script": "def mydate = new Date(doc['timeOfBooking'].value); mydate.format('HH')",
        "lang": "groovy"  
      }
    }
  }
}

这是前面查询的响应:

  "aggregations" : {
    "ticketTrends" : {
      "buckets" : [ {
        "key" : "14",
        "doc_count" : 9
      }, {
        "key" : "02",
        "doc_count" : 1
      }, {
        "key" : "08",
        "doc_count" : 1
      } ,
  { …. } , { …. }
]
    }
  }
}

下图显示了前面响应的可视化表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

分析推论:我们注意到,预订在晚上和接近营业时间结束时更加活跃。

The most preferred or hottest weekday for travel

问题:管理层注意到有一个基于工作日的订票趋势。他们认为他们需要抓住这一趋势,以便找到一些关于它的洞察力,在此基础上我们可以采取必要的行动。

如何解决它:虽然我们有日期,但我们还没有以一周中的一天格式存储它。为此,我们可以使用 Elasticsearch 的脚本支持来获取每个日期对应的星期几。

Note

这是用于获取星期几的代码:

def mydate = new Date(doc[''timeOfBooking'].value);
return  mydate.format('EEEEEE')";

在这里,我们将提取日期值并将其转换为常规日期对象。我们使用标准日期格式将此对象转换为星期几字符串值。

以下是 Elasticsearch 请求:

{
  "query": {
    "range": {
      "timeOfBooking": {
        "gte": "2014-09-01 00:00:00",
        "lt": "2014-10-01 00:00:00"
      }
    }
  },
  "aggregations": {
    "ticketTrends": {
      "terms": {
        "script": "def mydate = new Date(doc[''timeOfBooking'].value); mydate.format('EEEEEE')",
        "lang": "groovy"
      }
    }
  }
}

以下是上述查询的响应:

  "aggregations" : {
    "ticketTrends" : {
      "buckets" : [ {
        "key" : "Tuesday",
        "doc_count" : 3
      }, {
        "key" : "Thursday",
        "doc_count" : 2
      }, {
        "key" : "Wednesday",
        "doc_count" : 2
      }, {
        "key" : "Friday",
        "doc_count" : 1
      }, {
        "key" : "Monday",
        "doc_count" : 1
      }, {
        "key" : "Saturday",
        "doc_count" : 1
      }, {
        "key" : "Sunday",
        "doc_count" : 1
      } ]
    }
  }

下图 显示了前面响应的可视化表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

分析推论:我们在此注意到大部分预订是在星期三进行的。

The pattern between a passenger's purpose of visit, ticket type, and their sex

问题:在极少数情况下,管理层认为乘客的访问目的、机票类型和乘客性别之间可能存在很强的相关性。作为一个研究案例,他们想检查这三者之间是否存在任何相关性。它注意到所有这三个维度之间的相关性,并认为它需要用数字来捕捉它。

如何解决:如前所述,我们可以进行任何级别的嵌套聚合。这意味着要实现我们当前的目标,我们需要做的就是在第一级使用 purposeOfVisit 字段创建一个三级聚合, ticketType 在第二层,sex 字段在第三层。

以下是 Elasticsearch 请求:

{
  "query": {
    "range": {
      "timeOfBooking": {
        "gte": "2014-09-01 00:00:00",
        "lt": "2014-10-01 00:00:00"
      }
    }
  },
  "aggregations": {
    "purposeOfVisit": {
      "terms": {
        "field": "passengerDetails.purposeOfVisit"
      },
      "
    aggregations 
    ": {
      "ticketType": {
          "terms": {
              "field": "ticketType"
          },
         "aggs": {
              "sex": {
                  "terms": {
                      "field": "sex"
                  }
              }
          }
        }
      }
    }
  }
}

这个响应indexterm"> 前面的查询:

  "aggregations" : {
    "purposeOfVisit" : {
      "buckets" : [ {
        "key" : "WorkRelated",
        "doc_count" : 6,
        "ticketType" : {
          "buckets" : [ {
            "key" : "Business",
            "doc_count" : 6,
            "sex" : {
              "buckets" : [ {
                "key" : "Male",
                "doc_count" : 6
              } ]
            }
          } ]
        }
      }, {
        "key" : "Personal",
        "doc_count" : 5,
        "ticketType" : {
          "buckets" : [ {
            "key" : "Business",
            "doc_count" : 3,
            "sex" : {
              "buckets" : [ {
                "key" : "Male",
                "doc_count" : 3
              } ]
            }
          }, {
            "key" : "Ecnomical",
            "doc_count" : 2,
            "sex" : {
              "buckets" : [ {
                "key" : "Female",
                "doc_count" : 1
              }, {
                "key" : "Male",
                "doc_count" : 1
              } ]  
            }
          } ]
        }
      } ]
    }
  }
}

下面的饼图给出了 你是前面响应的可视化表示:

读书笔记《elasticsearch-blueprints》使用Elasticearch进行分析

解析推断:在这个 特殊情况下,我们观察到业务类型票证和偏好密切相关。

Summary


Elasticsearch 的核心价值是在它之上运行查询和分析。所有这些实时性使 Elasticsearch 成为独特且广泛使用的组合。在这里,实时是指 Elasticsearch 一次提取一个文档的能力,并在文档提供索引后立即使文档可搜索。 Elasticsearch 具有广泛的分析功能作为其天生的支持。从第一天开始,它的构建方式就是所有这些功能都可以实时呈现。还必须注意,聚合的范围是它的查询分数。这意味着聚合的输入是查询的输出。匹配查询的文档通过聚合得到聚合结果。

本章的重点如下:

  • 嵌套聚合可用于组合各种聚合,例如术语、地理、日期直方图等,以解决许多情况下的分析需求。这个核心功能一旦掌握得当,就可以用来解决许多特定领域的聚合和分析需求。

  • Elasticsearch 提供了各种脚本钩子。它支持 Groovy、Python 和 JavaScript。 MVEL 以前是默认语言,但由于安全问题已在 1.4.0 版中删除。 Groovy 现在是默认语言。

  • 嵌套聚合可以进行到任意数量的级别。

在接下来的章节中,您将学习如何提高文本搜索的搜索质量。这包括对各种分析器的描述以及如何混合和匹配它们的详细描述。