1. 程式人生 > >被一個貌似簡單的老技術問題虐了幾天

被一個貌似簡單的老技術問題虐了幾天

公司的程式需要使用一套外部系統,是必須要用,沒得商量的那種,這套外部系統Since 2001年,有將近18年的歷史,跟它對接用什麼方式?說出來你可能不信,竟然是用生成Microsoft Access(簡稱MSAccess)檔案的方式,沒錯,就是生成mdb檔案,往mdb裡寫資料,然後丟給這套外部系統。   一個mdb,就是一個數據庫,檔案型資料庫,這也是很多我這個年代來的程式設計師接觸的第一個資料庫系統,我記得最早的時候我是用VB(不是VB.NET,那時沒有.NET)來訪問它的。後來我發覺在比較簡單的應用場景裡,根本就不需要把資料儲存到mdb中,直接讀寫帶一定格式的檔案比mdb訪問快得多;再後來我發現可以用一些壓縮演算法來生成更小巧的資料儲存檔案;再後來我發現xml是一種可讀性更好的資料儲存方式;再後來我發覺sqlite功能蠻強,不比MSAccess差,由於開源跨平臺,應用場景更豐富……總而言之,我沒有理由再使用mdb來儲存資料了。   若干年後(如果今天看的話就是許多許多年前了),我去了一家公司,我所在的部門有一套非常重要的程式,用VC++寫的,儲存資料的檔案的格式竟然就是mdb……VC++使用mdb檔案的方式相當繁瑣且容易出錯,我當時覺得十分不爽,於是努力替換掉它,把它之前需要好幾兆空間儲存的資料壓縮到幾十K,你沒看錯,縮小了兩個數量級,讀寫速度飛快,可這並不是一件令我有多少愉快回憶的事情,你想我這麼一個新人努力地把人家辛辛苦苦定下來的東西“推倒重來”,人家會怎麼想?   又若干年後,命運又跟我開了個玩笑:對不起,雖然已經是2019年了,你還是得繼續通過mdb檔案交換資料!我:……  
  現在公司的程式並非2019年才誕生,早有了,使用.NET Framework訪問mdb檔案比VC++輕鬆無數倍,雖然如此,我還是想進一步簡化工作,我寫了一套幫助類庫,讓程式無需關心具體操作,只需要準備好資料到一個數據字典中,把資料字典交給我的幫助類庫,就自動完成了填充mdb檔案的操作,非常方便。   在剛過去的2018年裡,我幹了一件事情,就是將大量的程式從.NET Framework上遷移至.NET Core,這裡邊自然遇到了不少障礙,其中一個“無法逾越”的障礙就是mdb的生成!   訪問mdb資料庫的正統方法是OLEDB介面,OLE這是個古老的概念,它基於COM(公共元件模型),COM的一個重要特點就是:Windows特有,並且,微軟沒有把它做成跨平臺的打算。   我花了很多時間尋找解決方案,未果,看來只能使用一些折中的做法:mdb的生成使用一個公共的Windows伺服器來做,我自定義了一套通訊規則,編寫了生成mdb的服務程式,還提供了一套方便的客戶端程式碼,呼叫者可以像原先那樣,直接一個方法生成mdb,不用關心中間經過了那些網路通訊步驟,還是一樣的方便。我的程式經過了全面的測試,確認無誤,一切看起來非常OK,直到系統正式使用的時候客戶反映mdb的生成頻頻出問題……   馬上檢視log,發現了這麼一個錯誤:“XXXX正由另一程序使用,因此該程序無法訪問此檔案。”XXXX便是我要生成的mdb檔案,我在生成mdb檔案的時候,把模板複製到臨時目錄一份,名字用GUID,不會重複的,開啟,填充資料,關閉,然後將mdb檔案返回給客戶端。這“正由另一程序使用”是什麼意思?我的檔案只可能是我這個程序使用啊。   再經過了N次測試,我本地就是重現不出這個問題,只有把程式部署到伺服器上才會出現,並且不能每次都重現,我的本地和伺服器的差別是IIS Express和IIS的差別,但對IIS這麼紛繁複雜的配置,我感覺要從中找出什麼端倪來簡直是大海撈針,搜尋相關資訊,無果。(不喜歡Windows伺服器很大程度上是不喜歡IIS,對,我就這麼旗幟鮮明)   這個問題的本質就是:我關閉了OLEDB的連線後,mdb檔案似乎還處於鎖定狀態,想用程式讀取這個mdb檔案返回的話就會出錯。好,這個難不倒我,我開始尋找一些解決方法。 

1,禁用連線池

 OLEDB資料庫連線預設也是有連線池的,你關閉一個數據庫連線,實際上並不是真的關閉它,而是讓它返回到池中,準備給下次開啟連線時使用。我很快找到了禁用連線池的選項,禁用之,測試,OK,上線!問題依舊。

2,GC.Collect

 我雖然開始學.NET的時候就知道垃圾回收,可直接顯式地使用這個方法,是第一次,按照微軟官方文件的說法,一般情況下並無GC.Collect的必要,呼叫這個方法能強制釋放一些記憶體資源,也許能強迫資料庫真正關閉。但上線後很快發現問題依舊。

3,外部複製

經過嘗試,我發覺竟然可以用Windows的copy命令來複制被開啟著的mdb,我靈光一閃,對,伺服器也可以這麼幹,於是關閉mdb連線後呼叫了外部的copy命令,複製生成的mdb檔案,再把複製好的這個檔案返回給客戶端,perfect!果然,客戶那邊不再反映什麼問題了。這個難題難道就這麼解決了嗎?圖樣!第二天,客戶找到我們,說我們生成的mdb檔案缺資料!這是大問題!甚至比生成不了mdb的問題還要打,前者的話他們可以再次嘗試,而後者則可能帶來業務上的資料錯誤。我馬上到伺服器上看,對比了生成的臨時檔案,我確認了問題還在!我這樣copy被鎖定的mdb檔案是未寫入完全的!有快取,我要想方法禁掉快取。

4,嘗試禁用OLEDB的寫入快取

Windows在將資料儲存至磁碟上的時候,其實都不是直接寫磁碟,而是使用了一種快取機制,先寫到快取,再快取到物理磁碟,這樣無疑提高了呼叫的速度,所以才有了Flush的概念,Flush就是立即將快取的資料沖掉,也就是強制立即寫入磁碟,而我現在要做的,是禁用掉這個快取,讓對mdb的操作更直截了當地寫到磁碟上,我費了不少力氣,在微軟的官方文件上找到了這個選項,但是這回更慘,本地測試報錯,原因是並不支援這個選項,可以理解為OLEDB只是介面,究竟支不支援,還得看引擎,訪問mdb的這個Jet引擎貌似不行。

5,更換引擎

Microsoft.Jet.OLEDB.4.0變更為Microsoft.ACE.OLEDB.12.0,嗯,具體的安裝檔案可以在微軟官網下載到。ACE.OLEDB.12.0這個應該更新一些,結果還是不行,並且一樣地不支援上一點提到的那個選項。

6,等待解鎖

 既然上面都不是辦法,那我就用重試的機制,讀取檔案失敗,丟擲異常,捕捉異常,間隔兩秒鐘,重試讀取,再失敗,再重試,最多嘗試兩分鐘,兩分鐘檔案總歸寫入完成了吧?結果還是不行,2分鐘還是無法讀取,且這回客戶那邊有些受不了這樣的漫長等待了,客戶問能不能恢復之前那個有資料缺失的版本?至少能用。

7,到底有沒有.NET Core下的訪問mdb檔案的第三方庫

微軟不提供支援,我找第三方行不行?很顯然,我之前找過,但這次我打算更認真仔細地找。最後找到了這麼一篇文章:《 Using Microsoft Access in .NET Core》,寫於2018年11月,還挺新,難怪我之前沒找到。文章提到了,.NET Core可以用ODBC介面訪問mdb,但微軟只提供了Windows版本的ODBC引擎,Linux版本的卻沒有提供,想在Linux環境下直接用ODBC訪問mdb的話得找第三方的方案,這裡就有一個: Access ODBC Driver,但並不免費,並且費用還不低。   看吧,老闆是肯定不會同意讓我去買這麼一個東西的。

8,ODBC

 好,既然上面提到ODBC,那我就切換到ODBC介面去,不用什麼OLEDB了,過程進展還挺順利,除了對MSAccess的型別的理解有少許不同外,ODBC和OLEDB介面差別很小,我很快就弄完了。測試,上線!嗯?這次居然貌似可以了。客戶用了一天下來,沒再發現什麼異常,我檢查了日誌,也沒再出現“正由另一程序使用”的問題。這次難道真的好了嗎?我已經有點焦頭爛額了。   好吧!至少到現在沒再出什麼問題。儘管我這幾天裡並不只在處理這個問題,但這個問題卻是這幾年來最讓我掉面子的問題了。第一它很老,第二它看起來很簡單,第三它似乎有很多種變通方法。但實際搞起來卻被它虐了許多回合,實在令人唏噓。  
  半年前一個許多年前的老同事突然QQ我,說他在解決一個技術問題的時候搜到了我的部落格,於是想起了我,挺懷念當初跟我學技術的日子,因為2018年了,他居然在做VB,沒錯,不是VB.NET,是VB,本文開頭提到的那個VB,這是一套極其古老的系統,他還在維護著,我能理解他的苦悶,但這就是生活。