美文网首页
Azure Search 如何做数据上传

Azure Search 如何做数据上传

作者: 简单的怪石头 | 来源:发表于2020-01-03 09:59 被阅读0次

    当采用Azure Search 来搜索结构化数据,在考虑怎样构建合理的结构,以及怎样合理使用query方法的同时,也需要考虑到怎样把数据同步到search index上。Azure Search对数据上传提供了两种方式。

    • 通过rest API的方式上传数据。官方提供的SDK也是根据rest API来实现的。
    • 通过Search Indexer直接将数据源映射到search index上,其会主动爬取数据源并上传到search index上去。

    这两种方式差别在从数据提供方的角度来看是push和polling的区别。

    第一种方式是数据提供方需要将自己的数据主动push到search index上,一般需要自建服务去抓去数据并组合成搜索需要的结构,并上传到search index,如图所示:

    push-mode-of-upload-data-to-search-index.png

    第二种方式是数据提供方允许indexer接入自己的数据源,indexer就可以按照既定的查询数据源并mapping到search index上,这对于数据提供方来讲就是一种polling的方式,因为其不用自建服务。

    polling-mode-of-upload-data-to-search-index.png

    那么选择使用哪种方式会更好呢?这个是要由不同的业务需求来驱动决定的,当然更需要明确每种方式的特点以及限制是什么,这里大概列一下。

    对于第一种push方式的rest API而言,有如下特点及限制

    • 每次提交的文本大小应该限制在16M以下
    • 每次提交的文本数最大在1000个
    • 两次顺序的push请求之间没有时间的限制,从而单个文档能够达到秒甚至毫秒级别的更新

    rest api limitation

    对于第二种polling方式的Indexer而言,有如下的特点及限制

    • 能够直接访问数据源,批量抓去数据的速度非常快,一分钟内能完成数万条数据的更新
    • 对数据源有所限制,其可以是在Azure 云上的sql server数据库、blob storage等,具体请看链接:data source input
    • Indexer运行的可以是一次性的,也可以是定时任务的方式。但是定时任务有最短5min的时间限制

    以上是我认为可能会影响决策的这两种方式需要考虑的点。那么基于这些点,我们应该怎样做决策呢。分享一下心得,当你需要解决的问题是如何往search index上初始化数据时,你需要问这样一些问题:

    1. 你的数据源在哪儿?
    2. 能接受的最小时间的初始化时间是多少?

    当你的数据源没法满足Indexer所需求的datasource的类型,那么你没有必要再在第二种方案上去纠结了。如果你希望整个过程耗时较短并且开发成本低,那么第二种方案会是一个好的选择。是否好调试也应该是需要考虑的一个点,但是两种方式其实都是可调试的,只是对与第二种方案来讲其调试方式可能仅仅只能使用azure提供的rest API通过获取indexer运行的状态来调试,第一种因为相当于要自己写一个抓取数据并上传数据的应用,调试方式可以相对灵活。

    当你需要解决的问题是如何往search index上持续的更新数据,你可能需要问:项目对数据实时性的要求怎样?如果对实时性要求极高,那么显然indexer的绝对不是一个好的选择,因为它最多能做到5分钟去跑一次,也就是说最少需要5分钟才能将数据更新到search index上,况且当你的数据量较大的时候,甚至都不能保证,一次的运行能在5分钟之内跑完。说到这里就需要提一下Search indexer在运行时的特性:

    1. 每次运行都是以全量的方式运行,即每次都会抓取声明为dataSource中的所有数据
    2. 抓取的数据会以mergeOrUpload的方式同步到search index,即如果已经存在对应的文档就以合并的方式更新,如果不存在就创建一条新的文档
    3. 如果当前运行的indexer没有运行完,但是已经超过了下一次运行的时间,它会直接忽略下一次,直到当前次跑完

    所以如果对实时性要求较高,应该使用push的方式去更新数据,关于这块具体的设计,我们将放到另一篇文章中说明。相反如果对实时性要求不高,那么Indexer也会是一个好的选择。

    数据的初始化以及数据的持续更新是两个不同的问题,他们的解决方案有时候不能共用。我之前的项目就经历这样的情况,文档的数据量级在千万,客户需要我们至少在一分钟之内将更新数据同步到search-index中,并且要在尽量快的时间之内完成数据的初始化。所以在持续更新的问题上我们只有选择push的方式,但是当我们期待着使用这种push的方式也能完成初始化的工作的时候,发现我们需要做额外的数据准备工作,种种问题导致无法满足时间的短的需求,所以我们采用了indexer的方式去做了初始化,千万的数据也能在十多个小时之内完成。

    关于第一种的实践及设计,计划在另一篇文章中讲。本文将继续介绍第二种方式的使用,分享一下自己在这个过程中遇到的一种常见的问题。

    如何构建Indexer

    Indexer是Azure提供同步数据到Search Index的一种方式。我们将基于从Azure sql server Database到Search Index去讲述Indexer的实现过程。

    一个indexer的DataSource在sql server来讲可以是一张表或者一个视图,如果search index的结构比较简单,其字段和某个表对应的字段一样并且类型也一样,那么我们可以以这个表为dataSource。但情况可能常常不是这样的,因为我们在设计search index的时候其数据结构往往是比单个表要复杂的。例如我们search index的结构如下:

    {
      Id : GUID,
      Name : "sring",
      Age: num,
      Addresses: [
        {
          LineOne: "string",
          City: "string",
          GeoLocation: {
            type: "Point",
                coordinates: [longitude, lititude]
          }
        },
      ],
      GraduatedSchool: {
        Name: "string",
        Country: "string"
        CountryCode: "string"
      }
    }
    

    但是对应到数据库表却是一个较复杂的映射关系, 如下面的类图所示:

    table-relation-map.png

    在这种情况下,我们不得不去构建一个视图,这个视图的结构应该和search index 的结构一致。你可能会问这样一些问题,面对复杂结构indexer真的能正确的映射吗,对此我们当时也有疑问,毕竟Azure在官方的文档上面也说,它只支持一些简单数据结构的映射。但是经过我们的几次尝试,发现只要能够保证从视图中查询出来的每一条数据能在JSON序列化之后和search index结构一模一样,那就能将其应用于indexer作为dataSource。

    所以我们就花费了一些时间去尝试构建这种结构的视图。最终这个视图的创建sql如下。

    CREATE VIEW IndividualView AS
    SELECT
      ind.Id,
      ind.Age,
      (
        SELECT
          CONCAT(
            ind.FirstName,
            ind.LastName,
          )
      ) AS Name,
      (
        SELECT
          s.Name,
          mc.Code AS CountryCode,
          mc.Name AS Country
        FROM School AS s
        LEFT JOIN  MetadataCountry AS mc ON s.CountryCode = mc.Code
        WHERE
          s.Id = c.SchoolId FOR JSON PATH,
          WITHOUT_ARRAY_WRAPPER
      ) AS GraduatedSchool,
      (
        SELECT
          child_addresses.*
        FROM (
            (
              SELECT
                adr.LineOne,
                adr.City,
                'Point' AS 'GeoLocation.type',
                JSON_QUERY (
                    FORMATMESSAGE(
                      '[%s,%s]',
                      FORMAT(
                        m.Longitude,
                        N'0.##################################################'
                      ),
                      FORMAT(
                        m.Latitude,
                        N'0.##################################################'
                      )
                    )
                  )
                AS 'GeoLocation.coordinates',
              FROM Address AS adr
              WHERE
                adr.IndivdiualId = ind.Id
            )
          ) AS child_addresses FOR JSON PATH
      ) AS Addresses,
    FROM Individual AS ind;
    

    涉及到一些SQL函数,在使用过程中也发现其实SQL提供了很多丰富的功能,所以对于构建复杂结构的VIEW基本都没有什么问题。

    这里需要特殊说明的就是GeoLocation,在Search index上声明了一个字段是GeographyPoint类型,在上传的时候就需要将数据整理成GeoJson 的格式。具体来说就是如下的结构

    {
        type : 'Point',
        coordinates : [longitude, latitude]
    }
    

    具体怎么转换可以参见以上创建View的sql。

    成功构建了视图之后,dataSource的创建还没有结束,需要在Azure portal或者调用API来正真的创建,这个过程需要提供数据库的connection string, 并且还需要保证在Azure 云上面数据库的防火墙是允许被Search Service访问的。假设我们使用API的方式来创建,需要发送一下的请求:

    POST /datasources?api-version=2019-05-06 HTTP/1.1
    Host: {{service-host}}
    api-key: {{api-key}}
    Content-Type: application/json
    
     {
         "name" : "individual-data-source",
         "type" : "azuresql",
         "credentials" : { "connectionString" : ""}, //提供可访问的connection string
         "container" : { "name" : "IndividualView" } //指定视图名
     }
    
    

    创建结束之后就可以创建Indexer了,这个过程也可以使用portal或者API的方式,这里给rest API的例子,需要发送一下的请求:

    POST /indexers?api-version=2019-05-06 HTTP/1.1
    Host: {{service-host}}
    api-key: {{api-key}}
    Content-Type: application/json
    
    {
        "name": "individual-indexer",
        "description": "indexer for individual",
        "dataSourceName": "individual-data-source",
        "targetIndexName": "individual-index",
        "parameters": {
            "maxFailedItems": "15",
            "batchSize": "500"
        },
        "schedule": {
                "interval": "PT15M",  //每15分钟跑一次
                "startTime": "2020-01-01T00:00:00Z"
        }
        "fieldMappings": [
                { "sourceFieldName": "Id", "targetFieldName": "Id" },
            { "sourceFieldName": "Name", "targetFieldName": "Name" },
            { "sourceFieldName": "Age", "targetFieldName": "Age" },
            { "sourceFieldName": "Addresses", "targetFieldName": "Addresses" },
            { "sourceFieldName": "GraduatedSchool", "targetFieldName": "GraduatedSchool"}
        ]
    }
    

    可以看到,在创建过程中我们可以指定很多参数具体参数的说明可以参考create indexer rest api. 这里有一点需要说明的是,声明字段映射的时候,只用声明第一层结构的映射就可以,甚至如果你能确保View的结构以及字段名和search index的一样,你甚至可以不用指明fieldMappings参数。

    至此indexer 的构建就完成了,如果指定了schedule参数,indexer就会在指定的开始时间开始运行,如果没有指定,在构建完成之后,indexer就会立马执行。后续的调试可以调用get indexer status API来查看运行状态。

    相关文章

      网友评论

          本文标题:Azure Search 如何做数据上传

          本文链接:https://www.haomeiwen.com/subject/qixzoctx.html