IM服务器要实现的最基本功能就是消息的转发。——好像是一句废话!

这就意味着IM服务器要为每个登录用户创建一个与该用户信息相关的内存上下文,为方便描述我们在这里称之为:user_context。user_context中一般包含这些基本信息:用户id、昵称、peer端的ip和端口,以及最重要的用于通信的socket。

用户连接上线时,需要malloc一个user_context块,用于存储上述信息,用户断开连接时,需要free这个user_context块。

IM服务器要随时维护这张user_context列表,这张表我们在这里称之为:list_user_context。这张表非常重要,im服务器要根据这张表进行消息的转发。如果100个用户登录,list_user_context表中就有100个元素,10万个用户就有10万个元素,用户间聊天时,IM服务器就需要反复查询list_user_context,从而确定转发的消息要发送到哪个用户的机器上。

举个例子:用户A发消息给用户B,基本流程如下:

1、A将消息发送给IM服务器;

2、IM服务器解析消息,获取该消息的接收人为B;

3、IM服务器查询list_user_context表,找到B的user_context(里面有B的连接通道socket);

4、IM服务器将A的消息转发给B;

正常流程都没有问题,我们说下特殊的情况(注意不是异常情况):

【特殊情况一】

A在发送消息给B时,B突然退出客户端程序。

此时IM服务器接收到2个来自IO层的事件:

事件1:A发给B的聊天数据

事件2:B的掉线通知

这两个事件会触发IM服务器进行如下两个操作,

操作1:查询list_user_context表,找到B的user_context(一个指向该内存的指针),并准备转发A的消息。

操作2:查询list_user_context表,找到B的user_context,从表中移除并准备释放指向该内存的上下文。

这两个操作可能是在不同的线程中执行,实际上在IOCP这种完全异步的模型下,这种可能的几率非常大。这时候B的user_context所在的内存区就是“临界区”,操作不当就会导致访问“野指针”,从而导致整个IM服务器挂掉。当然你可以给list_user_context表加把锁,加锁可以减少访问野指针的几率但还是无法完全避免这种情况的发生。

如果IM服务器先执行释放操作,也就是“操作1”,则是安全的,当“操作2”执行时,由于查找不到B的user_context,就会认为B已离线,并放弃发送操作。但如果“操作1”先执行,IM服务器首先获得了指向B的user_context指针,刚准备发送数据时,CPU的时间片切换到了“操作2”上,并把B的user_context释放,之后,CPU时间片又切换到“操作1”上,此时im server会访问之前 查到的B的user_context内存区,这时访问异常,服务器程序崩溃。这种几率看似很小,但在高并发且聊天繁忙时,还是会发生。注意这种情况不是异常情况,而是在真实的业务场景中会实实在在并且经常发生的情况。

当然,你可以将锁的范围扩大,也就是从“临界区”数据访问扩大到操作层面上,也就是将整个发送操作和释放操作进行加锁,从而确保CPU在时间片切换时仍能保证读、写、删除等操作的原子性。这种方式虽然安全了,但显然会让你的服务器从底层IOCP的完全异步,退化为一个业务层面上的完全同步。

如果1万人同时聊天的话,其结果将是灾难性的。如果是群聊的话,就会更加复杂,如果A所在的群有100人,这就意味着IM服务器要将消息转发给群中的其他99人。这99人可能会在此时发生各种情况,比如某些人突然退出或者突然退群。

【特殊情况二】

先说一下IM服务器和WEB服务器在设计上的最大不同。理解这一点,就能体会到IM服务器设计上的复杂性。 WEB服务器,也就是基于HTTP协议的服务器,其业务可以抽象为:请求应答式服务, 即客户发送请求,服务器响应请求,一问一答。即使是用POST命令上传文件也是基于请求应答式,只不过发送请求的数据特别长而已。服务器在没有收到请求时,不会主动发送数据给客户端,这点非常重要,也就是说同一时间要么只有一个读操作,要么只有一个写操作。

“请求应答式”业务,如果放在IO层看就是读写同步,服务器从IO中读完请求后开始向IO中写响应。实际上大部分应用协议都是基于请求应答式,比如:Telnet、FTP、POP3、SMTP。。。,这种方式在业务层面处理起来比较简单。

另一种业务模式,就是:“非请求应答式”,比如IM,读写之间没有联系,读写操作可能同时存在。

A在给B发送消息的时候,可能会同时收到B发来的消息,甚至还有其它人的消息,这时A要时刻保持“读”监听状态,同时也会进行“写”操作。对于IM服务器来说,既要保持对A的“读”监听(用于接收A发来的消息),也可能要对A进行“写”操作 (转发其他人发给A的消息)。

假设A和100人同时进行聊天,就意味着IM服务器可能要不停的对A的IO进行“写”操作。即使A不发送消息,IM服务器也要保持对A的“读”监听。如果此时,A退出聊天客户端程序,而此时尚有98条消息正在准备发送A。那些存在于内存中的98条消息,该如何释放?

服务器捕获到A离线,开始准备释放A的user_context,此时服务器正在向A转发群聊中来自不同用户的消息给A(上述98条消息),这时一旦处理不当就会导致内存访问异常,从而造成服务器崩溃。

当然这种情况下,你也可以通过加锁来解决,但遇到的问题和上述 【特殊情况一】 一样,你可能要锁的不是一个数据临界区,而是一个完整的操作,从而确保操作的原子性,避免内存访问异常。但过多的加锁会导致IM服务器性能大打折扣。

如果是IM服务器集群则会更加复杂,不同的用户会登录在不同的IM服务器上,A可能在服务器1上,B可能在服务器2上。A给B转发消息时,可能B已从服务器2上离线。如果支持群聊的话,就更加复杂了。

举一个极端的例子:

假设存在一个100台IM服务器的集群,你有99个好友,你和他们分别登录在上述100台服务器上,也就是说大家彼此登录在不同的IM服务器上。此时你要和每一位好友聊天就需要知道每位好友当前登录在哪台IM服务器上,你给每个好友发送的消息都需要进行服务器间的转发。如果你和他们分别建立N个群组的话,则每台IM服务器都要知道每个人所在的群组,从而进行群组消息的转发。

总结一下,IM服务器的业务复杂就在于:用户间会频繁的进行交互。回到文章开头的那句废话:IM服务器要实现的最基本功能就是消息的转发。正是由于消息的转发,才会导致临界区的存在,因为某一时刻在线的用户,可能在你给他发送消息时,已经下线。

IM服务器编写的难度和复杂性就源于这句废话。 因为你编写不是一个简单的demo,而是要处理和解决所有可能的意外和异常情况,从而让你的服务器健壮、可靠和稳定。

最后多说几句:

单机并发量越高,需要的集群机器就越少,成本就越低,整个系统的复杂度也会降低。假设需要开发一个支持千万级在线聊天的IM服务器。

如果单机支持1万,则需要1000台IM服务器,如果单机支持10万,则只需要100台。显性成本就是需要多购买900台服务器,如果1台服务器价格1万,则要多付出900万。隐性成本就是每台服务器每年的电费或机房托管费,假设每台每年成本为1千元,则每年要多付出90万。此外,服务器集群越多,复杂度越高,开发成本越高,运维成本也就越高。所以要尽量采用好的IO模型开发服务器端,比如linux下的epoll,windows下的iocp,从而提升单机的并发量。

IM服务器:开发一个高并发的IM服务器难在哪的更多相关文章

  1. 配置开发支持高并发TCP连接的Linux应用程序全攻略

    http://blog.chinaunix.net/uid-20733992-id-3447120.html http://blog.chinaunix.net/space.php?uid=16480 ...

  2. HttpServer:一款Windows平台下基于IOCP模型的高并发轻量级web服务器

    HttpServer的特点1.完全采用IOCP模型,实现真正的异步IO,高并发.高可靠: 2.支持4G以上文件下载: 3.支持断点续传: 4.轻量级,体积小,服务器文件仅200多K,无任何依赖库: 5 ...

  3. 学习游戏服务器开发必看,C++游戏服务器开发常用工具介绍

    C++游戏服务器开发常用工具介绍 在软件开发过程中需要使用的工具类型实属众多,从需求建模到软件测试,从代码编译到工程管理,这些工具都对项目有着不可替代的作用.庄子有云,"吾生也有涯,而知也无 ...

  4. C++ HttpServlet 高并发多线程 HTTP 服务器(转)

    from:http://www.oschina.net/code/snippet_568966_43193   C/C++ 程序虽然执行效率高,但程序员在开发 WEB 应用时却因为没有好的 WEB 开 ...

  5. Python开发一个多并发的FTP SERVER

    允许同时支持多用户在线 用户认证 用户空间配额 权限限制 可上传下载,上传下载中显示进度条 用户可远程切换目录,查看服务端文件列表等 可断点续传

  6. Web服务器-服务器开发-返回固定页面的HTTP服务器(3.3.1)

    @ 目录 1.注意 2.代码 关于作者 1.注意 浏览器解析的时候偶\r\n才算一个换行符 发送的str要编码,这里使用的是utf8 其他的都和上一篇没有什么区别 这里主要返回的是固定的网址 2.代码 ...

  7. 询问Spring Bott和高并发框架两个问题

    这里我问两个问题,请大神告诉我. 第一个问题,如果我想用Spring Boot开发企业级的微服务,我该看哪些资料?比如数据库该如何配置?消息中间件该怎么设置?等等.或者可以推荐给我几本这方面的书. 第 ...

  8. Golang语言快速上手到综合实战高并发聊天室

    需要的联系我:QQ:1844912514 Go是Google开发的一种编译型,可并行化,并具有垃圾回收功能的编程语言.2015,Go迎来了全迸发的一年.时隔一年,回头再看,Go已跻身主流编程语言行列. ...

  9. 高并发应对:淘宝CDN缓存服务器部署探秘

    转自:http://server.chinabyte.com/6/12663506.shtml “好,时间到,开抢!”坐在电脑前早已等待多时的宋兰(化名)一看时间已到2011年11月11日零时,便迫不 ...

随机推荐

  1. HCNP Routing&Switching之路由过滤工具Filter-Policy

    前文我们了解了路由控制技术中路由策略和路由匹配工具IP-Prefix相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15314262.html:今天我们 ...

  2. video.js视频播放插件

    1 初始化 Video.js初始化有两种方式. 1.1 标签方式 一种是在<video>标签里面加上class="video-js"和data-setup='{}'属性 ...

  3. rationrose安装步骤

    Rational Rose是Rational公司出品的一种面向对象的统一建模语言的可视化建模工具.用于可视化建模和公司级水平软件应用的组件构造. 就像一个戏剧导演设计一个剧本一样,一个软件设计师使用R ...

  4. Linux系列(35) - 光盘yum源搭建(2)

    光盘搭建yum源 背景 当前Linux服务器没有网络,yum源下载好了,在光盘中 step-01 挂载光盘 mkdir /mnt/cdrom #建立挂载点 mount /dev/cdrom /mnt/ ...

  5. ☕【Java技术指南】「JPA编程专题」让你不再对JPA技术中的“持久化型注解”感到陌生了!

    JPA的介绍分析 Java持久化API (JPA) 显著简化了Java Bean的持久性并提供了一个对象关系映射方法,该方法使您可以采用声明方式定义如何通过一种标准的可移植方式,将Java 对象映射到 ...

  6. Java-基础-JDK动态代理

    1. 简介 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 比如:我们在调用 ...

  7. P2179-[NOI2012]骑行川藏【导数,二分】

    正题 题目链接:https://www.luogu.com.cn/problem/P2179 题目大意 给出\(E\)和\(n\)个\(s_i,k_i,u_i\)求一个序列\(v_i\)满足 \[\s ...

  8. P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】

    前言 话说在\(Loj\)下了个数据发现这题的名字叫\(fgo\) 正题 题目链接:https://www.luogu.com.cn/problem/P5405 题目大意 \(n\)张卡的权值为\(1 ...

  9. CF1392G-Omkar and Pies【dp】

    正题 题目链接:https://www.luogu.com.cn/problem/CF1392G 题目大意 两个长度为\(k\)的起始和目标01串. \(n\)个操作交换起始串的两个位置,选择一段长度 ...

  10. C#开发BIMFACE系列41 服务端API之模型对比

    BIMFACE二次开发系列目录     [已更新最新开发文章,点击查看详细] 在建筑施工图审查系统中,设计单位提交设计完成的模型/图纸,审查专家审查模型/图纸.审查过程中如果发现不符合规范的地方,则流 ...