Служба WCF с хранилищем на основе XML. Проблемы с параллелизмом? - PullRequest
1 голос
/ 06 августа 2009

Я запрограммировал простую службу WCF, которая хранит сообщения, отправленные пользователями, и отправляет эти сообщения предполагаемому пользователю по запросу.На данный момент постоянство реализовано путем создания имени пользователя .xml файлов со следующей структурой:

<messages recipient="username">
  <message sender="otheruser">
     ...
  </message
</messages>

Возможно, что несколько пользователей могут отправить сообщение одному и тому же получателю по адресув то же время возможно одновременное обновление XML-файла.Служба WCF в настоящее время реализована с привязкой basicHttp без каких-либо условий для одновременного доступа.

Какие существуют риски параллелизма?Как мне с ними бороться?Блокировка ReadWrite для доступного файла XML?В настоящее время служба работает максимум с 5 пользователями, может возрасти до 50, но не более.

РЕДАКТИРОВАТЬ: Как указано выше, клиент будет создавать новый класс обслуживания при каждом вызове.(InstanceContext - это PerCall, не имеет значения ConcurrencyMode) Это присуще использованию basicHttpBinding с настройками по умолчанию для службы.

Код ниже:

   public class SomeWCFService:ISomeServiceContract
        {
          ClassThatTriesToHoldSomeInfo useless;

          public SomeWCFService() 
          {
             useless=new ClassThatTriesToHoldSomeInfo();
          } 

          #region Implementation of ISomeServiceContract
          public void IncrementUseless()
          {
            useless.Counter++;
          }
          #endregion
        }

ведет себя так, как если бы он был написан:1015 *

  public class SomeWCFService:ISomeServiceContract
        {
          ClassThatTriesToHoldSomeInfo useless;

          public SomeWCFService() 
          {} 

          #region Implementation of ISomeServiceContract
          public void IncrementUseless()
          {
            useless=new ClassThatTriesToHoldSomeInfo();
            useless.Counter++;
          }
          #endregion
        }

Таким образом, параллелизм никогда не будет проблемой, пока вы не попытаетесь получить доступ к некоторым хранимым извне данным, например, в базе данных или в файле.Недостатком является то, что вы не можете хранить какие-либо данные между вызовами методов службы, если не сохраните их внешне.

Ответы [ 4 ]

2 голосов
/ 07 августа 2009

Если ваш WCF-сервис является одноразовым и гарантированно будет таким, то вам не нужно ничего делать. Поскольку WCF позволяет обрабатывать только один запрос за раз, параллельный доступ к файлам имени пользователя не является проблемой, если только операция, обслуживающая этот запрос, не создаст несколько потоков, обращающихся к одному и тому же файлу. Однако, как вы можете себе представить, синглтон-сервис не очень масштабируем и, как я полагаю, не тот, который вам нужен в вашем случае.

Если ваша служба WCF не является одноэлементной, то одновременный доступ к одному и тому же файлу пользователя является очень реалистичным сценарием, и вы обязательно должны его решить. Несколько экземпляров вашей службы могут одновременно пытаться получить доступ к одному и тому же файлу для его обновления, и вы получите « не может получить доступ к файлу, потому что он используется другим исключением процесса » или чем-то подобным. Это означает, что вам необходимо синхронизировать доступ к пользовательским файлам. Вы можете использовать монитор (блокировка), ReaderWriterLockSlim и т. Д. Однако вы хотите, чтобы эта блокировка работала для каждого файла отдельно. Вы не хотите блокировать обновления для других файлов, когда происходит обновление для другого файла. Таким образом, вам нужно будет поддерживать объект блокировки на файл и блокировать этот объект, например

//when a new userfile is added, create a new sync object
fileLockDictionary.Add("user1file.xml",new object());

//when updating a file
lock(fileLockDictionary["user1file.xml"])
{
   //update file.
}

Обратите внимание, что этот словарь также является общим ресурсом, который требует синхронизированного доступа.

Теперь работа с параллелизмом и обеспечение синхронизированного доступа к совместно используемым ресурсам с соответствующей степенью детализации очень сложна не только с точки зрения разработки правильного решения, но также с точки зрения отладки и поддержки этого решения. Отладка многопоточного приложения - это не весело и сложно воспроизвести проблемы. Иногда у вас нет выбора, но иногда у вас есть. Итак, Есть ли какая-то конкретная причина, по которой вы не используете или не рассматриваете решение на основе базы данных? База данных будет обрабатывать параллелизм для вас. Вам не нужно ничего делать. Если вас беспокоит стоимость покупки базы данных, есть очень хорошие проверенные базы данных с открытым исходным кодом, такие как MySQL и PostgreSQL, которые вам ничего не будут стоить.

Другая проблема с подходом на основе XML-файлов заключается в том, что их обновление будет дорогостоящим. Вы будете загружать XML из пользовательского файла в памяти, создавать элемент сообщения и сохранять его обратно в файл. По мере роста xml этот процесс займет больше времени, потребует больше памяти и т. Д. Это также ухудшит вашу масштабируемость, поскольку процесс обновления будет дольше удерживать эту блокировку. Плюс, ввод / вывод стоит дорого. Есть также преимущества, которые приходят с решением на основе базы данных: транзакции, резервные копии, возможность легко запрашивать ваши данные, репликация, зеркалирование и т. Д.

Я не знаю ваших требований и ограничений, но я думаю, что решение на основе файлов будет проблематичным в будущем.

2 голосов
/ 06 августа 2009

Вам необходимо прочитать файл перед добавлением в него и записью на диск, поэтому у вас есть (довольно небольшой) риск попытки двух пересекающихся операций - вторая операция читает с диска до того, как первая операция записала на диск, и первое сообщение будет перезаписано при подтверждении второго сообщения.

Простой ответ может заключаться в том, чтобы поставить ваши сообщения в очередь, чтобы обеспечить их последовательную обработку. Когда сообщения получены вашей службой, просто сбросьте содержимое в очередь MSMQ. Иметь другой однопоточный процесс, который читает из очереди и записывает соответствующие изменения в файл XML. Таким образом, вы можете гарантировать, что вы пишете только один файл за раз и решаете любые проблемы параллелизма.

1 голос
/ 12 августа 2009

Основная проблема заключается в том, что при доступе к глобальному ресурсу (например, к статической переменной или файлу в файловой системе) необходимо убедиться, что вы заблокировали этот ресурс или каким-либо образом сериализовали доступ к нему.

Мое предложение здесь (если вы хотите просто сделать это быстро, без использования базы данных или чего-либо еще, что было бы лучше), было бы вставить ваши сообщения в структуру очереди в памяти из вашего служебного кода.

public MyService : IMyService
{
     public static Queue queue = new Queue();
     public void SendMessage(string from, string to, string message)
     {
          Queue syncQueue = Queue.Synchronized(queue);
          syncQueue.Enqueue(new Message(from, to, message));
     }
}

Затем в другом месте вашего приложения вы можете создать фоновый поток, который читает из этой очереди и записывает в файловую систему по одному обновлению за раз.

void Main()
{


    Timer timer = new Timer();
    timer.Tick += (o, e)
       {
           Queue syncQueue = Queue.Synchronized(MyService.queue);
           while(syncQueue.Count > 0)
           {
                Message message = syncQueue.Dequeue() as Message;
                WriteMessageToXMLFile(message);
           }
           timer.Start();
       };
    timer.Start();

    //Or whatever you do here
    StartupService();
}

Это не красиво (и я не уверен на 100%, что оно компилируется), но оно должно работать. Это своего рода подход «сделай это с теми инструментами, которые у меня есть, а не с инструментами, которые я хочу», я думаю, ты ищешь.

Клиенты также отключаются как можно скорее, вместо того, чтобы ждать, пока файл будет записан в файловую систему, прежде чем они отключатся. Это также может быть плохо ... клиенты могут не знать, что их сообщение не было доставлено, если ваше приложение закрывается после того, как они отключились, а фоновый поток еще не написал свое сообщение.

Другие подходы здесь так же действительны ... Я хотел опубликовать подход сериализации, а не подход блокировки, предложенный другими.

НТН, Anderson

0 голосов
/ 12 августа 2009

Ну, так получилось, что я сделал что-то почти то же самое, за исключением того, что это были не сообщения ...

Вот как бы я справился с этим.

Ваша служба сама общается с центральным объектом (или объектами), который может отправлять запросы сообщений на основе отправителя.

Объект, относящийся к каждому отправителю, поддерживает внутреннюю блокировку при обновлении чего-либо. Когда он получает новый запрос на изменение, он может читать с диска (при необходимости), обновлять данные и записывать на диск (при необходимости).

Поскольку в разных потоках будут происходить разные обновления, внутренняя блокировка будет сериализована. Обязательно снимите блокировку, если вы вызываете какие-либо «внешние» объекты, чтобы избежать сценариев взаимоблокировки.

Если ввод / вывод становится узким местом, вы можете рассмотреть различные стратегии, включающие размещение сообщений в одном файле, отдельных файлах, а не немедленную запись их на диск и т. Д. Фактически, я бы подумал о хранении сообщений для каждого пользователя. именно в этой папке.

Самое важное, что каждый экземпляр службы действует, по сути, как адаптер для центрального класса, и что только один экземпляр один класса будет когда-либо отвечать за чтение / написание сообщений для данного получателя. Другие классы могут запрашивать чтение / запись, но они фактически не выполняют это (или даже не знают, как это выполняется). Это также означает, что их код будет выглядеть как «AddMessage (сообщение)», а не «SaveMessages (GetMessages.Add (сообщение))».

Тем не менее, использование базы данных является очень хорошим предложением и, вероятно, избавит вас от многих головных болей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...