背景
最近跟售后經理吃飯,他跟我再次談起兩年前為公司臨時寫的一個客戶端,仍然非常激動的跟我說,這個客戶端完爆了公司其他版本的客戶端,包括最老的Delphi寫的,Asp.Net寫的,以及最新的Wpf寫的客戶端。無論是多么大的界面(集成的機房多),這個系統都是瞬間打開,而且運行非常穩定,一旦成功部署之后基本沒有任何問題。
這個版本的客戶端僅僅只是一個臨時替代的版本:原來的Delphi客戶端實在是太慢了,在大型的數據中心監控中需要4~5分鐘才能進入主監控界面,而版本的客戶端又經常存在不穩定的情況(IE瀏覽器不支持7*24小時的異步刷新),最新的Wpf客戶端又還在設計階段,于是臨危受命決定開發一個臨時過渡版本,當時也只是開發了一個月,沒想到竟然如此成功,至今仍讓我們的售后部門津津樂道。這中間其實沒有太多高深的技術,但是卻有很多的開發技巧以及編程的思想。我至今仍然看到很多人都在犯這么一些簡單的錯誤(例如VS2010工具箱的加載項),導致他們的系統非常緩慢,但是他們卻總是抱怨是編程語言的問題,是windows系統的問題,是機器的性能不行……
我決定把我的一些實踐經驗跟大家分享:不是非得你有多么牛逼的技術,才能做出一個穩定快速的系統,更多的時候,它取決于你是否有一個產品的意識,是否讓你的軟件真正貼近用戶。
系統界面與功能
先來看看原來的系統界面是怎樣子的:
其功能如下,我新寫的客戶端增加了支持生成OCX控件的功能:
整個系統的物理架構是這樣的:
原系統存在的問題
頁面之間切換卡
數據顯示慢
地點的報警狀態顯示不準確且存在延遲
報警并發較多時卡頓更嚴重
客戶端性能優化的基本手法
我們來看看通過一些什么手法能夠解決原來的系統存在的這些問題。
按需獲取
大部分的情況下,我們其實所能看到的東西都是極其有限的,無論系統是多么龐大,功能多么的豐富,其實呈現給用戶的都是極其有限的。
監控界面的按需獲取
前面說了,監控主界面里的界面都是組態的,是由工程師拖拉控件上去實現的,大家也看到上面圖形還算豐富,主要是使用了大量的圖片,因此我們系統中在保存這些組態界面的時候,同時也保存了界面圖片的字節流。大型的數據中心由于界面較多,這些界面加起來是可能會超過1G大小的。這么大的界面,如果都是直接加載到界面中,首先就要費不少的時間,即使是在內網的情況下,假設你網絡能夠1s下載20M左右,也要50秒,接近1分鐘,遇上網絡高峰,花個1~2分鐘并不奇怪。
我們是否有必要把所有界面都加載進來呢,當然沒有。我們只需加載第一個界面,其他界面在需要的時候(用戶點擊或者發生告警需要跳轉的時候)才加載,這樣我們的速度里面就提升了,這就是按需加載!
當然說的輕巧,實際做的會有很多問題。比如,如何實現不實現頁面又能知道該頁面是否告警(必須解析每個界面上的控件,才能知道某個界面包含了哪些控件,才知道監控指標告警在哪個界面上)?
我的步驟如下:
按需刷新界面上的數據
做監控系統,除了告警頁面必須實時通知到客戶外,監控數據界面,其實只需展示當前顯示頁面的數據即可。
怎么做呢,我們可以提供一個單獨的程序來管理所有接收到的數據,然后再提供一個獲取當前數據的接口給客戶端,具體請看下面更改的架構。
有些人可能會疑問,為什么不直接在采集器中提供這個接口呢?因為這是組態界面,界面上的控件要取哪個采集器的數據是未知的,所以把數據放在一起統一管理會更加方便。而且采集器可以7*24小時工作,而客戶端是經常要打開關閉的……
VS2010中的反例
如果用過VS2010開發自定義的Winform組件,那么大家對它的工具箱加載自定義組件這個功能肯定印象深刻,每次選擇添加項,然后選擇自定義控件dll的時候,都非常痛苦,尤其我電腦比較忙而又裝了不少插件的情況下,為了一個非常簡單的功能,我需要花費4分多的時間來打開那個選擇文件的界面,這個界面加載了一大堆我絕大多數時候都用不上的COM組件,我實在沒法想象開發這個功能的程序猿是怎么想的。還好,在VS2013中微軟總算是改進了這個功能,但是做得還不夠。按我的想法,完全可以把COM組件部分異步加載,給出正在加載的提示即可,可以立即顯示“選擇”按鈕,這樣體驗性立即上升了一個層次。
延遲加載
延遲加載是指用到的時候,再去進行實際的構建。
樹形菜單的延遲加載
樹形菜單的樹形節點的構建就是一個最適合解釋的例子。大家可以嘗試加載1000個樹形節點然后構建成一棵樹,看看在Winform中需要多長的時間。我們的實際中有沒有必要這么去做呢?
各位可以思考下自己查看樹形導航的時候,是不是從根節點到子節點最后到葉子節點這樣一步步看下去的,大部分的時候,其實我們只需首先看到根節點即可。例如下面這個:
對于這種情況,我們完全可以把樹形節點都獲取,但是先只創建只有根節點的一棵樹,在用戶點擊之后加載子節點,如果已判斷過,則不執行加載的操作?;镜姆椒ㄊ窃赥ag中附加一個字段指示子節點是否已經加載,參考代碼如下:
private void lect(object sender, rgs e)
var myNode = e.Node.Tag as NTier.Model.;
if (myNode == null) return;
if (myNode. == false)
//還沒有加載數據,主要是指機房節點
rm(e.Node, myNode); //加載樹形子節點
//已加載了數據,則生成相應的界面
(myNode);
這里延遲加載與按需加載有點類似,區別是,延遲加載必須把所有數據加載進來,但是并不構建成一棵UI樹,而是在用到的時候再去生成。
右鍵延遲初始化
另一個地方就是每個控件的右鍵菜單。因為每個右鍵菜單顯示的內容是需要根據控件的類型以及相關的權限來判斷的,但是我們看到右鍵菜單的時候一定是人為進行操作才能顯示出來,因此沒有必要再界面生成的過程中去為每個控件生成對應的右鍵菜單,而是在彈出右鍵菜單時進行相關的判斷,延遲右鍵菜單的生成。
化曲為直
我們知道,如果要查看一棵樹的所有節點,常用的方法就是使用遞歸進行廣度遍歷或者深度遍歷。但是,在樹形節點較多的時候,遍歷其實是非常耗時的。在我們這個系統中,告警是必須要最先處理的,因此,我在系統中使用類型緩存了每個屬性節點與它相關聯的數據類型(ID),從而能夠在發生告警時馬上定位到指定樹形節點。
緩存,還是緩存
緩存界面
我們系統是組態的界面,這就限制了界面的生成必須是動態的。如果我們采用按需加載的方式,那么界面的生成就是實時的,怎么能夠做到快速的進行頁面的切換呢?
var = .(this, , myNode.AgentBm); //創建Panel
在這里,我專門寫了一個界面的緩存類,如果沒有緩存,則動態創建,如果有緩存,就直接返回緩存的界面。同時,根據界面的最新的打開時間和點擊次數,對緩存的界面進行管理。我們知道,整個大型系統中,其實用戶關注的界面也是有限的,一般他們只會關注最重要的幾個界面,最常用的也是這幾個界面。通過緩存的管理,不但能夠實現界面之間的快速切換,同時也減少了系統占用的內存。我整個客戶端程序文件大小壓縮之后在500k之內,而運行期間占用內存基本維持在50M左右。
緩存數據
查看上面改造過后的架構,我們知道現在獲取數據是在打開界面之后再去獲取,直到建立連接并取得數據之后,才能在界面上顯示,這個過程一般會耗時1~2秒,網絡差的情況會更糟。怎樣才能讓用戶更為快速的確定我們的系統已經運行了呢?這里我們通過一個簡單的辦法,集中服務端通過定時把當前監控到的數據寫入控件的屬性中,在系統加載控件的同時把這個值顯示出來,這樣可以看起來好像是系統馬上獲取到了數據。而由于緩存的值是定時把最新值寫入進去的,這種做法在很大程度上保證了緩存中的數值是正確的。
異步,還是異步
異步是提高程序響應和用戶體驗的不二法寶。C#中的控件和大部分流操作類等都提供了支持異步操作的方法:和EndXXX.它的原理也非常簡單,使用時,把操作加入線程池,執行完成之后調用一個回調函數。
一個用戶體驗良好的系統,應該能夠合理的使用異步操作,確保執行UI更新時以及執行耗時的操作時不會阻塞。大部分人在寫代碼的時候,總是直接進行調用,在控件較少或者完成簡單任務的時候,你一般都感覺不出來,但是在控件數量多的時候,我們很容易就感覺到界面卡,不流暢。
我在新系統開發的時候,就有意識的在控件加載、控件數據刷新、控件告警狀態切換等操作中使用了異步的操作,讓系統在打開界面時完全感覺不到卡的跡象。
不過使用異步要時刻記得,異步可以提高用戶體驗性,但是不會有性能上的實質提升,如果感覺到數據響應有延遲,你還是得花功夫找到根本的原因。
歸并處理
界面數據刷新歸并處理
我們來看看原來界面是怎么刷新數據的:
打開界面->刷新數據->新建一個線程->定時刷新數據->關閉界面->關閉線程。
對Windows系統有足夠了解的人都知道,新開一個線程都是非常耗費資源的。這種情況,我們是可以在整個系統中,提供一個統一管理的刷新線程,只需對當前需要刷新的界面進行刷新即可:
刷新線程->判斷當前界面是否存在->定時刷新數據
結合上述的異步操作,我們的控件在刷新數據的時候非常的流暢。
告警跳轉歸并處理
上面我們提到了,在系統發生告警時,必須要跳轉到報警的頁面,這個機制在大量告警并發的時候,就會有非常大的問題,很可能我們的系統就會在不同的界面中進行跳轉而卡死。對于系統的用戶來說,在1~3秒內的多個告警,我們其實可以處理為一個告警,我們只需往最后一個告警發生的頁面跳轉即可,這樣既達到了相應的效果,也減少了系統的壓力。這就是告警并發時的歸并處理。
視覺欺騙
在一些情況中,我們確實短時間沒有辦法對性能進行提升了,花費的時間卻要要這么多,這種情況下,我們有些什么好的做法呢?
給出提示信息或者進度條
如果大家經常用手機登陸微博、微信等,肯定對這些app加載圖片有過一些體會,尤其如果你是在網絡較差的情況下,同樣是要等1分鐘才能加載出圖片,如果這個app沒有任何提示,那么,過了30秒或者20秒,你就有可能受不了把他點掉了,因為你感覺它似乎已經過了幾分鐘,還有可能遙遙無期;而如果這個app能夠提示當前下載的字節數、當前下載的進度,那么,1分鐘的等待,你似乎也能接受,這畢竟是網絡引起的問題。這就是一種視覺上的欺騙。
在一個系統的加載過程中,有提示信息和沒提示信息,有進度條和沒進度條,給人感覺的速度是不一樣的,即使從實際的情況來看這兩者沒有任何差別。
偷偷加載
很多時候,我們系統的運行需要從服務器中獲取一些最新的數據,以支撐基本的運行。這部分時間是你必不可少的,很多人都認為這是沒有任何辦法優化的,其實不然。我們很多程序其實都提供了一個用戶名和密碼的輸入框,其實在用戶輸入的過程中,我們還是可以利用的。在彈出登陸窗到輸入賬號和密碼到登入系統的過程中,一般都會有3~5秒的時間。
我看到很多人寫程序,彈出登錄框就老老實實的彈出,然后在輸入完用戶名和密碼之后在進行數據的獲取和加載,實際上,我們已經浪費了這些時間。如果你能有效利用這3~5秒,那么,你就已經贏在了起跑線。
簡化數據
視覺欺騙的另外一個重要運用,就是在曲線的渲染中。在機房監控中,我們有些設備的監控比較頻繁,一天產生的數據高達幾萬條,把這么多的數據繪制到一條24小時的曲線上,我們將會看到很多密密麻麻的點,繪制這些點非常的耗時和耗資源。而我們提供曲線給用戶查看的目的是什么呢,是想查看一天的趨勢變化,過多密集的點其實是沒有必要的,大家看看下圖,如果數據點更多的話,第二個曲線會更加密集,看起來會像一條粗大的直線:
通過簡單算法對曲線進行壓縮之后,顯示歷史趨勢的速度非常的快,非常的流暢。我們對比上面兩條曲線,其實對用戶來說,或許更喜歡第一條曲線,因為他反應的趨勢更為優美,有木有?
使用單元測試輔助開發
在我的博文中,我一直強調使用單元測試,無論是開發還是重構。我覺得這個無論是怎么強調都不為過的。
在開發的過程,我們應該有意識的按單元測試的目的來構建我們的函數、類、以及程序集,如果你的函數符合單元測試要求的話,一般都是比較容易重構和維護的。另外,我們開發的過程中,很多時候需要驗證某個功能是否可用,使用單元測試,將會很快速的幫你完成這個驗證操作。我看我們很多程序員開發效率都不高,尤其在開發一個大型系統的時候,喜歡把整個系統開起來調試,或者是在系統里面加上各種配置或者條件編譯來進行調試,這種習慣非常不好。在程序中加入配置容易讓程序結構出現混亂,代碼的閱讀體驗也不好,很多時候如果我們忘記去掉這個配置,很容易就對發布的系統產生較大的影響。
使用單元測試另外一個好處是,我們可以隨時針對某個方法進行性能上的測試,發現哪些代碼對我們的系統造成了較大的影響。我習慣連私有的函數也一起加入測試,以下是調用私有函數的一個輔助方法:
public static object (Type type, string , object , object[] @params)
const flags = . | . | .Static;
var = type.(, flags);
var result = .Invoke(, @params);
return result;
提供完善的日志信息
在日常的開發中,我一直跟我的同事強調日志的重要性。相信有一定開發經驗的都知道在系統中寫日志,但是,怎么把日志寫好,很多人都把握不了。在這里我提幾點建議:
之前我們的系統是自己實現的日志組件,我用C#重寫時,引入了NLog日志組件,我覺得這個日志組件非常好用。另外,我還專門提供了一個UI界面的調試窗,以便實施工程師在現場調試的時候能夠快速定位問題。
在實際運行的過程中,因為有良好的日志信息,我很快能夠排查很多的問題,而大部分的問題都是因為配置導致的。我一致跟研發的同事強調,盡可能的不要相信現場工程師給你的判斷,應該要現場工程師提供證據給你,而要提供什么樣的證據,作為一個研發,你才是最清楚的。好的日志系統應該能夠根據日志信息精確的定位到問題,在離線的情況下能夠最大程度的反應當前系統的配置、運行狀態、以及錯誤信息。
優化的結果
最終用C#重寫的客戶端在各方面變現都非常的好,系統非常穩定,整個系統進入在2s左右,頁面切換在1s左右,最重要的是,客戶端跟系統的大小沒有關系,適應大小的數據中心。我們看看新老系統在加載過程中的一個對比:
很明顯,通過上述手法進行一些優化之后,我們的系統在各個步驟都有了提升,而且通過異步、緩存、欺騙等方式讓一些步驟可以同步進行,大大加快了系統的加載和相應。
總結
我希望通過這篇文章,把客戶端優化的一些方法分享出來,供大家參考。這其中沒有什么高深的知識,也沒有說要你必須采用怎樣的編程語言,僅僅是通過一些簡單的手法,并綜合應用,就能把一個系統的響應速度從4分鐘提升到只需兩秒。當然,我們還有其他很多的方法,比如分布式……無論是什么樣的技巧,我覺得有一些基本的原則是要遵循的:
回顧一下這篇文章講的內容:
程序穩定性
如何升級手機系統版本
一、進入設置 > 系統和更新,點擊軟件更新。
二、當系統檢測到新版本時,您可以手動下載升級包更新版本,或通過夜間升級功能更新版本:
1、手動下載升級包:點擊新版本 > 下載并安裝 。下載升級包會消耗一定的流量,建議您在 WLAN 環境下進行操作。
升級完成后,手機會自動重啟。
如何更新系統最新版本
如果您使用的是華為手機,當您的手機有新的軟件版本發布時,手機會提示您下載并安裝更新。
提示:
1、在線升級前,請確認手機未安裝過非官方版本,否則存在升級失敗的風險,且可能引入未知問題。
2、請在升級前備份個人信息(QQ、微信等第三方應用需單獨備份)。
3、升級后,部分第三方應用程序可能對新系統不兼容而導致不可用。這是因為部分第三方應用程序對新系統響應較慢,請耐心等待第三方應用程序的新版本。
4、升級過程中,請勿進行手動關機或重啟手機、插拔存儲卡(若支持)等操作。
一、進入設置 > 系統和更新,點擊軟件更新。
二、當系統檢測到新版本時,您可以手動下載升級包更新版本,或通過夜間升級功能更新版本:
1、手動下載升級包:點擊新版本 > 下載并安裝 。下載升級包會消耗一定的流量,建議您在 WLAN 環境下進行操作。
升級完成后,手機會自動重啟。
2、夜間自動升級:點擊設置,同時開啟WLAN 下自動下載和夜間安裝開關。
手機將在凌晨 2:00 至 4:00 時自動安裝升級包,并重啟手機。
在此時間段內,手機需處于開機鎖屏狀態,且未設置 2:00 至 4:00 之間的鬧鐘。升級電量需大于 35%,如果電量不足,請提前插入充電器。
如需關閉系統自動更新,請在軟件更新界面點擊設置,關閉 WLAN 下自動下載開關。
安卓版本過低怎么升級
安卓系統太低升級方法如下:
1、每一部手機都有自帶的設置功能鍵,但是每部手機的設置系統都不一樣,根據自己的手機不同尋找相應更新。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。