GGTalk 开源即时通讯系统源码剖析之:服务端全局缓存
继上篇《GGTalk 开源即时通讯系统源码剖析之:数据库设计》介绍了 GGTalk数据库中所有表的结构后,接下来我们将进入GGTalk服务端的核心部分。
GGTalk对需要频繁查询数据库的数据做了服务端全局缓存处理,这样做一来大大降低了数据库的读取压力,二来在客户端的请求到来时,服务端能更快地响应,极大地提升了用户体验。这篇文章将会详细剖析关于 GGTalk服务端全局缓存的设计与实现。还没有GGTalk源码的朋友,可以到 GGTalk源码下载中心下载。
(资料图片仅供参考)
首先,我们需要了解 GGTalk服务端的三大核心,其分别是:
消息处理:处理来自客户端的消息;全局缓存:将用户和群组的数据缓存在内存中;数据库交互:对数据库中的信息进行增删改查。1. 消息处理此部分的代码位于
GGTalk/TalkBase/Server/Core/ServerHandle.cs
当一个客户端的请求进来时,首先会进入消息处理环节,根据用户传递的消息号,进入不同的逻辑分支。以修改用户信息为例:
//(客户端逻辑代码)/// /// 修改个人资料。/// public void ChangeMyBaseInfo(string name, string signature, string department) { //... this.rapidPassiveEngine.SendMessage(null, this.talkBaseInfoTypes.ChangeMyBaseInfo, data, "", true); //...}当一个用户信息被修改时,会调用如上方法,然后通过调用 rapid客户端引擎上的 SendMessage方法发送一条消息(其中 data为用户信息的 byte[]数组)。
//(服务端逻辑代码)public void Initialize() {//... this.rapidServerEngine.MessageReceived += new ESBasic.CbGeneric(rapidServerEngine_MessageReceived);//...} 客户端发送消息会触发 rapid服务端引擎上的 MessageReceived事件,最终程序流程会来到如下图的地方。
根据客户端传递在消息号来匹配对应的 if分支,然后进行对应的处理。
2. 全局缓存此部分的代码位于
GGTalk/TalkBase/Server/Core/ServerGlobalCache.cs
接着前面修改用户信息的例子:
if (informationType == this.talkBaseInfoTypes.ChangeMyBaseInfo) {//... this.serverGlobalCache.UpdateUserInfo(sourceUserID, contract.Name, contract.Signature, contract.OrgID); TUser user = this.serverGlobalCache.GetUser(sourceUserID);//...}消息处理后会来到如上 if分支,其中分别调用了 serverGlobalCache上的 UpdateUserInfo和 GetUser方法,下面是这两个方法的具体实现。
/// /// 获取目标用户,如果缓存中不存在,则从DB加载。/// public TUser GetUser(string userID) { TUser user = this.userCache.Get(userID); if (user == null) { user = this.dbPersister.GetUser(userID); if (user != null) { this.userCache.Add(userID, user); } } return user;}此方法会从全局缓存获取用户数据,若缓存中不存在,则会从数据库中查询,并将查询到的用户数据存入缓存中,方法最终返回用户数据。
// 更新用户信息public void UpdateUserInfo(string userID, string name, string signature, string orgID) { TUser user = this.GetUser(userID); if (user == null) { return; } user.Name = name; user.Signature = signature; user.OrgID = orgID; user.Version += 1; user.DeletePartialCopy(); this.dbPersister.UpdateUserInfo(userID, name, signature, orgID, user.Version);}此方法先去获取用户的信息,修改用户信息,然后通过调用 user上的 DeletePartialCopy方法清除用户的缓存,最后再更新数据库中用户的信息。
此部分的代码位于
GGTalk/GGTalk.Server/DBPersister.cs
同样在这个修改用户信息的例子中,在前面的讲解中有涉及到两处与数据库的交互,分别是 GetUser和 UpdateUserInfo方法的调用。下面是这两个方法的具体实现:
// 获取用户信息public GGUser GetUser(string userID) { GGUser user = null; user = db.Queryable().Where(it => it.UserID == userID).First(); return user;} // 更新用户信息public void UpdateUserInfo(string userID, string name, string signature, string orgID, int version) { db.Updateable(it => new GGUser() { Signature = signature, Name = name, OrgID = orgID, Version = version }).Where(it => it.UserID == userID).ExecuteCommand();} 在数据库的交互环节,我们使用的是 sqlsugar来操作数据库(这是一个开源的ORM框架,若想了解其详细用法,请移步sqlsugar文档)。
二. 服务端全局缓存1. 代码位置看到这里,相信你对 GGTalk服务端的三大核心有了一定的了解,接下来将会详细介绍关于 GGTalk服务端全局缓存的设计。
由于在 GGTalk服务端中对用户和群组信息查询过于频繁,故而 GGTalk将用户和群组的信息缓存在服务端内存之中,进而达到减少资源消耗和更快的服务端响应的好处,但这样做同时也会增加编码的复杂度,那么 GGTalk是如何在其中进行取舍的呢?下面将介绍具体实现。
2. ServerGlobalCache类public class ServerGlobalCache where TUser : TalkBase.IUser where TGroup : TalkBase.IGroup{ private IDBPersister dbPersister; //... private ObjectManager userCache = new ObjectManager(); // key:用户ID 。 Value:用户信息 private ObjectManager groupCache = new ObjectManager(); // key:组ID 。 Value:Group信息 //...} ServerGlobalCache类就是 GGTalk服务端全局缓存的核心实现了,这个类接受两个泛型参数,TUser和TGroup,并要求TUser必须是TalkBase命名空间中的IUser接口的实现类或子类。TGroup必须是TalkBase命名空间中的IGroup接口的实现类或子类。
IUser:用户基础接口,定义了关于用户一系列的属性和方法。/// /// 用户基础接口。/// public interface IUser : IUnit { List GroupList { get; } UserStatus UserStatus { get; set; } string GetFriendCatalog(string friendID); string GetUnitCommentName(string unitID); string Signature { get; set; } string OrgID { get; set; } /// /// 用户使用状态 /// UserState UserState { get; set; } bool IsFriend(string userID); List GetAllFriendList(); void ChangeHeadImage(int defaultHeadImageIndex, byte[] customizedHeadImage); DateTime PcOfflineTime { get; set; } DateTime MobileOfflineTime { get; set; }} IGroup:群/讨论组的基础接口,定义了一系列关于群/讨论组的属性和方法。/// /// 群、讨论组 基础接口。/// public interface IGroup : IUnit { GroupType GroupType { get; } string CreatorID { get; } DateTime CreateTime { get; } List MemberList { get; } void AddMember(string userID); void RemoveMember(string userID); string Announce { get; set; } void ChangeMembers(List members);} 除了这两个泛型参数外,我们可以发现 ServerGlobalCache类中还有三个字段,这三个字段是 ServerGlobalCache类中所有方法的核心,其作用如下:
dbPersister:与数据库进行交互;userCache:与用户缓存相关;groupCache:与群组缓存相关。关于这三个字段,在后面的具体场景会展开更加详细的介绍。
3. 缓存数据的实现关于服务端缓存,最关键的就是 userCache和 groupCache字段了,其中 userCache用于缓存用户的信息;而 groupCache用于缓存群组的信息。
首先我们来看关于这两个字段的类型ObjectManager:public class ObjectManager
ObjectManager是对Dictionary的二次封装,支持多线程安全,使用起来也更方便。这个类接受两个泛型参数,我们通过传入不同的泛型可以实现不同数据的管理(在 GGTalk服务端中,仅管理了用户和群组的数据)。
其内部的Dictionary就是用来将用户或群组的数据存储在内存中,达到缓存数据的目的。
4. 将数据库中数据的读入内存(缓存数据)我们来看 ServerGlobalCache类中如下两个方法:
从名字上来看我们很容易就知道这两个方法的意思,预加载用户数据和预加载群组数据,这两个方法的主要作用就是将数据库中用户和群组的数据加载到内存中。首先通过 dbPersister字段来从数据库中查询到所有用户和群组数据,通过foreach遍历,分别调用userCache和groupCache上的Add方法将每一条数据存储到前面提到的objectDictionary字段中,也是就存储在了服务端程序运行时的内存里面。
看到这里,关于 ServerGlobalCache类的基础设施你已经了解的七七八八了,接下来都是基于这些基础设施而实现的方法了。在这里我要纠正一个你可能感到疑惑的点,本篇文章不是介绍服务端缓存吗,这里怎么扯到数据库的增删改查呢?
因为往往数据缓存和数据源之间存在着一些联动,所以 ServerGlobalCache类的作用不仅仅是缓存数据,同时也存在大量获取数据库中的数据的方法,这也是为什么在类里面会有一个dbPersister字段,当然关于具体从数据库中读取数据的方法不在这个类里边(回顾 GGTalk三大核心)。
接下来,让我们看看 ServerGlobalCache类还有什么:
我们可以看到,这些折叠的部分的代码行数几乎占据了 ServerGlobalCache类的百分之九十,这是正是对数据库和数据缓存的操作,每个折叠代码块的注释都对应着 GGTalk数据库的一张表。
接下来我们主要分析一下关于用户和群组的部分操作,看看 GGTalk服务端是如何对数据库和数据缓存进行操作的。
首先来看一个简单的,添加新用户操作:
/// /// 插入一个新用户。/// public void InsertUser(TUser user) { this.userCache.Add(user.ID, user); this.dbPersister.InsertUser(user);}这个方法接受一个TUser类型的参数,参数中包含用户的必要信息,然后分别添加到用户缓存和插入到数据库中。
接下来,再看最开始讲三大核心的那个例子:
/// /// 获取目标用户,如果缓存中不存在,则从DB加载。/// public TUser GetUser(string userID) { TUser user = this.userCache.Get(userID); if (user == null) { user = this.dbPersister.GetUser(userID); if (user != null) { this.userCache.Add(userID, user); } } return user;}现在再来看是不是很清晰了呢,这个方法用于查询单个用户,接受一个用户ID,首先会从用户缓存中查找这个用户,如果缓存中不存在,则从数据库中查找,在用户存在的情况下再将其存入内存之中。
接下来再分析两个关于群组操作的方法。
1、根据群组ID获取群组信息:
/// /// 获取某个组/// public TGroup GetGroup(string groupID) { TGroup group = this.groupCache.Get(groupID); if (group == null) { group = this.dbPersister.GetGroup(groupID); if (group != null) { this.groupCache.Add(groupID, group); } } return group;}和获取用户信息类似,此方法首先会在群组缓存中查找对应ID的群组,若群组不存在,则会从数据库读取对应ID的群组,并且在群组存在的情况下将其存入内存之中。
2、解散群组操作
public void DeleteGroup(string groupID) { TGroup group = this.GetGroup(groupID); if (group == null) { return; } foreach (string userID in group.MemberList) { TUser user = this.GetUser(userID); if (user != null) { user.QuitGroup(groupID); this.dbPersister.UpdateUserGroups(user); } } this.groupCache.Remove(groupID); this.dbPersister.DeleteGroup(groupID); this.dbPersister.DeleteAddGroupRequest(groupID);}这个方法接受群组ID作为参数,首先会调用GetCroup方法依次从内存和数据库中读取关于目标群组的数据(如果缓存中没有的话)。若群组存在,则从群组的MemberList属性中遍历用户ID,再通过GetUser方法查询用户数据,通过用户的QuitGroup退出群组,然后在数据库中更新用户的信息。在这个群组中的每一个存在的用户都退出群组后,从群组缓存中清除该群组的数据。然后再同步数据库中的群组表的数据,以及在数据库中申请加入群组表中删除加入此群组的记录。
将数据库中的数据缓存在内存中是一把双刃剑,若是将大量的数据保存在内存中,这会大大加大内存的占用,存在程序因为内存不足而导致程序崩溃的风险。如何避免这样的事情发生,这要求我们对内存保持足够的敏感。最后,希望这篇文章能够对你有所帮助。在接下来的一篇我们将介绍GGTalk服务端的虚拟数据库。敬请期待:《GGTalk 开源即时通讯系统源码剖析之:虚拟数据库》
标签:
推荐文章
- 研究人员最新发现 单个细胞可同时处理成百上千个信号
- 长期暴露在光照下性能退化 科学家发现钙钛矿太阳能电池最大缺陷
- 陆军第73集团军某旅 创新升级模拟训练器材
- 陆军炮兵防空兵学院 毕业学员综合战术演习现地备课工作圆满完成
- 宁夏启动双百科技支撑行动 构建高水平产业创新体系
- 区域特色产业转型升级 四川屏山以“3+”模式推进科技创新工作
- 国内首颗以茶叶冠名遥感卫星 安溪铁观音一号发射成功
- 激发创新动能促进产业发展 无锡滨湖走出产业转型“绿色”路
- 走近网瘾少年们:他们沉迷网络的病根何在?
- 节后第一天北京白天晴或多云利于出行 夜间起秋雨或再上线
- 走访抗美援朝纪念馆:长津湖的寒冷,与战斗一样残酷
- 绥化全域低风险!黑龙江绥化北林区一地调整为低风险
- 农业农村部:确保秋粮丰收到手、明年夏季粮油播种
- 中国故事丨“沉浸式”盘点今年的教育好声音!
- 升旗、巡岛、护航标、写日志,他们一生守护一座岛
- 他从一窍不通的“门外汉”,到重装空投“兵专家”
- 获2021年诺奖的蛋白,结构由中国学者率先解析
- “双减”后首个长假:亲子游、研学游需求集中释放
- 天山脚下,触摸丝路发展新脉动
- 且看新疆展新颜
- 《山海情》里“凌教授”的巨菌草丰收啦
- “双减”出台两个月,组合拳如何直击减负难点?
- IP类城市缘何吸引力强?玩法创新带动游客年轻化
- 面对婚姻,“互联网世代”的年轻人在忧虑什么?
- 沙害是自然界的恶魔,而他是荒沙碱滩的征服者
- “辱华车贴”商家及客服被行拘,处罚要不放过每一环
- 网游新政下,未成年人防沉迷的“主战场”在哪?
- 160万骑手疑似“被个体户”?平台不能当甩手掌柜
- 报告显示:这个国庆假期,粤川浙桂赣旅游热度最高
- 陈毅元帅长子忆父亲叮嘱:你们自己学习要好,就可以做很多事儿
- 北京国庆7天接待游客超861万人次 冬奥线路受青睐
- 从1.3万元降到700元,起诉书揭秘心脏支架“玄机”
- 都市小资还是潮流乐享?花草茶市场呈爆发性增长
- 国庆主题花坛持续展摆至重阳节
- 警方查处故宫周边各类违法人员12人
- 云南保山:170公里边境线,4000余人日夜值守
- 线上教学模式被盯上,网络付费刷课形成灰色产业链
- 全国模范法官周淑琴:为乡村群众点燃法治明灯
- 嘉陵江出现有记录以来最强秋汛
- 中国科技人才大数据:广东总量第一,“北上”这类人才多
- 神经科学“罗塞塔石碑”来了:迄今为止最完整的大脑细胞图谱
- 多地网友投诉遭遇旅游消费骗局,呼吁有关部门严查乱象
- 受南海热带低压影响 海南海口三港预计停运将持续到10日白天
- 农业农村部:确保秋粮丰收到手、明年夏季粮油播种
- 广州10月8日至20日对所有从省外来(返)穗人员实施核酸检测
- 辽宁省工信厅发布10月8日电力缺口橙色预警
- 受琼州海峡封航影响 10月7日、8日进出海南岛旅客列车停运
- 这场红色故事“云比拼”,穿越时空为我们指引方向
- 陕西支援14省份采暖季保供用煤3900万吨
- 汾河新绛段发生决口
- 看,生机勃勃的中国
- 百闻不如一见——北京大学留学生参访新疆
- 新疆霍尔果斯市2例无症状感染者新冠病毒均为德尔塔变异株
- 哈尔滨市南岗区爱达88小区将调整为低风险地区
- 国庆假期全国道路交通总体安全平稳有序
- 假期怎么过得这么快?国庆5.15亿人次出游,你咋过的?
- 国庆假期北京接待游客861.1万人次
- 山西平遥消防4天29次救援:拖着腿走路也要完成任务
- 新疆兵团可克达拉市:195名密接者已全部隔离医学观察
- 国庆假期中国预计发送旅客4.03亿人次
- 公安部交管局:国庆假期日均出动警力18万余人次,5位交警辅警牺牲
- 国庆假期中国国内旅游出游5.15亿人次
- 新疆哈密市巴里坤县发生4.3级地震 震源深度9千米
- 冷空气自西向东影响中国大部地区 气温将下降4℃至6℃
- 2021年MAGIC3上海市青少年三对三超级篮球赛落幕
- 国庆假期广西累计接待游客逾3611万人次 实现旅游消费272.41亿元
- 新疆伊犁州:妥善做好滞留旅客安置返回工作
- 新疆霍尔果斯无症状感染者新冠病毒属德尔塔变异株 未发现高度同源的基因组序列
- “数说”杭州无障碍改造:触摸城市“爱的厚度”
- 受南海热带低压影响广东将暂别高温天气
- 浙南沿海村村发展有妙招 搭乘共富快车打造“海上花园”
- 世界第一埋深高速公路隧道大峡谷隧道出口端斜井掘进完成
- 直径2米“面气球”亮相 山西首届“寿阳味道”美食大赛启幕
- 厦门同安区四区域调整为低风险 全市无中高风险地区
- 哥伦比亚遇上广州:洋茶人“云上”喫茶 传播中国茶“味道”
- 新疆兵团第四师可克达拉市1名无症状感染者为餐饮从业人员
- 中国国庆假期出行热:数字改变“关键小事”
- 添加陌生人为好友 内蒙古两女子被骗126万
- 南沙港铁路国庆假期不停工 力争今年年底开通
- 新疆霍尔果斯两例无症状感染者新冠病毒均属德尔塔变异株
- 哈尔滨一地风险等级调整为低风险
- 哈尔滨市学校有序恢复线下教学
- 受热带低压影响 琼州海峡北岸等待过海车辆排长龙
- 铁路迎返程高峰 西安局集团公司加开79趟高铁列车
- 铁路人国庆雨中巡查排险记:一身雨衣、一把铁锹保安全畅通
- 水能载物亦能“生金” 浙江遂昌山村以水为媒奔共富
- 科学拦峰错峰削峰 嘉陵江洪水过境重庆中心城区“有惊无险”
- 山西解除持续近90小时的暴雨四级应急响应
- 安徽黄山国庆假期迎客12万余人 旅游市场稳步复苏
- 从进“培训班”到看《长津湖》
- 厦门中高风险地区清零 撤除离厦通道查验点
- 济南趵突泉地下水位创1966年以来最高纪录
- 杭州“十一”假期后初中取消统一早读
- “颜值担当”里的中国,映照“万物和谐”新气象
资讯
行业动态
-
环球热议:得梅毒的主要原因是什么? 如何预防梅毒效果好
- 环球热议:得梅毒的主要原因是什么? 如何预防梅毒效果好
- 视讯!咸菜煮熟晒干容易保存 咸菜煮熟晒干如何保存
- 环球最新:网传陕西韩城一高中生虐猫?教育部门:学校已进行批评教育
- 天天速讯:承德露露: 重大信息内部报告制度
- 当前短讯!中疾控:未来一段时间 各地会陆续迎来疫情高峰
- 全球热讯:维康药业董秘回复:如有相关机构调研,公司将及时披露相关活动记录内容,敬请关注公司公告
- 每日消息!亿纬锂能12月26日现2笔大宗交易
- 每日短讯:宏润建设(002062.SZ)拟不超10亿元投资高效光伏电池组件项目 总投资产能5GW
- 即时看!重庆完成首例简易程序单边预约定价安排谈签
- 视焦点讯!紫光股份获“北京高精尖企业100强”第5名、“北京数字经济企业100强”第6名等五项殊荣

