服务端缓存的持久化存储机制

sky_551
2020-09-07

持久化存储概述

持久化存储是将服务器中的缓存Cache转存储到电脑磁盘的过程,服务器关掉重启数据不会丢失掉,它会重新从电脑磁盘中读取数据恢复到服务器缓存Cache中。
Scut缓存持久化
Scut的存储分为:服务器的缓存,Redis缓存数据库,DB数据库(SQL/MySql);
先说说Scut的数据加载步骤:

根据定义实体的EntityTable信息,判断StorageType属性是从Redis数据库还是从DB数据库加载数据;

如果是Redis,则使用实体名和实体主键(EntityField(True))做为RedisKey,取出数据;

如果是DB数据库,则从表名为实体名,查询字段为实体主键的数据;

如果实体的是ShareEntity的子类,它是加载全部的数据,不再使用实体主键筛选数据;


数据加载到服务器缓存Cache后,需要取出来使用或修改其中的数据,那修改完后,它怎么存储到DB数据库或Redis数据库呢?
接下来看看Scut的数据更新步骤:

从缓存中取出的实体对象,当修改它的属性时会触发Changed事件,会告诉Scut有数据改变了需要持久化数据到DB数据库或Redis数据库中;

当收到Changed件事通知时,会将此实体对象先放入内存队列(是服务器的内存)中,会有一个子线程负责每间隔100ms处理内存队列,将数据写入到Redis的消息队列中,这时还并没有更新到DB数据库或Redis数据库(为了因Changed事件触发频繁造成拥堵,这里使用内存队列作层缓冲而不是直接写入Redis消息队列);

接着会有多个线程间隔100ms处理Redis消息队列(每个线程只负责相应的子队列,默认启用2个,可以配置增加),它将实体数据存储到Redis数据库中,如果有开启Db数据库存储会提交给另一个RedisSql消息队列;

Sql消息队列负责将Sql语句更新DB数据库,由于DB数据库它写入性能不高,这里采用的延迟时间默认为5分钟来缓冲实体数据的写入量(实体主键相同只会写入一次),默认启用2个线程来处理Sql消息队列;如果要实时写入数据库,调用DataSyncManager.SendSql方法。

到这时实体数据才持久化成功,如果在此之前业务层有调用CacheReLoadLoadFrom方法,它加载的数据是旧的数据会造成修改的实体数据丢失,因此只有在服务器启动初始化时使用LoadFromReload方法,其它地方谨慎使用;


以上步骤数据更新由DataSyncQueueManager类负责处理。

另外提示:

Redis数据库中带'__'开头的是KeyRedis消息队列,带'$'开头的是实体存储数据;

'__'开头并且有Error结束的Key,表示处理异常的队列,这个队列需要手机处理,判断是否可以直接删除掉;

消息队列产生的异常会记录在Log目录的Exception子目录下,监控消息则在Warn目录;
Redis缓存数据转移
由于随着时间的推移,有些玩家流失,那么这部分的数据会占用Redis的内存,造成Redis占用很大的内存; Scut将这部分流失的玩家数据定义为冷数据,可以将它从Redis中转移到数据库中存储(Temp_EntityHistory表),当这部分玩家又有上来时,会自动从数据库中加载到Redis中,转变化热数据。

接下来看看如何转移Redis数据:

我们需要制作一个工具,创建一个新的Console项目,调用Scut类库dll,工具的配置文件与游戏项目的配置相同,根据自己的业务编写相应的脚本。

调用CacheFactory类的RemoveToDatabase方法,代码如下:

1

2

3

4

5

6

7

8

9

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数据转移后需要开启冷数据加载开关

在默认情况下,考虑加载性能是不会从冷数据表加载数据的,需要手动开启,在项目配置中增加一段配置,如下:

1

add key="Cache.IsStorageToDb" value="true" /

   转载自:运维之家(https://www.ttl178.com/blog/?aid=76

关注公众号,获取更多知识干货!!

关注二维码.gif





分享