ServiceStack/ASP.NET: Global object to be access b

2019-06-13 16:21发布

问题:

I am developing a web services project using the ServiceStack framework. I would like to create a global object(in my case, a SessionManager object for a GDS system I am working against, it has no relation to ASP.NET sessions) to be accessed by all incoming requests.

However, I am facing a problem whereby ASP.NET will create a new instance of my application and thereby create a new instance of my SessionManager a few times in it's life cycle. I verified this by putting a debugging line on Application_Start and Application_End protected methods in the Global.asax class and realized that the Global.asax class starts and end a number of times in its life cycle. I tried declaring my SessionManager in a static class and used it via a static construct but it still creates new instances of my SessionManager. Not sure why.

So my question is how can I create a proper global (in memory) object that can be access by all requests?

Initially I thought that by using the IoC container and specifying its singleton scope that I could achieve a singleton object, but it doesn't seems like this is the case in the ASP.NET world. So please pardon me for my knowledge in the ASP.NET area as i come from a front end development background. Hope to gain some knowledge in this area from some of the experts in this community. May thanks in advance!

回答1:

I am facing a problem whereby ASP.NET will create a new instance of my application and thereby create a new instance of my SessionManager a few times in it's life cycle. I verified this by putting a debugging line on Application_Start and Application_End protected methods in the Global.asax class and realized that the Global.asax class starts and end a number of times in its life cycle.

IIS Application Pool Recycling:

What you are seeing here is IIS recycling the application pool. IIS does this to try and prevent memory leaks. You can configure the recycling to occur at specific intervals.

I tried declaring my SessionManager in a static class and used it via a static construct but it still creates new instances of my SessionManager. Not sure why.

Unfortunately static variables don't survive recycling, so if your application is recycled you have to create a new instance of your SessionManager class. Which means you will need to handle persisting and restoring its state across application instances.

By default the recycling process uses an overlapped mechanism, whereby it starts an new instance of your application before terminating the old instance. This means there is no downtime to users while the application instance is shutdown and started. Unfortunately this means that you can't save the state of SessionManager in Application_End and restore it in Application_Start in the new instance because Application_End of the current instance will be called after the other application is up and running. So if you were going to do it that way, you would need to disable overlapping. But remember if you disable overlapping, there may be a small downtime then while the recycling occurs.

This article explains the recycling and the considerations.

How I would handle this:

  • Disable application pool recycling overlapping
  • Create a static instance of SessionManager that is created once when the application starts, in Application_Start.
  • In Application_End save the state of SessionManager to persistent storage so it can be restored in the same state when initialised in Application_Start. Perhaps serialise the state of JSON or XML.

Initially I thought that by using the IoC container and specifying its singleton scope that I could achieve a singleton object, but it doesn't seems like this is the case in the ASP.NET world.

Once you have solved the recycling issues, you don't need to use IoC to access the static object in ServiceStack as long as it is in global scope.


Maintaining the interval schedule after application restart

I have two solutions to maintaining the interval schedule. Solution 1, is simple and requires no external dependencies, though it does require to persist a date value, but this could be to a simple text file. Solution 2, is generic in that most platforms have support for it, with little configuration.

  1. I would use a timer to run the event every 10 minutes, then record the time of the last successful check of the sessions in persistent storage (i.e. a text file, database or external cache). Then if your application is restarted, when it starts up simply determine how long it should be until the next check. This will mean that IIS application pool recycling restarts shouldn't effect the interval.

    Pseudo Code:

    const int interval = 10; // Run every 10 minutes
    double timerInverval = 60 * interval; // In seconds
    
    // Get last run from peristent storage
    DateTime? lastRun = GetLastRunTime(); // Substitute with appropriate call from storage
    
    // Determine elapsed time
    if(lastRun.HasValue) {
        var secondsRemainingUntilNextRun = (lastRun.Value.AddMinutes(interval) - DateTime.Now).TotalSeconds;
        if(secondsRemainingUntilNextRun <= 0){
            // Run immediately, the web application has been down and missed running the SessionManager job
            SessionManager.CheckSessions(); // Substitute with appropriate call
        } else {
            timerInterval = secondsRemainingUntilNextRun;
        }
    }
    
    // Set a timer to trigger the SessionManager job after timerInterval seconds
    timer.interval = timerInterval;
    
  2. Alternatively you could create a scheduled task that calls your web application and triggers this action. If the task is triggered independently of the web application, then it doesn't have to worry about maintaining the schedule if the application is restarted. I believe Azure has a scheduler service, or if you run a cloud instance then you can create a system scheduled task.



回答2:

Your requirements are contradicting: you want in-memory store and you want it to be reliable and persistent and survive IIS application pool recycles. The system memory just isn't such a reliable store. If you need some persistent data you should consider using what is designed for this purpose: a database or even a file on the hard drive for example.

And of course to optimize the performance you could use an in-memory caching layer to avoid hitting your persistent layer every time you need to access the information. Another advantage to using a persistent store is that if your application is hosted in a webfarm across multiple nodes, all those nodes will be able to share the same data.

Just don't rely on IIS recycling. No matter what options you are tweaking in the IIS console, the AppPool might someday simply die wiping everything you have stored in memory and there's nothing you could do about it.



回答3:

ServiceStack supports redis out of the box, as well as several other caching providers: Memcached, Azure, Disk... so the choice of where to locate your global session provider is still up to you!

You should combine caching mechanism and the singleton pattern. So you define a class that has access to the underlying cache provider, all request have once single entry point to your session manager, and use this cache provider as your data repository.

It will survive recycling, crashes and it will make your life easy, once you have to scale your application.