聊天室总结
1. 我刚来公司的时候,聊天室是单机的方案:即只有一个chatroom服务,以chatroomId作为targetId, 这意味着,一个聊天室的所有行为,都只能在一台server上。包括加入、退出聊天室;聊天室发消息、聊天室发Notify,以及最终的用户来聊天室拉取消息。
2. 随着聊天室业务的爆发性增长,单机方案已经无法撑住高并发的用户量。这时的架构改进为:把notify和拉取消息的行为,拆分到新增加的chatroommessage服务中,即按业务进行了架构拆分,chatroommessage以userId作为targetId, 这样,当一个聊天室人数很多时,可以通过扩容硬件来分担Notify和拉取的行为。且可将chatroom和chatroommessage部署在不同的server上,实现了读写分离(由chatroom服务做发消息(写),由chatroommessage做拉取消息(读)),由于发消息的时候,需要保证一个聊天室内的msgId是唯一的,所以每发一条消息,都要通过加写锁来生成一个msgId, 所以发消息和聊天室中所有的成员是耦合的。
3. 随着聊天室业务量的继续增涨,chatroom再次成为了系统的瓶颈,当发消息并发达到一定程度时,每秒几万条消息的时候,chatroom服务无法撑住。这时,再次按业务将chatroom服务做架构拆分,拆分出chatroomsendmessage,chatroomsendmessage(以userId作为targetId)用于消息丢弃策略,即每个聊天室每秒钟最多可通过200条消息(其中low level msg最多100条,其余为hight level msg)。
注:这200条消息是个单点的数,如果部署10台chatroomsendmessage, 则需要把每台节点每秒钟可通过大约20+条,以此类推。然后在chatroom在做一次消息丢弃,保证每个聊天室每秒钟最多通过200条消息。
该消息丢弃策略是以手机sdk每秒钟最多处理100条消息为依据的(没接收一条消息,睡眠10毫秒)
4. 今后架构的发展方向:
1)将发消息与聊天室成员做解耦
目前解耦的主要障碍在于,生产msgId时,依赖于该聊天室最新一条消息的时间,若要聊天室最新消息的时间,则需要没写入一条,都排队加锁。
那么为什么msgId需要这样做呢?李淼是有两点原因:1. 为了保证msgid唯一;2. 为了保证消息有序。
我的想法:1. 要保证msgId唯一,可以通过appId_chatroomId_userId_currentTime做hashcode来保证唯一;
2. 怎样保证消息有序呢?如果发消息放到了chatroomsendmessage服务(以userid作为targetId), 那么对于同一个聊天室,分布在不同的chatroomsendmessage节点上的用户发消息时,有可能造成,同一个聊天室有两条消息的发消息时间完全一样。-- 这个问题怎样解决有待于思考。
2)当一个聊天室人数过多,比如50万人同时加入,也可能把chatroom单点撑爆,采用的办法是,把JoinChrm和ExitChrm的行为放到chatroommessage节点上来做,然后再同步到chatroom节点。这样,就可以在chatroom节点上做查询聊天室人数,查询聊天室成员,聊天室是否存在等行为。
思考:如果这样的话,为什么不把聊天室成员,维护在公用的redis中呢?当然,公用的Redis的访问速度肯定比不上内存那么快。
最好的办法是,使用LRUHashMap来存储聊天室成员,如果聊天室成员太多,可以存放到本地的redis或者ssdb中。
3)需要解决的问题:
《1》当chatroommessage的节点上没有人时,仍然给该节点广播消息,这样造成了不必要的内存浪费。解决方案是,使用公用的redis为每个聊天室存储消息队列。仅用于第一次加入聊天室时拉取历史消息。
《2》当服务器扩容后,怎样保证聊天室中的人不丢?解决方案:也是通过公用的redis里面保存聊天室中的所有人。
《3》第一次加载DB时,改用异步而不是同步,并设定好初始值:目的是服务器万一撑不住时,重启server后第一次加载DB值时不会造成请求积压。
《4》chatroom中的行为,是否都是不得不放在那的,如果不是,则放到chatroomsendmessage中。
《5》chatroom中实际上只需要存储最新的500人即可(调用查询聊天室成员接口,最多返回500)。然后存储总人数。
《6》QueryChatroomActor还在直接查询DB,这样如果有用户恶意攻击,可能DB服务器挂掉·。
《7》chatroom和chatroomsendmessage中向chatroommessage和chatroom通信时出口的LinkedBlockingQueue的队列的数目,可以是CPU核数*2, 因为是IO内网操作,有IO的等待时间。理想的启动线程数=[任务执行时间/(任务执行时间 - IO等待时间)] * CPU核数
《8》按userId作为targetId,这样通过hashcode取模的方式决定落到哪台server, 是否是平均分布的?大部分userId落到同一台上的概率有多大?--> 等高并发来时,去生产环境bc.log上去检验下。--> 用了一致性Hash,误差率在十分之一左右,比用随机算法略差
《9》用户因为离线被踢出聊天室后,并没有通知客户端,这样有些用户很诧异,为什么可以发成功消息,但确收不到消息。-- 对于发消息不自动加入聊天室的app。-- 现在客户端添加了重连机制:当手机断线,再连上线之后,会自动调用加入聊天室方法。
《10》离线30秒,或者错过30条消息被踢出聊天室,要是普通聊天室成员就算了,但对于主播(很多主播不允许发消息),一旦被踢出聊天室(当他的手机信息短时间不好),就看不到用户发的消息了,如果他退出再加入,会造成聊天室关闭。所以我建议增加专门针对主播的逻辑代码。
《11》当消息并发高时,可能会造成对红包消息的丢失,我建议如果可以定义某种类型的消息是永远不可丢失的是最理想的。
《12》聊天室对于自定义的消息,在网络不好的情况下,会有拉取到重复的消息,怎样解决呢?limiao说root cause是因为server端发的Notify有问题,导致client来重复拉取。
《13》使用LinkedBlockingQueue实现异步的时候,如果Queue满了,则新来的会被直接丢弃,所以写代码的时候要考虑这种场景。
《14》目前chatroom单点,如果他挂了,聊天室中的数据就全部没了,即时转到其他chatroom服务器,那么当时聊天室中的数据也全丢了。但因为聊天室中的数据是有状态的,例如聊天室中的人,所以建议把聊天室中的成员持久化存储到redis,即使单点chatrom挂了,仍然可以从Redis中恢复聊天室中的成员数据。聊天室中的消息丢就丢了,没关系。
4)将发消息(chatroomsendmessage)和聊天室成员(chatroom)解耦的剩余未解决问题:
《1》是否分发与是否禁言:
先看内存,如果内存为空,则从DB中查询。--> 如果放到chatroomsendmessage中,问题在于,从DB中加载的时候,可能需要加载全量的禁言和NotForward到以userId为targetId的内存服务器,造成一定空间的浪费。
综上,暂时不把NotForward和Forbidden放到chatroomsendmessage中
《2》消息模板路由:
不可以放到chatroomsendmessage中。--> 因为消息模板路由,是需要计算MsgId来发消息,所以也需要该聊天室的最新消息。跟正常发消息同理。所以等正常发消息和chatroom解耦时,才能将消息模板路由跟chatroom解耦。
5. 聊天室技术精粹:
1) 通过LinkedBlockingQueue做生产者、消费者,实现异步缓冲,
好处:1)保证发送方快速响应 2)限流(处理不过来时自动丢弃)
缺点:1)不能实时处理;2)费内存
2) 通过构建CUP数目的LinkedBlockingQueue来充分使用CPU
3) 对map进行set值时,使用putIfAbsent方式,避免写锁的性能瓶颈,且不会造成多次对map进行set。
缺点:当map没有完全set完时,其他线程读该map,数据不全。
4) 所以DB, jedis的写操作,都通过LinkedBlockingQueue来实现异步操作
5) 可通过Jmx来实时控制用到的参数,且可通过jmx来刷新同步数据库。
6) 消息队列采用ConcurrentSkipList来替代TreeMap, 是因为读写时跳表的数据结构优于红黑树。
7) 采用LRUHashMap来保存量大的map值,如聊天室成员,以防止数据过多造成内存泄露
8) DB值加载到内存,不做实时,而是一段时间(例如2小时)同步一次,但第一次加载时要全部同步DB数据,如负载过高时重启,这个过程比较耗时,可能造成线程积压,解决方案是,第一次加载DB值时,先使用事先设定好的初始值,然后在异步地从DB中加载DB的值到内存。
9) 采用log4j2, 异步写,可线上通过jmx实时修改Log级别。
6. 总结
要保证聊天室能处理高并发,要做到下面几点:
1. 结构是可伸缩,可扩展的,即聊天室人无限增加、消息量无限密集的时候,要可以通过增加硬件可以撑住。
2. 对于代码级别的优化:根本思路是尽量避免出现O(n)操作,尽量避免加锁(只要在业务运行范围内的不准确,如putIfAbsent)
3. 不直接操作DB, 而是所有的操作都是内存操作。尽量避免对持久化存储做同步读写操作(写操作改成异步写,读操作可以一段时间同步到内存一次)
分享到:
相关推荐
Windows 环境下 C 语言多线程实现网络编程多人聊天室总结 在 Windows 环境下实现多人聊天室需要使用 C 语言和多线程技术来实现网络编程。下面是关于这个主题的知识点总结: 第一部分:Windows Socket 编程 * 使用...
【基于TCP的聊天室(Java实现)】 在计算机网络中,TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。这个基于TCP的聊天室项目利用了Java语言的特性,结合TCP协议,实现了用户之间的实时...
总结来说,【phpAJAX聊天室】涉及到的技术点主要包括:PHP服务器端编程,MySQL数据库管理,AJAX异步通信,JavaScript DOM操作,HTML/CSS页面构建,以及安全防护策略。这些技术共同构建了一个动态、实时、交互性强的...
网络聊天室设计说明书 绪论 网络聊天室是一种广泛应用于网络中的即时通讯方式,它具有操作简便和功能丰富等特点。由于其便捷性和易于实现的特性,网络聊天室成为学习Java语言和面向对象编程思想的理想项目。网络...
总结起来,"ichat1.89聊天室"是一个包含多个组件的即时通讯应用,不仅有聊天和社交功能,还支持服务器端脚本测试、数据同步、文件转换等高级功能。用户可以通过这个压缩包快速部署和使用聊天室,而开发者则可以通过...
《Java程序设计实训》报告的主题是构建一个多人聊天室,旨在通过这个项目加深对Java语言的理解,特别是Java的多线程、GUI编程以及Socket网络编程技术。以下是对这些知识点的详细说明: 1. **Java语言**:Java是一种...
本实验报告主要探讨了使用ASP.NET技术构建一个在线聊天室的过程,旨在提升学生对ASP.NET内部控件、内部对象、尤其是Application对象的掌握,以及数据库的建立、连接和数据存取能力。此外,实验还要求学生熟悉动态...
总结来说,基于TCP/IP的局域网聊天室是一种利用现代网络技术实现的局域网内即时通信工具,其核心优势在于实时性、可靠性以及无需外部网络接入。通过多线程、共享内存和Windows消息机制的运用,确保了系统的高效运行...
总结来说,这个易语言聊天室源码是一个涵盖了网络通信、多用户交互、数据存储和GUI设计等多个方面的综合实例。通过对源码的深入学习,开发者可以掌握易语言的网络编程技巧,理解客户端-服务器通信模式,以及如何构建...
总结起来,【仿QQ聊天室】项目是Java SE技术的一个实践案例,它涉及了Socket编程、多线程、网络通信、IO流处理等多个核心知识点。通过这个项目,开发者可以提升网络编程的能力,理解服务器和客户端之间的通信机制,...
总结起来,【台湾阿德聊天室4.0】是一个多功能的社交应用,提供丰富的交流方式,旨在满足用户在不同场景下的沟通需求。其特色在于聊天室的创建和管理,以及多样化的交互功能。通过持续的优化和升级,致力于为用户...
Java聊天室程序源码 2 需求分析 2.1 业务需求 1. 与聊天室成员一起聊天。 2. 可以与聊天室成员私聊。 3. 可以改变聊天内容风格。 4. 用户注册(含头像)、登录。 5. 服务器监控聊天内容。 6. 服务器过滤非法内容。 7...
【在线聊天室与Visual Studio...总结来说,基于Visual Studio开发的在线聊天室项目涵盖了Web开发的多个方面,包括前端设计、后端逻辑、数据库操作、实时通信等,体现了Visual Studio在Web开发中的强大功能和灵活性。
聊天室的设计包括:用户进行登录,选择聊天室,进行聊天,退出聊天室。 在聊天室中,用户只需输入一个用户名就可以进入聊天室,但是如果当前有人在使用该用户名,那么就必须换一个唯一的用户名。 具体要求: ...
总结,这个基于 Socket.IO 的聊天室项目提供了一个基础的实时交流平台。通过学习和改进,可以将其扩展为功能更完善的聊天应用,例如添加私信、表情、文件发送等功能,并优化用户界面和体验,提高系统的稳定性和安全...
总结,一个简单的Java聊天室项目涉及到网络编程、多线程、GUI设计等多个Java核心技术,通过`Chatroom.class`和`Chatroom.java`文件的协同工作,实现用户间的实时通信。随着技术的不断迭代,这样的聊天室可以逐步升级...
通过本课程设计的实践及其前后的准备与总结,复习、领会、巩固和运用软件工程课堂上所学的软件开发方法和知识,以此来完成Java聊天室的分析、设计、编码、测试等工作。 1.2. 任务 通过认真阅读老师所给的课程设计的...
【标题】:“登录界面的多人多聊天室” 在这个项目中,我们主要探讨的是如何使用Python编程语言和Tornado框架来创建一个多人实时聊天系统。Tornado是一个轻量级且高性能的Web服务器和异步网络库,它非常适合构建...
【基于Swing的多线程聊天室】是一个...总结,基于Swing的多线程聊天室是一个综合运用了GUI设计、多线程、网络编程以及数据处理等技术的项目。通过这种方式,可以创建出一个高效、响应迅速并且用户体验良好的聊天应用。
总结,构建一个Ajax无刷新的简单聊天室,需要理解Ajax的工作原理,熟练掌握JavaScript DOM操作,以及服务器端的数据处理。通过不断优化和扩展,我们可以创建出更高效、更安全、用户体验更佳的聊天室系统。