在 IIS 中发生网站重启时,如何检索已启动的 Ignite 实例?

How do I retrieve a started Ignite instance when a website restart occurs in IIS?

我在 ASP.NET MVC 5 网站中使用 Apache Ignite .NET EntityFramework 二级缓存。在我的开发机器上一切都按预期工作,但是当我尝试在生产 IIS 上使用 Web 部署发布网站时,事情变得复杂了。

当我在 IIS 上发布或重新启动网站时,我总是得到 class org.apache.ignite.IgniteException: Ignite instance with this name has already been started: GridName。错误很明显,Ignite 已经 运行.

我发现摆脱这个错误的唯一方法是重新启动网站的应用程序池。但是,我不想每次在生产中发布网站时都这样做(每周发生几次)。

我尝试在 global.asax Dispose() 或 Application_End() 中停止 Ignite 实例,但问题是 AppDomain 需要很多秒才能停止。因此,Ignite 有时间在它停止并导致上述错误之前尝试自行启动。

我还尝试调用 Ignition.TryGetIgnite() 来检索 运行 实例,而不是尝试启动它,但它始终 return 为空。查看 Apache Ignite Github 存储库中此函数的源代码,我看到 Ignition 对象只是在内存中保存一个静态节点列表并对该列表执行操作。由于此时AppDomain重启,列表为空但Ignite节点在JVM中仍然是运行

有没有办法检索 Ignite 实例或可靠地停止它,这样我就不需要每次都重新启动应用程序池?

这是一个已知问题:AppDomain 已停止,但 JVM 保持 运行(因为进程未停止),因此 Java 部分 Ignite 节点仍然存在。

解决方法是在 Application_End 事件中使用 Ignition.StopAll 停止所有 Ignite 节点,就像在 Global.asax.cs 中这样:

    protected void Application_Start()
    {
        ...

        using (new Mutex(true, "ignite_" + Process.GetCurrentProcess().Id))
        {
            var ignite = Ignition.TryGetIgnite() ?? Ignition.Start();
        }
    }

    protected void Application_End()
    {
        using (new Mutex(true, "ignite_" + Process.GetCurrentProcess().Id))
        {
            Ignition.StopAll(true);
        }
    }

这里需要互斥锁,因为旧域停止与新域开始重叠。 进程 ID 包含在互斥锁名称中以确保进程范围内的独占锁,但不是机器范围内的。


另一个解决方法,可能更健壮和干净:

1) 停止 AppDomain.DomainUnload:

中的所有节点
AppDomain.CurrentDomain.DomainUnload += (sender, args) => Ignition.StopAll(true);

2) 每次使用不同的IgniteConfiguration.GridName:

Ignition.Start(new IgniteConfiguration { GridName = Guid.NewGuid().ToString() });

这样,现有节点将不会阻止您启动新节点。最终,旧节点将被停止并从内存中清除。

文档:https://apacheignite-net.readme.io/docs/deployment#section-aspnet-deployment

不完全是我想要的,但我暂时解决了问题。

我所做的是在Application_End中,我按照Pavel的建议调用Ignition.StopAll(),但是由于Application_End有时需要很长时间才能被调用,新的AppDomain加载在 Ignite 在另一个 AppDomain 中停止之前,网站有时间启动和接收请求。

为了解决这个问题,我使用以下代码(可以改进)启动 Ignite:

//Try to get already launched Ignite Instance
IIgnite ignite = Ignition.TryGetIgnite(igniteName);

while (ignite == null)
{
    try
    {
        //Try to start Ignite
        ignite = Ignition.Start(igniteConfig);
    }
    catch (Exception) //If failing to start Ignite, wait a bit for the previous AppDomain to stop the Ignite running instance...
    {
        HttpRequest request = null;
        try
        {
            request = HttpContext.Current.Request;
        }
        catch { }

        //Check if there is a request coming from the same machine, if yes, cancel it.
        if (request == null || !(request.IsLocal || request.UserHostName.EndsWith("myhostname.com"))
            Thread.Sleep(1000);
        else
            throw new HttpException(500, "Server error. Server rebooting.");
    }
}

//Use ignite variable here...

请注意,在上面的代码中,我进行了额外的检查(可能不是每个人都需要),以检查是否有来自同一台机器的当前请求。如果您想知道原因,请阅读下文,否则只需获取代码并删除最后的 if else 部分并仅保留 Thread.Sleep().

如果请求来自同一台机器,则抛出HttpException 以取消请求,否则保留。我放这段代码的原因是,如果我在 IIS 上发布网站,我不希望消费者看到错误,但我不介意他们稍等一下,让之前的 AppDomain 停止。但是,如果我收到来自同一台机器(或网站)的请求,我希望请求中止,因为它可能源自旧 AppDomain 的代码,因此会延迟 Application_EndIgnition.StopAll() 来自旧 AppDomain。

希望未来的 Apache Ignite .NET 开发可以使实例关闭比此解决方法更容易和更可靠。