Staged Event-Driven Architecture

按照传统的编写应用程序的思路,当server接到请求,包装完成之后,分配到线程池中交给一个线程完成,返回。Java的servlet容器就是这么设计的,这么多年大部分的应用程序也是在这种模式下编写的。当任务的代价比较大,比如多次和数据库交互,并且可能和后端其他服务通信,这是线程被任务占用的时间就相对较长。如果请求的并发量很大,容器的线程池耗尽,新的请求就无法被处理,导致并发性无法提高,吞吐量也无法提高。

实际上在大部分应用程序里,所谓代价很高的任务,往往都是I/O Bound。涉及网络通信,磁盘读写,线程被迫wait,CPU却是空闲的。所以,I/O Bound的程序理论上都还有优化的空间。于是有了这种Staged Event-Driven Architecture,即SEDA。

SEDA的思路是将原先由一个线程完成的任务,分割为相对独立的多个阶段。每个阶段由专用的一组线程负责执行,阶段之间用过队列交互。又是上次提到的老话:If you cannot split it, you cannot scale it.

例如,我们的业务逻辑是读取请求,操作数据库,与后端通信,操作数据库,写回结果。如果采用NIO的方式,网络通信可以认为是非阻塞的。而目前与数据库的交互,通常还是阻塞式的风格。这样这五个阶段分别是非阻塞、阻塞、非阻塞、阻塞、非阻塞。组塞操作容易成为性能瓶颈,CPU时间用于等待,占用率不高,所以对组塞操作可以分配相对多的线程;非阻塞操作速度较快,为了避免快速的切换导致sy升高,采用和CPU核数一样多的线程即可。

这样,原本一个线程反复等待的阻塞操作,变成了部分线程等待的同时,其他线程仍然在处理自己的业务,有效使用CPU。利用的多核的优势,提高了CPU的占用,即提高了系统的处理能力。

而对外部接口来说,当读取数据的线程在读取完毕之后,将任务dispatch到相应队列即返回,前端可以保证很高的处理速度,并发性也可以保证。任务被积压在阻塞操作的队列中,而消费阻塞操作的线程要多于提供者,在一定程度上也保证了处理速度。再退一步说,当阻塞操作的速度却是无法消费大量任务时,前端可以根据队列的大小判断当前系统的load,拒绝服务。

当然,采用这种方式,只有在并发量提高到一定程度,并发成为系统瓶颈时才能体现价值。就单个操作而言,由于队列的传递,他的latency一定是有所上升的。

关于SEDA,可以参考Matt Welsh的相关论文:The Staged Event-Driven Architecture for Highly-Concurrent Server Applications。

Failure Detection

分布式系统中检测节点的工作情况,最直观的方法是采用心跳包的方式,通过定时发送心跳包,如果对端节点没有正常返回,则认为此节点处在failure状态,这时系统需要采取一定的措施来保证正常运行。目前,Zookeeper就是采用这种方式(org.apache.zookeeper.ClientCnxn)。

但是为了避免由于网络抖动等等意外因素导致的误报,通常在心跳包的基础上做一些改进。我在盛大做服务的路由代理时,代理和后端的服务通过心跳包保持连接。当心跳包出现异常,或服务调用出现异常(类似HTTP 500的系统异常),代理会记录当时的时间戳。根据配置,代理会存储最近50次的时间戳,并且检查他们的方差,当方差小于一个阈值时,就认定服务为failure状态,将服务从列表中剔除并发送报警。在一些测试里,这套机制是可以按照预期工作的。可惜这个系统最后没有上生产系统,所以也没有实际的运维经验。

最近看到了Cassandra使用的Failure Detection机制,叫做Phi Failure Detection Model。这个思路在一定程度上跟我的方式类似,在Cassandra的实现里,他会存储最近1000次失败的信息(org.apache.cassandra.gms.FailureDetector#sampleSize_)。此外,Cassandra存储的是失败的间隔,而不是绝对的时间戳(首次失败,存储的是心跳间隔的二分之一)。判断失败的φ值是通过这个公式就算:
φ = (now – last_failure) * lnE / mean(interval_samples)
当φ值大于配置的threshold时,Cassandra的Gossip机制认为这个节点为failure。默认的配置里,这个threshold值为8. 关于Phi Failure Detection Model,可以参考论文:Information Propagation on the Phi Failure Detector

而为了提高Zookeeper的Failure Detection的质量,去年的一个Google Summer of Code项目为Zookeeper引入了一些流行的检测模型,包括前面提到的Phi Failure Detector,以及Bertier的方法和Chen的方法。

Chen方法以一个固定的alpha常数,加平均到达时间作为估计的timeout值。

在Bertier方法中,用于判断心跳包失败的timeout时间通过三个参数gamma/beta/phi被动态修正:
error = now -ea – delay; (收到心跳包的时间-估计的心跳包到达时间-估计的延迟)
delay = delay + gamma * error; (根据gamma和error修正延迟)
var = var + gamma*(abs(error) – var); (error值的variation,即varn=Σ g(1-g)n-i*ei
ea = now + avg(‌interval); (根据心跳包的平均间隔修正估计时间)
timeout = avg(interval) + beta*delay + phi*var (计算新的timeout)
假如心跳包在timeout后到达,timeout还需要在上面的基础上加上常数moderationStep。

GSoC项目里,gamma/beta/phi/moderationStep的默认值分别为0.1/1.0/4.0/500。Bertier方法的timeout值根据网络情况自适应。

评价一种Failure Detector的标准,包括它发现failure节点的时间和误报率。这里有GSoC项目中作者进行的实验及结论,可供参考。

GNOME-Shell Extension for Exaile DoubanFM Plugin

从有这个动机到基本可用,花了整整一天的时间。现在可以通过GNOME-Shell的一个小菜单从外部控制Exaile豆瓣电台了。它的意思就是说如果你使用Gnome-Shell,你可以把Exaile界面扔到一个你不想看到的地方去,比如第n+1个workspace。然后通过左上角的菜单来控制豆瓣电台的播放。见多识广的你不会对此感到太惊奇,其实它和Ubuntu上的Sound Menu Indicator类似,不过是专门给豆瓣电台设计的。
Exaile with doubanfm plugin

要实现这个功能,需要播放器通过dbus来expose一些接口供外部程序调用,它和Sound Menu是一样的,通过dbus来解耦。开发起来比较困难的是目前GNOME-Shell的API还不成熟,还没有文档,传统的javascript object inspect方法似乎在这个mozjs的环境里也不能用,所以实在是无从知道一些API。更不要提通过gjs来调用dbus了。网上有一些文档起了很大的作用,如果你也感兴趣,这些文章是:

支持dbus的exaile-doubanfm-plugin会在stable之后合并到主干供大家使用。现在和gjs一样,它的API也不稳定,所以放在分支中:
https://github.com/sunng87/exaile-doubanfm-plugin/tree/dbus

gnome-shell extension的代码现在也放在github上(其实还不成熟的东西放到bitbucket比较好的)。有兴趣可以了解:
https://github.com/sunng87/exaile-doubanfm-gnome-shell-extension

以上都是unstable,仅供吊胃口之用。

Convert a Python function to Java anonymous class

When calling Java with Jython, anonymous inner class might be an issue because there is no such equivalent in Python.

In GUI programming, jython made additional effort on AWT event processing. You can pass a python function as some types of event listener.

def change_text(event):
    print 'Clicked!'

button = JButton('Click Me!', actionPerformed=change_text)
frame.add(button)

Described in the Definitive Guide of Jython:

This works because Jython is able to automatically recognize events in Java code if they have corresponding addEvent()* and *removeEvent() methods. Jython takes the name of the event and makes it accessible using the nice Python syntax as long as the event methods are public.

However, it does not work with Runnable, Callable and many other interfaces designed for anonymous usage.

To solve the incompatibility, I created a small decorator that converts Python function to a Java object.

Thanks to python’s dynamic magic, we can create class in runtime and assign modified method to a particular instance. The conversion is performed once the function loaded. Also, you can pass something as arguments to constructor.

With anonymous_class decorator, the example above can be written as:

from javax.swing import JButton, JFrame
from java.awt.event import ActionListener

frame = JFrame('Hello, Jython!',
            defaultCloseOperation = JFrame.EXIT_ON_CLOSE,
            size = (300, 300)
        )

@anonymous_class(ActionListener, "actionPerformed")
def change_text(dummy, event):
    print 'Clicked!'

button = JButton('Click Me!', actionPerformed=change_text)
frame.add(button)
frame.visible = True