简体   繁体   English

多个用户在同一个文件上写入

[英]Multiple users writing at the same file

I have a project which is a Web API project, my project is accessed by multiple users (i mean a really-really lot of users). 我有一个项目是一个Web API项目,我的项目由多个用户访问(我的意思是真的很多用户)。 When my project being accessed from frontend (web page using HTML 5), and user doing something like updating or retrieving data, the backend app (web API) will write a single log file (a .log file but the content is JSON). 当我的项目从前端(使用HTML 5的网页)访问,并且用户执行更新或检索数据之类的操作时,后端应用程序(Web API)将编写单个日志文件(.log文件,但内容为JSON)。 The problem is, when being accessed by multiple users, the frontend became unresponsive (always loading). 问题是,当被多个用户访问时,前端变得没有响应(总是加载)。 The problem is in writing process of the log file (single log file being accessed by a really-really lot of users). 问题在于编写日志文件的过程(真正非常多的用户正在访问单个日志文件)。 I heard that using a multi threading technique can solve the problem, but i don't know which method. 我听说使用多线程技术可以解决问题,但我不知道哪种方法。 So, maybe anyone can help me please. 所以,也许任何人都可以帮助我。 Here is my code (sorry if typo, i use my smartphone and mobile version of stack overflow): 这是我的代码(对不起,如果错字,我使用我的智能手机和移动版本的堆栈溢出):

public static void JsonInputLogging<T>(T m, string methodName)
{
    MemoryStream ms = new MemoryStream();
    DataContractJsonSerializer ser = new 
            DataContractJsonSerializer(typeof(T));
    ser.WriteObject(ms, m);
    string jsonString = Encoding.UTF8.GetString(ms.ToArray());
    ms.Close();
    logging("MethodName: " + methodName + Environment.NewLine + jsonString.ToString());
}


public static void logging (string message)
{
    string pathLogFile = "D:\jsoninput.log";
    FileInfo jsonInputFile = new FileInfo(pathLogFile);
    if (File.Exists(jsonInputFile.ToString()))
    {
        long fileLength = jsonInputFile.Length;
        if (fileLength > 1000000)
        {
            File.Move(pathLogFile, pathLogFile.Replace(*some new path*);
        }
    }
    File.AppendAllText(pathLogFile, *some text*);
}

You have to understand some internals here first. 你必须先了解一些内部结构。 For each [x] users, ASP.Net will use a single worker process. 对于每个[x]用户,ASP.Net将使用单个工作进程。 One worker process holds multiple threads. 一个工作进程拥有多个线程。 If you're using multiple instances on the cloud, it's even worse because then you also have multiple server instances (I assume this ain't the case). 如果你在云上使用多个实例,那就更糟了,因为那时你也有多个服务器实例(我认为情况并非如此)。

A few problems here: 这里有一些问题:

  • You have multiple users and therefore multiple threads. 您有多个用户,因此有多个线程。
  • Multiple threads can deadlock each other writing the files. 多个线程可以相互死锁写入文件。
  • You have multiple appdomains and therefore multiple processes. 您有多个应用程序域,因此有多个进程。
  • Multiple processes can lock out each other 多个进程可以互相锁定

Opening and locking files 打开和锁定文件

File.Open has a few flags for locking. File.Open有一些用于锁定的标志。 You can basically lock files exclusively per process, which is a good idea in this case. 基本上你可以按进程锁定文件,在这种情况下这是一个好主意。 A two-step approach with Exists and Open won't help, because in between another worker process might do something. 使用ExistsOpen的两步法无济于事,因为在另一个工作进程之间可能会有所作为。 Bascially the idea is to call Open with write-exclusive access and if it fails, try again with another filename. 基本上,我们的想法是使用独占访问权限调用Open ,如果失败,请再次尝试使用其他文件名。

This basically solves the issue with multiple processes. 这基本上解决了多个进程的问题。

Writing from multiple threads 从多个线程写

File access is single threaded. 文件访问是单线程的。 Instead of writing your stuff to a file, you might want to use a separate thread to do the file access, and multiple threads that tell the thing to write. 您可能希望使用单独的线程来执行文件访问,而使用多个线程来指示要写入的内容,而不是将您的内容写入文件。

If you have more log requests than you can handle, you're in the wrong zone either way. 如果您有比您可以处理的更多的日志请求,那么无论哪种方式,您都处于错误的区域。 In that case, the best way to handle it for logging IMO is to simply drop the data. 在这种情况下,处理它以记录IMO的最佳方法是简单地删除数据。 In other words, make the logger somewhat lossy to make life better for your users. 换句话说,让记录器有点损失,让用户的生活更美好。 You can use the queue for that as well. 您也可以使用队列。

I usually use a ConcurrentQueue for this and a separate thread that works away all the logged data. 我通常使用ConcurrentQueue和一个单独的线程来处理所有记录的数据。

This is basically how to do this: 这基本上是如何做到这一点:

// Starts the worker thread that gets rid of the queue:
internal void Start()
{
    loggingWorker = new Thread(LogHandler)
    {
        Name = "Logging worker thread",
        IsBackground = true,
        Priority = ThreadPriority.BelowNormal
    };
    loggingWorker.Start();
}

We also need something to do the actual work and some variables that are shared: 我们还需要做一些事情来完成实际工作和一些共享的变量:

private Thread loggingWorker = null;
private int loggingWorkerState = 0;
private ManualResetEventSlim waiter = new ManualResetEventSlim();
private ConcurrentQueue<Tuple<LogMessageHandler, string>> queue =
    new ConcurrentQueue<Tuple<LogMessageHandler, string>>();

private void LogHandler(object o)
{
    Interlocked.Exchange(ref loggingWorkerState, 1);

    while (Interlocked.CompareExchange(ref loggingWorkerState, 1, 1) == 1)
    {
        waiter.Wait(TimeSpan.FromSeconds(10.0));
        waiter.Reset();

        Tuple<LogMessageHandler, string> item;
        while (queue.TryDequeue(out item))
        {
            writeToFile(item.Item1, item.Item2);
        }
    }
}

Basically this code enables you to work away all the items from a single thread using a queue that's shared across threads. 基本上,这段代码使您可以使用跨线程共享的队列来处理单个线程中的所有项目。 Note that ConcurrentQueue doesn't use locks for TryDequeue , so clients won't feel any pain because of this. 请注意, ConcurrentQueue不使用TryDequeue锁,因此客户端不会因此而感到任何痛苦。

Last thing that's needed is to add stuff to the queue. 最后需要的是将东西添加到队列中。 That's the easy part: 这很简单:

public void Add(LogMessageHandler l, string msg)
{
    if (queue.Count < MaxLogQueueSize) 
    {
        queue.Enqueue(new Tuple<LogMessageHandler, string>(l, msg));
        waiter.Set();
    }
}

This code will be called from multiple threads. 此代码将从多个线程调用。 It's not 100% correct because Count and Enqueue don't necessarily have to be called in a consistent way - but for our intents and purposes it's good enough. 这不是100%正确,因为CountEnqueue不一定必须以一致的方式调用 - 但是对于我们的意图和目的而言,它已经足够好了。 It also doesn't lock in the Enqueue and the waiter will ensure that the stuff is removed by the other thread. 它也不会锁定Enqueuewaiter会确保其他线程删除了这些东西。

Wrap all this in a singleton pattern, add some more logic to it, and your problem should be solved. 用单例模式包装所有这些,为它添加更多逻辑,你的问题应该解决。

That can be problematic, since every client request handled by new thread by default anyway. 这可能会有问题,因为无论如何,默认情况下每个客户端请求都由新线程处理。 You need some "root" object that is known across the project (don't think you can achieve this in static class), so you can lock on it before you access the log file. 您需要一些在项目中已知的“根”对象(不要认为您可以在静态类中实现此目的),因此您可以在访问日志文件之前锁定它。 However, note that it will basically serialize the requests, and probably will have a very bad effect on performance. 但请注意,它基本上会对请求进行序列化,并且可能会对性能产生非常不利的影响。

No multi-threading does not solve your problem. 没有多线程无法解决您的问题。 How are multiple threads supposed to write to the same file at the same time? 多线程如何同时写入同一个文件? You would need to care about data consistency and I don't think that's the actual problem here. 你需要关心数据的一致性 ,我不认为这是实际的问题。

What you search is asynchronous programming. 你搜索的是异步编程。 The reason your GUI becomes unresponsive is, that it waits for the tasks to complete. GUI无法响应的原因是,它等待任务完成。 If you know, the logger is your bottleneck then use async to your advantage. 如果你知道,记录器是你的瓶颈,然后使用异步你的优势。 Fire the log method and forget about the outcome, just write the file. 触发日志方法并忘记结果,只需编写文件即可。

Actually I don't really think your logger is the problem. 实际上我并不认为你的记录器是问题。 Are you sure there is no other logic which blocks you? 你确定没有其他阻止你的逻辑吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM