从Azure SQL通过sp_invoke_external_rest_endpoint入队的Azure Queue消息成死信问题求助
我通过Azure SQL的sp_invoke_external_rest_endpoint成功将消息<QueueMessage><MessageText>HelloWorld</MessageText></QueueMessage>发送至Azure Queue,但队列触发的Azure Function取出该消息后,它最终进入了死信队列(poison queue)。若通过Azure门户添加相同消息内容(需勾选“Encode the message body in Base64”选项),则消息可被正常处理。推测这是编码问题,恳请提供解决方案。
可复现代码
SQL代码(Azure SQL Database)
DECLARE @QueueMessage_XML nvarchar(max) = N'<QueueMessage><MessageText>HelloWorld</MessageText></QueueMessage>'; DECLARE @QueueMessage nvarchar(max) = @QueueMessage_XML; DECLARE @Time datetime = SYSUTCDATETIME(); DECLARE @FormattedMoment nvarchar(50) = FORMAT(@Time, 'ddd, dd MMM yyyy HH:mm:ss' ) + ' GMT'; DECLARE @ContentType nvarchar(256) = N'"content-type":"application/xml"'; DECLARE @Accept nvarchar(256) = N'"accept":"application/xml"'; DECLARE @StorageApiMsVersion nvarchar(256) = N'"x-ms-version":"2023-11-03"'; DECLARE @MsDate nvarchar(256) = N'"x-ms-date":"' + @FormattedMoment + N'"'; DECLARE @HeadersArray table([HeaderItem] nvarchar(256) NOT NULL); INSERT INTO @HeadersArray ([HeaderItem]) VALUES (@ContentType), (@Accept), (@StorageApiMsVersion), (@MsDate); DECLARE @Headers nvarchar(4000) = N'{' + (SELECT STRING_AGG([HeaderItem], ',') FROM @HeadersArray) + N'}'; DECLARE @Method nvarchar(6) = N'POST'; DECLARE @TimeoutSeconds int = 30; DECLARE @Response nvarchar(max) = N''; DECLARE @VisibilityTimeoutSeconds int = 20; DECLARE @AccountName nvarchar(256) = N'stacctname'; DECLARE @QueueName nvarchar(256) = N'request'; DECLARE @QueueEndpoint nvarchar(256) = N'https://' + @AccountName + N'.queue.core.windows.net'; DECLARE @Url nvarchar(4000) = @QueueEndpoint + N'/' + @QueueName + N'/messages'; DECLARE @ReturnValue int = 0; EXEC @ReturnValue = sp_invoke_external_rest_endpoint @url = @Url, @payload = @QueueMessage, @headers = @Headers, @method = @Method, @credential = [https://stacctname.queue.core.windows.net], @timeout = @TimeoutSeconds, @response = @Response OUTPUT; SELECT @ReturnValue AS [ReturnValue], @Response AS [Response];
Azure Function代码
环境信息:
- 隔离模式(Isolated)
- C# .NET 8
- Visual Studio 2022 Version 17.9.0 Preview 1.0
using Azure.Storage.Queues.Models; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; namespace FunctionApp; public class DequeueRequest(ILogger<DequeueRequest> logger) { private readonly ILogger<DequeueRequest> _logger = logger; [Function(nameof(DequeueRequest))] public void Run([QueueTrigger("request", Connection = "AzureWebJobsStorage")] QueueMessage message) { //intentionally muted _ = message.Body; } }
排查更新
我已大致定位到需在SQL中通过BASE64_ENCODE(CAST(@Payload_String AS varbinary(max)))对消息进行Base64编码,但从队列取出消息后进行JSON反序列化时仍出现无法追踪的错误。直接使用原始字符串可成功反序列化,但调用message.Body.ToString()则失败并导致消息进入死信队列。
成功的反序列化代码
var request = JsonSerializer.Deserialize<Request>( @"{""RequestId"":80,""StartDate"":""1753-01-01T00:00:00"",""EndDate"":""9999-12-29T00:00:00""}")
失败的反序列化代码(导致消息成死信)
var request = JsonSerializer.Deserialize<Request>(message.Body.ToString());
解决方案
1. SQL端完成消息Base64编码并添加编码头部
Azure Queue要求Base64编码的消息需通过content-encoding头部告知服务编码类型,否则Function的QueueTrigger无法正确解码。修改SQL代码如下:
DECLARE @QueueMessage_XML nvarchar(max) = N'<QueueMessage><MessageText>HelloWorld</MessageText></QueueMessage>'; -- 将nvarchar(UTF-16)转换为varbinary后进行Base64编码 DECLARE @QueueMessage nvarchar(max) = BASE64_ENCODE(CAST(@QueueMessage_XML AS varbinary(max))); DECLARE @Time datetime = SYSUTCDATETIME(); DECLARE @FormattedMoment nvarchar(50) = FORMAT(@Time, 'ddd, dd MMM yyyy HH:mm:ss' ) + ' GMT'; DECLARE @ContentType nvarchar(256) = N'"content-type":"application/xml"'; DECLARE @Accept nvarchar(256) = N'"accept":"application/xml"'; DECLARE @StorageApiMsVersion nvarchar(256) = N'"x-ms-version":"2023-11-03"'; DECLARE @MsDate nvarchar(256) = N'"x-ms-date":"' + @FormattedMoment + N'"'; -- 添加content-encoding头部,告知服务消息已Base64编码 DECLARE @ContentEncoding nvarchar(256) = N'"content-encoding":"base64"'; DECLARE @HeadersArray table([HeaderItem] nvarchar(256) NOT NULL); INSERT INTO @HeadersArray ([HeaderItem]) VALUES (@ContentType), (@Accept), (@StorageApiMsVersion), (@MsDate), (@ContentEncoding); -- 新增编码头部 DECLARE @Headers nvarchar(4000) = N'{' + (SELECT STRING_AGG([HeaderItem], ',') FROM @HeadersArray) + N'}'; -- 后续URL、调用sp_invoke_external_rest_endpoint的代码保持不变
2. Function端正确解析消息内容
在Isolated模式下,QueueMessage.Body是BinaryData类型,默认ToString()使用UTF-8编码解析,但SQL中nvarchar转换为varbinary时用的是UTF-16编码,因此需指定编码解析:
using Azure.Storage.Queues.Models; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using System.Text.Json; namespace FunctionApp; public class DequeueRequest(ILogger<DequeueRequest> logger) { private readonly ILogger<DequeueRequest> _logger = logger; [Function(nameof(DequeueRequest))] public void Run([QueueTrigger("request", Connection = "AzureWebJobsStorage")] QueueMessage message) { // 使用UTF-16(Unicode)编码解析BinaryData,匹配SQL端的编码 var messageContent = message.Body.ToString(System.Text.Encoding.Unicode); // 反序列化JSON内容 var request = JsonSerializer.Deserialize<Request>(messageContent); // 后续处理逻辑 _logger.LogInformation("解析消息成功:RequestId={RequestId}", request?.RequestId); } } // 假设的Request实体类 public class Request { public int RequestId { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } }
关键原因说明
- SQL中
nvarchar采用UTF-16编码,转换为varbinary(max)时保留该编码,Base64编码后必须通过content-encoding: base64头部告知Azure Queue服务,否则服务会将其视为原始文本存储,导致Function读取时解码错误。 - Function端
BinaryData.ToString()默认使用UTF-8编码,与SQL端的UTF-16编码不匹配,会导致字符串乱码,最终引发JSON反序列化失败,消息进入死信队列。
内容的提问来源于stack exchange,提问作者drmssst

