« October 2015 | Main | December 2015 »

November 30, 2015

RPC 之恶

起因是最近有人在 skynet 邮件列表里贴了段错误 log ,从 log 显示,他在 table.sort 的比较函数里调用了 skynet 的 snax rpc 去获取远程数据。然后被 lua 无情的报了 attempt to yield across a C-call boundary 。

就 table.sort 不能 yieldable 的问题,其实在 lua 邮件列表里讨论过 。老大的说法是,这个 C 实现是递归的,想要在 C 层面保留上下文非常困难,如果勉强实现,也会大大降低正常不需要 yield 的 case 的性能,非常不划算。

通过这件事,我反而觉得 none-yieldable 的限制反而提前阻止了一个错误的实现,其实是应该庆幸的。

最容易发现的是性能问题。

在《Unix 编程艺术》一书 7.3.2 节讨论 RPC 时,这样批评道:

“ a. 无法准确预估出一个指定调用会涉及多少数据的列集和散集,b. RPC 模型往往鼓励程序员把网络交易视为无成本行为。即使在某个事务处理接口上只额外增加一个来回,往往也增加足够多的网络延迟,完全抵消了解析或列集的开销。”

这段话的语境是把 RPC 和传统的消息通讯方式相比较,针对那些认为 RPC 的效率更高的观点说的。

即使不看效率,单看 RPC 给编程实现带来的方便性,其实最终是增加了,而不是减少了系统的复杂度。原文引用:

“支持 RPC 的主要理由同时恰恰证明了 RPC 增加了,而不是降低了程序的全局复杂度。”……(那些基于 RPC 的框架) “随着人们使用经验的增长,都已经从 Unix 世界的视野中消失了。这完全可能是因为这些方法能够解决的问题还不如它们引发的问题多。”

关于 RPC 的批评,还可以看这篇 paper A Critique of the Remote Procedure Call Paradigm


snax 是对 skynet api 做的一个 rpc 封装,原意是让使用的人门槛更低。但做完我就后悔了,rpc 只是表面上简单而已,但我自己却直觉的排斥这种做法,所以也没有把它作为 skynet 其它基础组件的基础。当用户开始以 RPC 方式调用时,他真的有一种不就是调了个函数的错觉。

针对 table.sort 这个比较函数里做 RPC 调用这件事说,对 n 个数据排序,table.sort 使用的是传统的快速排序,一种内部排序算法。它注重的是减少比较次数,而视获得数据的成本为 0 。在排序过程中,每个元素都不只一次的取出来比较。而 RPC 获取元素的成本显然是很高的,所以初看来,这里造成了严重的性能问题。

但如果仅仅是性能问题倒还好,可这样做其实正确性也无法保证。

如果要排序的元素在远程,也就是在另一个地方变化(如果是不变量也不必储远程获取了)。那么显然排序过程中,这些量是易变的,也就是你多次取同一个元素是可能不一样的。这样,sort 算法无法避免出现 a > b , b > c , 而 c > a 的情况出现。排序算法是无法正常工作的。


用一个通俗点的例子:如果你想按全国所有中学的学生人数对学校做一个排序。你是先把所有学校的人数都做一个调研,记在一起再排序;还是对这些信息一无所知的情况下,先按排序算法流程走,算法要求比较某两个学校人数的时候,再去调查这两个学校的数据?

November 16, 2015

skynet 中实现一个 crontab 的方法

很多人问起,为什么 skynet 的时钟不随系统时钟变更。我的答案是,skynet 系统保证内部的计时机构是单调递增的,有了这个约束,用起来可以回避很多问题。

那么怎么做一个定时任务,好像 crontab 那样,而且可以随系统时间调整而变化。我的答案是,这是个纯业务层的需求,你爱怎么做怎么做,根据你的需求(业务量多少,定时是否频繁等)可以有不同的实现方式。但是总而言之,你需要在 skynet 里定制一个服务,可以按时驱动某种消息。而这个服务的定时机构,不应该完全依赖于 skynet 自己的内部时钟。

如果你的任务并不多,我可以描述一个最简单的实现方法:

在这个服务中,可以维系一张无序表,里面放着未来什么时候将触发什么任务。这些任务是其它位置注册过来的,任务只需要是一个独立的字符串即可,也可以是一个任务名带一个时间,比如 “9 点领奖” 这样。到点可以把这个串发送给订阅者。

串中带上时间的好处是,万一哪个环节出了问题,订阅方可以自己去重,丢弃那些已经做过的任务。

这样使用的流程就是,任务管理器向此服务注册一系列任务,约定每个任务的串。任务执行者去这个服务订阅对应的串,一旦到时间,就会获得通知。

这个服务可以使用一个 skynet 内置的 timeout 回调来触发任务。每个注册的新任务都比较一下历史所有任务,如果时间更进则注册一个新的 skynet.timeout 回调。而每次收到 skynet.timeout 提醒时,都检查一下最近的那个任务是否可以通知出去,如果发送完通知,则在剩下的任务中查找最近的一个。

这个算法有 O(n) 的复杂度,但实现会常简单。鉴于 n 通常都不大,我们用简单的方法就够了。

该服务还可以支持和系统时间同步。skynet 目前的机制是 skynet.now() 返回的是进程启动的时长,而 skynet.starttime() 返回的是进程启动的 UTC 时间。如果支持和系统时间同步,或是直接设置一个虚拟的时间,我们就只是修正一下内部记录的时间基点而已。服务的通知机制则和内部时间基点加起来做任务时间就可以了。

另外,还可以支持从外部(比如监听一个 socket 端口,或是 http 端口)直接发布一个任务,而不需要自己等待时钟。这样,你完全可以在外部系统的 crontab 中写好定时触发的任务,用命令行向 skynet 进程发布。

有了外部驱动的能力,你就不需要自己再重新发明轮子,再造一个 crontab 了。要知道实现一整套 crontab 兼容的 DSL 还是有一些工作量的。


最后,请警惕的是,如果你的 skynet 进程需要开上几周甚至几个月。skynet.now() 长期看是不可靠的。因为硬件时钟本身就不会特别准,系统为了保持一个长期稳定的时间,也是靠定期去网络对时实现的。而 skynet 进程一旦启动,就没有机会对时了。所以,如果你的进程需要工作很长时间的话,不要直接依赖 skynet.now() 来做长周期的定时任务。尤其在多台机器组成集群的架构下,还有可能多台机器长期工作时钟快慢不一致。

参考: Why is Ubuntu's clock getting slower or faster?

11 月 18 日 补:

之前 skynet 使用了 CLOCK_MONOTONIC_RAW 不会被 ntp 调整时钟频率影响,所以会导致长时间运行和真实时间不一致。其实改成 CLOCK_MONOTONIC 就好了。即可以保证时间连续性,又可以由 ntp 校对时间。

修改之后,就不会再有这个问题。

November 15, 2015

闲扯几句移动社交软件

我不用微信, 不过见过大概是什么样子。以前用过 icq/qq/gtalk ,现在也用的少了。目前流行的社交平台,也就是微博和 twitter 用一下吧。今天有些想法,闲扯几句。

我觉得,作为现在安装在手机上为主的社交用软件,主要应帮人解决的基本社交需求是:

  1. 给特定的人发特定的消息,或两人间讨论问题。这一点,以前手机短信就可以作到,只不过现代软件更可以发语音、视频、图片、地理位置等等,完善了这个用途。在现代软件普及之前,email 是一个对短信非常好的补充,可惜还不够普及。

  2. 建立新的社交关系。可以是社交场合找到陌生人,也可以是由熟人引见新朋友,还可以是自我推销。在过去只用手机做主要联系方式的时代,很多人利用名片完成这一点。也可以是向人打听手机号码。然后把新社交关系延伸到 1 里。

  3. 就某些话题在半公开场合一群人聚在一起扯淡。过去的网络论坛,后来的 qq 群,微博、微信,都满足了这个需求。这里提一下微博和 twitter 的主要区别之一,就是微博可以在一个主信息下,一堆人围绕着 po 主进行扯淡,这种信息组织方式和 twitter 很为不同。一开始我总觉得这种设计有些问题,但一直没想太清楚。

  4. 个人对外发布信息。一部分信息会引导为 3 ,另一部分仅仅是发布而已。

就需求 1 来说,点对点消息在通讯比例中应该是越来越少的,但却必不可少。我认为微信在这方面是走了回头路。

其实过去手机短信就已经足够好了,你只要知道对方的号码,无论对方是否确认,都可以发送信息。email ,twitter, 微博皆是如此。这才是最自然的需求。而微信和 qq 一样,都必须先建立其相互确认的好友关系才行(公众号的问题后面再谈)。

确认无非是为了减少垃圾信息的干扰,但今天,手机短信,email 的垃圾信息骚扰的根本并不在于无法过滤,而是其开放造成的。如果是腾讯这种封闭平台,很容易过滤掉垃圾信息才对。email 和短信的第 2 个问题是,如果你收到一个陌生人发来的信息,对方可能知道你是谁,而你不知道他是谁。而这个,完全可以通过账户的自我介绍,以及社会中的多数人给账号贴的公开标签来完成。这个标签当然也包括类似,垃圾信息制造者(广告)等,帮助接收者过滤信息。

我想一个更好的做法应该是,无论谁,只要和你建立了某种联系,并不需要通过你的确认就可以给你发送信息。这并不应该加了双向关系确认后才可以。而对于垃圾信息,应该有一种合理的举报和过滤(黑名单)机制来减少对人的干扰。

所以我们并不需要刻意的去维护一个好友/联系人名单。系统自然可以根据信息来往和社交关系分析,排列出和你关系密切的人,自然形成一个名单。我们在自己的设备/账号中,只需要保留少量关系密切的人的联系入口,等需要的时候,可以根据名字或标签,在自己的社交历史中搜索直接或间接的关联人就够了。

而在收到陌生消息后,我可以查看这个人是谁,以及他是如何关联到我的。对于亲密关系的人,消息应该排在优先位置。而当我被信息轰炸时,一些不紧要的消息会被系统暂时藏起来,在大量重要信息处理完后,才显示给我看。只有双方来回交流后,系统才自动给联系人升级,而不是依赖用户自己去给对方提权。

所以,联系人名单是系统动态维护的。暗含着权值,却不用告之用户。他到底是在某次聚会中当面互扫 QR 码认识的?还是被我的熟人引见的?还是通过六度关系搜索来的?在对方资料中都可以从系统查到。

这样便可以让用户放下维护联系人名单的心理负担了。


关于 qq 群,我一直觉得是信息骚扰超过了它的实用价值。就是一群人聚在一起扯淡嘛,这个当然需要。但是我们是不是应该关注一下更本质的东西。怎么对系统分类,并协助用户过滤。

所以我觉得扯淡还是需要一个话题开始,以这个话题为结束。微博、微信朋友圈大约都是这个意思。但我觉得远不够。一群人扯淡并总不需要围绕同一个话题,也不需要一个严格的管理者和发起者。仅仅只是一帮人可以在一起扯,随时都有人退出和加入。发起话题和召集这群人的人并不是什么特权用户。

我把它看成是一个临时的,有主题的聊天室或论坛帖。任何人都可以发起,然后围绕这个人的紧密关系圈拓展开,变成多人扯淡的线索。和微博一样,如果你关注,那么你才打开看,并考虑参与。系统要做的是把你可能关注的信息推送给你,然后再由你决定是否参与。

作为潜在话题关注者,我关心的是主题是什么,以及哪些我熟悉的人参与的,或是有没有人提到我。

由于在扯淡中歪楼是必然的,所以应该有一个功能就是歪楼,开启一个新线索。但人群还是这些人群,所有参与老话题的人,包括发言的,和一直关注的(每次有新信息都展开开),都会收到新话题(歪楼)开启的信息,但是否跟进就是他自己的选择了。

说到底,就是以话题为核心的一个网状结构。我们从任何一个扯淡聚集区,通过里面的线索跳跃到新的线索上,每个线索中都有一群人。关系近的线索,人群结构也比较接近,但没有明确的发起人和组织人。也没有固定的群组。


大概就是这些。胡思乱想而已。

November 03, 2015

终于升级了 linode 上的 ubuntu

我部署在 linode 上的这个 blog 是 09 年装的 ubuntu 系统。只在 10 年的时候升级到 ubuntu 的 10.04 LTS 版,后来就一直懒得升。

其实敲一行指令就可以了,可每次 ssh 上去就是懒得做。今天突然觉得不太妥了,试了一下 do-release-upgrade 居然失败了 :( 。google 了一下,大约是说我这个系统停止维护太久了,早就超过了最后期限,升不了了。

痛定思痛,打算亡羊补牢一把。我想了一下,其实最主要的业务就是一个为 codingnow.com 解析域名的 bind9 服务,一个 apache 一个 mysql ,重装一下系统的话,把数据链过来应该问题不大。正好 linode 前些年送了我 8G 硬盘空间,一直懒的用上,正好装新系统。

工作进行的比预想的要麻烦的多,这提醒了我,下次一定要及时升级系统,不能再这么折腾了。

在新分区上安装 ubuntu 14.04 LTS 倒是很简单,linode 后台一分钟搞定,重新 reboot 就可以切到新系统。然后挂接老分区当数据盘用。我必须把 /home 和 /var 目录指回老的数据盘,否则新分区空间太少了。这个工作倒容易,只是一开始忘记修改 fstab ,导致系统重启后跑不起来(/var 目录被我做了个软连接到未挂接的分区上,导致 log 文件创建失败)。

最先折腾 bind9 服务,发现直接复制数据盘上的 etc 配置目录是不行的,有几个新安装产生的 key 文件要保留,失败过一次后,马上修好了。随便清理了一些配置文件里的垃圾。

然后 mysql 安装后发现指向数据盘上的库启动失败了 :( 检查 log 发现是我 10 年前创建的库太老了,mysql 5.5 居然读不进来。只好重启回老系统,把数据库直接 dump 成文本,然后再在新系统里导入。这中间忘记了 mysql 的 root 密码,修改了 mysql 的配置关掉密码,进入重置了一下。另外 blog 等几个系统用到的库的密码也忘记了,分别在配置文件中找了回来。

apache 重新安装折腾的时间最长,现在默认装的是 2.4 版, 和我之前的版本居然配置文件中有些命令有差别,老是报访问权限不对,各种淡疼。google 了老半天,然后批量替换了配置文件中的旧写法。

折腾过程中发现 apache2 的 error log 特别巨大,居然有 7G 之多。原来是我一些不太用到的 php 编写的页面实在是太老了,和 php5 最新版本(早两年升级过一次)各种不兼容,报了海量的废弃警告。加上老有发垃圾信息的爬虫每天来尝试,导致 log 增长的飞快。耐着性子把各种不兼容的地方改了过来,总算清静了。


顺便我还研究了一下,为什么 blog 的留言现在这么慢,每次留言都要等 4,5 秒。打开 mysql 的慢查询记录,没有发现什么异常,留言过程中 top 也没看到 cpu 有什么暴涨。想来是卡在某种 IO 操作上了。最后发现是 blog 留言的后台用了一个 10 年前的反垃圾模块,会对外提交一个请求。估计最近两年那边不维护了,结果每次留言都卡在这个请求上好久。赶紧关掉这个没用的反垃圾插件,立刻流畅了。

就这样,我这个 blog 系统还能破罐子破摔坚持用几年吧,等哪天心情好了再重新搭一下。

可能各位同学在访问本网站时还会碰到一些奇怪的现象,那都是我维修工作没做到位,遇到的话可以留言或 email 告诉我一下 :)