April 15, 2014

对 skynet 的 gate 服务的重构

由于历史原因,skynet 中的 gate 服务最早是用 C 写的独立服务。后来 skynet 将 socket 的管理模块加入核心后又经历过一次重构,用后来增加的 socket api 重新编写了一遍。

目前,skynet 的各个基础设施逐步完善,并确定了以 lua 开发为主的基调,所以是时候用 lua 重写这个服务了。

如果是少量的连接且不关心性能的话,直接用 skynet 的 lua socket 库即可。这里有一个例子

gate 定位于高效管理大量的外部 tcp 长连接。它不是 skynet 的核心组件,但对于网络游戏业务,必不可少。

阅读全文 "对 skynet 的 gate 服务的重构" »

April 02, 2014

lua-conf 让配置信息在不同的 lua 虚拟机间共享

有时候我们的项目需要大量的配置表(尤其是网络游戏) 。因为主要用 lua 做开发,我们倾向于直接用 lua table 保存这些配置常量。

海量的数据有两个问题:

这些配置数据在运行期是不变的,但树型结构复杂,放在 lua 虚拟机内会生成大量的 gc object ,拖慢 lua 的垃圾收集器。因为每次扫描都需要把所有配置数据都标记一遍。

在服务器端,我们使用 skynet 框架,会启动数千个 lua 虚拟机。如果每个虚拟机都加载一份配置信息,会带来大量的内存浪费。

阅读全文 "lua-conf 让配置信息在不同的 lua 虚拟机间共享" »

April 01, 2014

内存安全的 Lua api 调用

Lua 的 API 设计的非常精良,整个 lua 核心库把内存管理都托管给了 lua_Alloc 这个用户注入的函数。任何时候在发生内存不足,lua 的 api 都可以正确处理异常。

考虑一下 lua_newtable 或是 lua_pushlstring 这些 api ,它们都需要创建新的 gcobject ,这些时候如果发生 lua_Alloc 分配不出内存怎么办?这些 api 可都是无返回值的。

lua 的行为是:抛出一个内存错误,如果外界没能捕获这个错误,则触发 panic 函数。

在编写将 lua 嵌入到宿主程序中的一个常见的错误是:

先用 lua_newstate 创建出一个 lua 虚拟机,然后直接调用 luaL_openlibs 等函数初始化它。如果你希望你的代码足够严谨,就必须了解,初始化的过程是有可能遇到内存申请不到的情况的。

正确的做法在 lua 自带的解释器实现中有一个很好的范例:你可以写一个 lua c function ,在里面做后续对 lua_State 的操作。在 lua_newstate 后,立刻 lua_pcall 这个 C 函数,而不是直接调用它。这样,所有的内存异常都会被这次 pcall 捕获住。

btw, 早期版本的 lua 有一个 lua_cpcall 函数,自从 lua 支持 light c function 后就去掉了这个 api 。

阅读全文 "内存安全的 Lua api 调用" »

March 27, 2014

在不同的 lua vm 间共享 Proto

在 skynet 这种应用中,同一个系统进程里很轻易的就会创建数千个 lua 虚拟机。lua 虚拟机本身的开销很小,在不加载任何库(包括基础库)时,仅几百字节。但是,实际应用时,还需要加载各种库。

在 lua 虚拟机中加载 C 语言编写的库,同一进程中只会存在一份 C 函数原型。但 lua 编写的库则需要在每个虚拟机中创建一份拷贝。当有几千个虚拟机运行着同一份脚本时,这个浪费是巨大的。

我们知道,lua 里的 function 是 first-class 类型的。lua 把函数称为 closure ,它其实是函数原型 proto 和绑定在上面的 upvalue 的复合体。对于 Lua 实现的函数,即使没有绑定 upvalue ,我们在语言层面看到的 function 依然是一个 closure ,只不过其 upvalue 数量为 0 罢了。

btw, 用 C 编写的 function 不同:不绑定 upvalue 的 C function 被称为 light C function ,可视为只有原型的函数。

如果函数的实现是一致的,那么函数原型就也是一致的。无论你的进程中开启了多少个 lua 虚拟机,它们只要跑着一样的代码,那么用到的函数原型也应该是一样的。只不过用 C 编写的函数原型可以在进程的代码段只存在一份,而 Lua 编写的函数原型由于种种原因必须逐个复制到独立的虚拟机数据空间中。

阅读全文 "在不同的 lua vm 间共享 Proto" »

March 23, 2014

Skynet 新的 socket.channel 模式

大部分外部网络服务都是请求回应模式的,skynet 和外部数据库对接的时候,直接用 socket api 编写 driver 往往很繁琐。需要解决读取异步回应的问题,还需要正确处理连接断开后重连的问题。

这个周末,我试着给 skynet 的 socket 模块加了一个叫做 channel 的模式,用来简化这类问题的处理。

可以用 socket.channel { host = hostname, port = port_number } 创建出一个对象。这个对象用来和外部服务器通讯。

阅读全文 "Skynet 新的 socket.channel 模式" »

March 11, 2014

linode 广告时间

这是一篇关于 VPS 虚拟主机服务的广告。

经营这个 blog 的 9 年间,我收到过十多次 email ,希望可以在我的 blog 上挂上文字广告条,都被我拒绝了。我不喜欢广告,但推荐觉得不错的东西是例外。

4 年前我把 blog 服务从网易的服务器迁到 linode 时,我写了一篇 blog 。没想到之后 3 年多仅靠推荐就支撑了 vps 的开销。

前天收到 linode 新账单,发现很久没有推荐新用户了(每推荐一个新用户可以有 20 美元的回扣),账户里已经花的差不多。我希望可以保持让这个 blog 自己负担起它的 vps 费用,所以重新写一篇吧 :)

如果有朋友也想租用 vps ,可以 点这个链接 或者或者在 linode 填我的 referral code : 538bab39bc1265a2ce54115d1f86e2bc81e4d133 。

在国内,个人租用虚拟主机必须使用海外的服务,相信对于我的读者来说是一个常识。即使你不谈政治,也希望有一个不受约束的自由发言地,对吧?即使不写点什么,一个稳定的翻墙通道、一个可以随意放点文件的网络储存地都是有必要的。出差到外地,不清楚当地的 wifi 是否安全,最好的方法就是让自己的设备先 vpn 到自己的 vps 上,这样可以大大减少被劫持的几率。

linode 是老牌的 vps 提供商,他绝对不是最便宜的,但却是我知道的最好的。我用的 20 美元一月的服务,用国内的信用卡(比如招商银行的)就可以买单。这些年用下来没有出过什么问题,我的虚拟主机每次重启后都是几百天的稳定运行时间。重启往往是因为自己想把操作系统升级。或是平均两年一次的机房调整(通常伴随着硬件升级),那都是提前通知,自己登陆到控制面板一键完成的。

我对 linode 的客服相当满意,提过几次 ticket ,无论是什么时间,都在 5 分钟内就有人工回复。通常一两次交流就搞定问题了。提 ticket 基本都是换机房。一开始我在美国机房,后来发现日本开了新机房速度相当快就要求换过去。后来日本 linode 的 IP 大批被墙,又换回美国。这几次折腾都是几分钟搞定。

linode 的带宽根本用不完。记得一开始似乎是 100G 一个月,现在已经加到了 2T 。平常翻墙看看 youtube 也只用的了一个零头。我刚开始用的时候,空间是给的 16 G ,现在已经加到了 48G 。8 个 CPU 核心 1G 内存对于个人用户绰绰有余了。

友情提示:域名注册也千万不要选用国内的服务商。迁移不方便,更逃不过备案等等烦心事。我从万网迁到 name.com 后非常满意,供参考。

March 04, 2014

谈谈陌陌争霸在数据库方面踩过的坑( Redis 篇)

注:陌陌争霸的数据库部分我没有参与具体设计,只是参与了一些讨论和提出一些意见。在出现问题的时候,也都是由肥龙、晓靖、Aply 同学判断研究解决的。所以我对 Redis 的判断大多也从他们的讨论中听来,加上自己的一些猜测,并没有去仔细阅读 Redis 文档和阅读 Redis 代码。虽然我们最终都解决了问题,但本文中说描述的技术细节还是很有可能与事实相悖,请阅读的同学自行甄别。

在陌陌争霸之前,我们并没有大规模使用过 Redis 。只是直觉上感觉 Redis 很适合我们的架构:我们这个游戏不依赖数据库帮我们处理任何数据,总的数据量虽然较大,但增长速度有限。由于单台服务机处理能力有限,而游戏又不能分服,玩家在任何时间地点登陆,都只会看到一个世界。所以我们需要有一个数据中心独立于游戏系统。而这个数据中心只负责数据中转和数据落地就可以了。Redis 看起来就是最佳选择,游戏系统对它只有按玩家 ID 索引出玩家的数据这一个需求。

我们将数据中心分为 32 个库,按玩家 ID 分开。不同的玩家之间数据是完全独立的。在设计时,我坚决反对了从一个单点访问数据中心的做法,坚持每个游戏服务器节点都要多每个数据仓库直接连接。因为在这里制造一个单点毫无必要。

根据我们事前对游戏数据量的估算,前期我们只需要把 32 个数据仓库部署到 4 台物理机上即可,每台机器上启动 8 个 Redis 进程。一开始我们使用 64G 内存的机器,后来增加到了 96G 内存。实测每个 Redis 服务会占到 4~5 G 内存,看起来是绰绰有余的。

由于我们仅仅是从文档上了解的 Redis 数据落地机制,不清楚会踏上什么坑,为了保险起见,还配备了 4 台物理机做为从机,对主机进行数据同步备份。

Redis 支持两种 BGSAVE 的策略,一种是快照方式,在发起落地指令时,fork 出一个进程把整个内存 dump 到硬盘上;另一种唤作 AOF 方式,把所有对数据库的写操作记录下来。我们的游戏不适合用 AOF 方式,因为我们的写入操作实在的太频繁了,且数据量巨大。

阅读全文 "谈谈陌陌争霸在数据库方面踩过的坑( Redis 篇)" »

Misc

Categories

Archives

Recent Comments