请选择 进入手机版 | 继续访问电脑版

NoahFrame

 找回密码
 Register Now
搜索
热搜: redis mysql tutorial
查看: 2882|回复: 2

第十一章 NF分布式服务器解决方案— 存储解决方案

[复制链接]

30

主题

111

帖子

652

积分

Administrator

Rank: 9Rank: 9Rank: 9

积分
652
发表于 2017-6-2 15:48:06 | 显示全部楼层 |阅读模式
NF(https://github.com/ketoo/NoahGameFrame)全称为 NoahGameFrame。

NF最早为客户端设计,后来随着时代的变化,而为自己又转为服务器开发,故在吸收了众多引擎的优点后(包含Ogre的插件模式&模块化管理机制,Bigworld的数据管理&配置机制,类似MYGUI的接口层次设计),经过多年演化和实践,变成了一套游戏开发J解决方案。方案中包含开源的服务器架构,网络库(站在libevent的肩膀上),和unity3d的demo源码。现在NF已经在多个公司的多个项目中使用,其中包含知名产品 《全民无双》。

关键词

NoahGameFrame/NoahFrame/NFrame/NF
集群/负载均衡/分布式
网关服务器 GateServer 心跳 多线程/线程池 开源网络框架/模型
一致性hash算法/ConsistentHash
游戏开发中的设计模式/数据结构
Socket Nagle/粘包/开源游戏服务器/ Game Server
插件/模块/插件加载/热更新




通过前面的一些设计类的文章,大家可能已经发现一个共同点了,NF的开发者都很懒,懒得写多余的代码 - -@其实吧,人类因为懒得走,发明的汽车;懒得洗衣,发明了洗衣机;懒得写字,发明了打印机……人因为懒惰而进步,因为懒惰,所以发明了各种工具代劳,各种工具的发明使用,就是社会进步的标志。因此我从来不觉得每天晚上还在show加班的同学他们是勤快的。。。用了NF,你不需要加班!


关于存储方案,现在主流一般是采用redis+mysql作为存储方案---redis存储热点数据, mysql作为落地备份。这篇文章的内容主要为redis存储部分(读取数据和存储只反序列化),通过提供的一些实例代码来剖析NF存储数据的详细过程。

NF中有一套抽象的事件系统,所有的对象创建,销毁,都会触发相应的事件:


  1. enum CLASS_OBJECT_EVENT
  2. {
  3.     COE_DESTROY = 0,
  4.     COE_BEFOREDESTROY,
  5.     COE_CREATE_NODATA,
  6.     COE_CREATE_LOADDATA,
  7.     COE_CREATE_BEFORE_EFFECT,
  8.     COE_CREATE_EFFECTDATA,
  9.     COE_CREATE_AFTER_EFFECT,
  10.     COE_CREATE_HASDATA,
  11.     COE_CREATE_FINISH,
  12. };
复制代码


通常来说,玩家下线时,客户端会发送下线消息到game,玩家掉线时,gate会通过网络状态发送下线消息到game,这块我们也不需要关心。如果要清晰的知道何时存储/加载player的游戏数据,首先需要注册player对象的class事件回调函数OnObjectPlayerEvent。这样每一个player的创建,销毁就会得到回调。我们可以在player创建的COE_CREATE_LOADDATA事件中去redis数据库加载属性数据,在销毁COE_DESTROY时间中去存储数据。与此同时,其他任何class,都可以通过这个方法来实现加载数据和存储数据,而且save, load, attach 数据的代码,都是同一份,达到最大程度的代码共用。其中代码如下(可以直接在NF代码中搜索)

  1. int NFCPlayerRedisModule::OnObjectPlayerEvent(const NFGUID & self, const std::string & strClassName, const CLASS_OBJECT_EVENT eClassEvent, const NFDataList & var)
  2. {
  3.         if (CLASS_OBJECT_EVENT::COE_DESTROY == eClassEvent)
  4.         {
  5.                 OnOffline(self);
  6.                
  7.                 m_pLogModule->LogNormal(NFILogModule::NF_LOG_LEVEL::NLL_INFO_NORMAL, self, "start to save data", NFGetTimeMS());

  8.                 SavePlayerData(self);

  9.                 m_pLogModule->LogNormal(NFILogModule::NF_LOG_LEVEL::NLL_INFO_NORMAL, self, "saved data", NFGetTimeMS());
  10.         }
  11.         else if (CLASS_OBJECT_EVENT::COE_CREATE_LOADDATA == eClassEvent)
  12.         {
  13.                 OnOnline(self);

  14.                 m_pLogModule->LogNormal(NFILogModule::NF_LOG_LEVEL::NLL_INFO_NORMAL, self, "start to attach data", NFGetTimeMS());

  15.                 AttachData(self);

  16.                 m_pLogModule->LogNormal(NFILogModule::NF_LOG_LEVEL::NLL_INFO_NORMAL, self, "attached data", NFGetTimeMS());
  17.         }
  18.         else if (CLASS_OBJECT_EVENT::COE_CREATE_FINISH == eClassEvent)
  19.         {
  20.                 mxObjectDataCache.RemoveElement(self);
  21.         }

  22.         return 0;
  23. }
复制代码


对上面的代码进一步跟进,我们可以在SavePlayerData函数中看到他调用了NFCCommonRedisModule类的SaveCachePropertyInfo函数:
  1. bool NFCPlayerRedisModule::SavePlayerData(const NFGUID & self)
  2. {
  3.         m_pCommonRedisModule->SaveCachePropertyInfo(self, m_pKernelModule->GetObject(self)->GetPropertyManager());
  4.         m_pCommonRedisModule->SaveCacheRecordInfo(self, m_pKernelModule->GetObject(self)->GetRecordManager());
  5.         
  6.         return true;
  7. }
复制代码


这个函数SaveCachePropertyInfo内部只是调用了NFCCommonRedisModule类的ConvertPropertyManagerToPB函数,把NF的property转换为2个list(一个property-name,  一个property-value),然后存储到redis的一个hashmap中去:
  1. bool NFCCommonRedisModule::SaveCachePropertyInfo(const NFGUID& self, NF_SHARE_PTR<NFIPropertyManager> pPropertyManager, const int nExpireSecond)
  2. {
  3.     NF_SHARE_PTR<NFINoSqlDriver> pDriver = m_pNoSqlModule->GetDriverBySuit(self.ToString());
  4.     if (!pDriver)
  5.     {
  6.         return false;
  7.     }

  8.     std::vector<std::string> vKeyList;
  9.     std::vector<std::string> vValueList;
  10.     if (!ConvertPropertyManagerToPB(pPropertyManager, vKeyList, vValueList))
  11.     {
  12.         return false;
  13.     }

  14.     if (vKeyList.size() != vValueList.size())
  15.     {
  16.         return false;
  17.     }

  18.     std::string strKey= GetPropertyCacheKey(self);

  19.     if (!pDriver->HMSet(strKey, vKeyList, vValueList))
  20.     {
  21.         return false;
  22.     }
  23. }
复制代码


上面的代码看出来了,关键在于,ConvertPropertyManagerToPB函数,此函数负载了2套系统中的数据转换业务。因为NF的属性系统(property)是kv结构的,和redis天然容易结合,那么存储player的属性的时候,自动转换为redis的一个hashmap即可,这样即可得到property的kv到redis的kv映射关系,加载的时候也只需要读取player的那个hashmap覆盖到NF的属性系统即可,对于业务层来说,完全屏蔽了底层细节。代码如下:


  1. bool NFCCommonRedisModule::ConvertPropertyManagerToPB(NF_SHARE_PTR<NFIPropertyManager> pPropertyManager, std::vector<std::string>& vKeyList, std::vector<std::string>& vValueList)
  2. {
  3.         for (NF_SHARE_PTR<NFIProperty> pProperty = pPropertyManager->First(); pProperty != NULL; pProperty = pPropertyManager->Next())
  4.         {
  5.                 const int nType = pProperty->GetType();
  6.                 if (!pProperty->GetCache() && !pProperty->GetSave())
  7.                 {
  8.                         continue;
  9.                 }

  10.                 const std::string& strPropertyName = pProperty->GetKey();
  11.                 const std::string& strPropertyValue = pProperty->ToString();

  12.                 vKeyList.push_back(strPropertyName);
  13.                 vValueList.push_back(strPropertyValue);
  14.         }

  15.         return true;
  16. }
复制代码

至此,我们一通过此份代码,轻松的非嵌入其他模块,而得到了数据存储功能,而redis高效的存储机制和丰富的数据结构,也足够游戏业务使用。

关于record:

record在存储过程中,会统一通过pb压缩成一串字符串,作为kv结构的v,而record的name也会作为k和property存储在同一个hashmap中,因此record在redis中存储的是非明文。

关于堵塞问题:

一些读者担心同步读取redis数据库存在堵塞的问题,因此新版本的NF在nosql插件中加入了协程特性来解决这个问题(目前暂只在develop分支只支持linux).


NF项目为开源的分布式服务器解决方案,其中包含了网络库,actor库,以及数据驱动等新技术,能大幅提升开发效率节省开发周期以及提高程序的稳定性。
项目地址 https://github.com/ketoo/NoahGameFrame
如感觉对您有帮助,请给与star,同时也邀请广大同行参与开发和维护,作者QQ 342006,交流QQ群 341159815。
欢迎转载,转载请注明来源,本文版权归作者所有!







回复

使用道具 举报

0

主题

1

帖子

16

积分

Newbie

Rank: 1

积分
16
发表于 2018-4-17 15:46:09 | 显示全部楼层
见证了NF的成长,我相信它未来一定可以的!
回复 支持 反对

使用道具 举报

30

主题

111

帖子

652

积分

Administrator

Rank: 9Rank: 9Rank: 9

积分
652
 楼主| 发表于 2018-4-18 16:51:19 | 显示全部楼层
tonyanny 发表于 2018-4-17 15:46
见证了NF的成长,我相信它未来一定可以的!

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | Register Now

本版积分规则

 

GMT+8, 2018-10-21 07:35 , Processed in 0.076337 second(s), 29 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表