ASP NET Web 表单 URL 重写太慢

Posted

技术标签:

【中文标题】ASP NET Web 表单 URL 重写太慢【英文标题】:ASP NET Web Form Url Rewrite too slow 【发布时间】:2021-06-30 12:16:44 【问题描述】:

我有一个需要 URL 重写规则的网站 (SITE_DOMAIN)。 该网站采用 asp.net Web 形式。

例如 SITE_DOMAIN/abbigliamento/donna/jeans 是 SITE_DOMAIN/Products/Donna/0/42/1

我有一个名为 Rewrites 的表,其中包含这些字段

在 Global.asax 我有

   public static List<Rewrite> rewrites = null;
   public static string oldChiave = "";

   public void GetRewrites() 
   
      if (rewrites == null)
         rewrites = Rewrite.getRules(); //reads from table (about 5000 rows)
   


protected void Application_BeginRequest(object sender, EventArgs e)
   
      
      GetRewrites();
      String fullOriginalPath = Request.Url.ToString();
      int index = fullOriginalPath.IndexOf('/', fullOriginalPath.IndexOf(SITE_DOMAIN)) + 1;
      string chiave = fullOriginalPath.Substring(index).ToLower();

      if (oldChiave != chiave)
      
         oldChiave = chiave;            

         Rewrite r = rewrites.Find(y => y.Chiave == chiave);

         if (r != null)
         
            string url = "/" + r.Pagina;
            if (r.Param1 != null)
                url += "/" + r.Param1;
            if (r.Param2 != null)
                url += "/" + r.Param2;
            if (r.Param3 != null)
                url += "/" + r.Param3;
            if (r.Param4 != null)
                url += "/" + r.Param4;
            if (r.Param5 != null)
                url += "/" + r.Param5;
            Context.RewritePath(url);
         

         //se non ho trovato la chiave all'interno delle chiavi potrebbe essere la composizione dei parametri in Param1,2,3,4,5 es /Products/Uomo/0/0/1, deve ritrasformarsi in Scarpe-Uomo
         string[] param = chiave.Split('/');
         if (param.Length == 5)
         
            r = rewrites.Find(x => x.Pagina == param[0] &&
                    x.Param1 == param[1] &&
                    x.Param2 == param[2] &&
                    x.Param3 == param[3] &&
                    x.Param4 == param[4]);

            if (r != null)
                Response.Redirect("/" + r.Chiave);

         
         if (param.Length == 6)
         
            r = rewrites.Find(x => x.Pagina == param[0] &&
                    x.Param1 == param[1] &&
                    x.Param2 == param[2] &&
                    x.Param3 == param[3] &&
                    x.Param4 == param[4] &&
                    x.Param5 == param[5]);

            if (r != null)
                Response.Redirect("/" + r.Chiave);
         
      

   

网站太慢了,问题我确定就在这里。

当我单击链接时,Global.asax Application_BeginRequest 方法被触发 3 次或更多次。

还有其他我可以使用的方法或我可以使用的任何 3 部分 dll 吗?

附言。为了我的 global.asax 的完整性,我也有方法

    using Microsoft.AspNet.FriendlyUrls;
    protected void Application_Start(object sender, EventArgs e)
   
      RouteTable.Routes.EnableFriendlyUrls();
      RouteTable.Routes.MapPageRoute("", "home", "~/Default.aspx");
      RouteTable.Routes.MapPageRoute("", "carrello", "~/Carrello.aspx");
      RouteTable.Routes.MapPageRoute("", "contatti", "~/Contatti.aspx");
      RouteTable.Routes.MapPageRoute("", "checkout", "~/Checkout2.aspx");
      RouteTable.Routes.MapPageRoute("", "logout", "~/Logout.aspx");
      RouteTable.Routes.MapPageRoute("", "pagamenti", "~/Pagamenti.aspx");
      RouteTable.Routes.MapPageRoute("", "chi-siamo/scarpe-online-di-marca", "~/ChiSiamo.aspx");
      RouteTable.Routes.MapPageRoute("", "i-miei-ordini", "~/PageOrdini.aspx");
      RouteTable.Routes.MapPageRoute("", "pre-checkout", "~/PreCheckout.aspx");
      RouteTable.Routes.MapPageRoute("", "privacy-and-cookies", "~/PrivacyAndCookies.aspx");
      RouteTable.Routes.MapPageRoute("", "ricerca-prodotto/Filtri/Pagina", "~/ProductsSearch.aspx");
      RouteTable.Routes.MapPageRoute("", "il-mio-profilo", "~/Profilo.aspx");
      RouteTable.Routes.MapPageRoute("", "registrazione", "~/Registrazione.aspx");
      RouteTable.Routes.MapPageRoute("", "resi", "~/Resi.aspx");
      RouteTable.Routes.MapPageRoute("", "spedizioni", "~/Spedizioni.aspx");
      RouteTable.Routes.MapPageRoute("", "termini-e-condizioni", "~/TerminiECondizioni.aspx");
      RouteTable.Routes.MapPageRoute("", "grazie", "~/Thanks.aspx");
      RouteTable.Routes.MapPageRoute("", "product/ProductId", "~/Product.aspx");
      RouteTable.Routes.MapPageRoute("", "products/Menu/Marca/Categoria/Pagina", "~/Products.aspx");
      RouteTable.Routes.MapPageRoute("", "blog/Pagina", "~/Blog.aspx");
      RouteTable.Routes.MapPageRoute("", "lista-dei-desideri/Pagina", "~/Wishlist.aspx");
      RouteTable.Routes.MapPageRoute("", "blogpost/NewsId", "~/BlogPost.aspx");

   

这种重写是固定的,因此它们不需要存储在上一个表中。

【问题讨论】:

如果您为此使用中间件,我认为它会给您带来更好的性能。 infoworld.com/article/3445867/… 您还没有真正尝试过分析每个逻辑块实际执行所需的时间,因此您将获得不同的解决方案,而无法衡量哪个更适合您的情况。 【参考方案1】:

由于您对第三方解决方案持开放态度(“我可以使用任何其他方法或我可以使用的任何 3 部分 dll 吗?”)您可以考虑以下内容。

    选项 1:由于您的网站是使用 Microsoft 技术堆栈构建的,因此我假设您将在 IIS 网络服务器上部署该解决方案。如果是这种情况,您可以使用 IIS 上的 Rewrite 模块扩展来进行所有的重写。您的规则将保存在 web.config 文件中。 选项 2:使用第三方软件

选项 1 步骤

打开 IIS 管理器。选择您的网站。 单击功能视图中的 URL 重写。如果您没有看到此选项,则可能尚未安装 URL 重写扩展。从这里 [https://www.iis.net/downloads/microsoft/url-rewrite] 获取并安装它。 单击“操作”窗格中的添加规则(位于右侧) 在“添加规则”对话框中选择空白规则 创建您的规则。规则选项很容易解释。

由于您的规则存储在数据库中,您可能需要编写代码以从数据库中读取规则(您似乎已经这样做了)并重新格式化规则以确认为 IIS 规则格式。归根结底,规则是名称-值对。假设您有一个 prod.aspx 页面,其中包含显示产品详细信息的产品 id 和尺寸参数,并且您当前的 URL 是“/products/prod.aspx?id=1234&size=3”。规则映射中的“原始值”为“/product-details”,IIS 规则映射对话框中的“新值”为“/products/prod.aspx?id=1234&size=3”。原始值指定我们要重写的 URL 路径,新值指定我们要重写的 URL 路径。

您可能需要使用带有 URL 重写模块的自定义重写提供程序来直接与您的 SQL 表交互以导入规则。您可以在此处阅读有关如何执行此操作的更多信息:https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-custom-rewrite-providers-with-url-rewrite-module

选项 2

Helicon Tech (http://www.isapirewrite.com/) 重写 ISAPI Ionic 的 Isapi 重写过滤器 (https://github.com/DinoChiesa/IIRF)

我自己没有使用过上述任何软件产品,因此我无法告诉您它们的效果如何。尝试它们需要您自担风险。

【讨论】:

【参考方案2】:

正如其他答案所建议的那样,有不同的 MiddleWare 选项可用或 Url-Rewrite 模块可以在您正在运行的应用程序之外应用,但如果您不了解您的实际瓶颈在哪里,您如何确定任何这方面的改变会有效吗?

这里有多个复合问题。

    网站太慢了

    您对太慢的定义是什么?您希望达到什么响应时间?

    虽然复杂的路由逻辑看起来像是一个主要的候选者,但即使它看起来效率低下,最坏的情况下,这只会给每个响应增加几百毫秒的时间。 知道每个页面请求(取决于其中的体系结构和代码)通常会发出多个请求,有时是 10 到 100 次单独的内容请求,问题可能不是路由,而是每一项的内容,或者可能只是瓶颈中的一些请求。 使用浏览器中的开发工具来确定页面生命周期中哪些单个请求执行时间最长,缓慢响应可能 是一个重要因素。

    加载路线列表

    虽然我们假设的数据库调用一次加载到静态变量中,但代码模式是模棱两可的,而不是执行这个“根据请求”,它应该被强制到 Application_Start 中的单个调用中。但是不要忽视确保此调用高效的重要性,如果这个简单的数据库查找响应时间过长,那么我们可以放心地假设所有其他对数据库的查询也会很慢,这就是 最可能FACTOR 影响您的响应时间。

    5K 记录听起来不算多,但在某些框架和低成本架构中,冷启动时间可能会增加几秒钟,在这种情况下,您应该考虑从数据库中读取数据的编译或部署步骤到本地文件或使用 T4 模板或类似的东西从规则生成代码。

    您需要知道Rewrite.getRules() 需要多长时间执行才能围绕这一点做出决策,像这样的简单代码将有助于捕获获取规则所花费的时间,如果这是不可接受的,那么这是一个明确的优化地方,我希望 5k 行在生产环境中仍能在 1 秒内返回:

    public static List<Rewrite> rewrites = null;
    public static TimeSpan _getRules_Duration = TimeSpan.Zero;
    
    public void GetRewrites() 
    
       if (rewrites == null)
       
           var sw = System.Diagnostics.Stopwatch.StartNew();
           try
           
               rewrites = Rewrite.getRules(); //reads from table (about 5000 rows)
           
           finally
           
               sw.Stop();
               _getRules_Duration = sw.Elapsed;
           
        
    
    

    发布_getRules_Duration 的值,如果超过 1 秒,则表明您的数据访问实现存在严重错误。如果像这样的简单查询需要太长时间,那么您的整个数据驱动的网站从一开始就注定要失败,请参阅以上几点,如果一个页面对数据库进行多次单独点击,那么这些查询中的每一个也可能会受到影响表现不佳的 DAL

    即使 1 秒也很慷慨,我刚刚分析了一个更复杂的 EF 查询,返回超过 10K 行,包含 4 级包含关系数据,全部在 100 毫秒内返回,整个页面响应更接近700 毫秒,所以即使我要优化数据库查询,最好的情况是整个页面加载恢复到接近 600 毫秒,那么这样的最小改进值得付出努力吗?

    糟糕的 DAL 可能是由糟糕的代码或糟糕的 ORM 逻辑实现造成的,但是我们经常忽略已部署网站所运行的运行环境和资源。 Web 客户端、Web 服务器、DAL 和数据库之间的延迟会给您的代码带来关键的物理硬件瓶颈,如果您无法提高带宽或资源,则可能需要考虑。

    将加载路由的调用移到Application_Start 中,以明确该数据在应用程序生命周期中仅加载一次,但每个请求都需要它,因此您想及早知道它是否会失败:

    protected void Application_Start(object sender, EventArgs e)
    
        GetRewrites();
    
        RouteTable.Routes.EnableFriendlyUrls();
        ...
    
    

    重构你的逻辑以便你可以测试它

    现在,OP 和所有其他帖子都关注用于匹配路由的逻辑,但类似于加载时间,如果路由逻辑只需要 200 毫秒来评估,那么我们最多只能将每个单独的响应时间减少数量。让我们将这个逻辑重构为我们可以测试和测量的格式:

    private string oldChiave;
    protected void Application_BeginRequest(object sender, EventArgs e)
    
        String fullOriginalPath = Request.Url.ToString();
        int index = fullOriginalPath.IndexOf('/', fullOriginalPath.IndexOf(SITE_DOMAIN)) + 1;
        string chiave = fullOriginalPath.Substring(index).ToLower();
    
        if (oldChiave != chiave)
        
            oldChiave = chiave;
            if (TryGetRewritePath(chiave, out string rewriteUrl))
                Context.RewritePath(rewriteUrl);
            else if (TryGetRewritePath(chiave, out string redirectUrl))
                Context.Redirect(redirectUrl);
        
    
    
    public bool TryGetRewritePath(string chiave, out string url)
    
        Rewrite r = rewrites.Find(y => y.Chiave == chiave);
    
        if (r != null)
        
            string url = "/" + r.Pagina;
            if (r.Param1 != null)
                url += "/" + r.Param1;
            if (r.Param2 != null)
                url += "/" + r.Param2;
            if (r.Param3 != null)
                url += "/" + r.Param3;
            if (r.Param4 != null)
                url += "/" + r.Param4;
            if (r.Param5 != null)
                url += "/" + r.Param5;
            return true;
        
        return false;
    
    
    public bool TryGetRedirectPath(string chiave, out string url)
    
        //se non ho trovato la chiave all'interno delle chiavi potrebbe essere la composizione dei parametri in Param1,2,3,4,5 es /Products/Uomo/0/0/1, deve ritrasformarsi in Scarpe-Uomo
        string[] param = chiave.Split('/');
        if (param.Length == 5)
        
            Rewrite r = rewrites.Find(x => x.Pagina == param[0] &&
                    x.Param1 == param[1] &&
                    x.Param2 == param[2] &&
                    x.Param3 == param[3] &&
                    x.Param4 == param[4]);
    
            if (r != null)
            
                url = "/" + r.Chiave;
                return true;
            
        
        if (param.Length == 6)
        
            Rewrite r = rewrites.Find(x => x.Pagina == param[0] &&
                    x.Param1 == param[1] &&
                    x.Param2 == param[2] &&
                    x.Param3 == param[3] &&
                    x.Param4 == param[4] &&
                    x.Param5 == param[5]);
    
            if (r != null)
            
                url = "/" + r.Chiave;
                return true;
            
        
    
        return false;
    
    

    现在,与GetRewrites 示例一样,我们可以再次使用秒表记录每个请求的持续时间,这里我们将仅追踪信息,您可以根据需要进行调整,可能存储最大值或平均处理时间

    总的来说,您需要数据来告知您这是否是您的整体性能问题的根源。

     protected void Application_BeginRequest(object sender, EventArgs e)
     
         String fullOriginalPath = Request.Url.ToString();
         int index = fullOriginalPath.IndexOf('/', fullOriginalPath.IndexOf(SITE_DOMAIN)) + 1;
         string chiave = fullOriginalPath.Substring(index).ToLower();
    
         if (oldChiave != chiave)
         
             oldChiave = chiave;
    
             var sw = System.Diagnostics.Stopwatch.StartNew();
             if (TryGetRewritePath(chiave, out string rewriteUrl))
             
                 sw.Stop();
                 // log out the duration
                 System.Diagnostics.Trace.WriteLine($"URL Rewrite evaluated in: sw.ElapsedMillisecondsms. 'chiave' => 'rewriteUrl'");
                 Context.RewritePath(rewriteUrl);
             
             else if (TryGetRewritePath(chiave, out string redirectUrl))
             
                 sw.Stop();
                 // log out the duration (this includes the above dureation AS WELL)
                 System.Diagnostics.Trace.WriteLine($"URL Redirect evaluated in: sw.ElapsedMillisecondsms. 'chiave' => 'rewriteUrl'");
                 Context.Redirect(redirectUrl);
             
             else
             
                 sw.Stop();
                 // log out the duration (this includes the above dureation AS WELL)
                 System.Diagnostics.Trace.WriteLine($"NO REDIRECT evaluated in: sw.ElapsedMillisecondsms. 'chiave'");
             
         
     
    

    OP 请发布您的代码完成这些功能所需的时间,您可以据此决定路由处理是否是重要因素。

    Chiave 逻辑

    您的代码中另一个明显的问题是您试图根据Chiave 是否已更改来防止对路由逻辑进行多次评估。如果您的站点为路径前缀的多个值提供请求,那么我不确定这个逻辑是否完全正确。如果两个不同业务Chiave身份下的不同用户同时使用你的站点,那么每个用户都会导致oldChiave之前的值被覆盖并执行相同的逻辑。

    作为一个最小步骤,请确保 oldChiaveinstance 成员,而不是 static 成员。但我不确定这是否真的对您的问题有帮助,您可能想要实现的是以下类型的逻辑:

    On Request:
       - If the current URL has already been evaluated for redirect, use the previous result
       - Otherwise, check if we need to redirect, and if we do, cache the result for next time.
    

    像往常一样,有很多不同的代码模式可以解决这个问题,但首先重要的是知道这是否会缩短您的响应时间,如果可以,提高多少.这只能通过分析逻辑来确定。

注意这里使用了静态,我们不知道哪个应用程序实例可能正在为请求提供服务。

   static Dictionary<string, Tuple<string, string>> rewriteCache = new Dictionary<string, Tuple<string, string>>(); 
   protected void Application_BeginRequest(object sender, EventArgs e)
   
       String fullOriginalPath = Request.Url.ToString();
       int index = fullOriginalPath.IndexOf('/', fullOriginalPath.IndexOf(SITE_DOMAIN)) + 1;
       string chiave = fullOriginalPath.Substring(index).ToLower();

       if (rewriteCache.TryGetValue(chiave, out Tuple<string, string> cacheItem))
       
           if(cacheItem != null)
           
               if(cacheItem.Item1 != null) Context.RewritePath(cacheItem.Item1);
               if(cacheItem.Item2 != null) Context.Redirect(cacheItem.Item2);
           
       
       else
        
           if (TryGetRewritePath(chiave, out string rewriteUrl))
           
               rewriteCache.Add(chiave, new Tuple<string, string>(rewriteUrl, null));
               Context.RewritePath(rewriteUrl);
           
           else if (TryGetRewritePath(chiave, out string redirectUrl))
           
               rewriteCache.Add(chiave, new Tuple<string, string>(null, redirectUrl));
               Context.Redirect(redirectUrl);
           
           else
           
               // Cache the no redirect scenario
               rewriteCache.Add(chiave, null);
           
       
   

【讨论】:

【参考方案3】:

Application_BeginRequest 方法在我单击时被触发 3 次或更多次 一个链接

为了尽量减少调用 - Application_BeginRequest 会在所有请求上调用,而不仅仅是在 aspx 文件上。

您只能最小化 aspx 和处理程序文件。下面是一个关于如何做到这一点的示例:

    string sExtentionOfThisFile = System.IO.Path.GetExtension(HttpContext.Current.Request.Path);
    
if ( sExtentionOfThisFile.Equals(".aspx", StringComparison.InvariantCultureIgnoreCase) ||
     sExtentionOfThisFile.Equals(".ashx", StringComparison.InvariantCultureIgnoreCase)
)

    // run here your Rewrites

重写 = Rewrite.getRules(); //从表中读取(大约 5000 行)

重写 r = rewrites.Find(y => y.Chiave == chiave);

这是您在每次调用中进行 5000 次(可能是平均 2500 次)搜索循环的点。如果访问次数最多的页面位于列表的末尾,然后每次调用都有近 5000 个比较字符串 - 这是您遇到的主要问题。

为了在您的特定情况下更快,我建议使用Dictionary&lt;&gt; - 需要更多代码和不同的搜索方式 - 但会有所不同。

还可以使用不同的搜索方式优化该代码 - 这种方式使用 Dictionary&lt;&gt;

        r = rewrites.Find(x => x.Pagina == param[0] &&
                x.Param1 == param[1] &&
                x.Param2 == param[2] &&
                x.Param3 == param[3] &&
                x.Param4 == param[4]);

【讨论】:

您好,感谢您的回复。我有一些问题:1)尝试做第一个if。如果调用者页面本身是重写的,则扩展名是空的。 2)如果我做一本字典,直到 Rewrite r = rewrites.Find(y => y.Chiave == chiave);将替换为 Rewrite r=rewrites[chiave] 但我无法在 Where 条件下使用 Pagina 和 Param1,2 ecc 进行最后两个查找。 @Martina 也许不是 - 你需要多想并优化它 - 你可以添加额外的参数作为缓存结果 - 我不知道你有什么 - 但你可以肯定优化了很多。【参考方案4】:

我会添加 Aristos 所说的扩展验证,并且我可能还会搜索“查找”实现(使用二叉树或其他方法)以使其更快。

我认为这是问题所在。

对数据库的查询不是问题,因为它只运行一次。

另一种可能的解决方案是在更快的 redis 服务器中设置列表(尽管您需要运行一些测试来比较 redis 与 5000 项或更多项目的内存,以确保它不会过时)

【讨论】:

以上是关于ASP NET Web 表单 URL 重写太慢的主要内容,如果未能解决你的问题,请参考以下文章

IIS URL 重写与 URL 路由

使 ASP.Net 中的 URL 易于使用

在 web 表单 asp.net 中显示从 ajax Json 到 ul 标记的数据

asp.net、url 重写模块和 web.config

如何忽略asp.net表单中的路由url路由

asp.net web表单json返回结果