我们的网站遇到了与高 CPU 使用率相关的性能问题。 使用探查器时,我们已经确定了一个需要 ~35 秒才能返回的特定方法。
这是使用名为SagePay的支付网关时的回调方法。
我已经复制了下面此调用中的两个方法:
public void SagePayNotificationReturn()
{
string vendorTxCode = Request.Form["vendortxcode"];
var sagePayTransaction = this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode);
if (sagePayTransaction == null)
{
// Cannot find the order, so log an error and return error response
int errorId = this.exceptionManager.LogException(System.Web.HttpContext.Current.Request, new Exception(string.Format("Could not find SagePay transaction for order {0}.", vendorTxCode)));
ReturnResponse(System.Web.HttpContext.Current, StatusEnum.ERROR, string.Format("{0}home/error/{1}", GlobalSettings.SiteURL, errorId), string.Format("Received notification for {0} but the transaction was not found.", vendorTxCode));
}
else
{
// Store the response and respond immediately to SagePay
sagePayTransaction.NotificationValues = sagePayTransactionManager.FormValuesToQueryString(Request.Form);
this.sagePayTransactionManager.Save(sagePayTransaction);
ReturnResponse(System.Web.HttpContext.Current, StatusEnum.OK, string.Format("{0}payment/processtransaction/{1}", GlobalSettings.SiteURL, vendorTxCode), string.Empty);
}
}
private void ReturnResponse(HttpContext context, StatusEnum status, string redirectUrl, string statusDetail)
{
context.Response.Clear();
context.Response.ContentEncoding = Encoding.UTF8;
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream))
{
streamWriter.WriteLine(string.Concat("Status=", status.ToString()));
streamWriter.WriteLine(string.Concat("RedirectURL=", redirectUrl));
streamWriter.WriteLine(string.Concat("StatusDetail=", HttpUtility.HtmlEncode(statusDetail)));
streamWriter.Flush();
streamWriter.Close();
}
context.ApplicationInstance.CompleteRequest();
}
GetTransactionByVendorTxCode 方法是一个简单的实体框架调用,所以我排除了它。
是否有人在这方面有任何经验,或者他们能否看到可能导致此类问题的代码存在任何明显错误?
编辑:查看分析器提供的细分表,它说99.6%的时间花在System.Web.Mvc.MvcHandler.BeginProcessRequest()上。
编辑:使用分析工具New Relic,它说所有处理时间的22%都花在this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode)方法上。 这只是包含对存储库的 EF6 调用。 不过,该调用确实包含谓词参数,而不是预定义的条件。 可能是查询没有预编译吗?
这是我获得解决方案的第一步:
在此语句之前放入计时器开始,然后在完成时停止它。 告诉我们时间跨度。
var sagePayTransaction = this.sagePayTransactionManager.GetTransactionByVendorTxCode(vendorTxCode);
为此代码块放入另一个计时器:告诉我们与上述方法相比的相对时间。
using (StreamWriter streamWriter = new StreamWriter(context.Response.OutputStream))
{
streamWriter.WriteLine(string.Concat("Status=", status.ToString()));
streamWriter.WriteLine(string.Concat("RedirectURL=", redirectUrl));
streamWriter.WriteLine(string.Concat("StatusDetail=", HttpUtility.HtmlEncode(statusDetail)));
streamWriter.Flush();
streamWriter.Close();
}
最后在这里放另一个计时器:
context.ApplicationInstance.CompleteRequest();
将信息发回给我们,我将指导您进行下一步。 我们上面所做的是获取跨越本地和远程访问的指标,以查找主要问题。 我们将首先选择它,然后在需要时进一步取得进展。 只需告诉我们测量值是什么。
这里您可能需要考虑许多事项。
如果 GetTransactionByVendorTxCode 负责总处理时间的 22%,那么您将需要简化该方法涉及的所有内容,但随后仍需要继续在整个处理管道中捕获其他瓶颈。
您说该方法抽象了对 EF6 的调用,并且它传入了一个谓词表达式,该表达式在 Where 子句中用于构建最终查询。
如果查询很复杂,您是否考虑过委托给存储过程?由于您要返回实体,因此可以将其挂在 DbSet 上。 (在 DTO 的情况下,它将挂起 DbContext 的数据库属性)。
此外,您还需要查看谓词中使用的列上的索引。当前记录计数是多少? 您的查询是否会导致搜索或扫描? 您需要查看生成的查询计划;如果使用 SQL Server,请运行查询数据库引擎优化顾问。
也许有关您当前设置的更多详细信息将有助于提供更好的指导。