AWS步骤功能 - 等到事件

时间:2017-01-18 07:13:47

标签: aws-lambda aws-step-functions

我有一个用例,我将AWS Step功能在文件上传到S3时触发,从那里第一步运行ffprobe以从外部服务(如transloadit)获取文件的持续时间输出被写回S3。

我可以从该事件创建一个新的步骤函数,但是如果可以在原始步骤函数中有Await承诺然后继续到下一个函数,我就会徘徊 - 考虑到ffprobe可能需要更长的时间复出。

对于如何解决这个问题,我们非常感激。

6 个答案:

答案 0 :(得分:2)

AWS Step Functions现在支持将一流的长时间运行步骤的异步回调。

这类似于@mixja的答案,但经过简化。工作流程中的单个状态可以直接调用Lambda,SNS,SQS或ECS,并等待对SendTaskSuccess的调用。

有一个good example用于SQS的文档,其中步进功能发送消息并暂停工作流程执行,直到提供回调为止。 Lambda是等效的(假设像transloadit这样的主要处理过程发生在Lambda本身之外)

您的步进函数定义如下

"Invoke transloadit": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
  "Parameters": {
    "FunctionName": "InvokeTransloadit",
    "Payload": {
        "some_other_param": "...",
        "token.$": "$$.Task.Token"
     }
  },
  "Next": "NEXT_STATE"
}

然后在您的Lambda中执行类似的操作

def lambda_handler(event, context):
    token = event['token']

    # invoke transloadit via SSM, ECS, passing token along

然后,在主要的长期运行过程中,您将使用shell脚本/ CLI中的令牌aws stepfunctions send-task-success --task-token $token或类似的API调用发出回调。

答案 1 :(得分:1)

不能提出简单的解决方案,只有很少的方向可供探索。

首先,步骤函数有一种特定的方法来处理长时间运行的后台工作:活动。 https://docs.aws.amazon.com/step-functions/latest/dg/concepts-activities.html它基本上是一个队列。

如果您想要100%无服务器,这将是复杂或丑陋的。

  • 正如您所说,为每个文件创建新的步骤功能
  • 或者,状态机中的S3轮询循环使用自定义错误代码和Retry子句

如果您可以为后台工作人员分配“1/8微”实例,那么它不是优雅而是简单,可以通过即时反应实现。低硬件要求暗示我们将仅使用机器进行同步。

定义StepFunction活动,以示例video-duration命名。 为即时反应定义SQS队列或为持续时间结果轮询S3。

状态函数伪代码:

{
  StartAt: ffprobe
  ffprobe: {
    Type: Task
    Resource: arn:...lambda:launch-ffprobe
    Next: wait-duration
  }
  wait-duration: {
    Type: Task
    Resource: arn...activity:video-duration
    End: true
  }
}

后台工作者伪代码:

statemap = dict/map filename to result

thread1:
  loop:
    taskToken, input = SF.GetActivityTask('video-duration')  # long poll
    sync(key=input.filename, waiter=taskToken)
thread2:
  loop:
    msg = SQS.ReceiveMessage(...)  # or poll S3
    sync(key=msg.filename, duration=msg.result)

function sync(key, waiter, duration):
  state = statemap[key]
  if waiter:
    state.waiter = waiter
  if duration:
    state.duration = duration
  if state.waiter and state.duration:
    SF.SendTaskSuccess(state.waiter, state.duration)

S3触发伪代码:

if filename is video:
  SF.StartExecution(...)
else if filename is duration:
  content = S3.GetObject(filename)
  SQS.SendMessage(queue, content)

答案 2 :(得分:1)

当您将请求发送到transloadit时,请根据上传的文件密钥将s3中的步骤的taskToken保存在可预测的密钥中。例如,如果媒体文件位于&x 39://my-media-bucket/foobar/media-001.mp3',则可以创建包含当前步骤和存储的任务标记的JSON文件它在同一个桶中使用相同的密钥,例如' s3://ffprobe-tasks/foobar/media-001.mp3.json'。在步骤结束时将媒体发送到transloadit 在步骤中调用成功或失败 - 让它继续运行。

然后,当您收到s3通知表明transloadit结果已准备就绪时,您可以确定获取任务令牌的s3键(' s3://ffprobe-tasks/foobar/media-001.mp3' ),加载JSON(并从s3中删除它)并为该任务发送成功。步骤功能将继续执行中的下一个状态。

答案 3 :(得分:0)

好吧,我会从https://aws.amazon.com/blogs/compute/implementing-serverless-manual-approval-steps-in-aws-step-functions-and-amazon-api-gateway/

激励自己

您可以通过AWS Lambda函数替换此API网关,例如由S3事件触发(文档:http://docs.aws.amazon.com/lambda/latest/dg/with-s3.html)。只需确保您的任务有适当的超时。

答案 4 :(得分:0)

当我尝试将SFN组合到协调AWS Batch作业时,我也会遇到这个问题。 上面提到的做法是有问题的,因为你应该传递taskToken,因此你需要从一个lambda inside 状态机,从队列中轮询TaskToken,并将它传递给S3或某个地方,另一个lambda将提交活动状态。

问题是:当您轮询taskToken时,您无法知道它是否属于您的状态机实例。 您可以在同一个状态机的另一个实例上获取令牌。 就个人而言,我认为如果AWS支持这种功能,那将是很好的,他们很容易做到......

答案 5 :(得分:0)

您通常希望将异步任务作为步骤函数活动启动。此处的关键字是启动 - 换句话说,一旦您的活动有待处理操作,就会触发异步操作。这样做的原因是您需要与待处理活动相关联的任务令牌 - 然后只要您的" future"可以以某种方式包含此令牌(例如,您可以将其设置为引用或请求ID),然后您可以完成"使用SendTaskSuccessSendTaskFailure调用成功或失败的活动。

启动任务有两种方法:

  1. 轮询新活动。您可以设置CloudWatch预定活动,每隔n分钟拨打一次GetActivityTask

  2. 解雇新的"发起人"任务与步骤函数中的活动并行。此启动器执行与#1相同的操作并进行GetActivityTask调用,唯一的区别是它是立即触发的,并且不需要轮询机制。 GetActivityTask调用将阻塞,直到新的活动任务可用,因此竞争条件没有问题。请注意,您可能会从另一个执行中获取活动,因此此启动程序只需要考虑活动的输入,而不是发起者自己接收的输入。

  3. 以下是步骤函数中的#2:

    Initiating an activity

    InitiateManualApprovalActivity 任务相关的基本代码示例:

    import boto3
    import time
    
    client = boto3.client('stepfunctions')
    activity = "arn:aws:states:us-east-1:123456789012:activity:ManualStep"
    
    def lambda_handler(event, context):
        print(event)
        # This will block until an activity task becomes available
        task = client.get_activity_task(activityArn=activity, workerName="test")
        print(task)
        # Perform your task here
        # In this example we continue on in the same function,
        # but the continuation could be a separate event, 
        # just as long as you can retrieve the task token
        time.sleep(60)
        response = client.send_task_success(taskToken=task['taskToken'], output=task['input'])
        print(response)
        return "done"