问题描述
我有一个需要 Url 重写规则的网站 (SITE_DOMAIN)。 该站点采用 asp.net Web 形式。
例如 SITE_DOMAIN/abbigliamento/donna/jeans 是 SITE_DOMAIN/Products/Donna/0/42/1
在 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");
}
这个重写是固定的,所以它们不需要存储在以前的表中。
解决方法
由于您对第三方解决方案持开放态度(“我可以使用任何其他方法或我可以使用任何由 3 部分组成的 dll?”),您可以考虑以下内容。
- 选项 1:由于您的网站是使用 Microsoft 技术堆栈构建的,我假设您将在 IIS 网络服务器上部署该解决方案。如果是这种情况,您可以使用 IIS 上的重写模块扩展来完成所有重写。您的规则将保存在 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”。在 IIS 规则映射对话框中,规则映射中的“原始值”将是“/product-details”,而“新值”将是“/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)
我自己没有使用过上述任何软件产品,因此,我无法告诉您它们的工作情况。风险自负。
,正如其他答案所建议的那样,有不同的 MiddleWare 选项可用或 Url-Rewrite 模块可以应用于您正在运行的应用程序之外,但是如果您不了解实际的瓶颈在哪里,您如何确定任何这方面的改变会有效吗?
这里有多个复合问题。
-
网站速度太慢
您对太慢的定义是什么?您希望达到什么响应时间?
- 虽然复杂的路由逻辑看起来像一个主要候选者,但即使它看起来效率低下,最坏的情况也应该只为每个响应增加几百毫秒。
- 知道每个页面请求(取决于其中的架构和代码)通常会发出多个请求,有时是 10 或 100 个单独的内容请求,问题可能不是路由,而是服务的问题每个方面的内容,或者可能只是瓶颈所在的一些请求。
- 使用浏览器中的开发工具来确定页面生命周期中哪些单个请求执行时间最长,缓慢响应可能不会 是一个重要因素。
-
加载路线列表
虽然我们假设的数据库调用被加载到静态变量中,但代码模式是不明确的,而不是“根据请求”执行这个,它应该被强制在
Application_Start
中进行一次调用。但是不要忽视确保此调用有效的重要性,如果这个简单的数据库查找需要很长时间才能响应,那么我们可以安全地假设对数据库的所有其他查询也会很慢,这是最有可能的响应时间的因素。-
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.ElapsedMilliseconds}ms. '{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.ElapsedMilliseconds}ms. '{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.ElapsedMilliseconds}ms. '{chiave}'"); } } }
OP 请发布您的代码完成这些功能所需的时间,您可以从中决定路由处理是否是重要因素。
-
Chiave 逻辑
您代码中的另一个明显问题是,您试图阻止根据
Chiave
是否已更改对路由逻辑进行多次评估。如果您的站点处理对路径前缀的多个值的请求,那么我不确定此逻辑是否完全正确。如果在不同业务Chiave
身份下运营的两个不同用户同时使用您的网站,那么每个用户都会导致oldChiave
的先前值被覆盖并执行相同的逻辑。作为最小步骤,请确保
oldChiave
是 instance 成员,而不是 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,我们不知道哪个应用程序实例可能正在为请求提供服务。
static Dictionary<string,Tuple<string,string>> rewriteCache = new Dictionary<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,string>(null,redirectUrl));
Context.Redirect(redirectUrl);
}
else
{
// Cache the no redirect scenario
rewriteCache.Add(chiave,null);
}
}
}
,
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
}
rewrites = Rewrite.getRules(); //从表中读取(大约 5000 行)
重写 r = rewrites.Find(y => y.Chiave == chiave);
这是您对每次调用进行 5000 次搜索循环(可能平均为 2500 次)的点。如果访问量最大的页面位于列表末尾,则进行成像,那么每次调用时都会有近 5000 个比较字符串 - 这就是您遇到的主要问题。
为了在您的特定情况下更快,我建议使用 Dictionary<>
- 需要更多的代码和不同的搜索方式 - 但会有所不同。
还使用不同的搜索方式优化该代码 - 这种方式使用 Dictionary<>
r = rewrites.Find(x => x.Pagina == param[0] &&
x.Param1 == param[1] &&
x.Param2 == param[2] &&
x.Param3 == param[3] &&
x.Param4 == param[4]);
,
我会添加 Aristos 所说的扩展验证,我也可能会搜索“查找”实现(使用二叉树或其他方法)以使其更快。
我认为这就是问题所在。
对数据库的查询不是问题,因为它只运行一次。
另一种可能的解决方案是在速度更快的 redis 服务器中设置列表(尽管您需要运行一些测试来比较 redis 与 5000 项或更多项的内存以使其面向未来)