Web項目演化系列--開啟分布式(分離數據層)
原本上一篇是打算寫分離數據層的,但是在思考的過程當中發現分離數據層的時候,有一些操作是要依賴分布式鎖的,因此先寫了分布式鎖。
對于有些項目的數據層提供的是業務接口的(返回業務所需的數據),那么當數據層壓力逐漸增大的時候,如需要使用緩存的時候,就需要開發人員去修改相應的數據接口使其使用緩存,緩存和各種數據查詢接口交錯在一起,整個數據層的代碼變得非常混亂,連重構都無法進行,只能推倒重做。所以很多的文章中,在講解數據層的時候,都是使用統一的數據接口,如:Find、Add、Save等,那么當需要緩存的時候,就可以在Find中直接擴展使其支持緩存,甚至可以引入緩存配置管理,對于不同的表之間進行可調度的緩存周期管理,使開發人員不需要知道緩存的存在,他們還是使用原先的Find,但是速度卻更快了,又或者冷熱數據管理、搜索引擎等,這便是大神們在經過多年開發總結下來的經驗。
將數據層從項目中分離出來成為一個獨立的項目,并將其發布于獨立的服務器,相對于單機系統而言,有如下幾個優點:
1、便于整合各方資源
2、降低成本
3、每個層的性能更加優秀且可伸縮性強,在不斷增加的負荷下,可以便利的增加節點數量。
4、某些次服務層出現錯誤的時候,不會導致項目的整體癱瘓。
5、不同層次可用不同語言實現
有優點就有缺點,最大的缺點就是較單機系統而言,分布式系統更加復雜,不僅系統本身結構會變得復雜,不同組件間的網絡會引入影響因素,調試困難等等。
雖然有不少的缺點,但是不去實踐是不會發現另一番天地的,不去挑戰看看永遠都只是局限在單機系統上,也不用害怕會遇到哪些問題,畢竟只有發現問題之后,才能想辦法解決,這是從書上無法學習到的,那么我們就開始今天的文章吧。
基礎CRUD
數據層是提供于數據庫進行操作的中間件,最基礎的功能就是CRUD,單機系統當中,調用CRUD接口的時候,要么通過拼接SQL要么通過ORM直接連接數據庫,而由于現在是分布式的,因此原先的調用方式就需要通過通信協議來實現了,假設原先的接口為:
public class DbResult { public bool Error { get; set; } public object Data { get; set; } } public interface IDb { DbResult Find(Dictionary<string, object> query); DbResult Add(object entity); DbResult Save(object entity); DbResult Remove(object entity); }
數據層那端的Find是以Query Object模式實現的,具體的格式可以查看此文章--《 Query Object--查詢對象模式(上) 》、《 Query Object--查詢對象模式(下) 》。
這里的數據層項目,如果使用原先抽取的mvcHandler方式來實現,那么IService的派生類就需要使用HttpWebRequest來實現了,大致的實現思路就是將相應的方法最后轉換成調用數據服務,Find實現代碼如下:
public DbResult Find(Dictionary<string, object> query) { var url = string.Format("{0}/{1}/{2}", ConnectionUri, this.table, "find"); var req = (HttpWebRequest)HttpWebRequest.Create(url); req.Method = "Post"; req.Accept = "text/plain, */*; q=0.01"; req.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; req.Timeout = 1000 * 30; req.KeepAlive = true; try { byte[] bytes = Encoding.UTF8.GetBytes( JsonConvert.SerializeObject(query)); req.ContentLength = bytes.Length; using (var reqStream = req.GetRequestStream()) { Stream requestStream = req.GetRequestStream(); requestStream.Write(bytes, 0, bytes.Length); } var res = new DbResult(); using (var resp = (HttpWebResponse)req.GetResponse()) { using (var respStream = resp.GetResponseStream()) { using (var reader = new StreamReader(respStream, Encoding.UTF8)) { string respContent = reader.ReadToEnd(); return JsonConvert.DeserializeObject(respContent); } } } } catch { return new DbResult { Error = true }; } }
事務
有了基礎的CRUD以后,接下來就要實現事務了,如果參考單機系統來實現事務的話,那么開啟事務以后,如果業務層出現問題導致主機重啟或宕機,那么數據層的事務將會無法關閉,從而引發問題。
因此需要使用其他的方式來實現,觀察事務可以得出從事務開啟到提交是一個整體,只有事務提交的時候才需要有狀態來判定事務的執行情況,而事務開啟時并可以不需要有執行結果的判定,因此可以將事務看成一個隊列,而中間的每一個CUD操作可以看成是它的元素,而每一個操作需要知道對應的是哪個表、執行哪個操作以及相應的表數據即可,代碼實現如下:
public class TransactionAction { public string Table { get; set; } public string Name { get; set; } public object Data { get; set; } } //IDb public interface IDb { //其他省略 void BeginTx(); DbResult CommitTx(); } //IDb實現 private List actions; public void BeginTx() { this.actions = new List(); } public DbResult CommitTx() { //http訪問數據層并設置actions為null } public DbResult Add(object entity) { if (this.actions != null) { this.actions.Add(new TransactionAction { Name = "add", Table = this.table, Data = entity }); } else { //單個增加 } }
結束語
到這里數據層的分離基本上就完成了,如果想要更高效的數據獲取,可以將訪問方式修改成socket,之所以不直接使用WPF或者webService是因為這都是基于C#來開發的,而且如果直接使用現成的框架來用的話,能學到的東西就不多了。
就到這里了,如果有什么問題或者錯誤的話,請給我留言,謝謝。