繁体   English   中英

use static class in ASP.net Web API

[英]use static class in ASP.net Web API

我正在使用自动翻译发布这个问题。 请原谅任何语法错误。

我已经使用 .net 框架和 ASP.net Web ZDB974238714CA8A1434A7CE1D08 构建了一个应用程序。 我已经为在 IIS 上运行的站点中的每个客户区域拆分了虚拟路径,并复制了相同的二进制文件以作为单独的应用程序运行。 应用程序在同一个应用程序池中运行。

最近,一些客户在几分钟内就提出了非常多的请求。 (我怀疑客户端的系统出现故障)。 我正在考虑将 static class 添加到我当前的应用程序中,以跟踪给定时间段内每个客户的请求数量,并在超过阈值时阻止它们。 从过去的 StackOverFlow 文章中,我发现“如果应用程序池被回收,static class 中的信息会丢失”,但我确定在这种情况下这不是问题。 为了我的目的,我只需要能够保留几分钟的信息。

但是,我还有一些问题无法找到答案,所以我想请教大家几个问题。

  1. 即使相同的二进制文件在同一个应用程序池中运行,static class 信息是否会为不同的应用程序单独保存?

  2. 即使在应用程序池被回收后,static class 的 static 构造函数是否会被执行?

  3. 如果我从 static class 中引用 Global.asax 中的字段,是否有问题?

  4. 从 static class 中引用 web.config 的内容是否有问题?

下面附上我的实验实现的源码。 I plan to call the static method "ExcessiveRequestCheck.isExcessiveRequest" of this static class after the Web API receives the request and identifies the user ID.

任何建议将不胜感激。

public static class ExcessiveRequestCheck
{
    private static Dictionary<string, ExcessiveRequestInfo> dicExcessiveRequestCheckInfo = new Dictionary<string, ExcessiveRequestInfo>();
    private static object initLock = new object();
    private static object dicExcessiveRequestCheckInfoLock = new object();

    //If possible, I want this process to be a static constructor
    public static Dictionary<int, int> dicExcessiveRequestSkipConditions
    {
        get
        {
            lock (initLock)
            {
                if (ExcessiveRequestCheck._dicExcessiveRequestSkipConditions == null)
                {
                    //if possible, I want to set this value from Web.config.
                    ExcessiveRequestCheck._dicExcessiveRequestSkipConditions = new Dictionary<int, int>() {
                        { 5, 3 }, { 15, 5 }, { 45, 10 }, { 120, 20 }
                    };
                }
                return ExcessiveRequestCheck._dicExcessiveRequestSkipConditions;
            }
        }
    }
    private static Dictionary<int, int> _dicExcessiveRequestSkipConditions = null;

    public const int BUFFER_CLEAR_MINUTES = 5;

    public static bool isExcessiveRequest(string userId)
    {
        ExcessiveRequestCheck.refreshExcessiveRequestCheckInfo();
        lock (ExcessiveRequestCheck.dicExcessiveRequestCheckInfoLock)
        {
            if (ExcessiveRequestCheck.dicExcessiveRequestCheckInfo.ContainsKey(userId) == false)
            {
                ExcessiveRequestCheck.dicExcessiveRequestCheckInfo.Add(userId, new ExcessiveRequestInfo() { countRequest = 1 });
                return false;
            }

            bool doSkip = false;
            ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[userId].countRequest++;
            foreach (KeyValuePair<int, int> pair in ExcessiveRequestCheck.dicExcessiveRequestSkipConditions)
            {
                if (ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[userId].lastRequesttTime.AddSeconds(pair.Key) > DateTime.Now)
                {
                    if (ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[userId].countRequest > pair.Value)
                    {
                        ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[userId].wasRequestSkip = true;
                        doSkip = true;
                    }
                }
            }
            ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[userId].lastRequesttTime = DateTime.Now;

            return doSkip;
        }
    }

    public static void refreshExcessiveRequestCheckInfo()
    {
        lock (ExcessiveRequestCheck.dicExcessiveRequestCheckInfoLock)
        {
            var keyList = ExcessiveRequestCheck.dicExcessiveRequestCheckInfo.Keys;
            foreach (string key in keyList)
            {
                if (ExcessiveRequestCheck.dicExcessiveRequestCheckInfo.ContainsKey(key))
                {
                    var value = ExcessiveRequestCheck.dicExcessiveRequestCheckInfo[key];
                    if (value.lastRequesttTime.AddMinutes(BUFFER_CLEAR_MINUTES) < DateTime.Now)
                    {
                        if (value.wasRequestSkip)
                        {
                            //this NLog instance was created in Global.asax.cs
                            WebApiApplication.logger.Fatal("skip request! user id=" + key);
                        }
                        ExcessiveRequestCheck.dicExcessiveRequestCheckInfo.Remove(key);
                    }
                }
            }
        }
    }
}

class ExcessiveRequestInfo
{
    public DateTime requestStartTime { get; set; } = DateTime.Now;
    public DateTime lastRequesttTime { get; set; } = DateTime.Now;
    public int countRequest { get; set; } = 0;
    public bool wasRequestSkip { get; set; } = false;
}

你的问题

即使相同的二进制文件在同一个应用程序池中运行,static class 信息是否会为不同的应用程序单独保存?

是的,它们是分开的

即使在应用程序池被回收后,static class 的 static 构造函数是否会被执行?

是的,保证在执行任何 static 方法之前调用 static 构造函数

如果我从 static class 中引用 Global.asax 中的字段,是否有问题?

只不过是从其他任何地方访问它

从 static class 中引用 web.config 的内容是否有问题?

只不过是从其他任何地方访问它

您的一般方法

拒绝服务

如果您试图减轻拒绝服务攻击或凭证填充攻击,您的方法可能不起作用,因为对您的服务的请求仍会导致负载被添加到您的服务器,并且如果它们正在执行凭证填充攻击,它将用数百万个条目填满您的字典,并可能导致您的应用程序崩溃。

如果您想有效缓解拒绝服务攻击,您可能需要更面向网络的解决方案,例如智能防火墙或WAF

速率限制

另一方面,如果您试图限制特定用户的活动(即速率限制),那么您的方法可能不是最好的,因为它不支持负载平衡——您的列表保存在进程内 memory 中。 对于每用户速率限制,您可能需要在所有服务器都可访问的中央数据存储中跟踪用户活动。

Static 构造函数

作为一般规则,您应该尽量避免使用 static 构造函数,或者保持它们非常简单,因为 static 构造函数中的失败将导致整个应用程序无法启动。 当心!

即使相同的二进制文件在同一个应用程序池中运行,static class 信息是否会为不同的应用程序单独保存?

如果通过不同的应用程序,您的意思是单独的 web 站点? 是的,它将与您为该应用程序池运行的每个 web 站点分开。

即使在应用程序池被回收后,static class 的 static 构造函数是否会被执行?

哼,有点乱。 仅当您调用 class 和给定的构造函数时,才会执行构造函数。 由于从未创建过 class 的实例,因此永远不会使用或触发“initialize/new”事件。 因此,任何带有参数的方法都将运行并正常工作 - 包括构造函数。 我建议在第一次使用时不会触发某些“事件”——在 static class 的上下文中它不会也没有意义,因为您从不创建实例。 所以,如果你有一些带参数的方法,那很好。 因此,在 class 的新实例的上下文中的构造函数没有任何意义 - (甚至认为静态不可能)。 没有触发“新”事件的概念,所以我看不出这个问题有什么意义。

如果我从 static class 中引用 Global.asax 中的字段,是否有问题?

好吧,class 中的值对所有用户都是全局的。 但是,这些值可以从 scope 中得到 go ,只要你愿意。 因此,零使用公共成员是切实可行的。 虽然应用程序池重新启动会重新设置那些 class 值? 他们几乎可以从 scope 中取出 go 。 它们对所有用户都是全球性的。 因此,在 static class 中保留值或尝试保留值不是生产代码的可行选择。 您可以在该 class 中使用方法(代码),但实际上不能依赖任何公共持久值来正确持久。 我不是 100% 确定,但即使只是一般的 .net 垃圾收集也可能会导致重新设置。

如果您需要保留此信息,则不能使用 static,您必须创建该 class 的实例并将其保留在 session() 中。 session 是每个用户。 static class 公共值将适用于每个用户 - 而不仅仅是当前用户。 实际上,这些值对所有用户都是全局的——但没有任何真正的控制或限制这些值将持续存在——你无法控制这一点,因此你不能为任何具有实际价值的系统采用这种概念和设计。

从 static class 中引用 web.config 的内容是否有问题?

读值? 没问题。 更新或修改值? - 一个巨大的不同问题。 您修改 web.config,这将触发应用程序池重启。

因此,您可以自由阅读任何文件 - 文本文件、xml 或其他文件,包括 web.config。 只要您不修改此类文件,就没有问题。

这里的主要问题?

假设或构建公共 static class 值将持续存在的设计是不切实际的。 当这些值可能 go 超出 scope 时,您拥有的零控制是您拥有零控制的东西,因此此类设计不能使用也不依赖持久的值。

当然还有许多 web 托管系统? 他们现在正在采用云计算。 这意味着从一个回传到下一个,您可能正在使用不同的服务器,这意味着这些值不能在 memory 中持续存在,因为从一个回传到下一个,或者一个 web 服务调用到下一个? 无论如何,您很可能会访问不同的服务器(并且它们不共享内存)(因此,这建议说使用基于 SQL 服务器的会话,或者至少将这些值保存在数据库中)。

事实上,如果你需要这样的持久值和数据? 然后使用数据库。 基于 web 的软件的整个想法是您在回发之间没有 state。 而且您正在尝试 go 甚至走上更糟糕的道路,但希望在对 web 站点的调用之间,一些全局值“可能”、“某种”和“也许”将持续存在。 答:你真的不能用任何可靠的 realm 做到这一点。

所以,你的大多数问题并不重要。 重要的是这些价值观要坚持下去,你不能依赖这样的设计。 如果您需要一些持久值,那么您必须采用支持该概念的系统和设计(viewstate、cookies 或 session())。

现在,我想您可以尝试一下,然后回来提供详细报告以及您的体验结果。 但是,有太多的坑,并且没有任何代码或系统控制 memory 中的持久值,我认为我不会在这条路上使用 go。

在 web 领域中,尝试将公共变量保留在 static class 中几乎没有任何意义。 你可以有代码,你可以有很酷的方法,你可以使用 session()。 但是,static class 中的持久值概念是一种没有意义的设计选择,也不能依赖。

Web 软件被假定为无状态软件,并且您必须对 static class 或实际上一般使用的代码做出很多假设。

暂无
暂无

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

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