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. AS插件快速生成javabean

    https://blog.csdn.net/u010227042/article/details/103803198

  2. Java从入门到精通(第5版)上半部分

    1.1java简介 先起了oak 橡树 这个名字,因为商标原因改为爪洼岛谐音的Java 一次编写,到处运行 java如何运行 java程序既是编译型又是解释型 Java版本 Java SE 标准版(开 ...

  3. Orchard Core入门配方和主题

    包含Orchard Core入门配方和主题 可以通过两个不同的NuGet包使用Orchard Core. OrchardCore.Application.Cms.Core.Targets Orchar ...

  4. 『GoLang』fmt包的使用

    目录 1. fmt 包初识 2. 格式化 verb 应用 2.1 通用 2.2 布尔值 2.3 整数 2.4 浮点数与复数 2.5 字符串和 []byte 2.6 指针 2.7 其他 flag 2.8 ...

  5. 关于Redis的十个高频面试问题

    文件来自大神的分析,小弟引用.希望更多的资源能被更多的人分享到!!! 一.Redis有哪些数据结构? 字符串String.字典Hash.列表List.集合Set.有序集合SortedSet. 如果你是 ...

  6. 《DotNet Web应用单文件部署系列》三、混淆dll文件

    众所周知,C#编译后的dll文件可被反编译,网上搜索"C# 反编译"会出现一大堆资料.为了提高反编译成本,我们必须对dll文件进行混淆处理. 目前,C#混淆工具很多,我推荐obfu ...

  7. Python Software Foundation

    The Python Software Foundation (PSF) is a 501(c)(3) non-profit corporation that holds the intellectu ...

  8. PyTorch固定参数

    In situation of finetuning, parameters in backbone network need to be frozen. To achieve this target ...

  9. 从0到1使用Kubernetes系列——Kubernetes入门

    基本概念 Docker 是什么 Docker 起初是 dotCloud 公司创始人 Solomon Hykes 在法国的时候发起的一项公司内部项目,Docker 是基于 dotCloud 公司多年云服 ...

  10. 【Azure 云服务】Azure Cloud Service 为 Web Role(IIS Host)增加自定义字段 (把HTTP Request Header中的User-Agent字段增加到IIS输出日志中)

    问题描述 把Web Role服务发布到Azure Cloud Service后,需要在IIS的输出日志中,把每一个请求的HTTP Request Header中的User-Agent内容也输出到日志中 ...