« September 2013 | Main | November 2013 »

October 30, 2013

skynet 中 Lua 服务的消息处理

最近为 skynet 修复了一个 bug ,Issue #51 。经查,是由于 redis driver 中的 batch 模式加锁不当造成的。

有同学建议把 batch 模式取消,由于历史原因暂时还保留。在很多其它 redis driver 的实现中也不提供类似机制。也就是依次提交多个数据库操作请求,不用等回应,最后再集中处理数据库返回的信息。

我的个人建议是在目前的 redis driver 基础上再实现一个独立服务,里面做一个连接池,让系统不同服务对数据库的读写工作在不同连接上,这样可能更好些。如果简单的实现一个数据库代理服务而不采用连接池的话,可能会面对一些意想不到的情况。

这是由 skynet 的 lua 模块工作方式决定的,下面解释一下。


skynet 中的不同服务是利用系统的多线程完全并行的。当你从服务 A 向 B 和 C 分别各发送一条消息时,并不能保证先发的消息先被处理。

而当你从服务 A 向 B 依次发送两条消息,那么先发的消息一定会被 B 先处理。

用 lua 实现的服务只是一个内嵌了 Lua 虚拟机的服务,也遵守上面的规则。但目前的实现中,由于使用了 lua 的 coroutine 机制,问题变得更复杂一些了。

如果 B 是一个 lua 服务,当 A 向 B 发送了两条消息 x 和 y 。skynet 一定保证 x 先被 B 中的 lua 虚拟机收到,并为 x 消息生成了一个 coroutine X ,并运行这个 coroutine 。然后才会收到消息 y ,重新生成一个新的 coroutine Y ,接下来运行。

大多数情况下系统是会保证运行次序的。可一旦 coroutine X 中调用了skynet 提供的 socket IO 处理,或是调用了 skynet.call skynet.sleep (注:skynet.send 不会导致挂起)等会导致 coroutine 挂起的指令,那么消息处理的执行流就被暂时挂起了。

和 erlang 的 process 不同,此时 skynet 挂起的是 lua vm 中的 coroutine ,服务 B 本身是可以继续处理消息的。这个时候,一旦 y 消息抵达,一个新的 coroutine Y 就会被创建并运行。看起来,B 中就有两条执行流程并行了。

从这个意义上来说 skynet 中 lua 虚拟机里的 coroutine 才能看成是 erlang 中 process 的(不完全)等价物。lua 虚拟机,也就是 skynet 中的一个服务,提供了一个共享环境,让不同的 coroutine 之间可以共享状态。这也是很多 bug 的滋生之处。


如果这个行为让你困扰的话,那么我建议另外实现一个 lua 库,去掉 coroutine 的部分,转而实现一个类似 erlang 的 mailbox 机制来接收 skynet 的 C 层转发过来的消息会更好一些。

或者,我今天给 skynet 增加了一个叫 mqueue.lua 的库,可以给每个服务定义一个消息队列,消息队列里的消息会被依次处理。

见 testqueue.lua 以及 pingqueue.lua 可以看到基本用法。

October 22, 2013

招聘 平台开发工程师

或许真是到了招聘季了, 感谢大家的热情. 到今天 (10 月 24 日) 我的邮箱里的简历已经多的处理不过来了。所以这次招聘暂时就到这里了。

等我们公司顺利发展下去,我们会提供更多的位置让大家有个满意的环境一起工作。


因为公司发展需要,我们又招聘了:)这次的职位是 简悦 的运营平台开发。

office2.jpg

上一次招聘不同,我们的平台开发部门已经有一定规模(目前有三名开发工程师),所以不一定需要有丰富的开发经验。我们欢迎应届生或能保证工作时间的实习生的加入。

工作内容包括但不限于面向用户的网站,内部运营系统,部分运维工具。目前工作中涉及到的技术包括但不限于:

  • HTML / CSS / Javascript
  • Python /Java / Objective-c
  • HTTP / AMQP / TCP
  • MongoDB / Mysql / Redis
  • Linux / iOS / Android

这个职位并不需要对提到的技术都熟悉,尤其对于没有工作经验的同学无相关经验要求。计算机基础知识扎实,能快速学习,愿意做一个一专多能的全端工程师即可;对于已经工作的同学,最好至少有一项技术非常优秀。

有兴趣加入我们的同学,可以 email 和我联系。如有可能,附上自己的作品或列出自己参与过的项目,开源项目更佳。

工作地点在广州天河区。公司雇有厨师,有自己的食堂提供所有人的中餐和晚餐。早上十点上班,一天八小时工作,周末节假日正常放假。

October 10, 2013

D 语言的数组和字符串

这个国庆假期,我读完了《D程序设计语言》 一书。里面读到了很多有趣的东西,挑一点写出来和大家分享一下。

字符串,数组和关联数组(hash 表)是最重要的三种数据结构,我们几乎可以利用它们模拟出任何更复杂的结构。Lua 就是这么干的,只不过 Lua 把数组和关联数组合并成一个 table 类型了。D 在语言层面对这三种数据结构支持的很好,概念定义非常清晰。这一篇只谈数组和字符串,不涉及 hash 表的部分。


数组可以看成是存放同一类型数据的连续内存。

在 C 语言中,数组和指针虽然是不同的类型,但编译器生成的代码却是相同的,可以说实质上,数组即指针。但将数组隐含有长度信息,即内存的范围。有些数组是固定大小的,在编译器就知道其范围;有些数组需要动态扩展大小,其范围是运行期确定,并可以改变的。无论如何,对数组的随机访问,缺乏边界检查的代码都隐藏着风险。

D 语言是一门期望有高安全性的同时又重视运行性能的语言。它在平衡这个问题上的解决方案很有趣。程序员可以指定一段代码是安全的,还是系统级的,还是是接口安全的。根据不同的标注来插入边界检查代码。在 debug 版中,即使是系统级代码,也会插入类似 assert 的契约检查。

由于 D 语言以 GC 为内存管理核心(且要求所有数据都是位置无关,可移动的),所以管理数组切片 Slice 就变得很简单。不同的Slice 引用同一块内存,不用担心数据生命期问题。扩展数组也可以根据需要重新分配内存,或是在原地扩展。

提到数组扩展,不得不谈一下 D 语言中结构的 postblit 。D 语言中,所有的 class 都是引用语义的,而 struct 是值语义的。C++ 中花了很多年想解决的一个性能问题就是源于 vector 扩展时,数据如何从旧的位置移动新位置的问题。在 stl 的 sgi 实现中,为 POD 结构增加的特化模板来提高复制效率;在 C++11 中又从语言层面增加了右值引用来实现移动语义,来解决反复析构构造对象带来的性能浪费。

而 D 语言中没有那些晦涩的移动构造,拷贝构造概念;它只有 postblit 。也就是数据都应该默认按位复制(blit),然后在 blit 后,再用 postblit 方法去修改新的副本。这种不动源对象,而只在副本上修改的移动钩子技术概念更简单清晰。而且编译器可以自行推导什么时候调用 postblit 才是必要的。这个技术不仅仅用来解决数组的扩展问题,也可以很好的搞定 C++ 中返回值优化问题。

对于固定大小的数组,D (2.0) 是按值类型处理的(动态数组则是引用类型),不同长度的数组是不同的类型,但它们都可以隐式转换(映射)成动态数组。比较短的固定数组做值传递的时候更方便高效,也符合其它基础类型的特征。长数组可以通过 ref 修饰按引用传递。


D 语言的有趣之处在于 string 其实就是一个不可变的动态数组,string 其实是 immutable(char) [] 的别名。它也同时支持 wstring 和 dstring 。而其它许多类 C 的语言,比如 C# java go C++ 等,string 则是一个独立的类型。

但 D 语言也为 immutable(char) [] 做了些特别的东西。首先编译器支持了很强大的字符串自面量的描述语法。不光是 C 语言已经支持的那些,还支持 2 进制表示 (0b10111 这样的),可读的 16 进制表示 ( x"7f 00 01" 这样的 ) , 所见即所得风格(取消转义)的字符串等等。

D 语言把 Unicode 作为其标准字符集,且默认选择 UTF-8/UTF-16/UTF-32 做默认编码。一个比较奇特的地方是 foreach 对 string 的迭代:

  string str = "中文";
  foreach (c; str) {
    ...
  }

如果这样处理中文字符串,这里中文被编码成 UTF-8 ,但 str 是 string 类型(而不是 wstring 或 dstring),foreach 会自动推导 c 这个变量的类型为 char ,然后按字节而不是按字逐个取出字符。

可一旦你这样写:

  string str = "中文";
  foreach (dchar c; str) {
    ...
  }

指定了 c 的类型为 dchar ,那么 foreach 就可以正确的按 UTF-8 规则分割字符,把中文按字取出来了。

由于 string 本质上是 immutable(char) [] ,一个引用类型。所以传递 string 非常廉价。immutable 变量还可以自由的跨线程共享。GC 可以保证 string 的生命期被正确的管理。

数组的 == 操作被改写过,所以可以正确的按值比较。字符串也是数组,所以 == 操作也能按字面意思正确运行。在 D 语言中,数组/字符串也可以用于 switch 语句,这非常的方便。如果真要比较两个字符串是否是同一个变量,D 语言提供了 is 比较操作来比较是否引用着同一个对象,而不像 C# 那样,你需要调用库函数 Object.ReferenceEquals(Object obj1, Object obj2) 。


ps. 尽管数组在 D 语言中足够高效,但依然提供了裸用指针的方法。只需要用 .ptr 取出数组的指针即可。但语言不再提供任何的安全性保障了。这并不推荐,而且你可以制定用 D 语言的一个子集 SafeD 做开发,这样大部分指针操作都被编译器禁止了。