Azure Search 如何做数据上传

当采用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来查看运行状态。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • 01 消费升级的背后 消费升级的主力消费者是中产阶级。 按江南春的说法,他们有三爱、三怕、三缺。 三爱:爱美,爱玩...
    老李写做阅读 1,214评论 4 88
  • 这一夜, 我送你一个问候:还好吗? 你送我一朵玫瑰:勿忘我; 天 黑压压的一片, 风 喧嚣穿透 整个 世界, 雪花...
    银河雅士阅读 762评论 3 31
  • 【每日一问】我们终于开课啦!接下来的14天,你准备怎样完成股票初级的学习任务呢? 是早就成竹在胸自信满满还是有些迷...
    安之若素_d13c阅读 348评论 0 0
  • 带媳妇看完电影,骑自行车送她回家。经过她们村的一片玉米地时,她突然就从自行车上蹦了下来,把我也拽停,扯着我...
    收稿商社阅读 5,225评论 4 16