NetSuite中使用SuiteScript 2.0的Map/Reduce脚本





0/5 (0投票)
关于在NetSuite云中使用Map/Reduce范例的说明。
引言
Map/Reduce类型的脚本是NetSuite的服务器端脚本,可用于处理大量记录。Map/Reduce类型的脚本仅在SuiteScript 2.0中可用。当Map/Reduce脚本执行时,除非脚本要求使用一个队列,否则将跨多个队列进行并行处理。因此,此脚本类型可用于批量处理或长时间运行的后台进程。
Map/Reduce脚本是map/reduce范例的实现,其中将大量记录拆分为较小的单元,对这些较小的单元应用处理需求,并在最后进行合并以进行分析。在此过程中,首先确定需要处理的记录。这可以通过在NetSuite中使用保存搜索或脚本内定义的搜索来完成。搜索到的记录被结构化为键/值对,其中每个键/值对都并行读取,并传递到下一步进行处理。这些步骤类似于处理管道上的单个可重用函数。NetSuite中的管道由五个不同的阶段组成,这些阶段按顺序执行。阶段基本上是在阶段内调用的函数。
每个阶段都有自己的治理限制。当达到治理限制时,脚本将自动挂起,即重新调度或重新使用。此脚本的部署记录上的“暂停分钟数”也可以设置暂停限制。因此,当达到限制或时间限制结束时,脚本将挂起。由于自动挂起,用户无需过多担心治理限制,而可以专注于高效的逻辑。Map/Reduce脚本的阶段、描述和每个阶段的治理限制在表1中进行了描述。
表1. Map/Reduce SuiteScript 2.0中的治理限制
阶段/函数 | 描述 | 治理(单位) |
---|---|---|
1. getInputData() | 这是获取数据的第一个阶段。在此阶段,可以执行搜索、读取记录以及将数据打包到数据结构中。例如,该函数可以通过运行搜索来返回NetSuite记录的搜索。键/值对将是搜索结果。 | 10,000 |
2. map() | 数据传递到此阶段是自动的。在此阶段,将发生数据分组。来自getInputData()的每个键/值对都会在每次函数调用时传递。如果脚本使用reduce函数,则输出数据将发送到shuffle,然后发送到reduce函数。否则,map的输出将直接发送到summary阶段(如果存在)。在此阶段可以进行任何数据预处理。例如,我们可能希望在处理键:值对时编辑某些记录。 | 每次调用1000次(每次任务10000次) |
3. shuffle() | 此函数根据键对值进行分组。此函数是默认提供的。在此阶段,系统将对发送到reduce阶段(如果已定义)的任何键/值对进行排序。如果未使用map函数,则此阶段将从getInputData阶段获取数据。例如,假设搜索返回12个键/值对,每个键代表一个项目记录,每个值代表该项目的定价数据。为简单起见,假设只有三个项目,一个项目有3个定价数据,另一个有5个定价数据,最后一个有4个定价数据由getInputData或map阶段返回。然后,shuffle阶段将提供三个键/值对。因此,我们将获得第一个项目的内部ID及其3个定价数据记录,第二个键及其5个定价数据记录,第三个键及其4个定价记录(有关示例,请参见表2和3)。 | 不适用(NA) |
4. reduce() | 此阶段从shuffle阶段获取数据。在此阶段,具有相同键的值似乎被分组在一起。此函数评估每个组中的数据,其中每个组在每次函数调用时都会传递。 | 每次调用1000次(每次任务10000次) |
5. summarize() | 此函数从Reduce函数获取数据。可以在此处构建逻辑以进行最终的数据处理。例如,可以在此处进行更新或文件/日志写入。 | 10,000 |
并非所有阶段都对Map/Reduce脚本的正常运行必需。根据处理需求或逻辑原因,适用不同的阶段。在五个阶段中,可以根据处理需求省略map或reduce函数。此外,如果不需要查看摘要或在前几个阶段完成后没有要做的事情,则可以完全省略summary阶段。
为了说明,本文档讨论了三种实体类型和两个示例。这些实体分别命名为Item(项目)、Pricing(定价)和Price Effective Date(价格生效日期)。记录类型“Item”包含id、name和inactive属性。“Pricing”实体包含internalId、unitPricing、category和item属性。“Price Effective Date”实体包含internalid、price、effectiveDate和item属性。它们之间的关系很简单,即一个项目可以拥有零个或多个Pricing和Price Effective Date记录。属性和关系在图1的对象关系图中显示。
第一个示例说明了map函数的用法,第二个示例说明了map和reduce函数的用法。在这两个示例中,getInputData都是必需的,而summarize是可选的。第一个示例说明了删除符合特定条件的全部项目价格数据的场景。第二个示例说明了为符合搜索条件的item创建“Price Effective Date”记录的场景。Price Effective Date与price data共享许多属性,并且要求对于每个price data,都需要在删除该项目现有的Price Effective Date记录后,为每个项目创建新的Price Effective Date记录。
在这两个示例中,脚本中都内置了一个搜索,该搜索根据定义的标准返回item id和相关的pricing data。或者,也可以使用保存搜索。本文档附带的示例在代码中使用定义的搜索。getInputData()阶段中执行的搜索会返回item id、pricing category、pricing data的internalid以及pricing data的unitPrice。说明的示例记录显示在表2中。
表2. Item Price Data示例。
ID | 定价类别 | internalId.pricing | unitPrice.pricing |
---|---|---|---|
1001 | 1 | 101 | 10 |
1001 | 2 | 102 | 20 |
1001 | 3 | 103 | 30 |
1002 | 1 | 104 | 5 |
1002 | 2 | 105 | 10 |
1002 | 3 | 106 | 15 |
1002 | 4 | 107 | 5 |
1002 | 5 | 108 | 15 |
1003 | 1 | 109 | 1 |
1003 | 2 | 110 | 2 |
1003 | 3 | 111 | 3 |
1003 | 4
| 112
| 4
|
… |
|
|
|
第一个示例的代码列表显示在清单1中,该代码是用SuiteScript 2.0编写的,并使用了Map/Reduce。完成后,脚本将删除符合条件的项目的现有定价数据,其中符合条件的项目的确定方式是在getInputData阶段中通过定义的搜索来确定的。脚本遵循以下通用算法:
- 在getInputdata中读取项目和价格数据
- 在map阶段,删除从getInputData阶段传递过来的价格数据
- 在summary阶段,编写关于使用情况和描述性消息的审计日志
清单1. 示例1的代码列表
在上面的函数中,map函数接收如表2所示的记录,并且该函数获取定价数据的内部ID并删除记录。context.write(///)函数将此信息写入,然后传递给下一个函数(在此例中为summarize),该函数可用于汇总处理的记录数,当然这部分是可选的。在这个过程中,shuffle阶段只是将键与值聚合起来,并将数据传递给下一个阶段。删除记录的主要任务在此完成,因此不需要reduce阶段。
第二个示例涉及的不仅仅是删除记录。目的是读取定价数据并为项目创建附加的Price Effective Date记录。一个项目的Price Effective Date数量必须与该项目的Pricing数据数量相匹配。新创建的Price Effective Date必须具有不同的价格和不同的生效日期。为简单起见,假设价格比[ricing数据中反映的价格高2%,并且生效日期在此示例中是一个常量值。代码示例显示在清单2中,该代码遵循以下通用算法:
- 在getInputdata中读取项目和价格数据
- 在map阶段,创建一个数据结构,其中键为itemid,值为price data。
- shuffle阶段会将键:值减少,以创建一个数据结构,其中键为item id,值为该项目的price data列表。
- 在reduce阶段,将接收到item:[pricedata]。
- 删除项目的Price Effective Date
- 对于item中的每个PriceData
- 创建Price Effective Date
- 在summary阶段,编写关于使用情况和描述性消息的审计日志
清单2. 示例2的代码列表
对于第二个示例案例,getInputData函数保持不变,它将项目和价格数据传递给map阶段。map函数略有不同,它映射实际数据以创建数据结构,其中键是item id,值是pricing data。此数据在此阶段使用context.write(...)函数写入。shuffle阶段在后台运行,数据被聚合并传递给reduce阶段。shuffle阶段枚举所有传入的键:值对,并将结果对象传递给reduce阶段。在map和reduce阶段之间,数据按唯一的item ids进行缩减,reduce阶段将一个item与所有定价数据作为一条记录接收。
表3代表了映射和shuffle的结果,其中一个项目将有一个记录和所有的定价数据。Map/Reduce能够将表2中的映射数据生成到表3中的缩减数据。因此,在reduce阶段,一个项目只传递一次。代码示例显示了如何使用两个不同的函数来删除Price Effective Date记录并创建新的Price Effective Date。
表3. 缩减数据示例
ID | PriceData |
---|---|
1000 | [ {priceLevel:101, price:10, item:1000}, {priceLevel:102, price:20, item:1000}, {priceLevel:103, price:30, item:1000}, ] |
1001 | [ { priceLevel: 104, price: 5, item: 1001 }, {priceLevel:105, price:10, item:1001}, {priceLevel:106, price:15, item:1001}, ] |
搜索可能会返回数千条记录。然而,在NetSuite的Map/Reduce范例中,这些记录一次一条地从getInputData阶段传递到map或reduce阶段。如果目的是修改定价数据(或删除定价数据),如第一个示例所示,则可以在map阶段或reduce阶段修改记录。用例可以很简单,只需调用删除函数并传入要删除的记录的ID即可。我们可能需要加载记录的internalId.pricing并推送更新。在reduce阶段,多个队列会并行传递一个项目和相应的定价数据。这两个示例展示了Map/Reduce的两种不同模式。
第一个示例展示了如何使用多个队列来处理大量的记录。在此示例中,单个记录从getInputData阶段传递到map阶段。可以在Map阶段执行一定程度的处理。单个记录在map记录中作为独立进程进行处理。此模式可用于处理简单记录。例如,可以在此处对传递到上下文的记录执行CRUD操作,然后通过将键:值对传递到上下文来调用summarize。第二个示例使用了reduce阶段,在该阶段,在map阶段创建了一组新的键:值对以供进一步处理,并将其传递给reduce阶段。
在第二个示例中,我们首先枚举了一个项目的所有定价数据,进行了一次搜索调用以识别一个项目的price effective date记录,并在创建新的price effective date记录之前删除了这些记录。在这两个示例中,NetSuite都在后台提供了并行处理支持以及聚合或缩减支持,开发人员无需担心处理多个队列或缩减。
我提供的代码列表不完整。为了使脚本紧凑,我省略了几行错误处理。另外,使用常量作为Price Effective Date记录的价格字段和生效日期字段的决定是为了简化事情。在某些情况下,我们必须依赖一些重复使用的对象。例如,如果价格变动取决于其他某些业务逻辑。在这种情况下,我们可以利用NetSuite提供的缓存技术,而不是为所有项目调用搜索。我将写一篇后续文章,介绍如何通过缓存提高Map/Reduce的性能。
在本文档中,我试图详细介绍NetSuite中Map/Reduce函数的一些实际示例。我希望听到读者的评论或改进建议。我将根据需要更新博客。感谢您阅读本文档并提供评论。代码及其版本可以在我的GitHub存储库(https://github.com/benktesh/netsuite)的“Map Reduce Examples”文件夹下公开获取。