关于ets和mnesia的一些牢骚话

ErlangETSMnesia性能 by 达达 at 2012-07-12

要睡觉了,手机上看到微博上网友cc给我一条关于mnesia的消息,内容如下:

意中人:  将Mnesia的读写操作分别封装到两个gen_server中,这样达到的效果:
1、只有两个进程对Mnesia进行实际操作,
2、当需要Mnesia中的信息的并发量增多时,也不会出现Too many db tables的情况,
3、使用事务时,很难出现Mnesia is overloaded的警告,
4、read使用call模式write使用cast模式,实现伪读写分离

有兴趣参与这个话题的讨论,微博上又不方便长篇大论,于是就起身写了这篇文章。

关于这位网友的设计思路,我这里说下可能存在的问题吧。

erlang的进程是公平调度的,并发很高的业务交给少数进程去处理是不合适的,在C/C++可以设计由少数工作线程处理所有业务,在erlang里面这样做很容易就出现性能瓶颈,这点我在早先神仙道的广播机制上吃过亏的。可以很确定的说,erlang程序的设计原则就是能并行的就并行。

并且如果我没记错的话进程间消息发送接收的效率是比ets表操作慢很多的,并且消息发送和接受导致进程上下文切换量也会很大,所以把所有内存数据操作都交到少数几个进程处理是挺有风险的设计。

通过阅读代码可以看到mnesia其实是开了一张临时的ets表来存储事务的过程数据的,所以才会在高并发的时候出现too many db tables错误。

并且mnesia的事务处理是交给单个进程修改数据的,所以并发高的时候gen_server响应不过来,就出现overload警告了。

mnesia的设计方式导致读数据出现两种方式,普通读和脏读,普通读会把读取的数据跟事务表里的数据做下合并再返回,而脏读则是不合并就返回,可能读到脏数据。要保证数据完整性,必然要用普通读,但是这要在每次读取操作都额外消耗性能。

神仙道的设计思路是按玩家操作内存数据,每个连到游戏服的玩家都有自己进程,这些进程只管自己的数据,需要影响别人的时候就给对方进程发消息,这样的结构在erlang里面实现起来很自然。

按玩家操作数据使得内存数据库的事务只需要按玩家进程来处理,所有事情都是顺序发生的,不存在并发修改同一条数据的可能,也就不再需要全局性的事务控制,也不怕脏数据了。

mnesia是一个很通用的数据库,没办法让我们自己控制事物的颗粒度,也就没办法实现按玩家来执行事务。所以我是用ets自己构建内存数据库的,其实要做的并不多,关键就是事务和数据回写。

最近看云风的文章,似乎他们新项目的思路也是类似这样做法,虽然语言不一样,但是设计出发点是一样的,就是尽量让可以并行执行的事情并行的执行。

当然,就像mnesia的设计者不可能了解和满足所有人的需求一样,我提出的思路也不是适用于所有erlang项目的,每个项目的优化点在哪里,只有参与其中的人最为清楚,像上面这种优化就是从业务的特点上特事特办的优化,希望对大家有一定的启发作用。