문제

로드 밸런서에서 트래픽을 제공받는 웹 애플리케이션을 제어하는 ​​웹 애플리케이션이 있습니다.웹 애플리케이션은 각 개별 서버에서 실행됩니다.

ASP.NET 응용 프로그램 상태의 개체에 있는 각 응용 프로그램의 "in 또는 out" 상태를 추적하고 상태가 변경될 때마다 개체가 디스크의 파일로 직렬화됩니다.웹 애플리케이션이 시작되면 파일에서 상태가 역직렬화됩니다.

사이트 자체는 두 번째 최고 요청과 거의 액세스하지 않는 파일만 가져오는 반면, 어떤 이유로든 파일을 읽거나 쓰려고 시도하는 동안 충돌이 발생하는 것이 매우 쉽다는 것을 발견했습니다.정기적으로 서버에 롤링 배포를 수행하는 자동화된 시스템이 있기 때문에 이 메커니즘은 매우 안정적이어야 합니다.

누구든지 위의 사항에 대한 신중함에 의문을 제기하기 전에 그 이유를 설명하면 이 게시물이 기존보다 훨씬 길어질 것이므로 산을 옮기는 것을 피하고 싶다는 점만 말씀드리고 싶습니다.

즉, 파일에 대한 액세스를 제어하는 ​​데 사용하는 코드는 다음과 같습니다.

internal static Mutex _lock = null;
/// <summary>Executes the specified <see cref="Func{FileStream, Object}" /> delegate on 
/// the filesystem copy of the <see cref="ServerState" />.
/// The work done on the file is wrapped in a lock statement to ensure there are no 
/// locking collisions caused by attempting to save and load the file simultaneously 
/// from separate requests.
/// </summary>
/// <param name="action">The logic to be executed on the 
/// <see cref="ServerState" /> file.</param>
/// <returns>An object containing any result data returned by <param name="func" />. 
///</returns>
private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
    var l = new Logger();
    if (ServerState._lock.WaitOne(1500, false))
    {
        l.LogInformation( "Got lock to read/write file-based server state."
                        , (Int32)VipEvent.GotStateLock);
        var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate 
                                  , FileAccess.ReadWrite, FileShare.None);                
        result = func.Invoke(fileStream);                
        fileStream.Close();
        fileStream.Dispose();
        fileStream = null;
        ServerState._lock.ReleaseMutex();
        l.LogInformation( "Released state file lock."
                        , (Int32)VipEvent.ReleasedStateLock);
        return true;
    }
    else
    {
        l.LogWarning( "Could not get a lock to access the file-based server state."
                    , (Int32)VipEvent.CouldNotGetStateLock);
        result = null;
        return false;
    }
}

이것 대개 작동하지만 때때로 뮤텍스에 액세스할 수 없습니다(로그에 "잠금을 얻을 수 없음" 이벤트가 표시됨).이 문제는 로컬에서 재현할 수 없습니다. 프로덕션 서버(Win Server 2k3/IIS 6)에서만 발생합니다.시간 초과를 제거하면 후속 요청을 포함하여 애플리케이션이 무기한 정지됩니다(경합 조건??).

오류가 발생하면 이벤트 로그를 보면 이전 요청에 의해 뮤텍스 잠금이 달성되고 해제되었음을 알 수 있습니다. ~ 전에 오류가 기록되었습니다.

뮤텍스는 Application_Start 이벤트에서 인스턴스화됩니다.선언에서 정적으로 인스턴스화될 때 동일한 결과를 얻습니다.

변명, 변명:스레딩/잠금은 일반적으로 걱정할 필요가 없기 때문에 내 장점이 아닙니다.

무작위로 신호를 받지 못하는 이유에 대한 제안 사항이 있습니까?


업데이트:

적절한 오류 처리 기능을 추가했지만(정말 당혹스럽습니다!) 여전히 동일한 오류가 발생합니다. 기록에 따르면 처리되지 않은 예외는 문제가 되지 않았습니다.

단 하나의 프로세스만 파일에 액세스합니다. 저는 이 애플리케이션의 웹 풀에 웹 가든을 사용하지 않으며 다른 애플리케이션도 파일을 사용하지 않습니다.내가 생각할 수 있는 유일한 예외는 앱 풀이 재활용되고 새 WP가 생성될 때 이전 WP가 여전히 열려 있는 경우입니다. 그러나 작업 관리자를 보면 작업자 프로세스가 하나만 있는 동안 문제가 발생한다는 것을 알 수 있습니다.

@mmr:Monitor를 사용하는 것과 Mutex를 사용하는 것은 어떻게 다릅니까?MSDN 문서에 따르면 동일한 작업을 효과적으로 수행하는 것처럼 보입니다. Mutex로 잠금을 설정할 수 없는 경우 하다 false를 반환하여 우아하게 실패합니다.

주목해야 할 또 다른 사항:내가 겪고 있는 문제는 완전히 무작위인 것 같습니다. 한 요청에서 실패하면 다음 요청에서는 제대로 작동할 수도 있습니다.패턴도 없는 것 같습니다(적어도 다른 모든 패턴은 확실히 없습니다).


업데이트 2:

이 잠금은 다른 통화에는 사용되지 않습니다._lock이 InvokeOnFile 메서드 외부에서 참조되는 유일한 시간은 인스턴스화될 때입니다.

호출되는 Func는 파일에서 읽고 객체로 역직렬화하거나 객체를 직렬화하여 파일에 쓰는 것입니다.두 작업 모두 별도의 스레드에서 수행되지 않습니다.

ServerState.PATH는 정적 읽기 전용 필드이므로 동시성 문제가 발생할 것으로 예상되지 않습니다.

나는 또한 이것을 로컬(Cassini)에서 재현할 수 없다는 이전 요점을 다시 반복하고 싶습니다.


교훈:

  • 적절한 오류 처리를 사용하세요(duh!)
  • 작업에 적합한 도구를 사용하십시오(그리고 해당 도구의 기능과 방법에 대한 기본적인 이해를 갖추십시오).Sambo가 지적했듯이 Mutex를 사용하면 분명히 많은 오버헤드가 발생하여 내 응용 프로그램에 문제가 발생하는 반면 Monitor는 .NET용으로 특별히 설계되었습니다.
도움이 되었습니까?

해결책

필요한 경우에만 Mutex를 사용해야 합니다. 프로세스 간 동기화.

MUTEX는 프로세스 내 스레드 동기화에 사용될 수 있지만 모니터는 .NET 프레임 워크를 위해 특별히 설계되었으므로 리소스를 더 잘 활용하기 때문에 모니터를 사용하는 것이 일반적으로 선호됩니다.대조적으로, MUTEX 클래스는 Win32 구성의 래퍼입니다.모니터보다 강력하지만 MUTEX는 모니터 클래스에 필요한 것보다 계산적으로 비싼 인터 로프 전환이 필요합니다.

프로세스 간 잠금을 지원해야 하는 경우 다음이 필요합니다. 글로벌 뮤텍스.

사용되는 패턴은 매우 취약하며 예외 처리가 없으며 Mutex가 해제되었는지 확인할 수 없습니다.이는 정말 위험한 코드이며 시간 초과가 없을 때 이러한 코드가 중단되는 이유일 가능성이 높습니다.

또한 파일 작업이 1.5초 이상 걸리는 경우 동시 Mutex가 파일을 가져오지 못할 가능성이 있습니다.잠금을 올바르게 설정하고 시간 초과를 피하는 것이 좋습니다.

자물쇠를 사용하려면 이것을 다시 작성하는 것이 가장 좋다고 생각합니다.또한 다른 메서드를 호출하는 것 같습니다. 이 작업이 오래 걸리면 잠금이 영원히 유지됩니다.꽤 위험해요.

이는 더 짧고 훨씬 안전합니다.

// if you want timeout support use 
// try{var success=Monitor.TryEnter(m_syncObj, 2000);}
// finally{Monitor.Exit(m_syncObj)}
lock(m_syncObj)
{
    l.LogInformation( "Got lock to read/write file-based server state."
                    , (Int32)VipEvent.GotStateLock);
    using (var fileStream = File.Open( ServerState.PATH, FileMode.OpenOrCreate
                                     , FileAccess.ReadWrite, FileShare.None))
    {
        // the line below is risky, what will happen if the call to invoke
        // never returns? 
        result = func.Invoke(fileStream);
    }
}

l.LogInformation("Released state file lock.", (Int32)VipEvent.ReleasedStateLock);
return true;

// note exceptions may leak out of this method. either handle them here.
// or in the calling method. 
// For example the file access may fail of func.Invoke may fail

다른 팁

일부 파일 작업이 실패하면 잠금이 해제되지 않습니다.아마도 그럴 것입니다.파일 작업을 try/catch 블록에 넣고 finally 블록에서 잠금을 해제합니다.

어쨌든 Global.asax Application_Start 메서드에서 파일을 읽으면 다른 사람이 해당 파일에 대해 작업하지 않도록 할 수 있습니다(응용 프로그램 시작 시 파일을 읽는다고 말씀하셨죠?).응용 프로그램 풀 다시 시작 등의 충돌을 방지하려면 파일 읽기를 시도한 다음(쓰기 작업이 배타적 잠금을 사용한다고 가정) 1초간 기다렸다가 예외가 발생하면 다시 시도하면 됩니다.

이제 쓰기를 동기화하는 데 문제가 있습니다.파일을 변경하기로 결정한 방법이 무엇이든 간단한 잠금 문으로 다른 작업이 진행 중인 경우 쓰기 작업을 호출하지 않도록 주의해야 합니다.

여기에는 몇 가지 잠재적인 문제가 있습니다.

업데이트 2에 대한 편집:함수가 단순한 직렬화/직렬화 해제 조합인 경우 두 함수를 두 개의 다른 함수로 분리합니다. 하나는 '직렬화' 함수이고 다른 하나는 '역직렬화' 함수입니다.그들은 실제로 두 가지 다른 작업입니다.그런 다음 다양한 잠금 관련 작업을 수행할 수 있습니다.Invoke는 훌륭하지만 '일하는 것'보다 '멋진' 것을 선택하는 데 많은 어려움을 겪었습니다.

1) LogInformation 기능이 잠겨 있습니까?먼저 뮤텍스 내부에서 호출한 다음 뮤텍스를 해제한 후에 호출하기 때문입니다.따라서 로그 파일/구조에 쓰기 위한 잠금이 있는 경우 경쟁 조건이 발생할 수 있습니다.이를 방지하려면 통나무를 자물쇠 안에 넣으십시오.

2) C#에서 작동하고 ASP.NET에서도 작동한다고 가정하는 Monitor 클래스를 사용하여 확인해 보세요.이를 위해서는 단순히 잠금을 얻으려고 시도하고 그렇지 않으면 정상적으로 실패할 수 있습니다.이것을 사용하는 한 가지 방법은 계속해서 잠금을 얻으려고 노력하는 것입니다.(이유에 대해 편집:보다 여기;기본적으로 뮤텍스는 여러 프로세스에 걸쳐 있고 모니터는 하나의 프로세스에 있지만 .NET용으로 설계되었으므로 선호됩니다.문서에는 다른 실제 설명이 제공되지 않습니다.)

3) 다른 사람이 잠금을 설정하여 파일 스트림 열기에 실패하면 어떻게 됩니까?그러면 예외가 발생하고 이로 인해 이 코드가 잘못 작동할 수 있습니다(즉, 예외가 발생한 스레드가 여전히 잠금을 보유하고 있으며 다른 스레드가 이에 접근할 수 있습니다).

4) 기능 자체는 어떻습니까?다른 스레드가 시작됩니까, 아니면 완전히 하나의 스레드 내에서 시작됩니까?ServerState.PATH에 액세스하는 것은 어떻습니까?

5) ServerState._lock에 액세스할 수 있는 다른 기능은 무엇입니까?나는 경쟁/교착 상태를 피하기 위해 잠금이 필요한 각 기능이 자체 잠금을 갖도록 하는 것을 선호합니다.많은 스레드가 있고 각각이 동일한 개체에 대해 잠금을 시도하지만 완전히 다른 작업을 수행하는 경우 실제로 쉽게 이해할 수 있는 이유 없이 교착 상태 및 경합이 발생할 수 있습니다.전역 잠금을 사용하는 대신 해당 아이디어를 반영하기 위해 코드를 변경했습니다.(다른 사람들이 전역 잠금을 제안한다는 것을 알고 있습니다.나는 그 아이디어가 정말 마음에 들지 않습니다. 왜냐하면 이 작업이 아닌 어떤 작업을 위해 다른 것들이 그것을 잡을 가능성이 있기 때문입니다.

    Object MyLock = new Object();
    private static Boolean InvokeOnFile(Func<FileStream, Object> func, out Object result)
{
    var l = null;
    var filestream = null;
    Boolean success = false;
    if (Monitor.TryEnter(MyLock, 1500))
        try {
            l = new Logger();
            l.LogInformation("Got lock to read/write file-based server state.", (Int32)VipEvent.GotStateLock);
            using (fileStream = File.Open(ServerState.PATH, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)){                
                result = func.Invoke(fileStream); 
            }    //'using' means avoiding the dispose/close requirements
            success = true;
         }
         catch {//your filestream access failed

            l.LogInformation("File access failed.", (Int32)VipEvent.ReleasedStateLock);
         } finally {
            l.LogInformation("About to released state file lock.", (Int32)VipEvent.ReleasedStateLock);
            Monitor.Exit(MyLock);//gets you out of the lock you've got
        }
    } else {
         result = null;
         //l.LogWarning("Could not get a lock to access the file-based server state.", (Int32)VipEvent.CouldNotGetStateLock);//if the lock doesn't show in the log, then it wasn't gotten; again, if your logger is locking, then you could have some issues here
    }
  return Success;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top