消息转发的应用场景在现实中的应用非常普遍,我们常用的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

关注微信公众号