加入了美味书签

距离上次更新已经有大半个月的时间了,最近一直忙着搬家和适应新环境。现在我又跑到北京来工作,好在这次终于不是在上地。我记得在写工作第三年的愿望的时候,有一条就是能加入一个优秀的小团队。那么目前看来一切顺利,美味书签这样的团队就是我一直希望能找到的。

我们的团队是Youtube创始人陈士骏的AVOS公司中国分支,中国团队由江宏带领,目前有20人,包括技术(前端、后端、移动),编辑等等。我们的团队网站上一一介绍了每个成员,我就不重复了。

在工作上,眼下最好的事情,是我终于可以利用自己喜爱和擅长的技术,来主导一些系统的开发。我们会尽可能地用热爱的语言(毫无疑问是clojure)开发,这想起来就让人兴奋。我这个人对技术还是有一些洁癖和偏执的。这样虽然路漫漫坑无数,但是收获一定不小,过程也在把握中,青春岁月不可再蹉跎了。

最后欢迎大家都来用我们的产品

Slacker 0.8.0

A new release 0.8.0 of slacker has been pushed to clojars. Let’s go through the changes in this version.

Clojure 1.3 compatible

Slacker finally landed on clojure 1.3. It takes advantages of performance in 1.3. Also, you can use 1.3 API in slacker. For example, a timeout argument is supported in deref, which is useful when dealing with promise returned by slacker’s asynchronous call.

Performance Boost

The performance enhancement is on the highest priority in this release. I have migrated the NIO infrastructure to a new library called link. Now slacker 0.8.0 is at least 8x faster than previous release. There is significant improvement both on per-request latency and overall throughput. And the server thread model is optimized for data-intensive tasks. Heavy IO tasks in hosted functions won’t block the whole server.

slacker as a ring app

Instead of running default transportation, slacker now can be configured as a ring app and deployed on any ring adapter.

(use 'slacker.server)
(use 'ring.adapter.jetty)

(run-jetty (slacker-ring-app (the-ns 'slacker.example.api)) {:port 8080})

This will expose the name space slacker.example.api with HTTP. Functions could be called with following URL pattern:

http://localhost:8080/<namespace>/<function>.<content-type>

For instance: http://localhost:8080/slacker.example.api/timestamp.json

defn-remote

There is a minor update for the defn-remote macro.

In 0.7.0, you have to specify remote namespace with an option:

(defn-remote sc timestamp :remote-ns "slacker.example.api")

In 0.8.0, it’s more convenience:

(defn-remote sc slacker.example.api/timestamp)

To keep the core library compact, in 0.8.0, the cluster support has been moved to a standalone project slacker-cluster.

All above summarized my recent work in the slacker project. If you have any question with this library, feel free to drop me an email sunng@about.me .

Visualizing OpenStreetMap Nanjing Contribution

早上在prismatic上看到mapbox的一篇博客,介绍通过TileMill可视化OSM的贡献者,非常酷。于是我在南京的地图上也做了一个这样的可视化。

一个详细的大图在这里。虽然只做了南京的五个主要贡献者,基本上涵盖了大部分数据。

图例就不专门输出了
[user = 'Sunng'] { marker-fill: @magenta;}
[user = 'fuwuyuan'] { marker-fill: @blue;}
[user = 'sinopitt'] {marker-fill: @yellow;}
[user = 'larryy'] {marker-fill: @green;}
[user = 'zhengz'] {marker-fill: @red;}

MapBox家的东西真的非常酷,这家的技术以nodejs为主,围绕osm开发了不少产品。最近比较大的新闻,比如4sq转到osm上,其实就是转到这家的osm服务上。有兴趣你可以关注一下!

多线程服务器

写了挺长时间网络程序了,有些事到最近才弄明白,记录一下。

写一个高性能的服务器,传统的BIO方式基本上已经被淘汰了,很难获得理想的性能。所以现在都是以事件驱动的方式来写,在Linux上用epoll,Java平台上就是NIO。再向上,有一些包装的库,比如twisted比如libevent比如Java上的Netty。

Netty的server需要至少两组线程,BossPool和WorkerPool,。前者负责accept,后者负责r/w。通常的例子里,这就是仅有的两组线程。用户在框架之上的业务逻辑,写在handler里,以单线程的方式运行在worker线程上。

这样的方式在很多例子里很普遍。但是如果handler里的业务逻辑比较复杂,尤其是IO的等待过长(比如查询数据库),就会由于在handler上阻塞的时间过长,导致服务器读写的效率下降。所以通常是不能在这样环境下的handler里写IO阻塞的代码。

解决这个问题,一种方式是多线程,一种是全异步化。后者最典型的例子就是nodejs,坦白说编程模型非常复杂,对开发者要求较高。一种相对简单的办法,就是以多线程的方式,增加CPU的利用效率,来平衡IO阻塞带来的影响。IO等待时间的比重越大,线程数就可以陪得越高。牺牲一些sy的CPU时间,换取更高的利用率。

但是随之而来了另一个问题,因为在handler中使用了多线程的模型。对顺序收到的包,交由不同的线程并行处理,就没有办法保证返回时的顺序。客户端就无法了解刚刚返回的包是对应哪个请求的。

这个情况也有两种办法处理。其一是在线程管理上做文章,采用一种折中的办法。对于每一个客户端连接来说,仍然是占用同一个线程来处理。这样首先任务不会占用worker线程,其次在整体上仍然提高了CPU的利用率。但是这样做的缺点是,在单一客户端看来,任务仍然是串行执行: 三个需要耗时500ms的请求同时顺序发出,第三个至少要在1500ms之后才能收到响应,客户端的latency比较高。

另一种,就是在应用协议层面做一个冗余字段,通常叫做serial id或者transaction id。客户端为每一个请求生成一个这样的id,并存储这个id对应的回调。服务器端不需要对包的顺序作任何关注,只需要把这个id原封不动地拷贝回去即可。这样,服务器就可以自由调度线程来处理请求。以上面的例子,在不繁忙的情况下,三个响应在500ms左右就都可以到达了。

这种方式对应用协议有特殊的要求,但是比较常见的协议都预留了这个字段,Diameter协议甚至留了两个这样的字段来便于代理的实现。

slacker 0.7.x基于aleph,由于aleph / lamina无法侵入协议层面,所以采用的都是顺序的客户端和服务器。这种方式编程非常简洁,协议设计简单,实现起来很快。但是作为RPC框架,一旦客户的函数阻塞较长,就会影响整体性能。0.8.0开始,通过新的协议和link库支持,slacker采用了transaction id的方式,服务器端默认使用并行处理,客户端不再依赖顺序指定响应和回调。尽管在一些CPU为局限的测试里性能有少许下降(与单线程相比,增加了调度的成本),但是针对实际应用里IO等待较多的情况,新版本应该会表现出更好的综合性能。

以上这些,就是最近的心得,希望能解释清楚事件驱动服务器里的这些事情。

Slacker performance enhanced

In the slacker framework, performance issue becomes more and more critical as the basic features are almost completed. As mentioned in cnclojure meetup, I will focus on the performance enhancement in next release.

Now I have worked out a testable version. The new slacker core has been moved to a new NIO library, link. Compared with aleph, link is a thin wrapper of Netty. It takes some nice features from aleph (gloss codec dsl, elegant API), and drops the heavy abstraction, lamina. The new slacker client runs on a real nonblocking connection. Connection pooling is no longer needed.

I have some performance benchmark to visualize the improvement. The test was made on my laptop (Intel(R) Core(TM)2 Duo CPU T5870 @ 2.00GHz). It ran 400,000 calls with 40 threads on a local slacker server.

slacker 0.7.0 (clojure 1.2, aleph 0.2.0): 614005.059259msecs
slacker 0.7.1-SNAPSHOT (clojure 1.3, aleph 0.2.1-beta2): 409110.909142msecs
slacker 0.8.0-SNAPSHOT (clojure 1.3, link 0.2.0-SNAPSHOT): 42468.401522msecs

tps chart

Check out the new slacker on the 0.8-dev branch.