缓存


注:这是进阶内容,你可以在初次阅读的时候跳过它。

Evennia是一个完全持久化的系统,不论事物何时发生变化,它都会将变化存储到数据库中。由于访问数据库的开销相对较大,Evennia采用了大量的缓存方案。缓存通常意味着一旦数据从数据库中读出,它就被存在内存中以便之后快速检索。只有数据发生变化时才会再次访问数据库(并且同时更新缓存)。

除了少数特列,缓存的主要目的是为了提高速度、尽量减少服务器中存在的瓶颈。某些系统需要反复访问一些特定的数据,会造成对django API的请求不断积聚。而依据操作进行巧妙的缓存可以节省上百次的访问时间。

扩大缓存代价是增加内存消耗和系统复杂性。本篇文档会适当地解释所用的各种缓存策略。大多数用户不需要关心这些问题,但如果你想用Evennia解决难题,你应该了解你所看的东西。

除了Idmapper,所有的缓存方案都在 src/server/caches.py 中。你可以通过设置 GAME_CACHE_TYPE 关闭所有由该模块处理的缓存。

默认的 @server 命令可以列出大多数相关缓存的内存使用情况。


Idmapper

Evennia的Django模型对象都是经过 idmapper 扩展功能的。idmapper 是外部的第三方系统,在 src/utils/idmapper 中。idmapper 是游戏中所有数据库模型的按需存储的映射。因此当访问指定数据库对象时,实际上访问的是内存中的相同对象。这听起来可能很普通,但其实不然,普通的Django没有提供这样的保障。做类似 objdb.test = "Test" 的事(objdb是一个Django模型实例)并不安全,下一次将模型从数据库中取出时很可能会丢失 test 变量。由于Evennia的类型类是与Django模型绑定的,这可能会引发灾难。

Idmapper原本是Django网站的内存节省器。在网站中,对对象的访问是简单短暂的,这不太适合我们。所以我们扩展了Idmapper,让它不丢弃对存储对象的引用(不这样做会引发长久的、难以发现的bug)。

Idmapper是一种按需缓存,它只会缓存实际访问过的对象。虽然可以通过模型上的方法清除Idmapper缓存,但这并不一定表示它的内存会被释放,这取决于系统中是否有其它未释放的引用(这是Python引用计数的工作方式)。如果你需要清除Idmapper缓存,最安全的方式是软重载服务器(比如通过 @reload 命令)。

大多数开发者不需要关心Idmapper缓存,它只是让模型直观地工作。在多数情况下,它被看到只是因为Evennia的许多数据库模型是从 src.utils.idmapper.models.SharedMemoryModel 继承的。


对象上变量的缓存

在Evennia中,所有对象的所有数据库字段都是通过使用Python属性来缓存的。所以当你执行 name = obj.key 时,实际上不会直接访问对象上的数据库字段“key”,你只是在访问一个处理程序。该处理程序会读取字段 db_key,并调用 server.caches 模块将它的值缓存起来以备下次使用。

按照一致的名字方案,指定的字段属性 obj.foo 通常是数据库字段 obj.db_key 的处理程序。属性处理程序的方法总是名为 obj.foo_get()、obj.foo_set() 和 obj.foo_del()(并不是全都需要这些方法)。

除了缓存,属性处理程序还提供l了另一种功能,它们隐藏了对 Django 的管理。所以执行 obj.key = "Peter" 不只是将字符串“Peter”分配给数据库字段(并缓存),它还会为你调用 obj.save() 以更新数据库。

将模型字段隐藏起来给开发者造成了一个麻烦,那就是难以用标准的 Django 方法进行搜索。总的来说,如果你要用标准 Django 的 filter 方法来搜索,你就必须提供 db_key,而不是 key。Django 只知道前者,对于后者则会报无效字段的错误。如果你用Evennia自己的搜索方法就不必为此担心了,它们会在幕后为你搜索正确的东西。


类型类缓存


所有的类型类都缓存在数据库模型中。这样就可以通过 dbobj.typeclass 快速访问类型类。在幕后,会从 db_typeclass_path 存储的路径中导入类型类的定义(通过属性处理程序 typeclass_path 获得)。所有的检查和最终调试信息都会被处理,然后缓存最终结果。

缓存会发生异常的唯一情况是,类型类模块中有语法错误或其他导致程序终止的bug,此时会改为加载默认的类型类(定义在 settings 中)。错误会被汇报,而且不会进行缓存。这是为了能在下次尝试的时候重新加载类型类,直到问题被修复。


对象上通用属性的缓存


在对象上查找过的通用属性也会被缓存。这意味着在第一次访问或赋值之后,obj.db.attrname 的访问速度就和其它普通Python属性一样快了,这样可以避免在读取属性时再次检索数据库。在这方面 db 和 ndb 的工作方式相同。

由于在通用属性中可能会存储多个对象,系统无法缓存通用属性中数据的值(这可以让系统不必检测已存储的对象是否已在其它地方删除,而此时它仍然可以通过属性访问)。所以在每次读取时都会重新访问这些对象,会造成轻微的速度损失。


(原文:https://github.com/evennia/evennia/wiki/Caches    翻译:卢铱俊)