我可以自动将换行添加到AWS Firehose记录吗?

时间:2017-05-29 16:01:16

标签: amazon-kinesis amazon-kinesis-firehose

我正在尝试使用以下设置配置Kinesis Analytics应用程序:

  • 输入流是Kinesis Firehose,它采用字符串化的JSON值
  • SQL是一个简单的直通(以后需要更复杂但是为了测试,它只是通过发送数据)
  • 输出流是第二个Kinesis Firehose,它将记录传送到S3存储桶

稍后,我将使用Hive + JSONSERDE导入S3存储桶的内容,希望每个JSON记录都在自己的生产线上。 Firehose输出只会附加所有打破JSONSERDE的JSON记录。

可以将AWS Lambda数据格式化程序附加到输出流,但这看起来很昂贵。我想要的是使用换行符分割每条记录。

如果我没有使用Google Analytics应用程序,我会将换行符附加到每个Firehose记录中。在应用程序的SQL中没有办法做到这一点似乎很奇怪:

CREATE OR REPLACE STREAM "STREAM_OUT" (
  a VARCHAR(4),
  b VARCHAR(4),
  c VARCHAR(4)
);
CREATE OR REPLACE PUMP "STREAM_PUMP" AS
  INSERT INTO "STREAM_OUT"
    SELECT STREAM
      "a",
      "b",
      "c"
    FROM "SOURCE_SQL_STREAM_001";

添加Lambda数据格式化程序是最佳答案吗?我真的很想避免这种情况。

3 个答案:

答案 0 :(得分:2)

我有类似的要求在firehose生成的文件中添加新行。在我们的应用程序中,通过API网关调用firehose。

这在“集成请求”部分下的“正文映射模板”中指定。

API网关中的以下命令会为kinesis firehose记录生成新行。

方法1:

    #set($payload="$input.path('$.Record.Data')
")
        {
            "DeliveryStreamName": "$input.path('$.DeliveryStreamName')",
            "Record": {
            "Data": "$util.base64Encode($payload)"
        }
        }

如果您通过API网关调用firehose,这非常有效。

谢谢&问候, Srivignesh KN

答案 1 :(得分:2)

使用 Python 或 Node.js 的解决方案

我正在使用 DynamoDB Streams,我需要将这些记录保存到 S3 中。我实现了一个 Kinesis Firehose 流和一个 Lambda 函数。这有助于将我的记录作为 JSON 字符串输入 S3,但是,保存到 S3 中文件的每条记录都是内联的,即在一个连续的行中,因此我需要在每条记录的末尾添加一个新行添加它是为了让每条记录都在自己的行上。对于我的解决方案,我最终不得不进行一些 base64 解码/编码。

我是这样做的:

  1. 在创建 Kinesis Firehose 流时,启用“转换
    使用 AWS Lambda 的源记录”(选择“已启用”)。如果您已经创建了流,您仍然可以通过编辑现有流来启用此功能。
  2. 此时您需要选择另一个执行此转换的 Lambda 函数。就我而言,我需要 在每条记录的末尾添加一个新行,这样当我在文本编辑器中打开文件并查看它时,每个条目都在单独的一行上。

以下是我用于第二个 Lambda 的 Python 和 Node.js 测试解决方案代码:

添加换行符的Python解决方案:

import json
import boto3
import base64

output = []

def lambda_handler(event, context):
    
    for record in event['records']:
        payload = base64.b64decode(record['data']).decode('utf-8')
        print('payload:', payload)
        
        row_w_newline = payload + "\n"
        print('row_w_newline type:', type(row_w_newline))
        row_w_newline = base64.b64encode(row_w_newline.encode('utf-8'))
        
        output_record = {
            'recordId': record['recordId'],
            'result': 'Ok',
            'data': row_w_newline
        }
        output.append(output_record)

    print('Processed {} records.'.format(len(event['records'])))
    
    return {'records': output}

Node.js 添加换行符的解决方案:

'use strict';
console.log('Loading function');

exports.handler = (event, context, callback) => {

   
    /* Process the list of records and transform them */
    const output = event.records.map((record) => {
        
        let entry = (new Buffer(record.data, 'base64')).toString('utf8');
        let result = entry + "\n"
        const payload = (new Buffer(result, 'utf8')).toString('base64');
            
            return {
                recordId: record.recordId,
                result: 'Ok',
                data: payload,
            };
            
    });
    console.log(`Processing completed.  Successful records ${output.length}.`);
    callback(null, { records: output });
};

一些帮助我拼凑 Python 版本的优秀参考资料:

在上面的原始问题中,MrHen 希望在不使用第二个 Lambda 的情况下执行此操作。我能够在第一个 Lambda 中实现这一点,而不是使用 Kinesis Firehose 转换源记录功能。我通过从 DynamoDB 获取 newImage 并按以下顺序执行此操作:编码、解码、添加新行(“\n”)、编码、解码。可能有更清洁的方法。我选择使用第二个 Lambda 函数使用转换源记录功能,因为此时我觉得它更清晰。

就我而言,单个 Lambda 解决方案如下所示:

 # Not pretty, but it works! Successfully adds new line to record.
 # newImage comes from the DynamoDB Stream as a Python dictionary object,
 # I convert it to a string before running the code below.

    newImage = base64.b64encode(newImage.encode('utf-8'))
    newImage = base64.b64decode(newImage).decode('utf-8')
    newImage = newImage + "\n"
    newImage = base64.b64encode(newImage.encode('utf-8'))
    newImage = base64.b64decode(newImage).decode('utf-8')

答案 2 :(得分:0)

这是我们实施方式的基本示例。我们使用javascript将记录放入Kinesis Stream,并使用Firehose通过gzip压缩重定向到s3位置。稍后,athena将从s3位置查询从s3获取记录。

以下代码,用于在使用javascript代码发送到Kinesis Stream之前添加新行。

var payload = JSON.parse(payload);  
finalData = JSON.stringify(payload)+"\n";

var kinesisPayload = {};    
kinesisPayload.Data = finalData;    
kinesisPayload.StreamName = "kinesisStreamName");    
kinesisPayload.PartitionKey = "124";