我现在觉得持续 coding 也不错。
最近公司开了个会,有几个地方看了有所感触。
同一物件 另一物件
┌───┬───┐ ┌───┐
│ 讯 │ 讯 │ │ 资 │
│ 息 → 息 │ │ 料 │
│ 产 │ 暂 │─→│ 写 │
│ 生 → 存 │ │ 入 │
│ 源 │ 区 │ │ 区 │
└───┴───┘ └───┘
例如说我们想 log 玩家的战斗资料,那么当玩家“进入战斗”时,
讯息产生源就送出资料给讯息暂存区,请它“暂存一条玩家已进入
战斗的讯息”,例如进入战斗的时间啦、战斗发生的地点啦、战斗
的目标啦、....。
那么可以想像在某一段时间区间内,暂存区多半会储存两条以上的
讯息。
然后当玩家“结束战斗”时,讯息产生源一样送出资料给讯息暂存
区,例如结束战斗的时间、结束战斗的地点、玩家获得的战利品资
讯等,然后暂存区就将这个玩家从战斗开始到结束的一些该写入的
讯息,汇集后送往资料写入区去写入。
但是,当玩家的战斗有不正常中止(非结束)的情况时,因为讯息产
生源一直没有触发到该玩家已结束战斗的资讯,它就不会通知暂存
区要将暂存的资料送往写入区,当这种情况很频繁地发生又没有被
察觉时,就会造成暂存区的资料量增加,直到某一天“满了”造成
明显的问题状况时才被发现,而这问题开始发生、到问题爆出来,
中间被认为有问题的战斗历程就全部没有被完整纪录。
一、传统上其实上述三个方块多半被写在同一个物件里头,那分开
的好处其实显而易见,就是“分散工作量”,因为暂存讯息需
要经过处理后才暂存,写入讯息同样也需要经过处理后才写入
,当暂存很频繁时,由另一物件负责资料写入(及回存)似乎是
比较适当的做法。
mapping buff_data=([]);
┌────────────────┐
│ mapping save_data=([]); ├──┐
└────────────────┘ │
void create() │
{ │
::create(); │
seteuid(getuid(this_object())); │
┌────────────────┐ │
│if(file_exists(SAVE_FILES+".o"))│ │ 也就是说这三个区块可挪到
│ restore_object(SAVE_FILES); ├──┼─另一个物件
└────────────────┘ │
. │
. │
} │
│
┌────────────────┐ │
│int save_room() │ │
│{ │ │
│ save_object(SAVE_FILES); ├──┘
│ return 1; │
│} │
└────────────────┘
二、针对暂存的资料,应有一额外检核的机制,比方说可以预估
玩家“不可能有n秒以上的战斗”(例如 n = 3600),那就可
设计让物件每n秒对所有的暂存讯息做一次检查,检查项目
可包括
1.该玩家物件是否仍存在(比方有可能断线或不正常离线)
2.该玩家所战斗的目标是否仍存在(比方可能物件error消失)
3.该玩家物件是否正在战斗中
4.该玩家物件战斗的对象是否就是暂存讯息所储存的对象
5.该玩家是否仍在暂存讯息所纪录的地方进行战斗
.
.
当有其中一项不符合时马上就可判定该暂存讯息已经不正常
,此时就应该做适度的资料补正后送往写入区,或是剔除该
暂存资料,并以适当的方式通知管理者处理。
更简单的判断方式还包括当暂存区是以玩家的 name 当做主
key 去暂存资料时,则当该玩家前一笔暂存资料仍存在于暂
存区、该玩家却又触发讯息产生源去产生一笔新的暂存资料
时,就可以做如下判断
if(buff_data[ppl_name])
save_error("ERROR! "+ppl_name+": "+identify(buff_data[ppl_name])+"\n");
// 然后才做新的储存
buff_data[ppl_name] = .... ; // 旧的 buff_data 会被新的覆蓋
这样管理者事后至少有迹可寻,而且资料量也可预期不会肥
胖。
三、暂存资料方式的设定也很重要,以上面为例当暂存资料是以
ppl_name 为主 key 时,相较于以其它方式为主 key 的情
况、或是不使用 mapping 资料而改采阵列资料的串流储存
方式时,是比较保险的做法。
当然判断以 ppl_name 为主 key 的方式为可行的依据,就
是一个玩家不可能在同一时间触发两场战斗,但实际上却是
可能的,比方战斗中“突然又出现新的战斗目标”,则考量
到这种情况,就得做适度的修改,例如..
buff_data[ppl_name] = ({ ({暂存一,}),
({暂存二,}),
.
. });
这样有新的战斗讯息就累加上去,有新的战斗结束讯息时
就从阵列里面去找出符合的。
总之就是资料的储存方式必须要做事前的妥善分析规划。
四、暂存资料本身的检核。假设我们有个函数叫做 buff_size
,可用来检查暂存资料本身的大小时,那就可以在每一次
的暂存资料输入、或是每一次的暂存资料写入时,就做底
下的判断:
switch(buff_size(buff_data))
{
case 80:
sent_alarm_1("buff_data 储存量已达到 80%。");
break;
case 90:
sent_alarm_2("buff_data 储存量已达到 90%。");
break;
case 95:
sent_alarm_3("buff_data 储存量已达到 95%。");
break;
}
但是这毕竟不如主动式检核来得好,因为通常等到 80%
的警告讯息出现时通常情况就已经很严重了,而告警容量
订得太低则会经常收到告警“但其实系统正常运作”。
但它还是可以写,只是当做一个备用的告警就好,平常就
要注意不要让它 init 到告警值。
五、最后就是该系统运作状态的观看接口,不论是写成指令式
或是选单式,重点都在于要能即时知道系统目前的运作状
态甚至纪录的讯息详情、特定资料的搜寻、比对等等。
以上亦会做为个人在 sanc 的 coding 参考,当然实际上依照
需储存的资料内容来说,大部份的资料其实都不太有资料满溢
的情况,这是因为在系统建置之初就已考量好资料储存的格式
及内容范围,也就是先做好事前的预防,自可避免事后的问题
,但即便思虑再周详,也不能完全保证系统运作一段时间后不
会产生其它问题,因此最好还是在初期就把相关的检核机制也
一并加进去做把关,这样至少可更确保系统的稳定运作。
但是有个两难的问题就是,有时候也可能因为检核写的太优良
,导致“系统即便遇到问题还是能持续运作,但是该问题也同
时持续存在”的情况发生,而可能在某一个时间点导致更大的
问题状况出现。在漫画《无敌怪医》里面就有这样的情节,人
工肝脏一般都有产生血栓的问题,导致刚被植入到生物体内没
多久就出现血栓,但某人发明的人工肝脏“却因太过优秀”,
导致植物入生物体内后经过快半年都没出问题,某人就打算进
行人体实验,然后不幸的就是在人体实验前几天,被植入人工
肝脏的生物此时才出现血栓症状,而导致人体实验必须中止..
so,总之这也需要经验的判断。
Laechan@Sanc