3

Asp.net MVC アプリケーションには、動的に生成された Excel レポートをダウンロードするページがあります。クライアント アプリケーションは、Excel ファイルを生成する WCF サービスを呼び出し、ファイル名をクライアントに返します。WCF サービスは、OpenXML Sax Approach を使用して Excel ファイルを生成します。

サーバーはストアド プロシージャを呼び出し、データ リーダーを使用してデータを取得します。通常、ファイルには 10000 レコードが含まれます。テスト環境では、パフォーマンスの問題は発生しませんでした。本番環境で 10 人がレポートにアクセスすると、サーバー メモリが最大になり、CPU 使用率も 98% になります。このため、そのサーバー内のすべてのアプリケーションに問題が生じます。サーバーには 4GB の RAM しかありません。4 つのアプリケーションを実行しています。通常、私のアプリケーションはより多くのメモリを必要とします。

コードは次のとおりです。

public string GetMemberRosterHistoryFile(string VendorId, string versionId, DateTime FromDate, string ActionIndicator)
{
    string path = ConfigurationManager.AppSettings["FilePath"] + Guid.NewGuid() + ".xlsx";
    try
    {
        path = PathInfo.GetPath(path);
        log4net.ThreadContext.Properties["MethodName"] = "GetMemberRostersHistory";
        log.Info("Getting member rosters History");
        string sConn = ConfigurationManager.ConnectionStrings["VendorConnectContext"].ConnectionString;
        using (SqlConnection oConn = new SqlConnection(sConn))
        {
            oConn.Open();
            log.Debug("Connected");
            string sCmd = "pGetMemberRostersHistory";
            SqlCommand oCmd = new SqlCommand(sCmd, oConn);
            oCmd.CommandTimeout = Int32.MaxValue;
            oCmd.CommandType=CommandType.StoredProcedure;
            oCmd.Parameters.AddWithValue("@FromDate", FromDate.ToShortDateString());
            oCmd.Parameters.AddWithValue("@ActionIndicator", ActionIndicator);
            int index=1;
            StringBuilder programs = new StringBuilder();
            if (string.IsNullOrEmpty(versionId))
            {
                foreach (string value in GetPrograms(VendorId))
                {
                    if (index > 1)
                    {
                        programs.Append(",");
                    }
                    programs.Append(value);
                    index++;
                }
            }
            else
            {
                foreach (string value in GetPrograms(VendorId, versionId))
                {
                    if (index > 1)
                    {
                        programs.Append(",");
                    }
                    programs.Append(value);
                    index++;
                }
            }
            oCmd.Parameters.AddWithValue("@ProgramsList", programs.ToString());

            string[] FieldNames = new string[] 
                {
                        "ActionIndicator", 
                    "ChangeNotes",
                    "ActionEffectiveDate",
                    "MembershipTerminationDate",
                    "GPOId",
                    "GLN",
                    "HIN",
                    "Name1",
                    "Name2",
                    "AddressType",
                    "Address1",
                    "Address2",
                    "Address3",
                    "City",
                    "StateProvince",
                    "ZipPostalCode",
                    "Country",
                    "PhoneNbr",
                    "FaxNbr",
                    "RelationshipToGPO",
                    "RelationshipToDirectParent",
                    "DirectParentGPOId",
                    "DirectParentName1",
                    "TopParentGPOId",
                    "TopParentName1",
                    "MemberStatus",
                    "MembershipStartDate",
                    "OrganizationalStatus",
                    "ClassOfTradeName",
                    "DEA",
                    "DSHorHRSA",
                    "PHEffectiveDate",
                    "PHExpirationDate",
                    "BLPHEffectiveDate",
                    "BLPHExpirationDate",
                    "MMEffectiveDate",
                    "MMExpirationDate",
                    "BLMMEffectiveDate",
                    "BLMMExpirationDate",
                    "DIEffectiveDate",
                    "DIExpirationDate",
                    "LBEffectiveDate",
                    "LBExpirationDate",
                    "NMEffectiveDate",
                    "NMExpirationDate"
                    ,"BLMemberId"
                        ,"GPOCorporateGroup"
                        ,"GPOAffiliateGroup"
                        ,"GPO2AffiliateGroup"
                        ,"GPORelatedGroup"
                        ,"GPOIDNGroup"

                };
        string[] columnNames = new string[] 
                    {
                        "Action Indicator",
                        "Change Notes",
                        "Action Effective Date",
                        "Membership Termination Date",
                            "GPO ID",
                            "GLN",
                            "Health Industry Number (HIN)",
                            "Name 1",
                            "Name 2",
                            "Address Type",
                            "Address 1",
                            "Address 2",
                            "Address 3",
                            "City",
                            "State/Province",
                            "Postal Code",
                            "Country",
                            "Phone",
                            "Fax",
                            "Relationship to GPO",
                            "Relationship to Direct Parent",
                            "Direct Parent GPO ID",
                            "Direct Parent Name 1",
                            "Top Parent GPO ID",
                            "Top Parent Name 1",
                            "Member Status",
                            "Membership Start Date",
                            "Organizational Status",
                            "Class of Trade",
                            "DEA #",
                            "DSH and/or HRSA Number",
                            "Pharmacy Start Date",
                            "Pharmacy End Date",
                            "BL Pharmacy Start Date",
                            "BL Pharmacy End Date",
                            "Med Surg Start Date",
                            "Med Surg End Date",
                            "BL Med Surg Start Date",
                            "BL Med Surg End Date",
                            "Food Service Start Date",
                            "Food Service End Date",
                            "Laboratory Start Date",
                            "Laboratory End Date",
                            "NonMedical Start Date",
                            "NonMedical End Date"
                            ,"Broadlane ID"
                            ,"Corporate Group"
                            ,"Affiliate Group"
                            ,"2nd Affiliate Group"
                            ,"Related Group"
                        ,"IDN Group"
                    };
            //object result = oCmd.ExecuteScalar();
            //int count=(result!=null ? (int)result : 0);
            //oCmd.CommandText = "pGetMemberRostersHistory";
            //oCmd.CommandTimeout = Int32.MaxValue;
            using (SqlDataReader oReader = oCmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
                SAXExcelExporter exporter = new SAXExcelExporter();
                exporter.Export(oReader, columnNames, FieldNames, path, "MemberRoster");

            }
        }
        return path;
    }
    catch (Exception ex)
    {
        log.Error("In exception", ex);
        return null;
    }
}

輸出コード:

public void Export(SqlDataReader export, string[] columnNames, string[] fieldNames, string filename, string sheetName)
{
    Assembly _assembly = Assembly.GetExecutingAssembly();
    Stream stream = _assembly.GetManifestResourceStream("MA.VMS.Server.Template.xlsx");
    FileStream newfile = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
    stream.CopyTo(newfile);
    stream.Close();
    newfile.Close();
    using (SpreadsheetDocument myDoc = SpreadsheetDocument.Open(filename, true))
    {
        WorkbookPart workbookPart = myDoc.WorkbookPart;
        WorksheetPart worksheetPart = workbookPart.WorksheetParts.Last();
        string origninalSheetId = workbookPart.GetIdOfPart(worksheetPart);

        WorksheetPart replacementPart = workbookPart.AddNewPart<WorksheetPart>();
        string replacementPartId = workbookPart.GetIdOfPart(replacementPart);

        OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);
        OpenXmlWriter writer = OpenXmlWriter.Create(replacementPart);

        while (reader.Read())
        {
            if (reader.ElementType == typeof(SheetData))
            {
                if (reader.IsEndElement)
                    continue;
                writer.WriteStartElement(new SheetData());
    Row hr = new Row();
                writer.WriteStartElement(hr);

                for (int col = 0; col < columnNames.Length; col++)
                {
                    Cell c = new Cell();
                    c.DataType = CellValues.InlineString;
                    InlineString iss = new InlineString();
                    iss.AppendChild(new Text() { Text = columnNames[col] });
                    c.AppendChild(iss);
                    writer.WriteElement(c);
                }
    writer.WriteEndElement();

                //for (int row = -1; row < count; row++)
                while (export.Read())
                {

                    Row r = new Row();
                    writer.WriteStartElement(r);
                    //if (row == -1)
                    //{
                    //    for (int col = 0; col < columnNames.Length; col++)
                    //    {
                    //        Cell c = new Cell();
                    //        c.DataType = CellValues.InlineString;
                    //        InlineString iss = new InlineString();
                    //        iss.AppendChild(new Text() { Text = columnNames[col] });
                    //        c.AppendChild(iss);
                    //        writer.WriteElement(c);
                    //    }
                    //}
                    //else
                    //{
                        //export.Read();
                        for (int col = 0; col < fieldNames.Length; col++)
                        {
                            Cell c = new Cell();
                            c.DataType = CellValues.InlineString;
                            InlineString iss = new InlineString();
                            iss.AppendChild(new Text() { Text = GetValue(export[fieldNames[col]]) });

                            c.AppendChild(iss);
                            writer.WriteElement(c);
                        }
                    //}
                    writer.WriteEndElement();

                }

                writer.WriteEndElement();
            }
            else
            {
                if (reader.IsStartElement)
                {
                    writer.WriteStartElement(reader);
                }
                else if (reader.IsEndElement)
                {
                    writer.WriteEndElement();
                }
            }
        }

        reader.Close();
        writer.Close();

        Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().Where(s => s.Id.Value.Equals(origninalSheetId)).First();
        sheet.Id.Value = replacementPartId;
        workbookPart.DeletePart(worksheetPart);

    }
}

私が心配しています。proc を見ると、データが 26 秒で返され、Excel のダウンロードに 3 分以上かかります。

このシナリオについてどうすればよいですか? 私が考えている解決策は次のとおりです。

  1. Excel ダウンロードを非同期に移動し、ダウンロードへのリンクを送信します
  2. 2 つのアプリケーションを異なるサーバーにデプロイします。
  3. サーバーでメモリプロファイラーを実行する
4

3 に答える 3

1

おそらく問題は、Open XML SDK クラスと SAX メソッド クラス (特に OpenXmlWriter) の混合使用です。SDK には多くの DOM バゲージが含まれているため、速度が低下します。

この特定のケースでは、Cell クラスです。Worksheet、SheetData、および Row SDK クラス全体が OpenXmlWriter で書き出されましたが、Cell クラスは引き続き SDK バージョンのデータ入力を使用します。これがボトルネックです。これを試して:

List<OpenXmlAttribute> oxa;
for (int col = 0; col < fieldNames.Length; col++)
{
    oxa = new List<OpenXmlAttribute>();
    // this is the data type ("t"), with CellValues.String ("str")
    oxa.Add(new OpenXmlAttribute("t", null, "str"));

    // it's suggested you also have the cell reference, but
    // you'll have to calculate the correct cell reference yourself.
    // Here's an example:
    //oxa.Add(new OpenXmlAttribute("r", null, "A1"));

    writer.WriteStartElement(new Cell(), oxa);

    writer.WriteElement(new CellValue(GetValue(export[fieldNames[col]])));

    // this is for Cell
    writer.WriteEndElement();
}

また、CellValues.InlineString はインライン リッチ テキスト用です。プレーン テキストしかない場合は、CellValues.String 列挙値の方が簡単です。

私も少し前に記事を書きました。詳細については、こちらをご覧ください

于 2013-07-22T03:13:39.457 に答える
0

WCF に使用しているシリアライザーは何ですか。デフォルトでは、おそらく XML でシリアライズしています。私は、Microsoft Sync Services を使用して大量のデータをクライアントに送信する同様のプロジェクトをリードしていました。これには大量の Xml データが含まれていたため、サーバーとクライアントでのシリアル化と逆シリアル化が非常に遅くなりました。これは、返されるエンティティに含まれるすべての xml をサーバーでエスケープし、クライアントでエスケープ解除する必要があるためです。これは、大きなデータ チャンクで非常に低速でした。

これを回避するために、バイナリシリアライザーを使用しましたが、これはサーバー側の修正だけではなく、クライアントも変更する必要があります。これは、接続する唯一のクライアントが .Net クライアントであることを前提としています。

文字列を返す場合、WCF 応答は次のようになります。

<GetMemberRosterHistoryFile>
    <MethodParameters>
        <String>
            &lt;WordDocXml&gt;
                &lt;SomeXmlElement someAttribute=&quot;foo&quot; /&gt;
            &lt;/WordDoxXml&gt;
        </String>
    </MethodParameters>
</GetMemberRosterHistoryFile>

私の例ではhtmlエンコーディングを使用しましたが、これはWCFが行うと思いますが、それがポイントを示しています。.Net が大きなドキュメントに対してこれを行う必要がある場合を想像してみてください。

WCF Xml とバイナリ シリアル化の長所と短所

SOAP メッセージとしてではなく、バイナリ形式で WCF メッセージをシリアル化する

于 2013-07-22T09:08:41.857 に答える
0

それらはすべて良いアイデアです。ただし、パフォーマンスのチューニングについては、さらに検討します。

あなたは4GBのメモリを持っていると言います。1GB がすべてのオーバーヘッドで失われ、サーバー上の他のアプリケーションがアイドル状態になっているとします。したがって、10 件のレポートに対して 3GB が必要になります。それはそれぞれ300MBです。あなたのレポートはそのくらいの大きさですか?出力が 100MB であるとすると、切り取る必要のあるさまざまな内部表現を使用すると、簡単に 300MB に到達します。しかし、10MB しか生成していない場合は、いくつかの深刻なパフォーマンスの問題を解決する必要があります。

あなたはこれがすでに生産されていると言っています。したがって、簡単な応急処置は、このアプリケーションに大量の RAM を搭載した独自のサーバーを用意することです。これにより、メモリが大量に消費されている理由を突き止めるための時間が少し得られるはずです。

長期的な解決策として、あなたが提案したように、これを非同期プロセスにしたいと思います。数秒以上は非同期にする必要があると思います。クライアントがレポートのリクエストを行い、フロント エンドがこのリクエストをキューに入れます。一部のバックグラウンド ワーカーは、キューからこの要求を取得して処理します。完了すると、クライアントは戻ってきてダウンロードできます。

これにより、同時リクエストの数をより適切に制御できます。RAM が不足しないように、またはデータベースに過負荷がかからないように、これを調整する必要があります。確かに顧客はせっかちになりますが、アプリケーションがメモリを求めてスラッシングしている間、自分のシステムが停止するよりはましです。

于 2013-07-20T18:37:48.460 に答える