SmartRoute之大规模消息转发集群实现
消息转发的应用场景在现实中的应用非常普遍,我们常用的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
SmartRoute之大规模消息转发集群实现的更多相关文章
- 在Centos 7上安装配置 Apche Kafka 分布式消息系统集群
		Apache Kafka是一种颇受欢迎的分布式消息代理系统,旨在有效地处理大量的实时数据.Kafka集群不仅具有高度可扩展性和容错性,而且与其他消息代理(如ActiveMQ和RabbitMQ)相比,还 ... 
- 使用ARM模板在Azure中国大规模部署DCOS集群
		容器技术是目前非常流行的技术,尤其是在以Docker作为容器引擎的推动下,让容器的轻量级,可移植,自包含,隔离性等的上了一个新的台阶,目前谈及Dev/Ops,CI/CD很少能够绕过Docker的. A ... 
- 消息队列集群kafka安装配置
		1. 下载wget http://mirror.rise.ph/apache/kafka/0.11.0.0/kafka_2.12-0.11.0.0.tgz2. 安装tar xf kafka_2.12- ... 
- ActiveMQ消息队列集群的搭建
		1.准备activemq apache-activemq-5.12.0-bin.tar 2.解压文件 3.并将文件cp一份命名为activemq1 进入conf文件进行修改 修改属性为brokerNa ... 
- 大规模Elasticsearch集群管理心得
		转载:http://elasticsearch.cn/article/110 ElasticSearch目前在互联网公司主要用于两种应用场景,其一是用于构建业务的搜索功能模块且多是垂直领域的搜索,数据 ... 
- 用更云原生的方式做诊断|大规模 K8s 集群诊断利器深度解析
		背景 通常而言,集群的稳定性决定了一个平台的服务质量以及对外口碑,当一个平台管理了相当规模数量的 Kubernetes 集群之后,在稳定性这件事上也许会"稍显被动". 我们可能经常 ... 
- vivo大规模 Kubernetes 集群自动化运维实践
		作者:vivo 互联网服务器团队-Zhang Rong 一.背景 随着vivo业务迁移到K8s的增长,我们需要将K8s部署到多个数据中心.如何高效.可靠的在数据中心管理多个大规模的K8s集群是我们面临 ... 
- Karmada大规模测试报告发布:突破100倍集群规模
		摘要:在本文中,我们将介绍用于测试的相关指标,如何进行大规模测试,以及我们如何实现大规模的集群接入. 本文分享自华为云社区<突破100倍集群规模!Karmada大规模测试报告发布>,作者: ... 
- Redis(十)集群:Redis Cluster
		一.数据分布 1.数据分布理论 2.Redis数据分区 Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16 ... 
随机推荐
- JavaScript对象属性的基础教程指南
			JavaScript是使用“对象化编程”的,或者叫“面向对象编程”的.所谓“对象化编程”,意思是把JavaScript能涉及的范围划分成大大小小的对象,对象下面还继续划分对象直至非常详细为止,所有的编 ... 
- --@angularJS--较复杂的指令嵌套demo——综合小实例:登陆界面
			1.index.html: <!DOCTYPE HTML><html ng-app="app"><head> <title>c ... 
- 关于自己封装Web前端框架的思考和探索
			一.引言 首先这些年关于前端技术层出不穷,从最早的只用js做简单验证,到现在发现好像大前端已经无所不能了的感觉.特别是为了降低前端开发复杂度,涌现了一大批 的MVC/MVVM模式的前端框架,不停了刷新 ... 
- c#使用DotNetZip封装类操作zip文件(创建/读取/更新)实例
			DotnetZip是一个开源类库,支持.NET的任何语言,可很方便的创建,读取,和更新zip文件.而且还可以使用在.NETCompact Framework中. 下载地址在这里:http://dot ... 
- Atom 编辑器系列视频课程
			此课程为 Atom 编辑器系列课程,主要介绍了 Atom 的高效开发技巧以及必备插件. 课程列表 Atom编辑器系列课程 #1 - Atom简介 Atom编辑器系列课程 #2 - 设置简介 Atom编 ... 
- C++:C语言实现HTTP的GET和POST请求
			HTTP请求和IP/TCP 所谓的HTTP协议是基于IP/TCP协议的, 所以要获取远端的html数据只要创建socket对象就足够了: HTTP是基于IP/TCP加上了网络请求的固定格式, 比如: ... 
- Crystal框架配置参数加载机制详解?
			前言 定义 配置参数定义的形式 配置参数文件定义在哪里? 配置参数加载的优先级 如何使用配置参数? 最佳实践 Jar项目中如何定义配置参数? War项目中如何定义或重载Jar包中的配置参数? 开发人员 ... 
- css3 2d转换3d转换以及动画的知识点汇总
			css3 2d转换 2d转换的方法: 1.移动 translate(x, y) 可以改变元素的位置,x.y可为负值: 2.缩放 scale(x, y) 可以对元素进行水平和垂直方向的缩放,x.y的取值 ... 
- PLSQL语法深入浅出
			一:PLSQL概览:PLSQL 是Oracle公司在SQL基础上进行扩展而成的一种过程语言.PLSQL提供了典型的高级语言特 性,包括封装,例外处理机制,信息隐藏,面向对象等:并把新的编程思想带到了数 ... 
- JavaScript———从setTimeout与setInterval到AJAX异步
			setTimeout与setInterval执行 首先我们看一下以下代码打印结果 console.log(1); setTimeout(function() { console.log(2); },1 ... 
