SmartRoute之大規模訊息轉發叢集實現

smark發表於2017-03-09

        訊息轉發的應用場景在現實中的應用非常普遍,我們常用的IM工具也是其中之一;現有很多雲平臺也提供了這種基礎服務,可以讓APP更容易整合相關功能而不必投入相應的開發成本。對於實現這樣一個簡單功能並不複雜,對於現有的技術來說用.net提個通訊伺服器支援幾十W使用者相信也不是件困難的事情;但如果考慮可用性和更大規模那就需要下點功夫,並且對相關技術有深入的瞭解才能實現了。而在這裡主要講解一下如何通過SmartRoute來實現一個大規模的訊息轉發叢集的基礎服務。

        說到叢集那肯定由N個服務組成的一組應,那做一個訊息轉發叢集的基礎服務需要那些服務節點呢?分析一下主要包括兩大塊:註冊中心和訊息閘道器;閘道器用於和應用對接,而註冊中心則是明確應用所在位置。為了達到更好的可用性和更大規模支撐註冊中心和閘道器都是N-N的關係。

   

        看到這樣一個圖估計會把很不瞭解這方面的朋友會卡住,這樣一個東西實現會很複雜吧!其實在SmartRoute基礎之上實現這樣這樣一個叢集服務並不困難,不過對於訊息互動原理和設計還是需要了解一下。接下來講解一下如何用SmartRoute實現相應註冊中心和閘道器服務。

註冊中心

      註冊中心的作用很簡單就是儲存應用標識所在位置,當閘道器需要轉發訊息的時候告訴閘道器這個應用標識在那個位置上。除了這一功能外當然還要考慮可用性,主要包括多中心發現和註冊資訊現步等;同樣閘道器也具行指向多臺中心的負載能力。

	public interface ICenter : IDisposable
	{

		String ID { get; }

		INode Node { get; set; }

		IUserService UserService { get; set; }

		void Open();

	}

      中心的介面定義很簡單,主要都是內部針對SmartRoute的INode進行相關訊息操作。

		public void Open()
		{
			mCenterSubscriber = Node.Register<EventSubscriber>(ID);
			mCenterSubscriber.Register<Protocol.SyncUsers>(OnSyncUsers);
			mCenterSubscriber.Register<Protocol.CenterStarted>(OnOtherCenterStarted);
			mCenterSubscriber.Register<Protocol.Register>(OnSyncUser);
			mCenterSubscriber.Register<Protocol.UnRegister>(OnSyncUnRegister);
			Node.SubscriberRegisted += OnSubscriberRegisted;
			mStartServiceTimer = new System.Threading.Timer(OnOpen, null, 5000, 5000);
			Node.Loger.Process(LogType.INFO, "search other center...");
		}
		
		//處理使用者上線所在閘道器資訊
		private void OnReceiveUsersInfo(Message msg, Protocol.GetUsersInfo e)
		{
			string[] users = e.Receiver.Split(';');
			Protocol.GetUserInfoResponse response = new Protocol.GetUserInfoResponse();
			response.RequestID = e.RequestID;
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			foreach (string user in users)
			{
				Protocol.UserInfo info = UserService.GetUserInfo(user, status);
				if (info != null)
					response.Items.Add(info);
			}
			msg.Reply(response);
		}
		 //閘道器使用者下線
		private void OnUserUnregister(Message msg, Protocol.UnRegister e)
		{
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			UserService.Remove(e.Name, status);
			msg.Reply(status);
			Node.Loger.Process(LogType.INFO, "{0} user unregister", e.Name);
			//同步到其他中心節點
			if (mHasOtherCenter)
				mCenterSubscriber.Publish(CENTER_OTHER_TAG, e, ReceiveMode.Regex);
		}
		 //閘道器使用者上線
		private void OnUserRegister(Message msg, Protocol.Register e)
		{
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			UserService.Register(new Protocol.UserInfo() { Name = e.Name, Gateway = e.GatewayID }, status);
			msg.Reply(status);
			Node.Loger.Process(LogType.INFO, "{0} user register from {1}", e.Name, e.GatewayID);
			//同步到其他中心節點
			if (mHasOtherCenter)
				mCenterSubscriber.Publish(CENTER_OTHER_TAG, e, ReceiveMode.Regex);
		}

		//同步下線
		private void OnSyncUnRegister(Message msg, Protocol.UnRegister e)
		{
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			UserService.Remove(e.Name, status);
			Node.Loger.Process(LogType.INFO, "{0} user unregister", e.Name);
		}
		//同步上線
		private void OnSyncUser(Message msg, Protocol.Register e)
		{
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			UserService.Register(new Protocol.UserInfo() { Name = e.Name, Gateway = e.GatewayID }, status);
			Node.Loger.Process(LogType.INFO, "{0} user register from {1}", e.Name, e.GatewayID);
		}

		//同步其他中心上線資訊
		private void OnSyncUsers(Message msg, Protocol.SyncUsers e)
		{
			Node.Loger.Process(LogType.INFO, "sync user info to local!");
			Protocol.OperationStatus status = new Protocol.OperationStatus();
			foreach (Protocol.UserInfo item in e.Items)
			{
				UserService.Register(item, status);
			}
		}

		private void OnSubscriberRegisted(INode node, ISubscriber subscriber)
		{
			//發現其他中心服務,向服務發起同步使用者請求
			if (subscriber.Name.IndexOf(CENTER_TAG) == 0 && subscriber.Name != ID)
			{
				mHasOtherCenter = true;
				mReadyToStart = false;
				Node.Loger.Process(LogType.INFO, "find {0} center", subscriber.Name);
				Protocol.CenterStarted started = new Protocol.CenterStarted();
				started.Name = ID;
				mCenterSubscriber.Publish(subscriber.Name, started);
				Node.Loger.Process(LogType.INFO, "request sync user info ....");
			}
		}

		public INode Node
		{
			get; set;
		}

        實現並不複雜,主要是開啟相關訂閱並註冊訊息處理方法即可,主要針對註冊,同步和獲取使用者所在閘道器資訊。

閘道器

     閘道器的作用主要是接收訊息,從註冊中心獲取使用者標識對應的閘道器並把訊息推送過去;所以功能也並不複雜主要也是針對INode的操作。

 

	public interface IGateway : IDisposable
	{
		INode Node { get; set; }

		Protocol.OperationStatus Register(UserToken userToken);

		Protocol.OperationStatus UnRegister(string username);

		void SendMessage(string receivers, string sender, object message);

		void Open();
	}

    功能比較簡單使用者標識註冊和登出功能,還加上一個訊息推送方法即可。

public OperationStatus Register(UserToken userToken)
		{
			OperationStatus result;
			Register register = new Register();
			register.Name = userToken.Name;
			register.GatewayID = Node.DefaultEventSubscriber.Name;
			result = Node.DefaultSwitchSubscriber.SyncToService<Protocol.OperationStatus>(Center.USER_SERVICE_TAG, register);
			mUserActions[userToken.Name] = userToken;
			return result;
		}

		public void SendMessage(string receivers, string sender, object message)
		{
			MessageQueue.MessageItem item = new MessageQueue.MessageItem();
			item.ID = GetRequestID();
			item.Receives = receivers;
			item.Sender = sender;
			item.Data = message;
			mMsgQueue.Push(item);
			GetUsersInfo getinfo = new GetUsersInfo();
			getinfo.RequestID = item.ID;
			getinfo.Receiver = receivers;
			Node.DefaultSwitchSubscriber.ToService(Center.USER_SERVICE_TAG, getinfo);
		}

		public void Dispose()
		{
			if (mMsgQueue != null)
				mMsgQueue.Dispose();
		}

		public void Open()
		{
			mMsgQueue = new MessageQueue(this, 2);
			mMsgQueue.Open();
			Node.DefaultSwitchSubscriber.DefaultEventSubscriber.Register<GetUserInfoResponse>(OnGetUserInfoRequest);
			Node.DefaultEventSubscriber.Register<UserMessage>(OnUserMessage);
		}

		public OperationStatus UnRegister(string username)
		{
			UnRegister unregister = new UnRegister();
			unregister.Name = username;
			UserToken token = null;
			mUserActions.TryRemove(username, out token);
			return Node.DefaultSwitchSubscriber.SyncToService<OperationStatus>(Center.USER_SERVICE_TAG, unregister);
		}

中心啟動

      由於基於SmartRoute的設計,所以中心的啟動並不需要進行其他配置,直接開啟動行即可;對於多節點的中心怎辦?如果有需要多啟一個例項即可達到多中心負載能力。

	public class Program
	{
		public static void Main(string[] args)
		{
			INode node = NodeFactory.Default;
			node.Loger.Type = LogType.ALL;
			node.AddLogHandler(new SmartRoute.ConsoleLogHandler(LogType.ALL));
			node.Open();
			MRC.MCRFactory.Center.Open();
			System.Threading.Thread.Sleep(-1);		
		}
	}

閘道器應用

     閘道器的啟動和中心一樣,不過需要根據實際需要發起使用者標識註冊,註冊後就可以向叢集中的任何標識傳送訊息。

    public class Program
    {
        public static void Main(string[] args)
        {
            INode node = NodeFactory.Default;
            node.Loger.Type = LogType.ALL;
            node.AddLogHandler(new SmartRoute.ConsoleLogHandler(LogType.ALL));
            node.Open();
            MRC.MCRFactory.Gateway.Open();
            System.Threading.ThreadPool.QueueUserWorkItem(OnTest);
            System.Threading.Thread.Sleep(-1);
        }

        private static void OnTest(object state)
        {
            System.Threading.Thread.Sleep(10000);
            UserToken token = new UserToken("ken");
            token.Register();
            token.Receive = OnUserReceive;
        }

        private static void OnUserReceive(UserToken token, Protocol.UserMessage e)
        {
            Console.WriteLine("receive message from {0} {1}", e.Sender, e.Data);
        }
    }

    構建相應標識的UserToken註冊到閘道器,閘道器會自動把標識同步到中心;然後定義UserToken相應的訊息接收方法即可處理接收的訊息。實際應用中可以繼承UserToken並掛相應的客戶端連線然後當接收訊息做相應的網路轉發就可以達到使用者和使用者間的通訊。

    由一這樣功能並不複雜所以已經封裝起方便擴充套件應用,具體專案地址:https://github.com/IKende/SmartRoute.MRC

相關文章