開發高效能的 ASP.NET 應用程式

來源:互聯網
上載者:User
開發高效能的 ASP.NET 應用程式 

使用本主題中的準則所列出的方法有助於最大程度提高 ASP.NET Web 應用程式的輸送量。這些準則分為以下部分:

  • 頁面和伺服器控制項處理

  • 狀態管理

  • 資料訪問

  • Web 應用程式

  • 編碼實踐

頁面和伺服器控制項處理

下列準則提供了有效使用 ASP.NET 頁面和控制項的建議。

  • 避免到伺服器的不必要的往返行程 在某些情況下不必使用 ASP.NET 伺服器控制項和執行回傳事件處理。例如,在 ASP.NET 網頁中驗證使用者輸入經常可在資料提交到伺服器之前在用戶端進行。通常,如果不需要將資訊傳遞到伺服器以進行驗證或將其寫入資料存放區區,請避免使用導致到伺服器的往返行程的代碼,這樣可以提高頁的效能和並改善使用者體驗。您也可以不執行整個往返行程,而是使用用戶端回調從伺服器中讀取資料。有關詳細資料,請參見在 ASP.NET 網頁中不經過回傳而實現用戶端回調。

    如果您開發自訂伺服器控制項,請考慮讓它們為支援 ECMAScript (JavaScript) 的瀏覽器呈現用戶端代碼。通過以這種方式使用伺服器控制項,您可以顯著地減少資訊被發送到 Web 服務器的次數。有關更多資訊,請參見開發自訂 ASP.NET 伺服器控制項。

  • 使用 Page 對象的 IsPostBack 屬性來避免對往返行程執行不必要的處理 如果您編寫處理伺服器控制項回傳處理的代碼,有時可能需要代碼僅在首次請求頁時執行,而不是每次回傳時都執行。根據該頁是否是響應伺服器控制項事件產生的,使用 IsPostBack 屬性有條件地執行代碼。

  • 只在必要時儲存伺服器控制項檢視狀態 自動檢視狀態管理使伺服器控制項可以在往返行程中重新填充它們的屬性值,而您不需要編寫任何代碼。但是,因為伺服器控制項的檢視狀態在隱藏的表單欄位中往返於伺服器,所以該功能影響效能。瞭解在哪些情況下檢視狀態會有所協助,在哪些情況下它影響頁的效能,這樣是有協助的。例如,如果您將伺服器控制項綁定到每個往返行程上的資料,因為控制項的值會在資料繫結期間用新值替換,所以儲存的檢視狀態沒有用處。在這種情況下,禁用檢視狀態可以節省處理時間並減少頁的大小。

    預設情況下,為所有伺服器控制項啟用檢視狀態。若要禁用它,請將控制項的 EnableViewState 屬性設定為 false,如下面的 DataGrid 伺服器控制項樣本所示:

    <asp:datagrid EnableViewState="false" datasource="..."    runat="server"/>

    您還可以使用 @ Page 指令禁用整個頁的檢視狀態。當您不從頁回傳到伺服器時,這將十分有用:

    <%@ Page EnableViewState="false" %>
    注意

    @ Control 指令中還支援 EnableViewState 屬性以指定是否為使用者控制項啟用檢視狀態。

    若要分析伺服器控制項在頁中使用的檢視狀態的大小,請通過將 trace="true" 屬性包含在 @ Page 指令中啟用對該頁的跟蹤。然後在跟蹤輸出中,查看“控制項階層”表的“Viewstate”列。有關跟蹤和如何啟用它的資訊,請參見 ASP.NET 跟蹤。

  • 除非有特殊的原因要關閉緩衝,否則使其保持開啟狀態 禁用 ASP.NET 網頁的緩衝會導致大量的效能開銷。有關更多資訊,請參見 Buffer 屬性。

  • 使用 Transfer Server 對象或跨頁發送的方法在同一個應用程式中的不同 ASP.NET 頁之間重新導向 有關詳細資料,請參見將使用者重新導向到另一頁。

狀態管理

下列準則提供了有效進行狀態管理的建議。

  • 當不使用工作階段狀態時禁用它 並不是所有的應用程式或頁都需要具體使用者的工作階段狀態;您應該在不需要時禁用工作階段狀態。若要禁用頁的工作階段狀態,請將 @ Page 指令中的 EnableSessionState 屬性設定為 false,如下面的樣本所示:

    <%@ Page EnableSessionState="false" %>
    注意

    如果頁需要訪問會話變數,但不會建立或修改它們,則將 @ Page 指令中的 EnableSessionState 屬性設定為 ReadOnly

    還可以禁用 XML Web services 方法的工作階段狀態。有關更多資訊,請參見 使用 ASP.NET 和 XML Web 服務用戶端建立的 XML Web 服務。

    若要禁用應用程式的工作階段狀態,請在應用程式的 Web.config 檔案的 SessionState 節中將 Mode 屬性設定為 Off,如下面的樣本所示:

    <sessionState mode="Off" />
  • 針對應用程式需要,選擇適當的工作階段狀態提供者 ASP.NET 為儲存應用程式的會話資料提供了多種方法:進程內工作階段狀態、作為 Windows 服務的進程外工作階段狀態和 SQL Server 資料庫中的進程外工作階段狀態。(您還可以建立自訂工作階段狀態提供者,以在所選資料存放區區中儲存會話資料。)每種方法都有自己的優點,但進程內工作階段狀態是迄今為止速度最快的解決方案。如果只在工作階段狀態中儲存少量易失資料,則建議您使用進程內提供者。進程外工作階段狀態選項用於跨多個處理器或多個電腦縮放應用程式,或者用於您希望在伺服器或進程重新啟動時保留會話資料的情況。有關更多資訊,請參見 ASP.NET 工作階段狀態。

資料訪問

下列準則提供了在應用程式中有效進行資料訪問的建議。

  • 將 SQL Server 和預存程序用於資料訪問 在 .NET Framework 提供的所有資料存取方法中,使用 SQL Server 進行資料訪問是產生高效能、可縮放 Web 應用程式的推薦選擇。使用託管 SQL Server 提供者時,可通過儘可能使用編譯的預存程序而不是 SQL 命令獲得額外的效能提高。有關使用 SQL Server 預存程序的資訊,請參見將預存程序用於命令。

  • 將 SqlDataReader 類用於快速只進資料遊標 SqlDataReader 類提供了從 SQL Server 資料庫檢索的只進資料流。如果您可以在 ASP.NET 應用程式中使用唯讀流,則 SqlDataReader 類提供比 DataSet 類更高的效能。SqlDataReader 類使用 SQL Server 的本機網路資料轉送格式從資料庫連接直接讀取資料。例如,當綁定到 SqlDataSource 控制項時,通過將 DataSourceMode 屬性設定為 DataReader,您將獲得更好的效能。(使用資料讀取器會導致某些功能的丟失。)另外,SqlDataReader 類實現 IEnumerable 介面,該介面也使您可以將資料繫結到伺服器控制項。有關更多資訊,請參見 SqlDataReader 類。有關 ASP.NET 如何訪問資料的資訊,請參見通過 ASP.NET 訪問資料。

  • 儘可能快取資料和頁輸出 ASP.NET 提供了一些機制,它們會在不需要為每個頁請求動態計算頁輸出或資料時緩衝這些頁輸出或資料。另外,通過設計要進行緩衝的頁和資料請求(特別是在網站中預期將有較大通訊量的地區),可以最佳化這些頁的效能。與使用 .NET Framework 的任何其他功能相比,適當地使用緩衝可以更好地提高網站的效能。

    在使用 ASP.NET 緩衝時,應注意以下事項。首先,不要緩衝太多項。緩衝每個項都有記憶體開銷。不要緩衝容易重新計算和很少使用的項。其次,給快取項目分配的有效期間不要太短。很快到期的項會導致緩衝中不必要的周轉,並且會導致額外的代碼清除和記憶體回收工作。使用與“ASP.NET Applications”效能物件關聯的“Cache Total Turnover Rate”(緩衝總流通率)效能計數器,您可以監視緩衝中由於項到期而導致的周轉。高周轉率可能說明存在問題,特別是當項在到期前被移除時。(這種情況有時稱作記憶體壓力。)

    有關如何快取頁面輸出和資料請求的資訊,請參見 ASP.NET 緩衝概述。

  • 適當地使用 SQL 緩衝依賴項 ASP.NET 同時支援基於表的輪詢和查詢通知,具體取決於所使用的 SQL Server 的版本。所有 SQL Server 版本都支援基於表的輪詢。在基於表的輪詢中,如果表中的任何內容發生更改,所有接聽程式都會失效。這可能導致應用程式中不必要的改動。建議不要將基於表的輪詢用於具有許多頻繁更改的表。例如,建議將基於表的輪詢用於很少更改的目錄表。建議不要將基於表的輪詢用於訂單表,訂單表具有更頻繁的更新。SQL Server 2005 支援查詢通知。查詢通知支援特定查詢,從而減少在表更改時發送的通知數量。雖然它比基於表的輪詢提供更好的效能,但是它無法擴充到適應數千個查詢。

    有關 SQL 緩衝依賴項的更多資訊,請參見演練:將 ASP.NET 輸出緩衝與 SQL Server 結合使用或使用 SqlCacheDependency 類在 ASP.NET 中緩衝。

  • 使用資料來源分頁和排序而不是 UI(使用者介面)分頁和排序 DetailsView 和 GridView 等資料控制項的 UI 分頁功能可用於支援 ICollection 介面的任何資料來源對象。對於每個分頁操作,資料控制項查詢資料來源的整個資料集並選擇要顯示的行,並放棄其餘的資料。如果資料來源實現 DataSourceView 並且 CanPage 屬性返回 true,則資料控制項將使用資料來源分頁而不是 UI 分頁。在這種情況下,資料控制項僅查詢每個分頁操作需要的行。因此,資料來源分頁比 UI 分頁更高效。只有 ObjectDataSource 資料來源控制項才支援資料來源分頁。若要在其他資料來源控制項上啟用資料來源分頁,必須從該資料來源控制項繼承並修改其行為。

  • 平衡事件驗證的安全性受益和效能開銷 從 System.Web.UI.WebControls 和 System.Web.UI.HtmlControls 類派生的控制項可以驗證事件是否源自該控制項所呈現的使用者介面。這樣有助於防止控制項響應偽造的事件通知。例如,DetailsView 控制項可以防止 Delete(刪除)調用(控制項中本質上不支援該調用)的處理以及被操縱而刪除資料。此驗證會帶來一定的效能開銷。可以使用 EnableEventValidation 配置元素和 RegisterForEventValidation 方法控制此行為。驗證的開銷取決於頁上的控制項數量,並在幾個百分點範圍內。

    安全注意

    強烈建議不要禁用事件驗證。在禁用事件驗證之前,應該確保不會構造任何可能對應用程式具有意外影響的回傳。

  • 除非必要,否則避免使用檢視狀態加密 檢視狀態加密會阻止使用者能夠讀取隱藏檢視狀態欄位中的值。典型情況是在 DataKeyNames 屬性中帶有一個標識符欄位的 GridView 控制項。標識符欄位是協調對記錄的更新所必需的。由於不想要標識符對使用者可見,可以加密檢視狀態。但是,加密對於初始化具有恒定的效能開銷,並具有取決於被加密的檢視狀態大小的附加開銷。加密為每次頁載入而設定,因此在每次頁載入時都會發生相同的效能影響。

  • 使用 SqlDataSource 緩衝、排序和篩選 如果 SqlDataSource 控制項的 DataSourceMode 屬性設定為 DataSet,則 SqlDataSource 能夠緩衝查詢產生的結果集。如果以這種方式快取資料,則控制項的篩選和排序操作(使用 FilterExpression 和 SortParameterName 屬性進行配置)將使用緩衝的資料。在許多情況下,如果緩衝整個資料集,並使用 FilterExpressionSortParameterName 屬性進行排序和篩選,而不是使用帶“WHERE”和“SORT BY”子句的 SQL 查詢(對於這些查詢,每個選擇操作都要訪問資料庫),應用程式會運行得更快。

Web 應用程式

下列準則提供了使整個 Web 應用程式有效工作的建議。

  • 如果有大型 Web 應用程式,請先行編譯它 在第一次對應用程式資源(如頁)的請求中,Web 應用程式是批編譯的。如果應用程式中的頁都沒有編譯,批編譯功能會成批編譯目錄中的所有頁,以便更好地利用磁碟和記憶體。批編譯功能為 ASP.NET 帶來效能上的好處,因為它將許多頁面編譯為單個程式集。從已載入的程式集訪問一頁比每頁載入新的程式集要快。請注意,如果將多個頁面批編譯到一個目錄超出 BatchTimeout 屬性指定的秒數,則將分析並編譯單個頁面,以便請求能被快速處理。

    批編譯的缺點在於:如果伺服器接收到許多對尚未編譯的頁面的請求,那麼當 Web 服務器分析並編譯它們時,效能可能較差。要解決此問題,可以先行編譯應用程式。有關詳細資料,請參見 ASP.NET 網站先行編譯。

  • 在 Internet 資訊服務 5.0 上,在進程外運行 Web 應用程式 預設情況下,IIS 5.0 上的 ASP.NET 將使用進程外輔助進程為請求提供服務。此功能已被最佳化以提高輸送量。由於在進程外的輔助進程中運行 ASP.NET 有其功能和優點,建議在生產網站上使用它。

  • 定期回收進程 為了同時保證穩定性和效能,應該定期回收進程。經過較長的時間,有記憶體流失和 bug 的資源可以影響 Web 服務器的輸送量,而回收進程可以清理記憶體避免這類問題。但是,應當平衡定期回收的需求和過頻的回收,因為停止輔助進程、重新載入頁面並重新擷取資源和資料的開銷可能會超過回收的好處。

    在使用 IIS 6.0 的 Windows Server 2003 上啟動並執行 ASP.NET Web 應用程式不需要調整進程模型設定,因為 ASP.NET 將使用 IIS 6.0 進程模型設定。

  • 必要時調整應用程式每個輔助進程的線程數 ASP.NET 的請求結構試圖在執行請求的線程數和可用資源之間達到一種平衡。該結構將根據可用於請求的 CPU 功率,來決定允許同時執行的請求數。這項技術稱作線程門控。但是在某些條件下,線程門控演算法不是很有效。通過使用與“ASP.NET Applications”效能物件關聯的“Pipeline Instance Count”(管線執行個體計數)效能計數器,可以在 Windows 效能監控器中監視線程門控。

    當 ASP.NET 網頁調用外部資源,如執行資料庫訪問或 XML Web services 請求時,頁面請求通常停止並釋放 CPU 以處理其他線程,直到外部資源響應為止。如果另一個請求正在等待處理,並且線程池中有一個線程釋放,則開始處理這個正在等待的請求。這可能導致 ASP.NET 輔助進程或應用程式集區中存在大量同時執行的請求和許多正在等待的線程,而它們會影響 Web 服務器的輸送量,從而對效能產生不利的影響。

    為緩解這種情況,可以通過更改 Machine.config 設定檔的 processModel 節中的 MaxWorkerThreads 和 MaxIOThreads 屬性,手動設定對進程中的線程數的限制。

    注意

    輔助線程是用來處理 ASP.NET 請求的,而 IO 線程則是用於為來自檔案、資料庫或 XML Web services 的資料提供服務的。

    分配給進程模型屬性的值是進程中每個 CPU 每類線程的最大數目。對於雙處理器電腦,最大數是設定值的兩倍。對於四處理器電腦,最大值是設定值的四倍。對於有一個或兩個處理器的電腦,預設值就可以,但對於有兩個以上處理器的電腦的效能,進程中有一百或兩百個線程則弊大於利。因為額外的上下文交換導致作業系統將 CPU 週期花在維護線程而不是處理請求上,所以進程中有太多線程往往會降低伺服器的速度。線程適當的數目最好通過應用程式的效能測試來確定。

  • 對於廣泛依賴外部資源的應用程式,請考慮在多處理器電腦上啟用網路園藝 ASP.NET 進程模型協助啟用多處理器電腦上的延展性,方法是將工作分發給多個進程(每個 CPU 一個),並且每個進程都將處理器關聯設定為一個 CPU。此技術稱為網路園藝。如果應用程式使用較慢的資料庫伺服器或調用具有外部依賴項的 COM 物件(這裡只是提及兩種可能性),則為您的應用程式啟用網路園藝是有益的。但是,在決定對生產網站啟用網路園藝之前,您應該測試應用程式在網路園中的執行情況。

  • 禁用偵錯模式 在部署生產應用程式或進行任何效能測量之前,始終禁用偵錯模式。如果啟用了偵錯模式,應用程式的效能可能受到影響。有關設定偵錯模式的文法資訊,請參見編輯 ASP.NET 設定檔。

  • 最佳化 Web 服務器電腦和特定應用程式的設定檔以符合您的需要 預設情況下,ASP.NET 配置被設定成啟用最廣泛的功能集並盡量適應最常見的情況。可更改某些預設配置設定以提高應用程式的效能,具體取決於您使用的功能。下面的列表包含您應考慮的配置設定:

    • 僅對需要的應用程式啟用身分識別驗證 預設情況下,ASP.NET 應用程式的身分識別驗證模式為 Windows 或整合的 NTLM。大多數情況下,最好僅對需要身分識別驗證的應用程式在 Machine.config 檔案中禁用身分識別驗證,並在 Web.config 檔案中啟用身分識別驗證。

    • 根據適當的請求和響應編碼設定來配置應用程式 ASP.NET 預設編碼格式為 UTF-8。如果您的應用程式僅使用 ASCII 字元,請配置您的 ASCII 應用程式以獲得稍許的效能提高。

    • 考慮對應用程式禁用 AutoEventWireup 在 Machine.config 檔案中將 AutoEventWireup 屬性設定為 false,意味著頁面不會將頁事件綁定到基於名稱匹配的方法(例如 Page_Load)。如果禁用 AutoEventWireup,頁面將通過將事件串連留給您而不是自動執行它,獲得稍許的效能提升。

      如果想要處理頁事件,可以使用兩種策略之一。第一種策略是重寫基類中的方法。例如,可以為頁載入事件重寫 Page 對象的 OnLoad 方法,而不是使用 Page_Load 方法。(務必調用基方法以確保引發所有事件。)第二種策略是使用 Visual Basic 中的 Handles 關鍵字或 C# 中的委託串連來綁定到事件。

    • 從請求處理管線中移除不用的模組 預設情況下,伺服器電腦的 Machine.config 檔案中 HttpModules 節點的所有功能均保留為活動狀態。根據應用程式所使用的功能,您可以從請求管線中移除不用的模組以獲得稍許的效能提升。檢查每個模組及其功能,並按您的需要自訂它。例如,如果您在應用程式中不使用工作階段狀態和輸出緩衝,則可以從 HttpModules 列表中移除它們,以便請求在不執行其他有意義的處理時,不必調用這些模組。

編碼實踐

下列準則提供了編寫有效代碼的建議。

  • 不要依賴代碼中的異常 異常會大大地降低效能,所以您應該避免將它們用作控制正常程式流的方式。如果有可能檢測到代碼中可能導致異常的狀態,請執行這種操作,而不要捕捉異常本身和處理該狀態。常見的代碼檢測方案包括:檢查 null,將一個值分配給將分析為數值的 String,或在應用數學運算前檢查特定值。下面的樣本示範可能導致異常的代碼以及測試是否存在某種狀態的代碼。兩者產生相同的結果。

    C# 複製代碼
    // This is not recommended.try {   result = 100 / num;}catch (Exception e) {  result = 0;}// This is preferred.if (num != 0)   result = 100 / num;else  result = 0;
    Visual Basic 複製代碼
    ' This is not recommended.Try   result = 100 / numCatch (e As Exception)  result = 0End Try' This is preferred.If Not (num = 0)   result = 100 / numElse  result = 0End If
  • 適當地使用 自動記憶體管理 注意不要對每個請求使用過多記憶體(如在記憶體中儲存大型物件或資料集),因為這樣記憶體回收行程將必須更頻繁地做更多的工作。同樣,當不再需要對象時,請不要在代碼中保留不必要的對象引用,因為在它們仍然被引用的情況下,記憶體回收行程將無法釋放資源。

    避免使用含 Finalize 方法的對象,因為它們在後面會導致更多的記憶體回收行程工作。特別是在對 Finalize 的調用中永遠不要釋放資源,因為資源在記憶體回收行程調用其 Finalize 方法之前可能一直消耗著記憶體。最後這個問題經常會對 Web 服務器環境的效能造成毀滅性的打擊,因為在等待 Finalize 運行時,很容易耗盡某個給定資源的可用性。

    有關記憶體回收行程和自動記憶體管理的更多資訊,請參見自動記憶體管理。

  • 在Managed 程式碼中重寫調用密集型的 COM 組件 .NET Framework 提供了一個簡單的方法與傳統的 COM 組件進行互動。其優點是可以在保留現有 COM 組件投資的同時利用 .NET 的功能。但是在某些情況下,保留舊組件的效能開銷使得將組件遷移到Managed 程式碼是值得的。每一情況都是不一樣的,決定是否需要遷移組件的最好方法是對網站運行效能測量。建議研究一下如何將經常調用的任何 COM 組件遷移到Managed 程式碼。

    許多情況下不可能將舊式組件遷移到Managed 程式碼,特別是在最初遷移 Web 應用程式時。在這種情況下,最大的效能障礙之一是將資料從非託管環境封送到託管環境。因此,在互動操作中,請在任何一端執行儘可能多的任務,然後進行單個調用而不是一系列小調用。例如,公用語言運行庫中的所有字串都是 Unicode 的,所以應在調用Managed 程式碼之前將組件中的所有字串轉換成 Unicode 格式。

    一旦處理完任何 COM 物件或本機資源就釋放它們。這樣,其他請求就能夠使用它們,並且最大限度地減少了因稍後請求記憶體回收行程釋放它們所引起的效能問題。

  • 避免單一執行緒 Apartment (STA) COM 組件 預設情況下,ASP.NET 不允許 STA COM 組件在頁內運行。若要運行它們,必須在 .aspx 檔案內將 ASPCompat=true 屬性包含在 @ Page 指令中。這樣就將頁執行用的線程池切換到 STA 線程池,而且使 HttpContext 和其他內建對象可用於 COM 物件。避免使用 STA COM 組件是一種效能最佳化,因為它避免了將多執行緒 Apartment (MTA) 封送到 STA 線程的任何調用。

    如果必須使用 STA COM 組件,則應避免在執行期間進行大量調用,並嘗試在每次調用期間發送儘可能多的資訊。另外,避免在構造頁面期間建立 STA COM 組件。例如在下面的代碼中,在頁面構造時將執行個體化由某個線程建立的 SampleSTAComponent,而該線程並不是運行頁面的 STA 線程。這可能對效能有不利影響,因為要構造頁面就需要在 MTA 和 STA 線程之間進行封送處理。

    <%@ Page Language="VB" ASPCompat="true" %><script runat=server>Dim myComp as new SampleSTAComponent()Public Sub Page_Load()    myComp.Name = "Sample"End Sub</script><html><%    Response.Write(Server.HtmlEncode(myComp.SayHello))%></html>

    首選機制是延遲對象的建立,直到在 STA 線程下執行上述代碼,如下面的例子所示。

    <%@ Page Language="VB" ASPCompat="true" %><script runat=server>Dim myCompPublic Sub Page_Load()    myComp = new SampleSTAComponent()    myComp.Name = "Sample"End Sub</script><html><%    Response.Write(Server.HtmlEncode(myComp.SayHello))%></html>

    推薦的做法是僅在需要時或者在 Page_Load 方法中構造 COM 組件和外部資源。

    永遠不要將 STA COM 組件儲存在可以由構造它們的線程以外的其他線程訪問的共用資源(如緩衝或工作階段狀態)裡。即使 STA 線程調用 STA COM 組件,也只有構造此 STA COM 組件的線程能夠為該調用服務,而這要求封送處理對建立者線程的調用。此封送處理可能產生重大的效能損失和延展性問題。在這種情況下,請考慮使 COM 組件成為 MTA COM 組件或在Managed 程式碼中重寫該組件。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.