일반적인 Azure SignalR Service 문제 해결 가이드
이 문서에서는 고객에게 발생할 수 있는 몇 가지 일반적인 문제에 대한 문제 해결 참고 자료를 제공합니다.
액세스 토큰이 너무 김
가능한 오류
- 클라이언트 쪽
ERR_CONNECTION_
- 414 URI가 너무 김
- 413 페이로드가 너무 큼
- 액세스 토큰은 4K보다 길지 않아야 합니다. 413 요청 엔터티가 너무 큼
근본 원인
HTTP/2의 경우 단일 헤더의 최대 길이는 4K이므로 브라우저를 사용하여 Azure 서비스에 액세스하는 경우 이 제한으로 인한 오류 ERR_CONNECTION_
이(가) 발생합니다.
HTTP/1.1 또는 C# 클라이언트의 경우 최대 URI 길이는 12K이고 최대 헤더 길이는 16K입니다.
SDK 버전 1.0.6 이상에서는 생성된 액세스 토큰이 4K보다 큰 경우 /negotiate
에서 413 Payload Too Large
을(를) throw합니다.
솔루션
기본적으로 ASRS(Azure SignalR Service)에 대한 JWT 액세스 토큰을 생성할 때 context.User.Claims
의 클레임이 포함되므로 클라이언트에서 Hub
에 연결할 때 클레임이 보존되고 ASRS에서 Hub
로 전달될 수 있습니다.
경우에 따라 context.User.Claims
는 앱 서버에 대한 많은 정보를 저장하는 데 사용되며, 대부분은 Hub
가 아니라 다른 구성 요소에서 사용됩니다.
생성된 액세스 토큰은 네트워크를 통해 전달되며, WebSocket/SSE 연결의 경우 액세스 토큰은 쿼리 문자열을 통해 전달됩니다. 따라서 허브에 필요할 때 필요한 클레임만 ASRS를 통해 클라이언트에서 앱 서버로 전달하는 것이 좋습니다.
액세스 토큰 내에서 ASRS로 전달되는 클레임을 사용자 지정할 수 있는 ClaimsProvider
가 있습니다.
ASP.NET Core의 경우:
services.AddSignalR()
.AddAzureSignalR(options =>
{
// pick up necessary claims
options.ClaimsProvider = context => context.User.Claims.Where(...);
});
ASP.NET의 경우:
services.MapAzureSignalR(GetType().FullName, options =>
{
// pick up necessary claims
options.ClaimsProvider = context.Authentication?.User.Claims.Where(...);
});
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
TLS 1.2 필요
가능한 오류
- ASP.NET "서버를 사용할 수 없음" 오류 #279
- ASP.NET "연결이 활성화되지 않이 데이터를 서비스로 보낼 수 없습니다." 오류 #324
- "
https://<API endpoint>
에 대한 HTTP 요청을 만드는 동안 오류가 발생했습니다. 이 오류는 서버 인증서가 HTTPS 사례에서 HTTP.SYS로 제대로 구성되지 않은 경우에 발생할 수 있습니다. 이 오류의 가능한 원인은 클라이언트와 서버 간의 보안 바인딩이 일치하지 않는 것입니다."
근본 원인
Azure Service는 보안 문제에 대한 TLS 1.2만 지원합니다. .NET 프레임워크를 사용하는 경우 TLS1.2가 기본 프로토콜이 아닐 수 있습니다. 따라서 ASRS에 대한 서버 연결을 설정할 수 없습니다.
문제 해결 가이드
이 오류를 로컬로 재현할 수 있는 경우 내 코드만의 선택을 취소하고, 모든 CLR 예외를 throw하고, 앱 서버를 로컬로 디버그하여 throw되는 예외를 확인합니다.
내 코드만 선택 취소
CLR 예외 throw
앱 서버 쪽 코드를 디버그할 때 throw되는 예외 확인
ASP.NET의 경우 다음 코드를
Startup.cs
에 추가하여 자세한 추적을 사용하도록 설정하고, 로그에서 오류를 확인할 수도 있습니다.app.MapAzureSignalR(this.GetType().FullName); // Make sure this switch is called after MapAzureSignalR GlobalHost.TraceManager.Switch.Level = SourceLevels.Information;
솔루션
다음 코드를 Startup에 추가합니다.
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
400 클라이언트 요청에 대해 잘못된 요청이 반환됨
근본 원인
여러 개의 hub
쿼리 문자열이 있는지 클라이언트 요청에 확인합니다. hub
은(는) 보존된 쿼리 매개 변수이며, 서비스가 쿼리에서 둘 이상의 hub
을(를) 감지하면 400 오류가 반환됩니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
401 권한이 없음 클라이언트 요청에 대해 반환됨
근본 원인
현재 JWT 토큰 수명의 기본값은 1시간입니다.
ASP.NET Core SignalR의 경우 WebSocket 전송 형식을 사용하면 정상입니다.
ASP.NET Core SignalR의 다른 전송 형식인 SSE 및 긴 폴링의 경우 기본 수명은 기본적으로 최대 1시간 동안 지속될 수 있습니다.
ASP.NET SignalR의 경우 클라이언트에서 /ping
"연결 유지" 요청을 서비스에 전송하는 경우가 있습니다. /ping
이 실패하면 클라이언트에서 연결을 중단하고 다시 연결하지 않습니다. ASP.NET SignalR의 경우 기본 토큰 수명으로 인해 모든 전송 형식에 대해 연결이 최대 1시간 동안 지속됩니다.
솔루션
보안 문제를 해결하기 위해 TTL을 확장하는 것은 권장되지 않습니다. 이러한 401이 발생하면 연결을 다시 시작하기 위해 클라이언트에서 다시 연결 논리를 추가하는 것이 좋습니다. 클라이언트가 연결을 다시 시작하면 앱 서버와 협상하여 JWT 토큰을 다시 가져오고 갱신된 토큰을 받습니다.
클라이언트 연결을 다시 시작하는 방법은 여기서 확인하세요.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
404 클라이언트 요청에 대해 반환됨
SignalR 영구 연결의 경우 먼저 Azure SignalR Service와 협상(/negotiate
)한 다음, Azure SignalR Service에 대한 실제 연결을 설정합니다.
문제 해결 가이드
- 보내는 요청을 보는 방법에 따라 요청을 클라이언트에서 서비스로 가져옵니다.
- 404가 발생하면 요청의 URL을 확인합니다. URL이 웹앱을 대상으로 하고
{your_web_app}/hubs/{hubName}
과 비슷한 경우 클라이언트SkipNegotiation
이true
인지 확인합니다. 클라이언트는 앱 서버와 처음 협상할 때 리디렉션 URL을 받습니다. Azure SignalR을 사용하는 경우 클라이언트에서 협상을 건너뛰지 않아야 합니다. /negotiate
가 호출된 후 5초를 초과하여 연결 요청이 처리되면 다른 404가 발생할 수 있습니다. 클라이언트 요청의 타임스탬프를 확인하고, 서비스에 대한 요청의 응답이 느린 경우 문제를 제출합니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
ASP.NET SignalR의 다시 연결 요청에 대해 404가 반환됨
ASP.NET SignalR의 경우 클라이언트 연결이 끊어지면 연결을 중지하기 전에 동일한 connectionId
를 사용하여 세 번 다시 연결합니다. /reconnect
에서 영구 연결을 성공적으로 다시 설정할 수 있는 네트워크 간헐적 문제로 인해 연결이 끊어진 경우 /reconnect
가 도움이 될 수 있습니다. 예를 들어 라우팅된 서버 연결이 끊어져 클라이언트 연결이 끊어지거나 SignalR Service에 인스턴스 재시작/장애 조치(failover)/배포와 같은 내부 오류가 발생하는 경우도 있습니다. 연결이 더 이상 없으므로 /reconnect
에서 404
을(를) 반환합니다. 이는 /reconnect
에 필요한 동작이며, 세 번 다시 시도하면 연결이 중지됩니다. 연결이 중지되면 연결 다시 시작 논리를 사용하는 것이 좋습니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
클라이언트 요청에 대해 429(너무 많은 요청)가 반환됨
다음 두 가지 경우가 있습니다.
동시 연결 수가 제한을 초과함
무료 인스턴스의 경우 동시 연결 수 제한은 20입니다. 표준 인스턴스의 경우 단위당 동시 연결 수 제한은 1K입니다. 즉, 100개의 단위에서100K의 동시 연결을 허용합니다.
연결에는 클라이언트 및 서버 연결이 모두 포함됩니다 여기서 연결이 어떻게 계산되는지 확인합니다.
NegotiateThrottled
동일한 시간에 요청을 협상하는 클라이언트가 너무 많으면 제한될 수 있습니다. 이 제한은 단위가 많을수록 제한이 높아지는 단위 수와 관련이 있습니다. 또한 다시 연결하기 전에 임의 지연을 두는 것이 좋습니다. 여기서 다시 시도 샘플을 확인하세요.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
협상하는 동안 500 오류가 발생했습니다. Azure SignalR Service가 아직 연결되지 않았습니다. 나중에 다시 시도하세요.
근본 원인
이 오류는 연결된 Azure SignalR Service에 대한 서버 연결이 없을 때 보고됩니다.
문제 해결 가이드
서버에서 Azure SignalR Service에 연결하려고 할 때 오류 세부 정보를 확인할 수 있도록 서버 쪽 추적을 사용하도록 설정합니다.
ASP.NET Core SignalR에 대한 서버 쪽 로깅 사용
ASP.NET Core SignalR에 대한 서버 쪽 로깅은 ASP.NET Core 프레임워크에서 제공하는 ILogger
기반 로깅과 통합됩니다. 다음과 같이 사용하는 샘플인 ConfigureLogging
을 사용하여 서버 쪽 로깅을 사용하도록 설정할 수 있습니다.
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
logging.AddDebug();
})
Azure SignalR에 대한 로거 범주는 항상 Microsoft.Azure.SignalR
로 시작합니다. Azure SignalR에서 자세한 로그를 사용하도록 설정하려면 appsettings.json 파일에서 이전 접두사를 Debug
수준으로 구성합니다. 다음 예제를 참조하세요.
{
"Logging": {
"LogLevel": {
...
"Microsoft.Azure.SignalR": "Debug",
...
}
}
}
ASP.NET Core SignalR에 대한 서버 쪽 추적 사용
>=1.0.0
SDK 버전을 사용하는 경우 다음을 web.config
에 추가하여 추적을 사용하도록 설정할 수 있습니다(세부 정보).
<system.diagnostics>
<sources>
<source name="Microsoft.Azure.SignalR" switchName="SignalRSwitch">
<listeners>
<add name="ASRS" />
</listeners>
</source>
</sources>
<!-- Sets the trace verbosity level -->
<switches>
<add name="SignalRSwitch" value="Information" />
</switches>
<!-- Specifies the trace writer for output -->
<sharedListeners>
<add name="ASRS" type="System.Diagnostics.TextWriterTraceListener" initializeData="asrs.log.txt" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
클라이언트 연결 끊김
클라이언트가 Azure SignalR에 연결되는 경우 다른 이유로 인해 클라이언트와 Azure SignalR 사이의 영구 연결이 끊어질 수 있는 경우가 있습니다. 이 섹션에서는 이러한 연결 끊김이 발생할 수 있는 몇 가지 가능성에 대해 설명하고 근본 원인을 식별하는 방법에 대한 지침을 제공합니다.
클라이언트 쪽에서 확인되는 가능한 오류
The remote party closed the WebSocket connection without completing the close handshake
Service timeout. 30000.00ms elapsed without receiving a message from service.
{"type":7,"error":"Connection closed with an error."}
{"type":7,"error":"Internal server error."}
근본 원인
다음과 같은 다양한 상황에서 클라이언트 연결이 끊어질 수 있습니다.
- 들어오는 요청으로 인해
Hub
에서 예외를 throw하는 경우 - 클라이언트가 라우팅한 서버 연결이 끊어지면 서버 연결 끊김에 대한 자세한 내용은 다음 섹션을 참조하세요.
- 클라이언트와 SignalR Service 간에 네트워크 연결 문제가 발생하는 경우
- SignalR Service에 인스턴스 다시 시작, 장애 조치(failover), 배포 등과 같은 내부 오류가 있는 경우
문제 해결 가이드
- 앱 서버 쪽 로그를 열어 비정상 작업이 발생했는지 확인합니다.
- 앱 서버 쪽 이벤트 로그를 확인하여 앱 서버가 다시 시작되었는지 확인합니다.
- 시간 프레임을 제공하는 문제를 만들고, 리소스 이름을 이메일로 보냅니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
클라이언트 연결이 지속적으로 증가함
클라이언트 연결을 잘못 사용하면 발생할 수 있습니다. 누군가가 SignalR 클라이언트를 중지/삭제하는 것을 잊은 경우 연결이 계속 열려 있습니다.
Azure Portal 리소스 메뉴의 모니터링 섹션에 있는 SignalR의 메트릭에서 확인되는 가능한 오류
클라이언트 연결이 Azure SignalR의 메트릭에서 오랫동안 지속적으로 증가합니다.
근본 원인
SignalR 클라이언트 연결의 DisposeAsync
은(는) 호출되지 않으며 연결이 계속 열려 있습니다.
문제 해결 가이드
SignalR 클라이언트가 닫히지 않았는지 확인합니다.
솔루션
연결을 닫았는지 확인합니다. HubConnection.DisposeAsync()
를 수동으로 호출하여 사용 후 연결을 중지합니다.
예시:
var connection = new HubConnectionBuilder()
.WithUrl(...)
.Build();
try
{
await connection.StartAsync();
// Do your stuff
await connection.StopAsync();
}
finally
{
await connection.DisposeAsync();
}
일반적인 잘못된 클라이언트 연결 사용
Azure Function 예
누군가가 Function 클래스에 대한 정적 멤버로 만드는 대신 Azure Function 메서드에서 SignalR 클라이언트 연결을 설정할 때 이 문제가 발생하는 경우가 많습니다. 클라이언트 연결이 하나만 설정될 것으로 예상할 수 있지만 대신 메트릭에서 클라이언트 연결 수가 지속적으로 증가하는 것을 볼 수 있습니다. 이러한 모든 연결은 Azure Function 또는 Azure SignalR 서비스가 다시 시작한 후에만 삭제됩니다. 이 동작은 Azure Function이 각 요청에 대해 하나의 클라이언트 연결을 설정하고 함수 메서드에서 클라이언트 연결을 중지하지 않으면 클라이언트가 Azure SignalR Service에 대한 연결을 활성 상태로 유지하기 때문에 발생합니다.
솔루션
- Azure 함수에서 SignalR 클라이언트를 사용하거나 SignalR 클라이언트를 싱글톤으로 사용하는 경우 클라이언트 연결을 닫아야 합니다.
- Azure 함수에서 SignalR 클라이언트를 사용하는 대신, 다른 곳에서 SignalR 클라이언트를 만들고, Azure SignalR Service에 대한 Azure Functions 바인딩을 사용하여 클라이언트를 Azure SignalR과 협상할 수 있습니다. 또한 바인딩을 활용하여 메시지를 보낼 수도 있습니다. 클라이언트를 협상하고 메시지를 보내는 샘플은 여기서 찾을 수 있습니다. 자세한 내용은 여기서 확인할 수 있습니다.
- Azure 함수에서 SignalR 클라이언트를 사용하는 경우 시나리오에 더 나은 아키텍처가 있을 수 있습니다. 적절한 서버리스 아키텍처를 설계하는지 확인합니다. Azure Functions에서 SignalR Service 바인딩을 사용하는 실시간 서버리스 애플리케이션을 참조할 수 있습니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
서버 연결 끊김
앱 서버가 시작되면 Azure SDK가 백그라운드에서 원격 Azure SignalR에 대한 서버 연결을 시작합니다. Azure SignalR Service의 내부에서 설명한 대로 Azure SignalR은 들어오는 클라이언트 트래픽을 이러한 서버 연결로 라우팅합니다. 서버 연결이 끊어지면 해당 연결에서 제공하던 모든 클라이언트 연결도 닫힙니다.
앱 서버와 SignalR Service 간의 연결은 영구 연결이므로 네트워크 연결 문제가 발생할 수 있습니다. 서버 SDK에는 서버 연결에 대한 항상 다시 연결 전략이 있습니다. 또한 서버에 대한 대량 동시 요청을 방지하기 위해 사용자가 임의의 지연 시간을 사용하여 클라이언트에 지속적인 다시 연결 논리를 추가하는 것이 좋습니다.
정기적으로 Azure SignalR Service에 대한 새 버전 릴리스가 있으며, 때로는 Azure 전체를 패치 또는 업그레이드하거나, 때로는 종속 서비스 중단이 발생합니다. 이러한 이벤트로 인해 잠시 서비스가 중단될 수 있지만 클라이언트 쪽에 연결 끊기/다시 연결 메커니즘이 있는 한 클라이언트 쪽에서 발생하는 연결 끊김-다시 연결과 마찬가지로 영향은 최소화됩니다.
이 섹션에서는 서버 연결 끊김으로 이어질 수 있는 몇 가지 가능성에 대해 설명하고, 근본 원인을 식별하는 방법에 대한 몇 가지 지침을 제공합니다.
서버 쪽에서 확인되는 가능한 오류
[Error]Connection "..." to the service was dropped
The remote party closed the WebSocket connection without completing the close handshake
Service timeout. 30000.00ms elapsed without receiving a message from service.
근본 원인
ASRS(Azure SignalR Service)에서 서버-서비스 연결을 닫습니다.
서버 쪽에서 CPU 사용량이 높거나 스레드 풀이 부족하면 ping 시간 초과가 발생할 수 있습니다.
ASP.NET SignalR의 경우 알려진 문제가 SDK 1.6.0에서 수정되었습니다. SDK를 최신 버전으로 업그레이드합니다.
스레드 풀 결핍
서버가 결핍 상태에 있으면 메시지 처리에서 스레드가 작동하지 않는 것입니다. 모든 스레드가 특정 메서드에서 응답하지 않습니다.
일반적으로 비동기 메서드에서 이 시나리오가 동기화를 통한 비동기화 또는 Task.Result
/Task.Wait()
에 의해 발생합니다.
ASP.NET Core 성능 모범 사례를 참조하세요.
스레드 풀 결핍에 대해 자세히 알아보세요.
스레드 풀 결핍을 검색하는 방법
스레드 수를 확인합니다. 해당 시간에 급증하지 않는 경우 다음 단계를 수행합니다.
Azure App Service를 사용하는 경우 메트릭에서 스레드 수를 확인합니다.
Max
집계를 확인합니다..NET Framework를 사용하는 경우 서버 VM의 성능 모니터에서 메트릭을 확인할 수 있습니다.
컨테이너에서 .NET Core를 사용하는 경우 컨테이너에서 진단 수집을 참조하세요.
또한 코드를 사용하여 스레드 풀 결핍을 검색할 수 있습니다.
public class ThreadPoolStarvationDetector : EventListener
{
private const int EventIdForThreadPoolWorkerThreadAdjustmentAdjustment = 55;
private const uint ReasonForStarvation = 6;
private readonly ILogger<ThreadPoolStarvationDetector> _logger;
public ThreadPoolStarvationDetector(ILogger<ThreadPoolStarvationDetector> logger)
{
_logger = logger;
}
protected override void OnEventSourceCreated(EventSource eventSource)
{
if (eventSource.Name == "Microsoft-Windows-DotNETRuntime")
{
EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// See: https://learn.microsoft.com/dotnet/framework/performance/thread-pool-etw-events#threadpoolworkerthreadadjustmentadjustment
if (eventData.EventId == EventIdForThreadPoolWorkerThreadAdjustmentAdjustment &&
eventData.Payload[2] as uint? == ReasonForStarvation)
{
_logger.LogWarning("Thread pool starvation detected!");
}
}
}
서비스에 추가합니다.
service.AddSingleton<ThreadPoolStarvationDetector>();
그런 다음 ping 시간 제한으로 인해 서버 연결이 끊어지면 로그를 확인합니다.
스레드 풀 결핍의 근본 원인을 확인하는 방법
스레드 풀 결핍의 근본 원인을 확인하려면 다음을 수행합니다.
- 메모리를 덤프한 다음, 호출 스택을 분석합니다. 자세한 내용은 메모리 덤프 수집 및 분석을 참조하세요.
- 스레드 풀 결핍이 검색되면 clrmd를 사용하여 메모리를 덤프합니다. 그런 다음, 호출 스택을 기록합니다.
문제 해결 가이드
- 앱 서버 쪽 로그를 열어 비정상 작업이 발생했는지 확인합니다.
- 앱 서버 쪽 이벤트 로그를 확인하여 앱 서버가 다시 시작되었는지 확인합니다.
- 문제를 만듭니다. 시간 프레임을 제공하고, 리소스 이름을 이메일로 보냅니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
팁
클라이언트에서 발신되는 요청을 어떻게 볼 수 있나요?
ASP.NET Core를 예로 사용합니다(ASP.NET 예도 비슷함).
브라우저에서: 크롬을 예로 들면, F12 키를 사용하여 콘솔 창을 열고, 네트워크 탭으로 전환할 수 있습니다. 처음부터 네트워크를 캡처하려면 F5 키를 사용하여 페이지를 새로 고쳐야 할 수 있습니다.
C# 클라이언트에서:
Fiddler를 사용하여 로컬 웹 트래픽을 볼 수 있습니다. WebSocket 트래픽은 Fiddler 4.5부터 지원됩니다.
클라이언트 연결을 다시 시작하는 방법은 어떻게 될까요?
항상 다시 시도 전략을 사용하여 연결 논리를 다시 시작하는 작업이 포함된 샘플 코드는 다음과 같습니다.
문제 해결에 대한 문제 또는 피드백이 있나요? 알려주세요.
다음 단계
이 가이드에서는 일반적인 문제를 처리하는 방법을 알아보았습니다. 일반적인 문제 해결 방법을 자세히 알아볼 수도 있습니다.