Archive

Archive for May, 2009

他山之玉,以及其他

May 26th, 2009 :: jackyz

他山之玉

活跃于 .NET 技术圈的老赵同学,最近写了一个不错的文章系列——《一个轻量级Actor模型实现》。关于 ActorModel 可以参考《 Erlang Programming 》第七章 “并发” (98页)中的形象描述,也可以参照 Wikipedia 上 ActorModel 词条的严谨定义。

简而言之,所谓的 ActorModel 其本身的概念是极简单的,它只有两个基本的要素:每个 Actor 顺序地处理消息,多个 Actor 并发地执行。应该说,它是结构复杂(尤其是并行)应用程序的一种合理抽象。Joe Armstrong 在他的《 Erlang Programming 》第七章 “并发” 中认为:ActorModel 是从并发角度对人类社会的交互和协作行为进行模拟的结果。而在实际运用中,这一抽象模型,也早已广泛运用,其中的一些,我们还相当熟悉,比如:*nix 的进程和管道系统、 Email 系统、所有的 MQ 中间件,等等。作为抽象模型的 ActorModel ,它并不局限于某种特定的语言。换言之,采用任何一种语言(或者技术)都可以实现自己的 ActorModel 。

老赵以 .NET 实现的 ActorModel 原型,在基本特性上,已经具备了 ActorModel 的要素,细节可能仍有优化的余地。作为他自己独立思考和深入思辨的产物,不仅是对 .NET 技术领域的同学具有学习价值,对于 Erlangor 而言,因为他的模型是以 Erlang 作为样本的,而他实现过程的思辨,也给我们提供了相当独特的角度,反过来,对于我们理解 Erlang 本身的特性(为什么 Erlang 会被设计成这个样子),也很有帮助。

如下几个问题,建议 Erlangor 们在阅读老赵的文章时,顺便想想:

  • Erlang 的轻量级线程与 ActorModel 的任务调度有何关系。
  • Erlang 的动态类型+模式匹配方案和 ActorModel 的消息处理有何关系。
  • ActorModel 和 Singleton 以及 Lock-Free 又有何关系。

感谢 xushiwei 的 twitter 投递。

其他

以下是 blah 部分,含水量极高,请部分读者自行无视。

国内的 Erlang 应用进入“低调务实”阶段?

  • sohu 最新推出的 SNS —— “白社会”,使用 MochiWeb 作为 update notify 的 server 。打开 firebug 的 net 面板,很容易就能发现这一点(有请 sohu “白社会”的同学出来介绍一下详细情况)。
  • litao.cheng 同学在某公司也采用了 MochiWeb + erlang … 来实现某统计业务。
  • yufeng 同学在某公司采用 Erlang 实现动态集群。
  • xushiwei 同学在某公司采用 C 来实现 Erlang 的某些特性,据说效果不俗。
  • ……

再搞 Erlounge CN 的话,肯定会大有可观。

推荐阅读《Unix 编程艺术》

这一本书说起来是久闻大名,我却中了网络评论的毒,以为它是“拜 Unix 教”的《转X轮》或者是 Linux 老愤青 YY 的《中国不高兴》,所以一直也都没想过要找来看看。

某日,终于还是在图书馆(图书馆好地方啊)的一个书架前相遇了。随手抽出来翻了一下,本来准备扫一眼就放回去的,没想到就此读了一个下午,几乎到了让人放不下来的地步。相见恨晚啊。

尤其是书的第六章,作为一个 Erlangor 来说,共鸣尤为强烈。之前,总听人说 Erlang 的内部构造很象是一个 Unix 系的操作系统,这话听得多了,却总没什么感性认识,直到看了这本书,才觉得说得千真万确。

这种相似性,不只是体现在部分模块的命名/设计和很多思想的一脉相承上,更体现在 KISS 和以数据为核心的价值取向上。Erlang 的整个运行系统就好象是一个为并发进行过增强之后了的 Unix 系统(尤其是进程子系统和进程间通讯子系统),运行在它之上的程序就好比是 shell/perl 的脚本,以简单而强大的方式,通过由若干个彼此通讯的进程组成的计算网络来完成任务。

从这个角度,说 Erlang 是 Unix 核心思想朝并发方向的延伸和进化,并非只是 Erlangor 一厢情愿地想“往 Erlang 脸上贴金”的说法。

顺便查了一下这本书在 douban 上的评价,吃惊的发现进行过推荐的牛人严重扎堆。鄙视一下自己的火星。

关于 Concurrent Programming in Erlang Part2

知名的《Concurrent Programming in Erlang》一直都只在 Erlang.org 开放了 Part1 供大家免费下载,它的 Part2 因而充满了种种神秘的色彩。为了揭开这个谜底,我上次特地跑到“国家图书馆”外文阅览室,费了牛鼻子劲,终于将这本很可能是全中国唯一的一本,弄将出来瞧上了一瞧。下面是观感:

原来这本书的 Part2 内容比较杂,每个部分的篇幅内容也比较少,还略显有点过时(肯定的,是1996年出的,已经13年过去了)。尤其是其中讲 Database 的部分,似乎彼时连 Mnesia 都还没有成形,还有讲 GUI 的部分,使用也很少。其中的大部分内容,在网上都能找到其他的论述文章。可能唯有第 12 章 Operating Systems 的内容(也就是上述“Erlang OS 很象 Unix”的观点)似乎还没有看到相应的资料(知道的同学请补充之)。好在这部分内容关注的是 Erlang 的内部构造,它的缺失想必也不会太多的影响到大家的使用,所以关系应该不大。

情况就是这样子,貌似各位不需要惦记了。

misc

Erlang Factory 2009 Videos

May 20th, 2009 :: jackyz

Erlang Factory 大概算得上是 Erlang 世界在瑞典之外规模最大的会议了。之前在美国加州举办的 Palo Alto 为期两天,日程满满,好话题多到让人眼晕。这次 Erlang Factory 2009 – Palo Alto 放出了一堆好视频,地址在[这里](谁知道 PPT/PDF 在哪里可以看到?请跟帖分享之)。英国 London 在 6月22日到6月24日还会有另外一场欧洲 Base 的 Erlang Factory 不知道社区里面会不会有人参与其中呢?

misc

couchdb style offline storage?

May 9th, 2009 :: jackyz

Mozilla Labs 的 Atul Varma 发表了一篇博文[看这里],探讨使用一个浏览器内置的类似 CouchDB 的系统[看这里]来作为浏览器离线存储方案的可能。——于无声处闻惊雷!我得说,这真是一个天才的点子。

提起目前的 SqLite 方案,说实话,这的确是一个由 “直接神经传导” 产生的 “拿来主义” 方案(简而言之,就是说它很直观啦),而且确实可用。但,恐怕也很难有人能够再想出一个比 “在浏览器中使用 SQL ” 更让人昏昏欲睡的主意来了。就这一条,原本看起来还 “飘出未来的气味” 的浏览器开发,一个激灵,就能让人感觉出逼人的 “幽幽古意” 来。

本该如此,浏览器从来都是 JavaScript 的天下。一切设施都围绕 JavaScript 而存在,存储也不应该例外。这里的 CouchDB 其实和 Erlang 无关,它只是一个 “风格供应商” 而已。顺带的,如果在统一 Online/Offline 存储接口(这是 W3C 的问题) 之后要提供存储/同步服务(这是 Google 之类的问题) ,只有到了这个时候,才没准会有 CouchDB 的事(如果没有更好的替代的话)。但,创意真的是一个具有美感的东西。不是么?

唯一的疑惑是,我也知道 CouchDB 很久了,为什么就没有想到这个用法呢?

感谢 “力” 的投递,感谢 “zly06” 的推荐。

misc

[转] couchdb 上手指南

May 6th, 2009 :: jackyz

张沈鹏同学写了一篇不错的 couchdb 上手指南,以 wiki 格式发在 erlang-china 的 maillist 里。这里整理了一下格式,原文转贴出来,并郑重推荐之。

启动

balin couchdb #  ./utils/run

参数有

-h display a short help message and exit
-V display version information and exit
-a FILE add configuration FILE to chain
-A DIR add configuration DIR to chain
-n reset configuration file chain (including system default)
-c print configuration file chain and exit
-i use the interactive Erlang shell
-b spawn as a background process(作为后台进程)
-p FILE set the background PID FILE (overrides system default)
-r SECONDS respawn background process after SECONDS (defaults to no respawn)
-o FILE redirect background stdout to FILE (defaults to $STDOUT_FILE)
-e FILE redirect background stderr to FILE (defaults to $STDERR_FILE)
-s display the status of the background process
-k kill the background process, will respawn if needed
-d shutdown the background process(关闭)

配置

balin couchdb # vi etc/couchdb/local_dev.ini

可以指定端口号等,常用的有:

[httpd]
port = 12345
bind_address = 0.0.0.0

[admins]
用户名 = 密码

使用

http://123.123.123.123:12345/_utils/

可以创建数据库

python 中的使用

http://123.123.123.123:12345/_utils/database.html?python-tests

以操纵这个数据库作为演示,python库有几个函数比如update([...])不能用,不能用用户名密码等等,也许要修一下…

from couchdb import client
from couchdb.client import Document
server = client.Server('http://123.123.123.123:12345/')
 
#打开数据库
db = server['python-tests']
 
#创建一条数据
doc_id = db.create({'type': 'Person', 'name': 'John Doe'})
 
#获取一条数据,这个doc接口和字典一样
doc = db[doc_id]
 
#_rev是版本,_id是uuid
doc.items()
[(u'_rev', u'1-2963977070'),
(u'_id', u'4a36f238f4facbe08762b1a958cef39e'),
(u'type', u'Person'),
(u'name', u'John Doe')]
 
#可以自己指定主键
db['JohnDoe'] = {'type': 'person', 'name': 'John Doe'}
 
db['JohnDoe'].items()
[(u'_rev', u'1-2744716443'),
(u'_id', u'JohnDoe'),
(u'type', u'person'),
(u'name', u'John Doe')]
 
#更新
badman = db['JohnDoe']
badman[age]=1234
db['JohnDoe'] = badman
 
#删除,可以用db.delete(doc)来删除
del db['JohnDoe']
 
#遍历
for row in db.view('_all_docs'):
  
print row.id
 
#看数据库信息
db.info()
{
u'compact_running': False,
u'db_name': u'python-tests',
u'disk_size': 24381,
u'doc_count': 13,
u'doc_del_count': 0,
u'instance_start_time': u'1241518867280531',
u'purge_seq': 0,
u'update_seq': 21}
 
#文档可以有2进制的附件 put_attachment 用这个函数上传
# 查询,map_fun是一个js函数,emit是emit(key,value)。key,value均可是null
# web页面上有Select view查询,可以直接搜索测试
# 好像要用unicode字符 不然找不到 囧啊
 
db['/logo/xxx1.jpg']={"type":"logo","size":1}
db['/logo/xxx2.jpg']={"type":"logo","size":2}
db['/logo/xxx3.jpg']={"type":"logo","size":3}
db['/logo/xxx4.jpg']={"type":"logo","size":4}

Map函数

map_fun = u'''
function(doc) {
   if (doc.type=='logo')
       emit(doc._id, doc.size);
}
'''
 
for row in db.query(map_fun):
  
print row

输出

<Row id=u'logo/xxx1.jpg', key=u'logo/xxx1.jpg', value=1>
<Row id=u'logo/xxx2.jpg', key=u'logo/xxx2.jpg', value=2>
<Row id=u'logo/xxx3.jpg', key=u'logo/xxx3.jpg', value=3>
<Row id=u'logo/xxx4.jpg', key=u'logo/xxx4.jpg', value=4>

我们还可以加上reduce函数,比如:

  1. reduce_fun = u'''
  2. function(keys, values, rereduce) {
  3.    return sum(values)
  4. }
  5. '''
  6. for row in db.query(map_fun,reduce_fun):
  7.    print row

输出

<Row key=None, value=10>

reduce 中 rereduce变量的含义如下

  1. rereduce为false
    • key为array,element为:[key,id],key为map function产生的key,id为Document对应id
    • values为array,elements为map function产生的结果
    • 比如 reduce([ [key1,id1], [key2,id2], [key3,id3] ],[value1,value2,value3], false)
  2. rereduce为true
    • key为null
    • values为array,element为前一次reduce返回的结果
    • 比如reduce(null, [中间结果1,中间结果2,中间结果3], true)

[这里]有一些map/reduce演示的例子,比较好懂.

Creating Views
View 可以理解为索引了 不过这个索引不是实时的…
接着上文的例子

db["_design/test"]={
 
"views":
 
{
  
"all": {
    
"map": "function(doc) { if (doc.type == 'logo')  emit(null, doc) }"
  
},
  
"size_large_than_2": {
    
"map": "function(doc) { if (doc.size && parseInt(doc.size)>2)
emit(null,doc) }
"
  
},
  
"total_size": {
    
"map": "function(doc) { emit(null,parseInt(doc.size)) }",
    
"reduce": "function(keys,values) { return sum(values) }"
  
}
 
}
}

然后刷新

http://123.123.123.123:12345/_utils/database.html?python-tests

可以看到 select views中多了test,也可访问:

http://123.123.123.123:12345/python-tests/_design/test/_view/all

可以加上limit这一类参数

http://123.123.123.123:12345/python-tests/_design/test/_view/all?limit=2
http://123.123.123.123:12345/python-tests/_design/test/_view/all?limit=2&skip=1

这样可以分页,不过[这里]提到

A simpler method of doing this is to use the skip parameter to work out the starting document for the page, however this method should be used with caution. The skip \parameter simply causes the internal engine to not return entries that it is iterating over. While this gives the desired behaviour it is much slower than finding the first document for the page by key. The more documents that are skipped, the slower the request will be.

所以最好配合下面的startkey之类的来用skip
类似参数还有:

  • 排序 descending=false
  • 开始结束 startkey=”abc”&endkey=”abcZZZZZZZZZ” 可以用 docid startkey_docid=null
  • 合并结果 group=true 用法有点复杂,可以看[这里]

key可以是复杂的key,比如:

The query startkey=["foo"]&endkey=["foo",{}] will match most array keys with “foo” in the first element, such as ["foo","bar"] and ["foo",["bar","baz"]]. However it will not match ["foo",{"an":"object"}]

python中可以这样访问

for row in db.view('_design/test/_view/all'):
  
print row.id

输出

logo/xxx1.jpg
logo/xxx2.jpg
logo/xxx3.jpg
logo/xxx4.jpg

又如

for row in db.view('_design/test/_view/size_large_than_2'):
  
print row

输出

<Row id=u'logo/xxx3.jpg', key=None, value={u'_rev': u'1-3347158087',
u'_id': u'logo/xxx3.jpg', u'type': u'logo', u'size': 3}>
<Row id=u'logo/xxx4.jpg', key=None, value={u'_rev': u'1-1107796651',
u'_id': u'logo/xxx4.jpg', u'type': u'logo', u'size': 4}>

网络资源

  1. 根据网上的测试表明:couchdb 写入速度 比 mysql 慢4倍,创建索引速度 比 mysql 慢50倍
  2. couchdb 只写入不删除,需要定期做整理,类似垃圾回收的copy+删除,需要预留大量磁盘空间.
  3. 索引不是实时的,你可能看到的是旧的数据

我的个人看法
单单看性能,couchdb的确很不理想,但是couchdb可以把数据以view的方式展现,要什么,就新建什么样的view,这种随心所欲索引方式,在不少应用的场合,通过view的方式把这种查询结果持久化,可以大大减少了把传统意义上的重复且相似查询.

study