2011年11月16日星期三

mongo无法利用多核?

    太伤心了,本来以为mongo的速度很快呢。测试插入数据,结果当场被泼了冷水。
conn = pymongo.Connection('localhost', 27017)
db = conn['perform']
coll = db['test']

testdata = []
def init_testdata():
    for i in xrange(1000):
        s1 = ''.join([random.choice(string.hexdigits) for j in xrange(16)])
        s2 = ''.join([random.choice(string.letters) for j in xrange(200)])
        testdata.append((s1, s2))
init_testdata()

def insert_mongo():
    for s1, s2 in testdata: coll.insert({'_id': s1, 'content': s2})

def find_mongo():
    for s1, s2 in testdata:
        s = coll.find_one({'_id': s1})

def testfunc(funcname, times = 1000):
    from timeit import Timer
    t = Timer("%s()" % funcname, "from __main__ import *")
    print 'funcname: %s used %f' % (funcname, t.timeit(times) / times)

if __name__ == '__main__':
    # os.fork()
    # os.fork()
    init_testdata()
    testfunc('insert_mongo', times = 100)
    testfunc('find_mongo', times = 100)

    这个代码,在直接执行的情况下,获得结果是这样的。
funcname: insert_mongo used 0.179303
    折合成iops,也就是5500req/s的样子。打开os.fork后,结果变成了这样。
funcname: insert_mongo used 0.516131
funcname: insert_mongo used 0.526213
    只有3850req/s左右,我靠,比单进程慢那么多?打开四个进程试试?
funcname: insert_mongo used 1.039754
funcname: insert_mongo used 1.058093
funcname: insert_mongo used 1.058598
funcname: insert_mongo used 1.059101
    基本稳定下来了,差不多4000req/s的样子。而且,通过top发现,最关键的问题不在于io和内存,而是mongodb这货只有一个进程,最高吃到100%的CPU——也就是——无法利用多核。

    幸好,在读取测试中,情况不是那么糟糕。在单进程下是下面这样子
funcname: find_mongo used 0.350096
    2850req/s,双进程就变成了这样子。
funcname: find_mongo used 0.220384
funcname: find_mongo used 0.221446
    9000req/s!不但性能有所上升,而且更为惊喜的是,在top中检测发现,主要CPU消耗都放到了python这端。而分布系统的常识告诉我们,客户端的压力(就是应用服务器的压力)是可以很容易的通过添加服务器来解决的。在贝壳当前这台双核的机器上是无法进行进一步测试了,不过按照目前的状况预估,查询时即使只能使用单核,也可以支持10000req/s以上的性能。

    又是一个典型的高读低写数据库呐。也罢也罢,nosql中也就mongo的各种特性比较接近sql数据库,用来跳过ORM层直接做系统比较合适。如果使用memcache或者redis,性能倒是上去了,用起来就未免太蛋疼了一点。不过偷偷的透露一点,贝壳估计,使用redis后,性能还能上去5倍。

2011年11月15日星期二

语言的易读性

    何谓语言的易读性,简单来说,就是看到一段代码的时候,能够了解其意思。易读性最差的典型代表作是汇编语言和机器语言,因为在读这两种语言的时候,其实是你的大脑在替代模拟CPU的功效。说起来,自从汇编以后,每种语言多多少少都注重了人类阅读的习惯,像brainfuck这种特例是万难一见的。例如下面的例子。
    printf("hello, world\n");
    即使没有任何C基础的人,也能够看懂这是在做一个字符串打印。
    语言的易读性其实是语言非常重要的特征,比其他特征都重要。因为人类的大脑不可能记得所有的代码细节,并且能够直观的反应出如何修改。往往我们需要阅读一下代码,搞明白每段的意思,然后才能动手——哪怕这段代码出自自己手笔,只要过得一两个月,还是要重读一下的。正是因为读这个技能的使用频率非常高,所以语言的易读性非常直观的影响到语言的易用性。而易读性差的语言和习惯,目前来看有以下几个典型例子。

1.罗嗦
    典型代表是Java。下面是一个Java解压Zip的代码,引用自参考1。
public class Zip {
    
static final int BUFFER = 2048;

    
public static void main(String argv[]) {
        
try {
            BufferedInputStream origin 
= null;
            FileOutputStream dest 
= new FileOutputStream("E:\\test\\myfiles.zip");
            ZipOutputStream out 
= new ZipOutputStream(new BufferedOutputStream(
                    dest));
            
byte data[] = new byte[BUFFER];
            File f 
= new File("e:\\test\\a\\");
            File files[] 
= f.listFiles();

            
for (int i = 0; i < files.length; i++{
                FileInputStream fi 
= new FileInputStream(files[i]);
                origin 
= new BufferedInputStream(fi, BUFFER);
                ZipEntry entry 
= new ZipEntry(files[i].getName());
                out.putNextEntry(entry);
                
int count;
                
while ((count = origin.read(data, 0, BUFFER)) != -1{
                    out.write(data, 
0, count);
                }

                origin.close();
            }

            out.close();
        }
 catch (Exception e) {
            e.printStackTrace();
        }

    }

}
    我下面给出python版本。
import os, zipfile
with zipfile.ZipFile('filename.zip', 'w' ,zipfile.ZIP_DEFLATED) as zf:
    for name in os.listdir('.'): zf.write(name)
    罗嗦有什么坏处?当你需要理解一段代码的时候,需要上上下下翻动屏幕,并且仔细对比每个细节,才能理解这个代码的目的。这对于阅读来说是非常不友好的。

2.歧义
    典型例子是++,我给出这么一个例子。
i = (++j) + (j++) + i---i
    i是多少?脑子一团糨糊吧?关于自增自减的歧义,具体可以看参考2。当然,这并不是说C++设计的有问题,只是这个用法不可取而已。
    歧义的最大问题是,不借助具体的实现运行一下,基本没有希望了解这个代码是什么意思。这哪里叫可读,这叫不可读。歧义是不可读中最差劲的一种,一切产生歧义的代码都是坏的代码,例如我们下面的这个例子:
import os
def os(os): return os
    这个,return回去的到底是谁?os module?function?variable?运行一下我们知道,其实是返回了参数。但是这种代码骤然看到,鬼才能够反应的过来,写出这种代码的,上辈子都是非洲丛林里面的守林人,想bug想疯了吧。
    还有一种是变量名类似,例如只以大小写区分,或者以下这个例子:
def sl(s1): return sl
    您看出问题了么?没看出来?这到底要多脑残才会把变量弄的那么像函数名,导致return的时候把自己的函数给return回去阿?

3.依赖上下文
    什么叫依赖上下文?其实这并不是一个很好界定的问题。无论代码多么简洁,我们都需要调用其他函数。这个函数就是所谓的上下文。在拥有一定知识的前提下,我们的代码越上下文无关越好。如果一定要上下文有关,这个相关部分越确定越好。例如C++中的一个例子:
DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowChanged");
    谁能告诉我,为了看懂这个代码,我需要查看多少内容?首先,我需要查看DynamicFn和WM_Hooks_WMVAL_proto的定义,然后去检查DynamicFn的构造函数。如果只有一个构造函数,并且参数类型匹配,那么很幸运,事情就到此为止。如果不匹配,我还得查看是否可以编译通过,如果可以,是匹配了哪个构造函数。如果都不匹配,那么肯定发生了内隐转换(implicit cast),如果有两个函数都可以通过内隐转换进行匹配,例如下面这种:
template <typename T>
DynamicFn::DynamicFn(wstring t, char * c);
DynamicFn::DynamicFn(TCHAR * t, string c);
    天呐,这个不但依赖上下文,而且歧义了。更郁闷的是,随着UNICODE宏的变化,这两个函数的匹配行为还会产生变化。即使上面一切都没问题,您能够直观的从刚刚的一行代码中看出代码所要达到的目的么?从老程序员的习惯来猜测,好像是wm-hooks这个dll的WM_Hooks_WindowChanged函数进行SetHook,是不是,我懒得验证了。
    比较好的解决这个问题的方法叫做代码自描述性。例如上文,这种文法是比较容易理解的。(但是不保证意义一致,因为上文我还不确定是安装Hook还是仅仅生成对象包装,或者两个行为同时实施了?下文也只是伪代码)
create_function(u"wm_hooks.dll", "WM_Hooks_WindowChanged")

4.晦涩
    何谓晦涩?高级特性过多。典型代表C++,谁来读一下这个代码?
#pragma once
#pragma lib("curl")
using namespace std;

#ifndef RFB_MAINTHREAD
#define RFB_MAINTHREAD
namespace rbf{
extern "C" {
  class MainThread: public Thread, EventHandler {
    MainThread (explicit HANDLE hFD);
    virtual ~MainThread();
    inline static int run_wrapper() { return run(); }
    virtual run() = 0;
  };
}
}

2011年11月13日星期日

NSIS在64位下安装时无法写入注册表的问题

    最近公司碰到一个问题,NSIS在64位下安装时无法写入注册表。
    首先,这个问题不是UAC没有权限的问题,因为我使用administrator安装依然有问题。其次,问题和win2008没关系,只出现在64位上。
    问题在哪里呢?在写入注册表前,SetRegView 64,写入后换回32,问题解决。
    真TMD的。

2011年11月11日星期五

关于网站架构的几封邮件摘抄

我知道,我自己写过一个greenlet + epoll的实验性框架。
最主要的问题是,写到后来我发现,这东西对用户的要求太高了。要用好这种框架,用户必须具备系统经验,知道阻塞操作实际上是由非阻塞操作和上下文调度去模拟的,知道代码处处无阻塞(其实是不能有无调度的阻塞),能够想像系统是如何运行的。
这种人不会太多。在cpyug里面不算少,抓10个20个肯定能抓出来,抓上100个也不是没希望。但是实际在操作的时候,平摊到上海这么个地方,会python的也就见过那么不到100人,有这种要求的几乎可以一个个数出来。而且大多数已经在一个不错的公司里面有个不错的职位,你没法指望招个人来做事。
这也是为什么很多公司凡python必django的原因,毕竟用了django,虽然罕见,但是可以招人。用了tornado,能招的范围就少了很多。我自己做的这个实验性的玩意,风险大不说,HR角度来说,可选程序员只有一个。一旦在上面做了系统,不废弃系统的前提下,你压根没法谈判工资。。。
从语言角度来说,我更倾向于lisp,那个比较优美一些,而且也有编译成C的选项,速度不慢,天然的fp。问题是lisp从语义的自然可理解性来说非常差劲,那个传说中某AI实验室源码最后一页全是)并非空穴来风。对于新手入门而言,lisp成本更加高,使用lisp做系统,HR执行的难度也更高。haskell我并不懂,不过从语言理解来说,大概介于lisp和python之间吧。

协程型框架和进程/线程型框架相比,最大的好处就是减少了锁的问题。因为上下文切换的位置都是已知的,是否需要锁很容易考虑。很多时候甚至不需要严格锁定,只要置标志位就好,速度很快。使用fp,也可以大幅减少锁的问题,但绝对不是避免。目前的系统架构设计,已经越来越多的把锁的问题扔到了数据库层。
例如,我在操作一条记录的时候,一定会发生行级锁,否则就是不安全的。而在添加一条记录的时候,必然会修改这个表上关联的索引。而修改索引的瞬间,就会发生瞬时的锁定和解锁,否则也是不安全的。这个过程虽然对用户不可见,但是并非不存在。诚然,数据库访问是基于网络的,而基于网络的read是一个阻塞操作,在架构级别一定会调度到别的上下文执行。但是没意义阿,大规模的用户访问,除掉可以缓存的部分外,都被压到了数据库上进行读写。这些访问,在表级频繁的发生冲突,被各种锁序列化成顺序访问。到最后,我们不断的向系统中添加机器,来换取性能增长的时候,应用服务器实际上变成了问题最小的一个——小到用也许bash去写cgi都可以满足。与此同时,我们的数据库问题越来越大,还没法拆分——你没办法像应用服务器负载均衡那样把数据库拆到多个机器上去,然后让他们的写入性能成倍数增加。
无论是mongo,redis,还是mysql,都没有本质上的解决锁,尤其是写入锁的问题。mongo的读取性能可以上到15kreq/s,但是写入只有5kreq/s,而且好像还不能由sheding做加速——至少不是成倍级别的加速。mysql目前比较成熟的方案还是单写多读。当然,还有所谓水平拆分和垂直拆分的方法。垂直拆分对业务有要求,水平拆分只解决了大规模数据吞吐分布到多个存储媒体的问题,不解决索引访问的问题。redis压根没有自己的分布方案,你必须自己来做。
k-v受到热捧的原因之一,在它给了你一个从某个层面绕过这个问题的方法。目前写入锁最严重的点在于索引。无论是插入还是修改记录都需要在数据库上变更索引,而索引的变更就必然会发生锁。K-V的要点在于不允许在记录上做索引——所以mongo不是k-v数据库——从而允许用户将庞大的写操作分布到数十乃至数百台机器上的同时,获得倍数级别的性能增长。我们先不考虑添加/删除——这个是一致性哈希的目标,也不考虑可用性——这个是冗余的目标。仅从这点来说,k-v数据库受到热捧是有原因的。
问题是,这也不是解决问题,这只是绕过问题。相信使用k-v的人应该有所感受,这玩意根本没法替代常规数据库来用。没有事务,没有一致性隔离就算了。连索引都没有,这TMD的怎么用阿。目前来说,更加实际的使用还是用k-v来存储一些确实没必要进行索引的东西——例如大量小规模图片,用户的属性数据。

Zoom.Quiet <zoom.quiet@gmail.com>:
- 那么这样的话,可以考虑用 Erlang ,这货天然就是为了大分布高迸发服务发明的
   - 而且从语义行文角度看也很好理解
   - 更加要命的是 erl 提供了丰富到变态的动态调试工具,风骚无比的热部署无缝回滚...
   - 只是,摧悲的是 erl 对于计算无爱...
- 不过,反过来想一下:
   - 现在 web2.0 的世界,以及在爆发中的移动互联应用中,有什么是非要复杂关系查询的?!
   - 通过业务的良好统计,可以从业务角度就异步化
   - 那么,不论什么语言来开发,都没有阻塞问题存在了哈...
   - 这也是为毛 K/V 数据库得以商业应用的主要原因
- 另外,前述有人说 git 作存儲的思路也是个方向:
   - 既然分布式写入锁是个难题
   - 那么就直接只进行本地操作好了
   - 仅在必要时,进行分布式合并,这方面,各种版本控制系统都作得很好
   - 如果 redis 的bilog 文本对 git 合并是可耐受的,那不就是个山寨的分布异步安全锁了?

我觉得我的最终解决方案是到大学里面培训lisp课程,争取弄出一批语义上看C系列语言不顺眼,只能读懂lisp的变态出来。这种现象在自然界有广泛分布,地球上至少有1/4的人类在使用最流行的语言系统时有障碍,只能使用一种难用的要死的古老的,基于符号的语言系统,并且引以为傲。。。
业务角度异步化并不是最终方案,因为除了移动互联网应用外,数据库业务最赚钱的还是公司业务。公司业务的数据量不见得比移动互联网应用小,而且他们有钱。由于目前没办法,公司业务都是找oracle这种公司来处理,而且对性能没有要求。其实不是真的没要求,而是没法要求而已。
我觉得比较有前景的,是如何将索引分布,理论来说这是可以做的。一致性哈希,DHT,都有希望。问题是目前来说,安全的写入分布式的索引本身好像也是要锁的,这就没意义了哈。 我还没想过分布式的索引本身写入锁的冲突概率是多少,能降低一个数量级就值得玩玩看。

2011年11月9日星期三

openwrt配置——自动重启openvpn

    还记得如何配置openvpn么?手工配置有个问题,当我ppp0连接断掉,需要重启路由器的时候,网络会短暂的断开。然后,openvpn就失效了,导致各种混乱后果。为了解决这个问题,我测试了一下,做了以下设置。
-----/etc/hotplug.d/iface/30-openvpn-----
#!/bin/sh

[ "$ACTION" = "ifup" -a "$INTERFACE" = "wan" ] &&
[ -z "`/sbin/ifconfig tun0 2>&1 | grep inet`" ] && {
        /etc/init.d/openvpn start
}

[ "$ACTION" = "ifdown" -a "$INTERFACE" = "wan" ] &&
[ -n "`/sbin/ifconfig tun0 2>&1 | grep inet`" ] && {
        /etc/init.d/openvpn stop
}
-----end files-----
    好了,你重启外网连接的时候,就会自动连接openvpn。

参考:
OpenWRT下的动态DNS(用3322.org的服务)

2011年11月8日星期二

几个模板系统的性能对比

    对比目标,jinja2,cheetah,mako,webpy,bottle,tornado,django的性能。
    方法,随机生成一个二维数组,第一列是自增数据,第二列是长度为100的随机字符串,然后生成html,比较一次生成的时间。
    说明,如果模板有编译缓存,打开。有其他方法加速,打开。生成缓存,关闭。不计算随机数据生成时间,一次生成后一直使用。
    以下是文件有效内容,没用的都略去了。最后的顺序是因为我根据结果整理了一下调用次序。
-----testcheetah.tmpl-----
    <table>
      #for $i in $l
      <tr>
<td>$i[0]</td>
<td>$i[1]</td>
      </tr>
     

2011年11月6日星期日

openwrt配置——arptables配置

    让其他人的设备无法使用网络。
opkg install arptables

--------/etc/init.d/arptables--------
#!/bin/sh /etc/rc.common

start (){
        arptables -F INPUT
        arptables -A INPUT --src-mac aaa -j ACCEPT
        arptables -P INPUT DROP
}

stop (){
        arptables -F INPUT
        arptables -P INPUT ACCEPT
}
--------end files--------
    注意,千万把自己的mac地址写对了,否则一个/etc/init.d/arptables restart下去,你自己的机器就断线连不上了。不过一般来说,重启后arp限制会失效,因此可以重启来去掉限制。实在不行也可以拔下U盘,在电脑上进行mount和修改,然后再插回去启动设备。根据测试结果,内网还是可以访问的,不过路由器无法访问了。有一个链叫做FORWARD,也许改这个可以解决。但是我没有找到相关资料,因此没有下手。

2011年11月3日星期四

Openwrt pptp passthought

    你还在为使用openwrt路由器无法使用pptp客户端而烦恼么?请看这个页面。按照他的说法,输入以下句子就可以解决你的烦恼。
    If you use a pptp client behind an openwrt router, and pptp tunnel not work, look at thispage.

opkg update
opkg install kmod-ipt-nathelper-extra

    这个方法不仅对路由器内使用一个pptp有效,而且对多个pptp也有效。
    It's work for both single pptp tunnel and for multi pptp tunnels.
    然后,记得重启。
    Remember to reboot router.

2011年11月2日星期三

SHLUG Summit 2011

这次有我的演讲,转一下转一下,大家来捧场。

---------- Forwarded message ----------
From: ghosTM55 <ghosthomas@gmail.com>
Date: 2011/11/2
Subject: [shlug] [公告]SHLUG Summit 2011
To: shlug <shlug@googlegroups.com>


Hi all,我们SHLUG的2011年年度大会来了!

作为惯例,我们的年会会面向Linux新手以及初学者来进行Linux的宣传与知识普及,这次也不例外
这次我们将会来到松江大学城,在东华大学进行一场200人规模的交流会,欢迎有时间和兴趣的朋友一同前去参加

时间: 2011年11月6日(周日) 下午2点
地点: 东华大学 松江大学城校区
报名: 不需要
入场费用: 不需要
演讲主题:
 * 如何成为一名黑客
 * Debian GNU/Linux介绍
 * 实战Linux网络部署
隐藏关卡:
 * Ubuntu 11.10 Release Party

和去年一样,在这里我需要:
 1. 一些朋友能够来帮助到我们运维好本次SHLUG的年会(拍照,摄影,现场话筒传递,入场引导等)
 2. SHLUG的朋友在参与活动的时候尽可能坐在教室后排并请勿在演讲过程中大声讨论问题
 3. 大家对于此次活动的线上以及线下的帮忙宣传

在这里需要感谢东华大学开源社区的同学们的积极配合与帮助,为我们奔波于松江大学城各高校进行宣传
并且为我们找到了能够容纳200人的教室(具体教室号码尚未确定,我会在列表以及blog中进行更新,请持续关注)

欢迎各位参与本次年会并向我提出建议,谢谢

--
Thomas
Shanghai Linux User Group

http://ghosTunix.org
Twitter: @ghosTM55

2011年11月1日星期二

收稿子啦,宅男买数码

    淘宝上东西很多,360buy东西也很多,电脑卖场多如牛毛,听起来买个数码产品是个很容易的事情。其实才不是呢,尤其是对于对数码有要求的宅男来说,买个设备要多困难有多困难。
    贝壳上淘宝买一个充电器,要求也不高,5V/2A,可以用于我的台电P81HD平板。OK,上淘宝,找第一个卖家,狮王的四口充电器——结果,总电流不足,退货。第二个卖家,瑞能官方店——结果,有高频分量,在充电时无法正常操作,目前还在协商中。第三个卖家,三星的充电器——结果,充电有高频电流声,温度很高。gary说,他有个DELL的充电器也有类似问题。我很高兴的说,那是没事咯?他说,结果,爆了,连带烧了整个房间的电闸。然后拆开塑封看印刷,粗糙,肯定假货无疑,强行要求退货。第四个卖家,上去问,有货么?有货。真货么?真货。确定么?确定。我这么说吧,我不和您讲道理,到手后,我找三星干活的兄弟看。他说真货我就收货,他说假货我就退货,连邮费一起退。不退差评。
    ——先生?
    ——您好?
    ——还在么?
    OK,这就是淘宝的品质。当然,我不是说淘宝无好货。Thomas老婆在淘宝上买的DIR-825路由器还是挺好用的,我在上面订花什么的也不错。问题是,对于产品挑剔的宅男来说,在淘宝上买东西是一个艰难的抉择。应该说,淘宝基本无真货。
    那么京东之类的电子商城呢?
    贝壳买过他们的两箱秋叶原六类线,结果其中一箱的中心龙骨缺失。我问他们怎么回事,他们就只负责退货。还好,退货,换发票的过程都是OK的,但是没有一个人出来说一句,这是为什么,也没有人道歉。看看京东上面比较热门的东西,基本都能看到旧货贴。啊——运气不好,终于让我碰到旧货了。看来他们也没靠谱到哪里去。
    那咋办?
    OK,贝壳简单说一下一个想法。
    贝壳会收集您的文章,包括某个数码产品,您的购买时间,价格,个人评价,当然,最重要的,购买方式。因为文章要发在贝壳的blog上,所以您需要同意内容以cc-by-sa3.0相容版权发布。当然,文章会署您的名字。收集文章的前提是,我得认识您。您和贝壳是在哪里吃过饭,您帮贝壳解决过什么问题,物理世界里面互相有过交流。只要认识,我就会贴出,或者转发您的文章。注意,需要反复强调的是,里面需要提供购买方式。贝壳不介意那个卖家和您有没有亲戚朋友关系,只要这个东西是可靠的。为什么可靠?既然我认得您,我就相信您。一个人钻研半天技术,只为了其他几个宅买几样东西而说谎,这是不值得的。就好象贝壳写那么一大堆技术资料,开这么一个blog,不会为了几个钱的回扣专门骗你一回。因此,如果你认得贝壳,这个事情也是比较有保障的——至少你不会故意的被骗。至于运气好坏,个人感觉,这个就真不好说了。所以,如果您不认识贝壳,只是经常跑过来看技术资料的——那,就看您信不信了。不管您信不信,反正我信了。
    而对于来挑选东西的宅男们,贝壳要说明的是。贝壳买东西的风格,是偏好产品的用途和质量,而无视价格的。所谓无视,既包括可能因为某个卖家不靠谱或者某个型号不靠谱而购买相对比较贵的产品。也包括某个东西虽然名声不显,但确实很好用,而无视东西的品牌。因此,您可能会问,为什么贝壳这里只介绍DIR-825路由器,还有那么多更物美价廉的呢?例如buffalo的某款。首先,可能因为贝壳的朋友只有人用过DIR-825。其次,贝壳知道buffalo的路由器都是单AP,而贝壳本身是一定需要双AP的。或者您也可能会问,为什么我们对苹果这样的东西,周边居然配了一个名不见经传的充电器/触摸笔。实话说,只要好用,我不在乎。
    另外,我也希望从文章中挑东西的人,把您的经历也附加在下面。包括您买了同样产品,感觉如何?其他产品,好不好用?这也是给后来者一个借鉴。
    好吧,废话半天,现在贝壳开始征集稿子了。

PS:个人希望,如果有买了小米手机量产版本的小白鼠,能够跳出来说说情况。贝壳希望入一个,可是网络上说法满天飞,不知道该信谁啊。