前言
有段時間沒有更新博文了,一直在忙工做不多有時間靜下心來繼續研究點東西,說來也慚愧,歸咎緣由最主要的仍是由於懶惰。空想也是無論用的,有時候不少想法被扼殺到了搖籃裏,還沒開始作就放棄了,這是多數人會有的惡習,世界上最不缺乏的就是空想家,而是實踐者,有句俗話說的好不怕千招會,只怕一招絕,能踏踏實實作好一件事的人才是人生的贏家。另外在平時也有研究過不少有趣的技術,但每每是沒有研究到最後,只是研究瞭如何使用它,而後想要寫成文章就是很危險的事情,若是對某項技術研究的並不通透,這時候發表看法的話這樣只會害人,不會幫助人,要知道只知其一;不知其二最後害的會是本身。數據庫
1、架構淺析
上文是寫給本身的由於發現本身最近有懶惰的惡習,因此作一下小小的反省,好了言歸正傳。上文討論了在分佈式開發時會用的基本的技術,其中包括微軟的WCF、Windows Service、Message Queue等,其中的Message Queue主要是討論了微軟的MQ,由於筆者如今是一名.net的程序猿,因此從最基本的微軟MQ開始詳細討論了MQ的使用方法。在有了前幾篇的技術基礎以後就能夠來看這篇文章了。
該篇文章將會使用前幾篇文章討論到的技術來搭一套小的框架,主要是實現Application(電腦或者移動端)和Web Service之間互相的通訊,中間的消息中介服務使用上文討論到的MQ來實現,具體的架構以下圖所示:
關於上圖的實現,本例中的Application只是使用了Computer端的WPF來作的一個小的應用,消息隊列方是使用微軟的Message Queue來開發,Server Service開發的是Windows Service,遠程端的Web Service使用WCF來作的開發。具體開發的代碼將會在下文中詳細討論。
Note:這種架構下近端的實體、遠程端的實體、WebService的實體以及數據契約的結構必須保持一致,這樣在開發時能夠避免寫不少轉換的中間代碼。
2、架構代碼
2.1 近端App
應用程序端作的是簡單的WPF應用程序,模擬了近端應用程序在執行完成後發送的消息信息到消息隊列中,本例中的消息隊列存儲的是xml格式的對象信息,因此在發送消息對象時須要首先指定消息隊列中信息的存儲方式,具體的模擬代碼以下:編程
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Security;
- using System.Text;
- using System.Threading.Tasks;
- using System.Messaging;
- using System.Xml.Serialization;
-
- namespace MQSend
- {
- public class SendMQ
- {
- public void Send()
- {
- MessageQueue mq = null;
- if (!MessageQueue.Exists(".\\private$\\MSMQ"))
- {
- mq = MessageQueue.Create(".\\private$\\MSMQ");
- }
- else
- {
- mq = new MessageQueue(".\\private$\\MSMQ");
- }
- mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(Student),typeof(Teacher) });
-
- for (int i = 0; i < 6; i++)
- {
- mq.Send(new Student(){Id =i,Age = "13",Name = "張三"+i.ToString(),
- Teachers = new List<Teacher>()
- {
- new Teacher() { Id = 2,
- Name = "李老師"+i.ToString() }
- }
- });
- }
- mq.Close();
- }
- }
-
- [Serializable]
- public class Student
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Age { get; set; }
- public List<Teacher> Teachers { get; set; }
-
- }
- [Serializable]
- public class Teacher
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public List<Student> Students { get; set; }
- }
-
- }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Text;
using System.Threading.Tasks;
using System.Messaging;
using System.Xml.Serialization;
namespace MQSend
{
public class SendMQ
{
public void Send()
{
MessageQueue mq = null;
if (!MessageQueue.Exists(".\\private$\\MSMQ"))
{
mq = MessageQueue.Create(".\\private$\\MSMQ");
}
else
{
mq = new MessageQueue(".\\private$\\MSMQ");
}
mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(Student),typeof(Teacher) });
for (int i = 0; i < 6; i++)
{
mq.Send(new Student(){Id =i,Age = "13",Name = "張三"+i.ToString(),
Teachers = new List<Teacher>()
{
new Teacher() { Id = 2,
Name = "李老師"+i.ToString() }
}
});
}
mq.Close();
}
}
[Serializable]
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public List<Teacher> Teachers { get; set; }
}
[Serializable]
public class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> Students { get; set; }
}
}
Note:上面的代碼實體對象Student和Teacher存在多對多的關係,這種關係在序列化時每每會出現循環引用的問題,由於序列化其實是一種屬性的遍歷,會沿着對象一直向下循環序列化,因此在編程的時候必定要注意循環應用的
問題。
Note:另外在引用屬性集合時不要聲明接口類型的集合,由於在發送消息隊列進行序列化時不容許聲明接口類型的集合,不然會發生相似於「XX是接口,所以沒法將其序列化。」的問題。
運行該應用程序後,消息會被髮送到本機的消息隊列中,具體的消息示意圖以下:
2.2 遠程端Server Service
遠程端的Service本例開發的是Windows的服務,由於服務自開機以後能夠時刻的在運行,也就是說能夠時刻的獲取消息隊列中的消息,而後調用遠程端的Web Service對消息進行處理。因爲近端的消息發送的內容是xml序列化後的對象,因此在遠程端server在操做消息對象時須要將對象首先反序列化爲Service的實體對象(這裏的實體對象是指Web Service的實體對象),而後對實體對象作全部的操做。
另外在開發Service的時候最好中間能夠記錄Service的運行過程,也就是將Service的運行過程記錄到日誌文件中,由於Service在運行過程當中很容易出現問題,在出現問題時將問題的內容記錄到日誌文件中,這樣可以較快的找到並修復問題。
每一個Service都會有不少事件,其中使用最多的就是Service的開啓事件OnStart和結束事件OnStop,該例中在這兩個事件中分別添加了日誌記錄的功能,也就是在服務開啓和關閉時都會將服務的運行情況寫入日誌。另外在該服務中添加了一個Timer控件,該控件的Interval時間設置爲1000ms,這樣能夠實時的請求消息,而後對消息作操做,並保存日誌信息。
項目結構以下圖所示,項目中添加了系統的WebService,併爲該WebService添加了具體的代理類MQServiceClient以及工具類FileOperate。
架構
Service的具體代碼以下所示:
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Diagnostics;
- using System.IO;
- using System.Linq;
- using System.Messaging;
- using System.ServiceModel;
- using System.ServiceProcess;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using DistributeService.MQService;
-
- namespace DistributeService
- {
- public partial class Service1 : ServiceBase
- {
- public Service1()
- {
- InitializeComponent();
- }
-
- private ServiceHost host;
- private FileOperate fileOperate
- {
- get
- {
- return FileOperate.GetFileOperate();
- }
- }
-
- protected override void OnStart(string[] args)
- {
- try
- {
- var str = "▌▌▌▌▌▌▌MQService start at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n";
- fileOperate.WriteText(str, fileOperate.FileS);
- }
- catch (Exception ex)
- {
- fileOperate.WriteText(ex.Message, fileOperate.FileS);
- throw;
- }
-
- }
-
- protected override void OnStop()
- {
- Thread.Sleep(30000);
- var str = "▌▌▌▌▌▌▌MQService stop at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n"; ;
- fileOperate.WriteText(str,fileOperate.FileS);
-
- if (this.host!=null)
- {
- this.host.Close();
- }
- }
-
- private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
- {
- MessageQueue mq = null;
- if (MessageQueue.Exists(".\\private$\\MSMQ"))
- {
- mq = new MessageQueue(".\\private$\\MSMQ");
- try
- {
- mq.Formatter = new XmlMessageFormatter(new Type[] {typeof (Student)});
- var me = mq.Receive();
- var stu = me.Body;
-
- fileOperate.WriteText(stu.ToString() + "\r\n", fileOperate.FileM);
-
- var client = new MQHandlerClient();
- client.Add((Student) stu);
- client.Close();
- }
- catch (Exception ex)
- {
- fileOperate.WriteText(ex.ToString() + "\r\n", fileOperate.FileM);
- throw;
- }
- finally
- {
- mq.Close();
- }
-
- }
-
- }
- }
- }
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Messaging;
using System.ServiceModel;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DistributeService.MQService;
namespace DistributeService
{
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
private ServiceHost host;
private FileOperate fileOperate
{
get
{
return FileOperate.GetFileOperate();
}
}
protected override void OnStart(string[] args)
{
try
{
var str = "▌▌▌▌▌▌▌MQService start at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n";
fileOperate.WriteText(str, fileOperate.FileS);
}
catch (Exception ex)
{
fileOperate.WriteText(ex.Message, fileOperate.FileS);
throw;
}
}
protected override void OnStop()
{
Thread.Sleep(30000);
var str = "▌▌▌▌▌▌▌MQService stop at " + DateTime.Now.ToString() + "▌▌▌▌▌▌▌\r\n"; ;
fileOperate.WriteText(str,fileOperate.FileS);
if (this.host!=null)
{
this.host.Close();
}
}
private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
MessageQueue mq = null;
if (MessageQueue.Exists(".\\private$\\MSMQ"))
{
mq = new MessageQueue(".\\private$\\MSMQ");
try
{
mq.Formatter = new XmlMessageFormatter(new Type[] {typeof (Student)});
var me = mq.Receive();
var stu = me.Body;
fileOperate.WriteText(stu.ToString() + "\r\n", fileOperate.FileM);
var client = new MQHandlerClient();//.GetMqHandlerService();
client.Add((Student) stu);
client.Close();
}
catch (Exception ex)
{
fileOperate.WriteText(ex.ToString() + "\r\n", fileOperate.FileM);
throw;
}
finally
{
mq.Close();
}
}
}
}
}
其中的
WebSerivceClient
的代碼以下所示:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Management.Instrumentation;
- using System.Text;
- using System.Threading.Tasks;
- using DistributeService.MQService;
-
- namespace DistributeService
- {
- public class MQServiceClient
- {
- private static MQHandlerClient instance;
- private static object _object = new object();
-
- private MQServiceClient()
- {
- }
-
- public static MQHandlerClient GetMqHandlerService()
- {
- if (instance == null)
- {
- lock (_object)
- {
- if (instance==null)
- {
- instance=new MQHandlerClient();
- }
- }
- }
-
- return instance;
- }
-
- ~MQServiceClient()
- {
- if (instance!=null)
- {
- instance.Close();
- instance.Abort();
- }
- }
- }
- }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Instrumentation;
using System.Text;
using System.Threading.Tasks;
using DistributeService.MQService;
namespace DistributeService
{
public class MQServiceClient
{
private static MQHandlerClient instance;
private static object _object = new object();
private MQServiceClient()
{
}
public static MQHandlerClient GetMqHandlerService()
{
if (instance == null)
{
lock (_object)
{
if (instance==null)
{
instance=new MQHandlerClient();
}
}
}
return instance;
}
~MQServiceClient()
{
if (instance!=null)
{
instance.Close();
instance.Abort();
}
}
}
}
Note:這裏的代碼主要是作的消息處理,可是每每會出現錯誤,因此要記錄運行日誌,推薦使用開源的log4net或者Nlog,可是本例中是本身將堆棧信息寫入到文本文件當中,能夠方便查看。app
2.3 遠程端Web Service
遠程端Web Service公開了對消息的處理接口,主要是作的DB的增刪改查的操做。該例的Web Service使用的是WCF來開發的,後臺使用了EF 的Code First做爲系統的ORM框架,並使用FluentAPI來搭建了系統的映射部分,系統對外公佈了數據庫的增刪改查接口,具體的結構圖以下所示:
框架
整個遠程端的Web Service爲其它端提供了數據的操做,其中的WCF在部署到host後對外公開對數據庫的CRUD操做接口,向下的DAL層封裝了數據庫的上下文(DbContext)以及數據庫的表實體,DAL層實體對象到數據庫的映射規則被封裝到了Mapping層中,該層的映射使用的是FluentAPI來實現的,最終對數據庫的操做是EF框架作的操做。接下來將會展現下幾個主要層的代碼。
2.3.1 WCF層增刪改查
下面的代碼是使用WCF來開發的Service,該Service在建立客戶端對象時會同時建立數據庫的一個上下文對象,最後將數據同步到Context中,由EF管理並同步到DB中。
- using System;
- using System.Collections.Generic;
- using DAL;
- using Entitys;
-
- namespace DistributeWCF
- {
-
-
- public class MQHandler: IMQHandler
- {
- private TestContext context;
- public MQHandler()
- {
- this.context=new TestContext();
- }
-
- public bool Add(Student stu)
- {
- this.context.Students.Add(new Entitys.Student(){Name = stu.Name,Teachers = new List<Entitys.Teacher>()});
- this.context.SaveChanges();
- return true;
- }
-
- public bool Delete(int stuId)
- {
- var stu = this.context.Students.Find(new {stuId});
- this.context.Students.Remove(stu);
- this.context.SaveChanges();
- return true;
- }
-
- public bool Update(Student stu) { return true; }
-
- public List<Student> FindAll()
- {
- var lists = this.context.Students.SqlQuery("select * from student");
- var liststu = new List<Student>();
- foreach (var student in lists)
- {
- var stu = new Student();
- stu.StudentId = student.Id;
- stu.Name = student.Name;
- stu.Sex = student.Age;
- stu.Teachers = new List<Teacher>();
- foreach (var teacher in student.Teachers)
- {
- stu.Teachers.Add(new Teacher()
- {
- Id = teacher.Id,
- Name = teacher.Name,
- });
- }
- }
- return liststu;
- }
- }
-
-
- }
using System;
using System.Collections.Generic;
using DAL;
using Entitys;
namespace DistributeWCF
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "MQHandler" in code, svc and config file together.
// NOTE: In order to launch WCF Test Client for testing this service, please select MQHandler.svc or MQHandler.svc.cs at the Solution Explorer and start debugging.
public class MQHandler: IMQHandler
{
private TestContext context;
public MQHandler()
{
this.context=new TestContext();
}
public bool Add(Student stu)
{
this.context.Students.Add(new Entitys.Student(){Name = stu.Name,Teachers = new List<Entitys.Teacher>()});
this.context.SaveChanges();
return true;
}
public bool Delete(int stuId)
{
var stu = this.context.Students.Find(new {stuId});
this.context.Students.Remove(stu);
this.context.SaveChanges();
return true;
}
public bool Update(Student stu) { return true; }
public List<Student> FindAll()
{
var lists = this.context.Students.SqlQuery("select * from student");
var liststu = new List<Student>();
foreach (var student in lists)
{
var stu = new Student();
stu.StudentId = student.Id;
stu.Name = student.Name;
stu.Sex = student.Age;
stu.Teachers = new List<Teacher>();
foreach (var teacher in student.Teachers)
{
stu.Teachers.Add(new Teacher()
{
Id = teacher.Id,
Name = teacher.Name,
});
}
}
return liststu;
}
}
}
2.3.2 Fluent API的Mapping映射代碼
該例使用的是Fluent API來作的映射,由於這種映射在修改時會很是的方便,而且在開發時控制對象關係編譯的過程,因此使用此種方法映射功能,該種映射很是的簡單,只要熟悉映射的規則就能夠很容易操做。以下代碼演示了該例中學生和老師的多對多關係之間的映射。
對應的Teacher的Entity以及Mapping代碼以下:
- using System.Data.Entity.ModelConfiguration;
- using Entitys;
- namespace Mapping
- {
- public class TeacherMapping:EntityTypeConfiguration<Teacher>
- {
- public TeacherMapping()
- {
- this.ToTable("Teacher");
- this.HasKey(x => x.Id);
- this.Property(x => x.Id).HasColumnName("Id");
- this.Property(x => x.Name);
- this.HasMany(x=>x.Students)
- .WithMany(x=>x.Teachers)
- .Map(x => x.ToTable("StuTeahcer").MapLeftKey("TeacherId")
- .MapRightKey("StudentId")
- );
- }
- }
-
- public class Teacher
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public IList<Student> Students { get; set; }
- }
-
- }
using System.Data.Entity.ModelConfiguration;
using Entitys;
namespace Mapping
{
public class TeacherMapping:EntityTypeConfiguration<Teacher>
{
public TeacherMapping()
{
this.ToTable("Teacher");
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasColumnName("Id");
this.Property(x => x.Name);
this.HasMany(x=>x.Students)
.WithMany(x=>x.Teachers)
.Map(x => x.ToTable("StuTeahcer").MapLeftKey("TeacherId")
.MapRightKey("StudentId")
);
}
}
public class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Student> Students { get; set; }
}
}
對應的Student的Entity以及Mapping代碼以下:分佈式
- using System.ComponentModel.DataAnnotations.Schema;
- using System.Data.Entity.ModelConfiguration;
- using Entitys;
- namespace Mapping
- {
- public class StudentMapping:EntityTypeConfiguration<Student>
- {
- public StudentMapping()
- {
- this.ToTable("Student");
- this.HasKey(x => x.Id);
- this.Property(x => x.Id)
- .HasColumnName("Id")
- .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
- .HasColumnType("int");
- this.Property(x => x.Name);
- this.Property(x => x.Age);
- }
- }
-
- public class Student
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Age { get; set; }
- public IList<Teacher> Teachers { get; set; }
- }
-
- }
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using Entitys;
namespace Mapping
{
public class StudentMapping:EntityTypeConfiguration<Student>
{
public StudentMapping()
{
this.ToTable("Student");
this.HasKey(x => x.Id);
this.Property(x => x.Id)
.HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.HasColumnType("int");
this.Property(x => x.Name);
this.Property(x => x.Age);
}
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public string Age { get; set; }
public IList<Teacher> Teachers { get; set; }
}
}
Note:EF的實體到對象的映射有多種方式,最經常使用的是DataAnnotations數據註解以及Fluent API兩種方式來編寫映射。該例中的學生和老師屬於多對多的關係,在使用Fluent API添加映射的時候只須要在對象的一端添加相互之間的多對多關係,由於此種關係要生成中間表因此要使用ToTable來指定中間表的代表,以及使用MapLeftKey和MapRightKey指定表的主鍵。
上面的代碼就是遠程端的主要代碼,在運行代碼後會在數據庫中添加對應的表結構,開發時很簡單,生成的數據庫的表結構以下圖所示:
結語
由於本文只是提出了一種分佈式開發的方法,這種方法須要在項目中接受考驗才能評價架構的好壞,另外在開發時還須要作嚴格的管理,並統一編碼規範和驗收標準,還有不少須要注意的地方,本文提出的內容不必定所有正確,可是通過了筆者的測試,有什麼問題還請留言一塊探討。
版權聲明:本文爲博主原創文章,未經博主容許不得轉載。ide