AWS Lambda无错误重复调用问题求助(单次请求触发4次调用)
我有一个用于币安期货交易的AWS Lambda函数,预期单次请求仅触发一次函数执行,但实际出现了4次独立调用(每次请求ID均不同),排除了错误重试的可能。我已经调整过Lambda配置中的超时时间和重试次数,但问题仍未解决,求可行的解决思路。
函数代码如下:
from binance import Client import os def get_position_amt(bot, symbol): get_position = bot.futures_position_information(symbol=symbol) amt = float(get_position[0]['positionAmt']) return amt def close_all_position(bot, symbol): print('close_all_position') amt = get_position_amt(bot, symbol) if amt > 0: close_order = bot.futures_create_order( symbol=symbol, side="SELL", type="MARKET", quantity=amt ) print(close_order) elif amt < 0: close_order = bot.futures_create_order( symbol=symbol, side="BUY", type="MARKET", quantity=abs(amt) ) print(close_order) else: print("no position : amt is zero") def lambda_handler(event, context): bot = Client(api_key=os.environ.get('api_key'), api_secret=os.environ.get('api_sc')) data = eval(event['body']) side=data.get('side') symbol=data.get('market') amt = get_position_amt(bot, symbol) if side == "BUY": if amt < 0: close_all_position(bot, symbol) elif side == "SELL": if amt > 0: close_all_position(bot, symbol) ord_type = data.get('ord_type') if ord_type == 'limit': order = bot.futures_create_order( symbol=data.get('market'), side=data.get('side'), type="LIMIT", timeInForce='GTC', quantity=float(data.get('volume')), price=float(data.get('price')) ) print(order) elif ord_type == 'market': order = bot.futures_create_order( symbol=data.get('market'), side=data.get('side'), type="MARKET", quantity=float(data.get('volume')) ) print(order) elif ord_type == 'close': close_all_position(bot, data.get('market')) else: raise ValueError
排查触发源的重复请求
先确认调用Lambda的上游服务(如API Gateway、SQS、CloudWatch Events等)是否发送了重复请求。比如API Gateway若遇到客户端重试、多AZ冗余转发等情况,会导致多次Lambda调用。可以查看触发源的日志(如API Gateway访问日志),确认是否存在多个相同请求被发送。检查事件源映射配置
如果Lambda通过事件源(如SQS、Kinesis)触发,检查事件源映射的批量大小、重试配置,是否存在消息被重复读取的情况。另外,SQS的可见性超时若小于Lambda执行时间,会导致消息被重新处理,引发多次调用。添加幂等性处理(关键)
交易场景必须保证操作幂等,避免重复执行带来的风险。可以在请求中加入唯一请求ID,Lambda执行前先检查该ID是否已处理(比如存储到DynamoDB),若已处理则直接返回,不执行交易逻辑。示例修改如下:from binance import Client import os import boto3 from datetime import datetime dynamodb = boto3.resource('dynamodb') idempotency_table = dynamodb.Table('LambdaIdempotencyTable') # 需提前创建该DynamoDB表 def get_position_amt(bot, symbol): get_position = bot.futures_position_information(symbol=symbol) amt = float(get_position[0]['positionAmt']) return amt # 保留原有close_all_position函数 def lambda_handler(event, context): # 提取或生成唯一请求标识,优先用请求自带的ID,无则用Lambda请求ID request_id = event.get('requestId') or context.aws_request_id # 检查请求是否已处理 try: response = idempotency_table.get_item(Key={'request_id': request_id}) if 'Item' in response: print(f"Request {request_id} already processed") return {'statusCode': 200, 'body': 'Request processed'} except Exception as e: print(f"Check idempotency failed: {e}") raise # 执行原有交易逻辑 bot = Client(api_key=os.environ.get('api_key'), api_secret=os.environ.get('api_sc')) data = eval(event['body']) side=data.get('side') symbol=data.get('market') amt = get_position_amt(bot, symbol) if side == "BUY": if amt < 0: close_all_position(bot, symbol) elif side == "SELL": if amt > 0: close_all_position(bot, symbol) ord_type = data.get('ord_type') if ord_type == 'limit': order = bot.futures_create_order( symbol=data.get('market'), side=data.get('side'), type="LIMIT", timeInForce='GTC', quantity=float(data.get('volume')), price=float(data.get('price')) ) print(order) elif ord_type == 'market': order = bot.futures_create_order( symbol=data.get('market'), side=data.get('side'), type="MARKET", quantity=float(data.get('volume')) ) print(order) elif ord_type == 'close': close_all_position(bot, data.get('market')) else: raise ValueError # 标记请求已处理 try: idempotency_table.put_item( Item={'request_id': request_id, 'processed_at': datetime.now().isoformat()} ) except Exception as e: print(f"Mark request processed failed: {e}") return {'statusCode': 200, 'body': 'Success'}查看CloudTrail日志溯源
通过AWS CloudTrail查看Lambda的调用记录,确认每次调用的发起者(是AWS内部服务还是外部请求),精准定位重复调用的源头。检查第三方回调机制
如果Lambda是被第三方服务(如币安Webhook)触发,确认第三方是否存在回调超时重试机制。这种情况下需要确保Lambda在处理完成后返回明确的成功响应(如200状态码),避免第三方重复回调。
内容的提问来源于stack exchange,提问作者Pineman

