解析大型.NET ERP系統 分佈式應用模式設計與實現

C/S架構的應用程序,將一些複雜的計算邏輯由客戶端轉移到服務器端能夠改善性能,同時也爲了其它方面的控制。.NET Remoting在局域網內調用的性能至關不錯。ERP系統中基於.NET Remoting和WCF構建一個應用程序服務器(Application Server)。html

分佈式應用設計目標:數據庫

1  客戶端的鏈接,服務器要能控制。服務器根據受權許可文件的內容,控制客戶端併發數。設計模式

2  服務器崩潰,客戶端要獲得通知,掛起當前數據輸入操做,當服務器可用時,客戶端可自動從新鏈接 。安全

3  支持數據加密,對敏感的數據可用加密的端口和通道傳輸。性能優化

4  支持數據壓縮,改善數據傳輸效率,由於要作一個壓縮與解壓縮動做,性能有所下降。服務器

5 安全控制,應用程序服務器阻止未受權的或未簽名的應用程序的鏈接。session

6 客戶端向服務器傳送大文件,傳送圖片須要時性能優化架構

7 服務器端發現錯誤時,支持堆棧傳回客戶端以診斷緣由。併發

8 開發和部署簡單方便。框架

先設計服務器與客戶端通訊的接口,一個簡單銷售合同數據表的訪問接口代碼以下所示。

 public interface ISalesContractManager
    {
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo);
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath);
        SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath);
        EntityCollection GetSalesContractCollection(Guid sessionId, IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);
        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete);
        SalesContractEntity SaveSalesContract(Guid sessionId, SalesContractEntity salesContractEntity, EntityCollection entitiesToDelete, string seriesCode);

        void DeleteSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);

        bool IsSalesContractExist(Guid sessionId, String ContractNo);
        bool IsSalesContractExist(Guid sessionId, IRelationPredicateBucket filterBucket);
        int GetSalesContractCount(Guid sessionId, IRelationPredicateBucket filterBucket);

        SalesContractEntity CloneSalesContract(Guid sessionId, String ContractNo);
        void PostSalesContract(Guid sessionId, String ContractNo);
        void PostSalesContract(Guid sessionId, SalesContractEntity salesContractEntity);
    }

再設計服務實現SalesContractManager,實現上面的接口。

 [CommunicationService("SalesContractManager")]
    public class SalesContractManager : ManagerBase, ISalesContractManager
    {
        public SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo)
        {
            return GetSalesContract(sessionId, ContractNo, null);
        }

        public SalesContractEntity GetSalesContract(Guid sessionId, String ContractNo, IPrefetchPath2 prefetchPath)
        {
            return GetSalesContract(sessionId, ContractNo, prefetchPath, null);
        }

注意到上面給上面的實現類添加了CommunicationService特性,也就是聲明實現類是一個服務。

 

先來回顧一下最簡單的.NET Remoting 客戶端與服務器端代碼設計模式。

服務器端的設計:

int port = Convert.ToInt32(ConfigurationManager.AppSettings["Port"]);
BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
provider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Hashtable();
props["port"] = port;
TcpChannel channel = new TcpChannel(props, null, provider);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ERP.BusinessLogic.SalesContractManager), "RemotingService", WellKnownObjectMode.SingleCall);

這裏是用代碼寫死服務類,能夠用配置文件增長服務類,也可用以反射的方法增長服務類。

客戶端調用的代碼以下:

ISalesContractManager  salesContractManager =(IPdmServer)Activator.GetObject(typeof(ISalesContractManager),string.Format("{0}RemotingService", ApplicationServerUrl));
if (salesContractManager == null)
        throw new AppException("Sever configuration error");
salesContractManager.SaveSalesContract(guid, salesContract);
 

改善服務器端代碼,讓服務器主動搜索系統中打上CommunicationService特性的服務類。當新增長服務類型時,框架可自動識別並加載服務類型:

Assembly assembly = typeof(Manager).Assembly;
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
           if (type.Namespace == "ERP.BusinessLogic.Managers")
            {
                    string serviceName = string.Empty;
                    object[] attributes = type.GetCustomAttributes(typeof(CommunicationService), true);

                     if (attributes.Length > 0)
                           serviceName = type.Name;

 if (!string.IsNullOrEmpty(serviceName))
                            {
                                if (clientActivatedServices.Contains(serviceName))
                                {
                                    RemotingConfiguration.RegisterActivatedServiceType(type);
                                }
                                else if (singletonServices.Contains(serviceName))
                                {
                                    RemotingConfiguration.RegisterWellKnownServiceType(type, serviceName + ".rem", WellKnownObjectMode.Singleton);
                                }
                                else
                                {
                                   RemotingConfiguration.RegisterWellKnownServiceType(type, serviceName + ".rem", WellKnownObjectMode.SingleCall);
                                }
                            }

這樣節省了開發人員的服務發佈時間。客戶端主要方法以下:

instance = ReflectionHelper.CreateObjectInstance<T>(type);

.NET Remoting可識別當前服務類型是否註冊過,若是有則會建立一個遠程代理,實現向服務器發送請求。

 

控制客戶端併發數:

.NET Remoting支持單件調用模式,客戶端不論調用次數,服務器端都只會是相同的一份對象,這樣可實現會話Session控制。ERP系統用戶登入時,檢查服務器登入會話表(Session,本質上是一個DataTable),判斷是否已經登入。同時也能夠實現併發用戶控制,當登入的用戶數超過受權許可規定的用戶數,可阻止登入。


服務器崩潰,客戶端要獲得通知,掛起當前數據輸入操做,當服務器可用時,客戶端可自動從新鏈接:
.NET Remoting支持客戶端服務器訂閱模式,服務器端可向客戶端發送消息。當服務器進程崩潰,或是沒法鏈接到數據庫等緣由發生時,須要及時向訂閱過的客戶端發送消息通知,客戶端界面收到通知後須要當即掛起ERP主界面,不容許任何操做。這樣可避免用戶辛苦的輸入數據後,點擊保存卻鏈接不上服務器,只好關閉從新輸入。

 

安全控制,應用程序服務器阻止未受權的或未簽名的應用程序的鏈接:

服務器控制客戶端的鏈接調用,在客戶端登入時,須要傳入當前客戶端程序集的版本,簽名標識Token,還有系統參數等,服務器端會將這些參數整合在一塊兒,用MD5計算出一個哈希值。只有客戶端傳入的參數值通過MD5運算後,與服務器中這些相同的參數值MD5運算以後的值,徹底相同。服務器端才容許客戶端繼續登入。

 

服務器端發現錯誤時,支持堆棧傳回客戶端以診斷緣由:

上面建立服務器端的代碼中,有如下兩句是爲了實現服務器端堆棧回傳到客戶端的,參考下面的代碼:

BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
provider.TypeFilterLevel = TypeFilterLevel.Full;


支持數據加密和數據壓縮:

使用自定義的GTCP信道(Channel)通訊,此通道支持加密傳輸和數據壓縮傳輸。

開發和部署簡單方便:

Code Smith 6.5的模板會幫助生成ISalesContractManager和SalesContractManager兩個類型的源代碼,經過上面的講解知道,只須要給SalesContractManager加上CommunicationService特性即實現服務類的部署。

 

客戶端向服務器傳送大文件性能:

爲了改善性能,對於文件傳輸類服務,單獨開放一個端口用於文件傳輸。

相關文章
相關標籤/搜索