IM服务器设计与进化

一:概述

IM:instant messaging ,中文翻译”即时通讯“,干啥的呢?就是我发个消息,你即时的就能看见。
那么IM消息的整个系统都有哪些东西呢?

  1. 客户端。用来发送玩家的消息给服务器,接收并显示群内玩家的消息;
  2. 服务端。用来接收并处理玩家的消息,以及给某玩家推送消息
    有人问客户端自己能不能干这事?不能,因为我想给老王发个消息,但老王在哪我是不知道的,因为老王和我没有连接,所以只好找个中间商,中间商就固定在某个位置,我俩分别于中间商连接,那我俩也就可以连接了。

二:仅支持单聊的IM消息服务器,IM服务器1.0版

还是我和老王,我俩都连接了服务器这个中间商,我俩想通个话,怎么整呢?

  1. 首先,我与服务器建立了连接,这个连接有一个唯一的标识,
  2. 然后,老王也与服务器建立了连接,这个连接也有一个唯一的标识
  3. 我想向老王发送一条消息:“吃了吗?”
  4. 那么我先向IM消息服务器发一条消息,说我想向老王发条消息。
  5. IM服务器接收到了这条消息,它看看在线的人里面有没有老王,发现有,然后把消息发送给老王,然后告诉我发送成功。
  6. 老王接收到这条消息,我本地在服务器告诉我成功之后,也显示了这条消息。

上边是通俗的讲法,下面是技术的讲法

  1. 我与im服务器建立长连接,如通过socketio建立了长连接 s1,登录信息与长连接的关系保存到服务器,如<playerId1: s1>
  2. 老王与im服务器建立长连接,如通过socketio建立了长连接 s2,登录信息与长连接的关系保存到服务器,如<playerId2: s2>
  3. 我发言,本质上是客户端通过s1,向服务器发送消息:
sendMessage: {playerId = playerId2, message = "吃了吗"}
  1. IM服务器接收到之后,在缓存中找有没有playerId2,如有,通过playerId2找到s2, 然后通过s2将message发送给playerId2, 同时将此信息发送给playerId1一份。
  2. 双方收到服务器推送的消息,分别展示这条消息

如此设计,一个简单的单聊IM服务器,就成型了。

那么它有什么问题?

  1. 只能单聊,群聊暂不支持。我想8个人一起聊,不能实现。
  2. 如果有人掉线了,发向此人的消息他接收不到,那人重新上线,也无法看到,简单来说这条消息丢了。

三:支持群聊和消息历史的IM服务器,IM消息服务器2.0版

1.0版本解决只能单聊和消息丢失的问题,那么如何解决?

通俗来讲:

  1. 通过发言前建立群组,把想群聊的这些人的信息归于一处,来支持群聊,这样就能知道你的发言应该发给谁谁谁
  2. 通过把发言备份到一个可靠的地方,作为历史发言记录,解决消息丢失的问题

下面是技术的讲法

  1. playerId1,playerId2,playerId3分别于im服务器通过socketio建立了长连接 s1,s2,s3
  2. playerid1想组建个群聊,成员分别是playerid1,playerId2,playerId3,因此向IM服务器发送组建群聊请求
  3. IM收到该请求,创建个群组的数据group: {playerIds:[playerId1,playerId2,playerId3]},然后告知playerId1成功,并且通知到playerId2和playerId3这个组建群聊成功
  4. playerId1,2,3接收到组建群聊成功消息后,在UI界面展示群组信息
  5. playerId1发言:"干啥去",playerId 向IM发送向某群聊发送消息请求
  6. IM收到该请求,查找到该群聊,查找到群聊中的playerid1,2,3,然后分别向playerId1,2,3发送此条消息,并且存储此条群聊的消息到数据库(如redis,mongodb)
  7. playerId1,2,3接收到IM的消息推送后,在各自的UI中展示这条消息

如果playerId3 断线之后,playerId2说了句“去吃饭”呢?

  1. 客户端保存有群聊最大的消息序号x,与im服务器建立长连接后,先问IM服务器询问目前最新的消息序号y,如x<y,说明客户端存着的消息是滞后的,因此通过请求IM服务器的方式拿到x之后的群聊消息,并及时更新自己的最大消息序号到y

看起来目前的版本已经支持了群聊,也解决了消息丢失问题。那么它还有什么问题?

  1. 当同时发言的人数多的时候,这台服务器会处理不过来,表现为消息发出去以后老半天不见动静

四,理论上支持无限人聊天的IM服务器,IM服务器3.0版

2.0版本的单台服务器在人多时的性能问题,该如何解决?
答曰,分布式。分布式也就是多服务的意思,这些服务可能在一个服务器,也可能在多个服务器,这种方式理论上能近乎无限的提升服务的性能,但他会带来一些挑战:

  1. 群聊里的人可能来自不同的服务器,因此消息发送将更复杂
  2. 当多人近乎同时发送消息时,需要保证群聊里的所有人完整,准确,顺序相同的拿到这些人的发言。不能顺序不一样,更不能缺失。

那么服务器该如何设计呢?
通俗来讲:

  1. 客户端创建的群组信息,要所有服务器都能看到,且都能获取,并且群组信息里除了要有用户的ID之外,还得有他在哪台服务器,这样才能找到这个用户在哪
  2. 用户上线时,需要更新一下群组信息,说明我目前连的是这台服务器
  3. 对于来自同一个群组内同时发言的这些请求,不管它有多少,整个服务器部分同时只处理一个

下面是技术的讲法

  1. playerId1,playerId2,playerId3分别于im服务器A,B,C 通过socketio建立了长连接 s1,s2,s3
  2. playerId1,2,3,分别更新自己在数据库中的数据,使server=A/B/C
  3. playerid1想组建个群聊,成员分别是playerid1,playerId2,playerId3,因此向IM服务器发送组建群聊请求
  4. IM收到该请求,查找数据库,找到playerId1,2,3的数据,拿到各自的server,并创建群组的数据
group: {players:[
  {id:playerId1,server:A,socket:s1},
  {id:playerId2,server:B,socket:s2},
  {id:playerId3,server:C,socket:s3},
]},

然后告知playerId1成功,并且通知发送消息给B和C,中的playerId2和playerId3这个组建群聊成功

  1. IM的全局kafka消费者实例,订阅群聊的topic : "群聊ID",xxxxx.subscribe('xxxx');
  2. playerId1,2,3接收到组建群聊成功消息后,在UI界面展示群组信息
  3. playerId1发言:"干啥去",playerId 向IM发送向某群聊发送消息请求
  4. IM接收到kafka的消息后,取出群聊消息,查找到该群聊,查找到群聊中的playerid1,2,3,拿到各自的server,然后向playerId1,2,3发送此条消息,并且存储此条群聊的消息到数据库(如redis,mongodb)
  5. playerId1,2,3接收到IM的消息推送后,在各自的UI中展示这条消息

对于多个服务来说,各服务的在线状态需要同步,如果当前的流量较大,还需要动态的新增服务,简单来说服务需要管理。kubernetes集群是个可行的方案。

看起来目前的版本已经支持了分布式的服务,那么它还有什么问题?

  1. 逻辑更复杂,客户端连接与消息处理的逻辑耦合在一起,IM服务器的代码将更加复杂,系统的可维护性降低
  2. 可测试性下降,单元测试和集成测试可能需要启动整个im服务器来进行
  3. 耦合性强,难以将消息的处理部分抽离出来,用于其他功能或服务
  4. 代码维护困难,很好理解,越复杂,就越难维护。

五,架构更优的IM服务器,IM服务器4.0版

3.0版本的高耦合问题,该如何解决?

通俗来讲:

  1. 将IM服务器的管理客户端连接,群组管理,发送消息的功能;与对消息的处理功能,分开,各自干各自的事情

下面是技术的讲法:

  1. 新建若干消息消费者服务器,这些服务器用于消息处理
  2. IM服务器只负责管理与客户端的连接,群组管理,向客户端发送消息,以及接收来自消费者服务器的消息

当玩家创建群组时:

  1. playerId1,playerId2,playerId3分别于im服务器A,B,C 通过socketio建立了长连接 s1,s2,s3
  2. playerId1,2,3,分别更新自己在全局redis集群中的数据,即playerId1的server=A,
  3. playerid1想组建个群聊,成员分别是playerid1,playerId2,playerId3,因此向IM服务器发送组建群聊请求
  4. IM收到该请求,查找数据库(redis),找到playerId1,2,3的server,并创建群组的数据,并保存到数据库(如redis)
group: {players:[
  {id:playerId1},
  {id:playerId2},
  {id:playerId3},
]},

然后告知playerId1成功,并且通知发送消息给B和C,中的playerId2和playerId3这个组建群聊成功

  1. IM通知消息服务,消息服务接到通知后开始subscribe 此topic, topic名字是群组的ID
  2. playerId1,2,3接收到组建群聊成功消息后,在UI界面展示群组信息

当玩家发言时:

  1. playerId1发言:"干啥去",playerId 向IM发送向某群聊发送消息请求
  2. IM接收到请求,验证playerId1的身份,将消息存入数据库,并向kafka发送一条消息。topic为群聊ID
    3.消息消费者拿到来自kafka的消息后,取出群聊的消息,在redis中找到该群聊,取出其中的playerId, 然后从全局redis中找到playerId对应的server,然后给对应的IM服务器发送消息,类似如{server:A,playerId:'playerId1',"message":"干啥去"}
  3. IM服务器接收到来自消息消费者的消息后,验证是否连接有playerId, 如果有,通过socketio向该用户发消息
  4. playerId1,2,3接收到IM的消息推送后,在各自的UI中展示这条消息

目前的架构,有如下特征:

  1. IM服务可以是任意多个,负责处理连接,管理群组,发送消息
  2. 消息消费者服务可以是任意多个,负责处理消息,形成明确的消息发送指令,并给到IM服务
  3. kafka作为中间件存在,负责异步转同步,流量削峰
  4. IM和消息消费者服务职责明确,有利于维护

看起来似乎不错了,那么它还有哪些问题?

  1. 鉴权,不是什么人都能发消息,因此需要增加鉴权
  2. 全局的redis来储存用户所在的server信息,一旦redis挂了,所有人都不能聊天了,因此可以考虑redis与mongodb结合,缓存+DB的形式,这将增加开发的复杂度

但主体的架构已经完成,总体来说是个还不错的IM服务器架构。

这里先空着,我将给出架构图:

// empty 

六:下一代IM服务器的方向在哪里

本着大胆想象的原则,让我们畅想一下下一代的IM服务器。
但我这里还没有具体的答案,但大概有一些关键词,如AI,去中心化和边缘计算,有待思考和讨论

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352