-
Notifications
You must be signed in to change notification settings - Fork 578
DataPersistence
此章节介绍服务端缓存的持久化存储机制
持久化存储是将服务器中的缓存Cache转存储到电脑磁盘的过程,服务器关掉重启数据不会丢失掉,它会重新从电脑磁盘中读取数据恢复到服务器缓存Cache中。
Scut的存储分为:服务器的缓存,Redis缓存数据库,DB数据库(SQL/MySql);
先说说Scut的数据加载步骤:
-
根据定义实体的EntityTable信息,判断StorageType属性是从Redis数据库还是从DB数据库加载数据;
-
如果是Redis,则使用实体名和实体主键(EntityField(True))做为Redis的Key,取出数据;
-
如果是DB数据库,则从表名为实体名,查询字段为实体主键的数据;
-
如果实体的是ShareEntity的子类,它是加载全部的数据,不再使用实体主键筛选数据;
数据加载到服务器缓存Cache后,需要取出来使用或修改其中的数据,那修改完后,它怎么存储到DB数据库或Redis数据库呢?
接下来看看Scut的数据更新步骤:
-
从缓存中取出的实体对象,当修改它的属性时会触发Changed事件,会告诉Scut有数据改变了需要持久化数据到DB数据库或Redis数据库中;
-
当收到Changed件事通知时,会将此实体对象先放入内存队列(是服务器的内存)中,会有一个子线程负责每间隔100ms处理内存队列,将数据写入到Redis的消息队列中,这时还并没有更新到DB数据库或Redis数据库(为了因Changed事件触发频繁造成拥堵,这里使用内存队列作层缓冲而不是直接写入Redis消息队列);
-
接着会有多个线程间隔100ms处理Redis消息队列(每个线程只负责相应的子队列,默认启用2个,可以配置增加),它将实体数据存储到Redis数据库中,如果有开启Db数据库存储会提交给另一个Redis的Sql消息队列;
-
Sql消息队列负责将Sql语句更新DB数据库,由于DB数据库它写入性能不高,这里采用的延迟时间默认为5分钟来缓冲实体数据的写入量(实体主键相同只会写入一次),默认启用2个线程来处理Sql消息队列;如果要实时写入数据库,调用
DataSyncManager.SendSql
方法。 -
到这时实体数据才持久化成功,如果在此之前业务层有调用Cache的ReLoad或LoadFrom方法,它加载的数据是旧的数据会造成修改的实体数据丢失,因此只有在服务器启动初始化时使用LoadFrom和Reload方法,其它地方谨慎使用;
以上步骤数据更新由DataSyncQueueManager类负责处理。
另外提示:
- Redis数据库中带'__'开头的是Key是Redis消息队列,带'$'开头的是实体存储数据;
- '__'开头并且有Error结束的Key,表示处理异常的队列,这个队列需要手机处理,判断是否可以直接删除掉;
- 消息队列产生的异常会记录在Log目录的Exception子目录下,监控消息则在Warn目录;
由于随着时间的推移,有些玩家流失,那么这部分的数据会占用Redis的内存,造成Redis占用很大的内存; Scut将这部分流失的玩家数据定义为冷数据,可以将它从Redis中转移到数据库中存储(Temp_EntityHistory表),当这部分玩家又有上来时,会自动从数据库中加载到Redis中,转变化热数据。
-
接下来看看如何转移Redis数据:
我们需要制作一个工具,创建一个新的Console项目,调用Scut类库dll,工具的配置文件与游戏项目的配置相同,根据自己的业务编写相应的脚本。
调用
CacheFactory
类的RemoveToDatabase
方法,代码如下:static void Main() { var userId = "278903"; var keys = PersonalCacheStruct.TakeOrLoad<UserHero>(userId).Select(t => t.GetKeyCode()).ToList(); CacheFactory.RemoveToDatabase(new[]{ new KeyValuePair<Type, IList<string>>(typeof(UserRole), new[] { userId }), new KeyValuePair<Type, IList<string>>(typeof(UserHero), keys), }); }
示例中将玩家为278903的角色UserRole实体数据与英雄UserHero实体数据转移到数据库的
Temp_EntityHistory
表中,因为UserHero使用了UserId+HeroId作为复合主键,一个角色下会有多个Hero的实例,要转移时必须将因角色下的所有Hero一起转移, 不能单独转移一个Hero实例。以上示例是针对指定的玩家,也可以从数据库查出一定时间未登录的玩家ID,再遍历处理。
-
Redsi数据转移后需要开启冷数据加载开关
在默认情况下,考虑加载性能是不会从冷数据表加载数据的, 需要手动开启,在项目配置中增加一段配置,如下:
<add key="Cache.IsStorageToDb" value="true" />