将SQS Lambda批次拆分为部分成功/部分失败

时间:2019-05-21 08:30:29

标签: aws-lambda amazon-sqs

AWS SQS-> Lambda集成使您可以批量处理传入消息,在此配置您可以在单个批次中接收的最大数量。如果在处理过程中抛出异常以表示失败,则一旦可见性超时已过,所有消息都不会从传入队列中删除,而是可以由另一个lambda进行处理。

出于性能原因,是否有任何方法可以保留批处理,但允许批处理中的某些消息成功(并从入站队列中删除),而只保留某些批处理未删除?

2 个答案:

答案 0 :(得分:1)

您唯一的选择是手动将失败的消息发送回队列,然后成功地向SQS进行答复,这样就不会重复。

您可以执行类似设置失败计数的操作,这样,如果所有消息失败,您可以简单地为所有消息返回失败状态,否则,如果失败计数<10(10是您可以从中获得的最大批处理大小) SQS-> Lambda事件),则可以将失败的消息分别发回队列,然后以成功消息进行回复。

答案 1 :(得分:1)

手动将失败的消息重新排队到队列中的问题是,您可能会陷入无限循环,在这些循环中,这些项将永远失败,然后重新排队并再次失败。由于他们被重新发送到队列中,因此每次重试计数都会重置,这意味着它们永远不会失败进入死信队列。您还会失去可见性超时的好处。这对于监视目的也是不利的,因为除非您手动检查日志,否则您将永远无法知道自己是否处于不良状态。

更好的方法是手动删除成功项,然后引发异常以使其余批次失败。成功的项目将从队列中删除,所有实际失败的项目将达到其正常的import io import logging from time import sleep import numpy as np from PySide2 import QtSvg, QtWidgets from PySide2.QtCore import Signal, Slot, QObject, QThread from PySide2.QtWidgets import QWidget, QPushButton, QApplication from transitions.extensions import GraphMachine logging.basicConfig(level=logging.DEBUG) class Client(QObject): # Client signals sig_move_done = Signal() sig_disconnected = Signal() sig_connected = Signal() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @Slot(int) def client_move(self, dest): print(f'Client moving to {dest}...') sleep(3) # some blocking function if np.random.rand() < 0.5: print("Error occurred during movement...") self.sig_disconnected.emit() else: print("Movement done...") self.sig_move_done.emit() @Slot() def client_disconnect(self): # do something then... on success do: self.sig_disconnected.emit() @Slot() def client_connect(self): # do something ... on success do: self.sig_connected.emit() # define states, transitions and extra args for transitions state machine: states = ['ready', 'moving', 'unknown'] transitions = [ {'trigger': 'move', 'source': 'ready', 'dest': 'moving'}, {'trigger': 'stopped', 'source': 'moving', 'dest': 'ready'}, {'trigger': 'disconnect_', 'source': ['ready', 'moving'], 'dest': 'unknown'}, {'trigger': 'error', 'source': ['ready', 'moving'], 'dest': 'unknown'}, {'trigger': 'connect_', 'source': 'unknown', 'dest': 'ready'} ] extra_args = dict(initial='unknown', title='Simple state machine', show_conditions=True, show_state_attributes=True) class ClientState(QObject): # machine signals sig_update_available = Signal() sig_move_requested = Signal(int) # can this be avoided ? see self.on_enter_moving sig_connect_requested = Signal() # can this be avoided ? def __init__(self, client, *args, **kwargs): super().__init__(*args, **kwargs) self.client = client # move client to seperate thread self.worker_thread = QThread() self.client.moveToThread(self.worker_thread) self.worker_thread.start() self.machine = GraphMachine(model=self, states=states, transitions=transitions, show_auto_transitions=False, **extra_args, after_state_change="update_available", send_event=True) # connecting Client signals to state machine triggers self.client.sig_disconnected.connect(self.disconnect_) self.client.sig_connected.connect(self.connect_) self.client.sig_move_done.connect(self.stopped) self.update_available = lambda *args, **kwargs: self.sig_update_available.emit() # can this be avoided ? see self.on_enter_moving self.sig_move_requested.connect(self.client.client_move) self.sig_connect_requested.connect(self.client.client_connect) def on_enter_moving(self, event): print(event.kwargs) dest = event.kwargs.get('dest', 0) # calling self.client_move() directly will cause self.client_move to be called from main thread... # calling it via a helper signal instead: self.sig_move_requested.emit(dest) def show_graph(self, **kwargs): stream = io.BytesIO() self.get_graph(**kwargs).draw(stream, prog='dot', format='svg') return stream.getvalue() class GUI(QWidget): def __init__(self, client_state): super().__init__() self.client_state = client_state # setup UI self.setWindowTitle("State") self.svgWidget = QtSvg.QSvgWidget() self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.svgWidget) self.btn_move = QPushButton("move") self.btn_connect = QPushButton("(re-)connect") self.layout.addWidget(self.btn_move) self.layout.addWidget(self.btn_connect) self.setLayout(self.layout) # Connect Slots/Signals ## machine -> GUI self.client_state.sig_update_available.connect(self.update_gui) ## GUI --> machine self.btn_move.clicked.connect(lambda: self.client_state.move(dest=np.random.randint(1, 100))) self.btn_connect.clicked.connect( self.client_state.connect_) # update UI self.update_gui() def update_gui(self): print("Update model graph and GUI...") self.svgWidget.load(self.client_state.show_graph()) if self.client_state.is_ready(): self.btn_move.setEnabled(True) self.btn_connect.setDisabled(True) if self.client_state.is_moving(): self.btn_move.setDisabled(True) self.btn_connect.setDisabled(True) if self.client_state.is_unknown(): self.btn_move.setDisabled(True) self.btn_connect.setEnabled(True) if __name__ == "__main__": import sys app = QApplication(sys.argv) client = Client() client_state = ClientState(client) gui = GUI(client_state) gui.show() sys.exit(app.exec_()) 周期并保留其visibility timeout的值,您将能够实际使用和监视无效字母队列。总体来说,这比其他方法要少。

注意事项

  • 如果部分批处理失败,则仅覆盖默认行为。如果所有项目都成功,则遵循默认行为
  • 由于您要跟踪每个队列项的失败,因此需要捕获并记录每个异常的发生,以便以后查看发生的情况