一、寫在最前
由於本人的技術水平有限,難免會出現錯誤。本文對任何一個人有幫助都是我莫大的榮幸,任何一個大神對我的點撥,我都會感激不盡。
二、技術選型
在2013年8月低的時候,公司中了XXX市場監督局肉品配送車輛監控的項目。整個系統軟件部分需要實現的功能不難,最大的難點就是服務器的系統要求是Linux的,其次就是10月底系統能夠初步成型。由於之前做的車輛監控系統都基於Windows的,要在短時間內完成這個項目,於是Mono就成了我的首選。張善友的博客,也成了我經常光顧的地方,后來通過跟張哥的一番溝通。最終采用了張哥推薦的方案,數據庫使用PostgreSQL,Web服務器使用國產的Jexus,Linux操作系統使用CentOS6.2(這個是客戶要求的)。其他都可以很好移植,之前的系統前台使用的Extjs+SilverLight+Asp.net,服務層使用的WCF,當時張哥有提醒我Mono下的WCF坑特別的多,ORM使用的是Nhibernate,地圖引擎依舊采用的DeepEarth。有張哥的點撥,我的信心滿滿的。看上去似乎我需要解決的問題就是搭建好Mono環境,搞定Mono下的WCF服務就可以大功告成了。
三、環境搭建
裝Linux系統,配置Mono環境,安裝Jexus。寫了個簡單的Silverlight + WCF的程序部署到Jexus,竟然成功跑來了,當時的我偷笑了。然而,當我真正把系統部署到Jexus上面,才知道噩夢才剛剛開始。登錄頁面都沒有過不去,此時估計很多人都會想要是在Linux下能Debug就好了,我也不例外。於是乎,我就折騰MonoDevelop,安裝過程中遇到各種奇葩問題,而且不支持SilverLight的項目,我放棄了。采用最原始的辦法,寫日志。經過幾天的折騰,終於可以跑起來了。
四、WCF攻克
由於在折騰WCF的時候,遇到的默默其妙的問題比較多,有些能夠通過Google搜索到。我這里就列舉三個。
1:尋址版本 AddressingNone不支持添加 WS-Addressing 標頭。如果你的WCF部署在IIS下能正常的調用,而部署在Jexus下確拋出這種異常。
檢查項目下引用的dll,估計某些dll在Mono下支持不是很好。在有可能出錯的異常寫日志,而不要拋出這個異常。比如下面這個簡單的例子,FluentNhibernate在Mono下的支持就不是很好。
public IList<Custmer> GetCustoemrs() { try { using (var session = SessionFactory.GetCurrentFactory().OpenSession()) { var query = session.CreateQuery("from Customer "); return query.List<Customer>(); } } catch (Exception ex) { LogHelper.WriteException("GetCustomers method raise error:", ex); //throw; } } public class SessionFactory { public static ISessionFactory GetCurrentFactory() { return sessionFactory ?? (sessionFactory = CreateSessionFactory()); } private static ISessionFactory CreateSessionFactory() { return Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString( x => x.FromConnectionStringWithKey("db"))) .Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>()) .BuildSessionFactory(); } private static ISessionFactory sessionFactory { get; set; } }
2:客戶端反序列化失敗
當我把WCF部署到Jexus,成功生成本地代理后,調用WCF就會一直報這個錯誤。究其原因,就是因為生成代理類的屬性指定了反序列化的順序,而服務端我是沒有指定的。所以,先把WCF服務部署到IIS下,然后客戶端地址指向部署IIS的WCF服務,重新更新服務,再把配置文件中地址指向部署在Jexus的WCF服務即可。
Jexus下Host的WCF服務生成的本地代理:
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true, Order=1)] public string CustomerCode { get { return this.CustomerCodeField; } set { if ((object.ReferenceEquals(this.CustomerCodeField, value) != true)) { this.CustomerCodeField = value; this.RaisePropertyChanged("CustomerCode"); } } }
IIS下Host的WCF服務生成的本地代理:
[System.Runtime.Serialization.DataMemberAttribute()] public string CustomerCode { get { return this.CustomerCodeField; } set { if ((object.ReferenceEquals(this.CustomerCodeField, value) != true)) { this.CustomerCodeField = value; this.RaisePropertyChanged("CustomerCode"); } } }
3:WCF服務導致Jexus的httpd worker不斷重啟
這個問題是困擾我最久的,在IE11下最為明顯,Service.svc文件的post請求,動不動就被Pending。在最開始我不知道Jexus一直在重啟,因為我看jws.log中沒有Jexus重啟的日志。后來得知可以通過 ps –ef | grep jws 指令可以查看進程的啟動時間,如下圖所示,可以看到httpd worker進程不斷的重啟。
問題找到了,又開始新的一輪折騰。首先,減少客戶端對Service.svc文件的post請求,其次將WCF回傳的數據進行壓縮處理。一番修改后,IE11下,故障依舊。於是想到了用WebService嘗試下,就把某個服務修改成WebService的方式后,貌似httpd worker不會重啟了,處理不過來請求的時候,只會中斷請求。既然這樣,我就狠心把WCF的綁定方式由CustomBinding的方式改成了BasicHttpBinding,然而故障還是那么的頑固存在。
經過一番測試,感覺可能是Jexus的問題,測試代碼:
class Program { private static ServiceClient client; private static int count = 0; static void Main(string[] args) { client = new ServiceClient(); client.GetCustomersCompleted += client_GetCustomersCompleted; int callCount = Convert.ToInt32(ConfigurationManager.AppSettings["CallCount"]); Console.WriteLine("Press begin to Call WCF Service:"); while (Console.ReadLine().ToLower() == "begin") { DoWork(callCount); } Console.Read(); } private static void DoWork(int callCount) { for (int i = 1; i <= callCount; i++) { ThreadPool.QueueUserWorkItem(CallService, i); } } private static void CallService(object state) { client.GetCustomersAsync(state); } static void client_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e) { if (e.Error == null) { Interlocked.Add(ref count, 1); Console.WriteLine("成功調用:{0}", count); } else { Console.WriteLine(e.Error); } } } }
如果是對WebService模擬並發發起請求的時候,httpd worker不會重啟,如果是WCF,httpd worker會不斷的重啟。沒有辦法只有咨詢Jexus的作者宇內了。跟他描述了問題,按照他說的更新了Jexus版本,優化了Linux。最后發了個測試工具給宇內,宇內發現Jexus在處理WCF請求的時候是有點問題。在這里還是要感謝宇內那么熱心的幫助我解決問題。
五、數據庫遷移
數據庫的遷移遇到的問題不是很多,借助navicat將數據從SqlServer導入到PostgreSQL。由於系統的業務不是很復雜,之前就采用了Nhibernate,需要修改配置文件中主鍵字段的映射,因為PostgreSQL中采用的是序列。之前SqlServer中一些稍微復雜點的查詢是采用存儲過程寫的,存儲過程的移植性不好,在PostgresSQL下這部分只能重新寫了。
<id name="TypeID" type="Int32" unsaved-value="0"> <column name="TypeID" length="4" sql-type="int" not-null="true" unique="true" index="PK_SysAllType"/> <generator class="sequence"> <param name="sequence">sysalltype_typeid_seq</param> </generator> </id>
六、地圖遷移
由於地圖服務器是單獨一台,最終是用Perl + Apache來實現的,實現也挺簡單的,幾十行代碼,就是地圖引擎請求一個圖片路徑,將圖片輸出就可以了。最開始的時候,用的是Asp.net,部署在Jexus上,但是不知道為什么某些圖片始終輸出不出來,最終還是放棄了。
最后附上一張系統遷移后成功后的圖片: