Aerospike UDF Development Guide

↘锁芯ラ 提交于 2019-12-10 19:46:03

User-Defined Functions (UDF) Development Guide

Introduction

在Aerospike中,UDF是指可以运行在Aerospike数据库服务器上的一段代码,使用lua脚本语言进行编程(也可以是被lua脚本调用的C语言)

Aerospike的UDF主要有两种,一种是针对单条记录的(Record UDF)和流式UDF(Stream UDF).

单条记录的UDF只能操作于一条记录,而流式UDF可以作用于记录流,它可以包含多个流操作符来执行非常复杂的查询

UDF的复杂性可以小到只有数行代码,大到包含了许多内部函数和外部函数的成千上万行代码

每一个UDF,应用的数据模型和记录之间的内部交互是值得深思的问题.通常UDF的开发流程是这样的:

  • 设置应用的数据模型
  • 设计UDF来对记录执行响应的函数
  • 创建/测试UDF
  • 注册UDF到Aerospike服务器
  • 循环进行测试/开发流程
  • 系统测试
  • 系统部署

UDF的设计和开发通常是一个迭代的过程,通常开始都很简单,慢慢的开始逐渐复杂

了解UDF的机制的最好办法就从创建Hello World Record UDF开始,下面这个例子将会展示如何创建和执行一个Hello World UDF

function hello_world(rec)
  return "Hello World!!"
end

Hello World UDF被某个记录调用,这个UDF只是简单的返回了Hello World给调用者

Developing Record-Based UDFs

开发基于记录操作的UDF

在Aerospike键值操作中,对每个记录调用一个记录UDF.通常,除了批量操作外,KV操作的目标对象只是一条记录。通常,一个记录UDF可以完成以下工作

  • 创建或者删除记录中的B
  • 读取记录中的所有Bin
  • 修改记录中的所有Bin
  • 删除或者创建某个Record
  • 参数在客户端进行构造和传入.
  • 服务端的返回结果集可以是多种类型 (e.g. string, number, list, map)

Example: Record Create or Update

在 ( Annotated Record UDF )的简单例子中我们展示了如何在Aerospike中创建或者修改一条记录

此外,开发人员可以在Aerospike命令行中进行如下操作进行操作记录

status = aerospike:create( rec )
status = aerospike:exists( rec )
status = aerospike:update( rec )
status = aerospike:remove( rec )

Example: Basic Statistics Management

通过 ( Statistics Record UDF )的简单例子,我们展示了UDF如何进行统计N条KV记录中Bin的(最大值,最小值,平均值,累计数)

此外,在例子中,我们可以使用lua模块的日志记录函数进行打印日志到日志文件或者控制台中

info(message)
debug(message)
trace(message)
warn(message)

日志函数会打印到日志文件或者控制台.下面中这个日志

trace("[ENTER]<%s>  Value(%s) valType(%s)",  meth, tostring(newValue), type(newValue));

将在日志或者控制台中生成日志输出:

Sep 21 2013 22:25:22 GMT: DETAIL (ldt): (/home/aerospike/src/lua/udf_samples.lua:160) [ENTER]<unique_set_write()> Value(Map("B"->71, "A"->70)) valType(userdata)

Aerospike Client Record UDF Invocation

Aerospike客户端对记录操作的UDF调用方式:

Record UDF: More Detail

记录型UDF的更多细节

Record UDF Development 章节提供了关于记录型UDF开发的更多细节

Record UDF Examples 展示了我们已经开发好的用于测试和文档目的的记录性UDF例子

Stream UDF

流式UDF

流式UDF被记录流调用而非单条记录

在记录上使用二级索引,我们就可以通过查询条件将记录子集以流的方式输出.流式UDF可以导出记录中Bin的值,获取记录的条数或者导出记录流类似的统计

大概的流程就是:

  • 创建二级索引
  • 在二级索引上创建查询
  • 对结果流对象使用UDF处理

下面是关于特定语言使用流式UDF的一些信息

Example: Simple Statistics

简单统计的例子

Stream UDF – Simple Statistics例子中, 我们对数据集进行了统计

Example: Word Count

在例子 Stream UDF - Word Count 中,我们统计了book中某个词出现的次数

Stream UDF: More Detail

流式UDF的更多细节

Stream UDF Development section provides more details on Stream UDF development.

Stream UDF Examples show several Stream UDFs that we’ve developed for both testing and documentation purposes.

Functional Benefits of UDFs

函数式UDF的好处

在使用UDF的时候,我们首先要确保List和Map数据类型操作的原子性,多个操作可以在Aerospike客户端,通过operate() 命令链式地操作同一条记录

直接操作单条记录的事务性能要比使用UDF更快,伸缩性更好.然而,很多场景下,相比于直接在应用端实现类似功能,UDF则更具有优势.

  • 扩展复杂数据类型(complex data types)的功能.使用新的原子操作,或者实现新的集合类型(参见记录UDF示例)

  • A background UDF, a record UDF applied to multiple records via scan or query, can be used to carry out maintenance.背景UDF(用于扫描和查询多条记录的UDF)可以用来进行维护

  • 使用流式UDF来实现聚合.


流式UDF例子–word count

Lua UDF – Word Count

Overview

概述

这个例子是发布在谷歌MapReduce论文上的算法的一个变体,该算法统计文档中每个单词出现的次数

Data Model

数据模型
每一条记录代表这本书的一篇论文,记录的结构由下面几个Bin组成

  • book_id – 改书的唯一标识
  • page_id – 论文的唯一标识
  • page_no – 页码
  • body – 论文内容主体

我们假设下面的二级索引是存在的:

  • book_id为bin上的数字类型的二级索引–这使得我们通过查询图书唯一标识,拿到改书的字数。
  • page_id为bin上的数字类型的二级索引–这使得我们通过论文唯一标识获取指定论文的字数。

Code

代码

我们创建两个Stream UDF:

  • 使用 aggregate() 操作来获取每篇论文单词出现的次数.
  • 使用 reduce() 操作将 aggregate()结果合并成一个结果

创建一个名为 word_count的UDF

function word_count(s)
  return s : aggregate(map(), page_words) : reduce(sum_words)
end

word_count()在流对象s上执行 aggregate()reduce() 操作

首先我们利用aggregate()操作将单个论文中单词和单词出现的次数进行映射

在这段代码中,我们将map 初始值设置为空,page_word() 作为aggregate()函数的参数

local function page_words(words, rec)
  -- read the 'body' bin
  local body = rec['body']

  -- iterate over each word in contents
  for word in string.gmatch(body, "%a+") do

    -- if words[word] is not nil, then use words[word], otherwise use 0 (zero)
    local count = words[word] or 0

    -- add 1 to the previous count
    words[word] = count + 1
  end

  return words
end

你可能会注意到,我们把words作为参数传入page_words()然后又返回.我们的目的主要是做单词和出现次数的映射.第二个参数从数据库中的一条记录了,我们都知道这是一条记录,或者说记录流,因为这是流的第一个操作.

aggregate()的结果是一个单词map,如果你集群中有多个节点的话,那么就有多个单词map,在大多数实例中,集群中每个Node都有可能有多个单词map,因为聚合是在很多个数据分区中进行的.

下面我们使用 reduce() 操作来讲多个单词map合并成一个map

在下面的代码中, sum_words() 是作为reduce() 操作的操作函数

local function sum_words(word1, words2)
  -- use map.merge() to merge two maps
  -- if the same name exists in both maps, then use the function to merge the values.
  return map.merge(word1, words2, math.sum)
end

我们将两个map使用 map.merge()进行合并 .这个merge函数是将两个键值对进行合并的.例子中使用了 math.sum()对两个map中相同键的值进行累加

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!