ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
ScutGame edited this page Jul 16, 2015 · 12 revisions

此章节介绍如何使用服务端的Cache缓存

目的与用途

面对开发人员使用数据库在操作数据时,一般会使用查询、删除、更新等Sql语句操作数据库;使用开发时间长又不免方便,对于游戏需要频繁读取和更新数据的需求,性能上又满意;那有没有更好的方案呢?这里给大家介绍一种NoSql的Redis内存数据库,它提供高读写的IO性能,可以满足我们的需求。

在Redis内存数据库基础上,为了更好的使用和性能,在服务器端增加一层缓存访问层(CacheStruct),有区分ShareCacheStruct和PersonalCacheStruct两种方式访问缓存数据(下面会介绍),对于常用的数据在第一次使用后,它会存留在内存中,一定时间内(24h)未使用则会自动移除,再次使用时会再次加载;在数据的增加、修改和删除操作上,使用CacheStruct对象来增加、修改和删除数据,它会监听数据的变动,采用异步的方式自动更新到Redis内存数据库中(更新间隔100ms);不需要关心与数据库或Redis内存数据库的交互。

游戏数值类型的配置数据,变动比较小,不需要在程序里对它修改,是只读类型的;此类数据可以使用SQL或MySql数据库存储,CacheStruct可以针对此类数据也可以缓存,提高读取性能;有修改时,可以重载数据。

注意:Redis内存数据库使用

  • 为了读取性能,它的数据也是在内存中的,不能随意关闭Redis内存数据库,需要等待它自动备份或手动备份后,才可关闭,否则数据会丢失;
  • 打开Redis.conf配置文件,搜索“save”配置项可查看它数据的同步频率配置;
  • “dbfilename”配置项是它的存储磁盘文件,用于备份或数据恢复, 需要自己手动备份;

缓存设计

它提供数据加载,数据重载,数据卸载,缓存查找,缓存修改删除等功能。

缓存存储结构

存储方式使用静态的字典模型,数据以结构化的实体对象Entity放入字典模型。

缓存结构:

缓存池(
缓存容器(
缓存项[ 实体对象 |
字典对象 |
队列对象 |
]
)
)

接下来介绍各个结构

1 缓存池(CachePool)结构如下:(实体类名:表示Entity的完整类名)


字典主键 | 字典值项 ----------------------
实体类名 | 缓存容器
----------------------

2 缓存容器(CacheContainer)结构也是一个字典模型,有Entity、Dictionary和Queue几种类型存储。

实体主键:表示Entity的属性有带“EntityField(true)"标记的主键值。

  • Entity类型存储:它用于公有的ShareEntity实体模型存储,使用ShareCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
实体主键 | 缓存项
----------------------
  • Dictionary类型存储:它用于私有的BaseEntity实体模型存储,PersonalId表示数据的所属者ID是Entity的GetIdentity方法返回值,可以使用UserID作为所属者ID,使用PersonalCacheStruct缓存访问
 字典主键 | 字典值项 ----------------------
PersonalId | 缓存项
----------------------
  • Queue类型存储:它用于存储一组Entity对象列表,缓存访问需要继承BaseCacheStruct基类扩展
 字典主键 | 字典值项 ----------------------
队列主键 | 缓存项
----------------------

3 缓存项(CacheItemSet)

  • 如果是Entity类型存储:存储Entity对象的值

  • 如果是Dictionary类型存储:值也是一个字典模型,如下

 字典主键 | 字典值项 ----------------------
实体主键 | Entity ----------------------
  • 如果是Queue类型存储:值是一个队列模型,如下
 Queue项
----------------------
Entity1
Entity2
...
EntityN
----------------------

缓存结构特点:

  • Entity结构:一般常用于Key/Value字典取值,查找速度快,加载数据时只能全部一起加载,过期时则全部一起移除;
  • Dictionary结构:针对一类或一组数据的存储方式,结构是字典的字典,查找同一类的数据速度快,加载数据时可以只加载同一类的数据标识ID的数据,过期时只会移除这一类数据,非常适用于存储玩家的数据,以玩家的ID归类;
  • Queue结构:用于存储一组数据,查找速度比较慢;

缓存更新机制

缓存使用Redis内存数据库为主要存储,监听对实体数据的变动操作放入消息队列等待同步处理,再由线程间隔100ms处理保存到到Redis内存数据库。

当然也支持可选配置的方式存储在SQL或MySql数据库,由于数据库的写入性能差,增加一个同步等待消息队列,间隔5分钟同步一次,如果当中有相同的实体多次操作,同步时只会被执行一次,可以减少更新的频率提供更高的性能。

缓存访问层

操作使用缓存的类它不存储缓存数据,只提供操作方法,缓存数据存储在缓存池CachePool对象中,操作缓存分为以下几种:

  • ShareCacheStruct:访问实体模型使用ShareEntity定义的实体对象;
  • PersonalCacheStruct:访问实体模型使用BaseEntity定义的实体对象;

提示:历史版本使用的操作类

  • ConfigCacheSet:早期版本使用访问ShareEntity实体操作类,可以使用此ShareCacheStruct类替换;
  • GameDataCacheSet:早期版本使用访问BaseEntity实体操作类,可以使用此PersonalCacheStruct类替换;

ShareCacheStruct缓存访问类

提供的常用方法:(Entity表示ShareEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, T, bool>):在缓存遍历数据,LoadingStatus为空时不触发数据加载,参数是Lamda表达式,第一个参数是主键,第二个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity FindKey(Keys):LoadingStatus为空时触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1);

  • Entity Find(Predicate<T>):LoadingStatus为空时触发数据加载,通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • List<Entity> FindAll(Predicate<T>, bool):LoadingStatus为空时触发数据加载,获取所有或通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad():将缓存中的数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter):尝试从数据库中加载数据更新回Redis存储中,用于Redis数据丢失时处理;

  • bool IsEmpty属性:判断一类的缓存是否为空;

  • LoadingStatus LoadingStatus属性:判断缓存数据加载的状态;

小提示:Lamda表达

  • 它是一个匿名的委托方法,需要匹配方法的参数类型、个数和返回值;参数名字可以随意取,等待被调用;
  • Net自带的有 Action(不带返回值)、Func(带返回值)、Predicate等委托类,可以直接使用,不需要使用Delegate再定义;
  • 例中定义一个参数为int类型且返回bool类型
  • void Main(Func<int, bool> fun){ fun(10); }
  • fun委托方法被调用:Main( p=>{ return true; }),Main参数同 bool method(int p){ return true;}

使用示例:

使用缓存操作时,需要先定义ShareEntity的子类,例定义一个排行榜的实体对象,如下:(了解详细Entity定义)

//定义UserRanking实体[Serializable,ProtoContract][EntityTable(CacheType.Entity,"ConnData")]publicclassUserRanking:ShareEntity{publicUserRanking():base(false){}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}[ProtoMember(2)][EntityField]publicstringUserName{get;set;}[ProtoMember(3)][EntityField]publicintScore{get;set;}}
  • 添加操作

使用ShareCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserRanking=newUserRanking(){UserID=1001,UserName="Scut",Score=90};varcache=newShareCacheStruct<UserRanking>();if(cache.Add(userRanking)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用ShareCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//修改多个属性值,只提交一次同步userRanking.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userRanking.UserName="Cocos";userRanking.Score=95;});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用ShareCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();varuserRanking=cache.FindKey(1001);if(userRanking!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userRanking);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使用ShareCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newShareCacheStruct<UserRanking>();UserRankinguserRanking=cache.FindKey(1001);userRanking=cache.Find(t=>t.UserID==1001);cache.Foreach((key,t)=>{if(t.UserID==1001){userRanking=t;returnfalse;//找到则退出}returntrue;});//1.筛选结果不要求顺序List<UserRanking>list1=cache.FindAll(false);//取有字母S开头的list1=cache.FindAll(t=>t.UserName.StartsWith("S"),false);//2.筛选结果要求以主键升顺List<UserRanking>list2=cache.FindAll();//取有字母S开头的list2=cache.FindAll(t=>t.UserName.StartsWith("S"));//3.自定以UserId降序List<UserRanking>list3=cache.FindAll(false);list3.QuickSort((x,y)=>{returny.UserID-x.UserID;});}
  • 查找主键相同的多种实体
staticvoidMain(){intkey=1001;Data1t1;Data2t2;ShareCacheStruct.Get(key,outt1,outt2);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){intkey=1001;Data1t1=ShareCacheStruct.GetOrAdd(key,newLazy<Data1>(()=>newData1()));}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newShareCacheStruct<UserRanking>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

PersonalCacheStruct缓存访问类

提供的常用方法:(Entity表示BaseEntity类型的对象)

  • bool Add(Entity):增加到缓存中,且自动同步Redis存储,如果存在不增加并返回false;

  • bool AddOrUpdate(Entity):增加到缓存,存在则更新,且自动同步Redis存储;

  • bool Delete(Entity):从缓存中删除实体,并同步到Redis;

  • bool RemoveCache(Entity):从缓存中删除实体, 但不同步;

  • void Foreach(Func<string, string, T, bool>):在缓存中遍历所有数据,不触发数据加载,参数是Lamda表达式,第一个参数分类所属ID(PersonalId),第二个是实体主键,第三个参数是实体值,最后是返回值,如果返回false则中断遍历;

  • Entity TakeSubKey(Keys):不触发数据加载,从所有缓存中查找,取出指定实体主键的对象,时间复杂度(n);

  • List FindGlobal(Predicate<T>):不触发数据加载,从所有缓存中查找,取出满足条件的所有数据,时间复杂度(n);

  • LoadingStatus TryFindKey(PersonalId, out entity, Keys):触发数据加载,通过实体的主键进行查找,以字典Key的方式取值,时间复杂度(1),如果LoadingStatus为空时触发加载,并将加载状态返回;

  • Entity FindKey(PersonalId, Keys):同TryFindKey;

  • LoadingStatus TryFind(PersonalId, Predicate<T>, out entity):触发数据加载,先通过PersonalId取出分类的所有数据,对字典取值时间复杂度(1);取的结果再通过Lamda表达式条件匹配查找,取满足条件的第一个,时间复杂度(n);

  • Entity Find(PersonalId, Predicate<T>):同TryFind

  • LoadingStatus TryFindAll(PersonalId, Predicate<T>, bool, out List): 触发数据加载,获取PersonalID的数据后再通过Lamda表达式条件匹配的数据,参数ture表示以实体主键排序后的结果列表;

  • List<Entity> FindAll(PersonalId, Predicate<T>, bool):同TryFindAll

  • long GetNextNo():获取Redis存储的自增编号,类似数据库中的自增编号,存储在Redis中以“EntityPrimaryKey_”开头的Key;

  • ReLoad(PersonalId):将PersonalId的缓存中数据更新回Redis,并重新加载数据到缓存中;

  • UnLoad():只将一类实体的缓存清空,Redis存储的数据并不会被清空;

  • bool TryRecoverFromDb(DbDataFilter, PersonalId):尝试从数据库中加载PersonalId的数据更新回Redis存储中,用于Redis数据丢失时处理;

  • void LoadFrom(Predicate<T> match): 从Redis库中加载匹配math条件的数据,速度比较慢;

  • bool IsEmpty属性:判断一类的缓存是否为空;

使用示例:

使用缓存操作时,需要先定义BaseEntity的子类,例定义一个背包的实体对象,如下:(了解详细Entity定义)

//定义UserPackage实体[Serializable,ProtoContract][EntityTable(CacheType.Dictionary,"ConnData")]publicclassUserPackage:BaseEntity{publicUserPackage():base(false){//必需要先为Item初始化Items=newCacheList<ItemData>();}[ProtoMember(1)][EntityField(true)]publicintUserID{get;set;}//此处用CacheList对象是因为它是线程安全的, 如果用数组或List则不是线程安装[ProtoMember(2)][EntityField(true,ColumnDbType.LongText)]publicCacheList<ItemData>Items{get;set;}protectedoverrideintGetIdentityId(){//指定分类的字体,以玩家的ID分类对象return(int)UserId;}}//CacheList的项必需继承EntityChangeEvent,对它修改时才会同步到Redis存储[ProtoContract]publicclassItemData:EntityChangeEvent{[ProtoMember(1)]publiclongItemID{get;set;}[ProtoMember(2)]publicshortItemLv{get;set;}[ProtoMember(3)]publicintNum{get;set;}}
  • 添加操作

使用PersonalCacheStruct向缓存池中添加一条UserRanking对象,代码如下:

staticvoidMain(){varuserPackage=newUserPackage(){UserID=1001};varcache=newPersonalCacheStruct<UserPackage>();userPackage.Item.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=1,Num=1});if(cache.Add(userPackage)){//这里只是增加到缓存池成功,同步到Redis存储是异步的,暂未提供成功回调Console.WriteLine("add ok.");}else{Console.WriteLine("add fail.");}}
  • 修改操作

使用PersonalCacheStruct修改缓存池中的数据时,需要先取出,再修改,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//修改多个属性值,只提交一次同步userPackage.ModifyLocked(()=>{//注意UserId是主键,不能去修改它userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=2,Num=1});userPackage.Items.Add(newItemData(){ItemID=cache.GetNextNo(),ItemLv=3,Num=1});});Console.WriteLine("modify ok.");}else{Console.WriteLine("Not found entity.");}}
  • 删除操作

使用PersonalCacheStruct删除缓存池中的数据,代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();varuserPackage=cache.FindKey(1001);if(userPackage!=null){//删除缓存并同时删除Redis中存储的数据cache.Delete(userPackage);Console.WriteLine("delete ok.");}else{Console.WriteLine("Not found entity.");}}
  • 查找操作

使PersonalCacheStruct查找缓存池中的数据,效率从高到低 FindKey > Find = Foreach > FindAll(false)(结果不排序) > FindAll(True)(结果排序),代码如下:

staticvoidMain(){varcache=newPersonalCacheStruct<UserPackage>();UserPaceageuserPackage=cache.FindKey("1001");if(cache.TryFindKey("1002",outuserPackage)==LoadingStatus.Success){if(userPackage==null){//当Redis存储中加载成功,且为空时,才增加数据,存在时则更新它userPackage=newUserPackage(){UserID=1002};cache.AddOrUpdate(userPackage);}}userPackage=cache.TakeSubKey(1002);userPackage=cache.Find("1001", t=>t.UserID==1001);varlist=newList<UserPackage>();cache.Foreach((personalId,key,t)=>{if(personalId=="1001"){list.Add(t)}returntrue;});list=cache.FindGlobal(t=>t.UserID==1001);}
  • 查找主键相同的多种实体
staticvoidMain(){stringuserId="1380001";Data1t1;Data2t2;PersonalCacheStruct.Get(userId,outt1,outt2);//If entity is null throw exception.}

如果有多个主键,只能取一种

staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.Get(userId,false,userId,key);//If entity is null throw exception.}
  • 以主键查找实体,不存在增加
staticvoidMain(){stringuserId="1380001";intkey=1001;Data1t1=PersonalCacheStruct.GetOrAdd(userId,newLazy<Data1>(()=>newData1()),key);}
  • 从数据库恢复数据到Redis
staticvoidMain(){intuserId=123;varcache=newPersonalCacheStruct<UserFriend>();varfilter=newDbDataFilter();filter.Condition="Userid = @UserId";filter.Parameters.Add("UserId",userId);cache.TryRecoverFromDb(filter,userId.ToString());}

Clone this wiki locally