每周回顾:直觉的力量

直觉(英语:Intuition),又称为直观,通常被描述为,一种不用经过太多思考过程,很快就能出现的直接想法、感觉、信念或者偏好。当我们有某种信仰,但是不确知它的原因时,通常会将它归于是一种直觉。认知科学认为这是因为生存的演化压力而产生的人类心智能力,让人类可以快速做出判断,采取行动,它通常与右脑连结在一起。心理学与超自然学对这个能力深感兴趣。(以上内容来自维基百科)

有很多次,在我向新人演示或者帮助Ta解决宕机或BUG的时候,他们都会用诧异的眼神看着我,一脸疑惑地问道:“这么快地定位到问题,你是怎么做到的?”,然后我很装逼地对Ta说:“直觉”。当然,相视一笑之后,我还是会整理一下思路,然后解释一下推理过程。

可以快速解决问题的直觉,并非朝夕之间就能获得的。至今做了七年服务器端开发,遇到过各种各样的宕机和BUG,凌晨三四点被叫到公司家常便饭,也就仅仅获得了针对我们游戏服务器出的问题的一些直觉而已,多数时候还是挺准的。每次当我遇到一些疑难问题的时候,我心里都会默默在念:Every thing happened has a reason。事出必有因,现在没有找到原因,只是因为对问题的认识和分析还不够,考虑的还不够全面,应该分析所有可能的已知因素。就这样,你必须熟练并理解手头的各种工具和代码。正所谓熟能生巧,见得多了,理解的深了,在别人看来很难的问题也不是问题了,这是一个持久的过程,需要大量的时间和机会,当然征服问题的信念也是很有必要的。

当一个BUG/宕机出现的时候:

  • 首先要弄清楚问题,要了解正常的代码逻辑,若是中途接手别人的代码,那就得硬着头皮去理解别人的代码。

  • 然后,思考问题出现的环节,然后针对出问题的环节仔细分析代码的每个分支,往往一些很难复现的BUG很容易在分析代码的过程中发现。

  • 最后要想到的才是复现BUG并使用调试工具进行追踪调试。

PS. 记得之前在笔试阅卷的时候,看到一个关于调试器的回答,问题是:“请写下你的调试工具及其简单用法?”,有一个同学的回答:“脑子”。开始拿来当笑话,笑过之余,想一下还是挺有道理的,脑子才是最牛逼的调试器,可以进行情景模拟,还原问题发生的各种可能性,请问GDB可以吗?哈哈。

工作篇

烦人的宕机

做游戏服务器开发的,宕机是永远的痛。话说,三个月前,自从某次维护后,只要有新服上线,在线人数过2W+,都会因同样的原因有几率宕机,当然影响范围非常小,每次可能也就几百人(相对于10W-20W而言),再加上几率比较小,其他事情比较多,多以每次看到同样的宕机文件,直接忽略了。

宕机不可怕,可怕的是宕机生成的core文件堆栈是乱掉的,一上gdb backtrace命令全是问号,这货就属于这样的。这不,上周末就又遇到了,受不了这样的精神折磨了,下定决心周一搞定这个宕机。指派了两位同学去查代码,自己抱着那个堆栈乱掉的core文件,研究了一上午,去寻找各种蛛丝马迹。到了下午,发现了一个可用的地址,各种折腾,最后找到了事发的一个回调对象,print一下这个对象,然后折腾一会儿,再print一下这个对象,“我去,我的眼睛花掉了还是怎么回事,两次print的内容竟然不一样,真是见鬼了”,咒骂了几句然后继续去折腾那个破core文件去了。时间过去了一个多小时,失望透顶啊,全部希望寄托在检查代码上了,忽然想到之前print的那个回调,为什么会不一样,然后就去代码里面看看,原来在两个不同的cpp文件里面定义了一个结构几乎一模一样的回调类,成员函数实现有所不同,结果gdb一会儿看到的是前面一个,一会儿看到的是后面一个。问题找到了,代码在链接的时候,两个回调的成员函数指向了其中某一个,两个成员函数不同的部分和当前在线人数有关系,也就是说触发条件是:编译时候链接了其中某一个;链接错误的回调被执行;在线人数满足一定条件。

切记:在不同编译单元里面若要写名字同样的类必须使用匿名名称空间,否则在链接的时候,链接到成员函数将会是排在前面的那个。

问题复现:

main.h:

1
2
3
struct CallBack {
virtual void exec() = 0;
}

main.cpp:

1
2
3
4
5
6
7
#include "a.h"
#include "b.h"

int main() {
func_a();
func_b();
}

a.h:

1
extern void func_a();

a.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "main.h"
#include "a.h"

struct CallBackXX : public CallBack {
void exec() {
std::cout << "callback: a.cpp"
}
};

void func_a() {
CallBackXX cb;
cb.exec();
}

b.h:

1
extern void func_b();

b.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "main.h"
#include "a.h"

struct CallBackXX : public CallBack {
void exec() {
std::cout << "callback: b.cpp"
}
};

void func_a() {
CallBackXX cb;
cb.exec();
}

执行”g++ -o main main.cpp a.cpp b.cpp”,输出结果:

1
2
callback: a.cpp
callback: a.cpp

执行”g++ -o main main.cpp b.cpp a.cpp”,输出结果:

1
2
callback: b.cpp
callback: b.cpp

按道理来说GCC应该报错的,我们使用的是gcc 4.4.5。

意外的收获Mongodb

最烦接手其它部门中途丢过来的东西,因为东西不是自己设计的,用到的技术和思维方式都会有差异,往往上手会比较麻烦。我们游戏平台有一个服务,产品觉得很好用,平台中心由于人手紧缺或其他原因不再进行技术支持了,基本上是废掉了,无奈我等苦逼啥都想做,啥都想接手,悲催的是接手了以后不光是我们自己项目用,其他项目也要用,那我干的事情算是为大家谋福利了啊。具体服务保密不能说,用到的技术有node.js和mongodb集群。node之前有用过,mongodb之前只听说过是一个nosql数据库,具体一无所知。

既然接手了,就得硬着头皮去学习mongodb。mongodb是一个可以替代面向对象数据库的nosql,和redis还有点不一样,它是介于key-value和关系数据库的一种数据库。里面有文档(document),集合(collection),数据库(db)等概念。一个db有N个collection,一个collection有N个document,而一个document则是一个json文本,可以表达任何基本或复合的对象。可以和关系型数据库做对比:docuemnt - rowcollection - tabledb - database

值得一提的是它强大的replSet和shard机制,搭建一个带复制、负载均衡的集群非常简单。比起redis的集群功能是要方便许多,当然redis是一个基于内存的key-value数据库,Mongodb是自动回把数据保存到磁盘的,具体性能如何,还不太确定。需要进一步使用才能给出结论。

PS. 做游戏开发的一旦染手产品或运营提的东西,那人基本上就离抓狂不远了!

学习篇

数理统计的几种分布和参数估计

继续复习数理统计相关知识,样本空间的一些概念,和关于抽样的几种分布:卡方分布、t分布和F分布。对于这几种分布的具体用法,还不是特别了解。了解了一下参数估计的几种方法:矩估计法;最大似然法;最小二乘法。学习一门学科,其实最能提起兴趣的方式就是了解它的发展历史,结合历史来学习数理统计将会是一件非常有意思的事情,找了两本相关的书:《女士品茶》和《数理统计学简史》。

机器学习的半监督学习

机器学习,按照是否有人工参与分为监督学习和非监督学习。监督学习,必须要手工标注的训练集,然后通过分类算法来进行学习;非监督学习,是指没有人工参与对数据进行聚类。往往标注数据工作量浩大,还不一定准确,标注的数据如果不具有代表性,还会产生有偏差的结果。所以,真实的机器学习往往采用半监督学习或者非监督学习,半监督学习主要是利用少量已标注的数据和大量的未标注的数据一起来进行模型的训练,往往能得到比监督学习要好的性能。非监督学习有LU学习、PU学习。大体的思路就是利用已标注的数据,来标注可能性比较高的未知类型的数据,然后持续迭代,直到算法收敛,收敛的条件是标注完所有数据或者给定一个可能性阈值。

LU指的是Labled and Unlabled,只需要一部分标注数据,包含正例和反例和未标注的数据,PU学习主要的算法有:EM算法、Co-Traing、自学习、直推式SVM和基于图的方法等等。

PU指的是Positive and Unlabled,仅需要标注出正例,然后利用这些正例和未标注数据进行学习。感觉PU学习的用处比较大,若需要识别一堆数据中的某一种类型,只需要标注你想要识别的类型,其他类型不用管。如果采用LU学习,则每种类型的数据都需要标注。

总结篇

  • 训练直觉。未知的知识通过学习获得,将记忆中的知识通过实践和不断的操练转换成为一种直觉,那么所有的问题都不是问题了。知识的广度和深度都很重要,选择你感兴趣的方向,深挖它,将它变为你的直觉,那么你就离Master不远了。

  • 适可而止。与信息和知识打交道的人,最怕的就是被其所淹没,一头扎进去出不了。有个听起来很装逼的名字,叫“分析瘫痪”,特别是那些有完美主义倾向的人,最容易犯这样的毛病。我自己虽然有这个意识,也经常犯这样的问题。什么叫分析瘫痪,是指你在做一件事情的时候,可能会牵扯到很多其他事情,然后牵扯到的一个事情有可能又会牵扯到其他的事情,形成一个事情/知识/信息网络。如果没有一个明确的目标,只顾做/学习遇到的所有东西,你就会迷失。比如,上一周,我要完成的事情是搭建平台的服务,但是它牵扯到Mongodb,这时我的目标应该是快速地搭建起这个服务,而并非深入研究Mongodb,只需大概了解一下即可。相反,坚信遇到的东西都必须要彻底弄懂,先学习一下Mongodb的用法,嗯,还不够过瘾,最好再看看Mongodb的源码,或许在你深入Mongoddb的时候,又会遇到一堆新鲜玩意,嗯,这个relpSet设计的真是精妙,这个集群简直Perfect,我再看会儿分布式相关的东西,了解一下,这下爽了,但是平台这个服务要到猴年马月才能搭建起来啊!恭喜你,如果是这样,那你就陷入分析瘫痪了。所以太好学了也不一定是好事,凡事适可而止。

David++