PHP 版的一致性hash算法

今天看豆瓣的架构PPT,其中有谈到Consistent Hashing的算法,网上搜了下,发现这方面的介绍也不算多。

通常比较常用的使用地方就是memcache分布式的时候,例如我有三台memcache,那么普通的hash算法,直接用你要取的key的hash值模取memcache的数量。

server = hash(key) mod server num;

但是如果要增加一台,那么将会有大量的缓存信息失效。所以更好的办法Consistent Hashing.

Consistent hashing算法:环状结构。虚拟节点来替换实体节点被分配到环状某一位置上(根据处理能力不同可以将一个实体节点映射到多个虚拟节点上)。主键为key的节点position = hash(key),在环上按照顺时针查找value大于position的第一个虚拟节点,由它对应的实体节点处理。下图中k就优先由虚拟节点 B来处理。

按照我的理解方式 就是将所有的cache服务器的标识分别hash到的值分布到0-pow(2,32)中. 然后将你要取的key的hash值进行比较。取第一个大于key的hash值的服务器。 如果从0-pow(2,32)也没有找到,那么则取最小hash值的服务器。

例如 上图中, 服务器 a 的hash值 为 1, b的hash值为 10, c的hash值为20, 我们取 key K 的hash值为5的位置,发现key K 大于 a 小于 b , 那么key k就存放于服务器b上, 如果 我们取 key x 的hash值为30的位置,发现 a,b,c三个服务器的hash值都小于 key x的hash值 , 那么 我们取 a 服务器作为存放服务器。

这样的存放方法,只会影响你最后一台服务器存放的key.而不会像取模方法的全局影响。

当然这样取key的效率就没有取模的高了,至于采用哪种方法,还是要根据你当前业务的规模来选择的。

另附上简单的算法

<?php

function consistent_hash_key($key,$servers){
   if(empty(
$servers)){
       return 
false;
   }
   
$hash_serv = array();
   
$hash_key sprintf("%un",crc32($key));
   foreach (
$servers as $server){
       
$hash_serv[sprintf("%u",crc32($server))] = $server;
   }
  
   
ksort($hash_serv,SORT_REGULAR);
  
   if(
count($hash_serv) == 1){
       return 
array_pop($hash_serv);
   }
   
$maxHash pow(2,32);
   foreach (
$hash_serv as $k => $v){
       if(
$hash_key $k){
           return 
$v;
       }
   }
   return 
array_shift($hash_serv);
}

for ($i=0;$i<100;$i++){
   echo 
consistent_hash_key('s:'.$i,array('a','b','c','d','e'))."t".consistent_hash_key('s:'.$i,array('a','b','c','d','e','f'))."n";
?>

一致性hash算法

一致性hash算法(Consitent Hash)用于解决动态cache问题 http://baike.baidu.com/view/1588037.html
memcach中一个具体实现算法:
初始化:
已有server m个和各自权重,构建 40*server个数*4个bucket,每个bucket实际上就是一个long值,按照权重分配给各个server,所有的bucket会分布在2的32次方的空间中,用一个TreeMap来存储。
double factor = Math .floor(((double) (40 * this.servers.length * thisWeight)) / (double) this.totalWeight);
            for (long j = 0; j < factor; j++)
            { 
                //md5结果为16个字节,每4个一组,转成一个long值(在0到2的32次方间)
                byte[] d = md5.digest((servers + "-" + j).getBytes());
                for (int h = 0; h < 4; h++)
                {
                    Long k = ((long) (d[3 + h * 4] & 0xFF) << 24)
                            | ((long) (d[2 + h * 4] & 0xFF) << 16)
                            | ((long) (d[1 + h * 4] & 0xFF) << 8)
                            | ((long) (d[0 + h * 4] & 0xFF));

                    consistentBuckets.put(k, servers);
                   
                }
            }

根据key来查找来分配server:
计算key的hash code,从TreeMap中查找大于它的节点,如果有则取第一个,没有则取TreeMap的第一个(相当于一个圆环)
        SortedMap<Long, String> tmap = this.consistentBuckets.tailMap(hv);

        return (tmap.isEmpty()) ? this.consistentBuckets.firstKey() : tmap
                .firstKey();

关于Linux的文件系统cache

关于linux中系统cache的测试:
当我们第一次访问某文件(或其中的一部分时),速度是比较慢的,但是再次访问就很快了,下面我们通过一个程序做一下冷热数据的访问速度的比较。

----------测试脚本------------
<?
$fp = fopen($argv[1], "r");
echo
microtime();
$offset = isset($argv[2])?$argv[2]:0;
fseek($fp,$offset);
$content = fread($fp,4096);
echo
"\n";
echo
microtime();
echo "\n";

fclose($fp);
?>

-----------------------------

测试结果:

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm
0.49064700 1258509003
0.49845600 1258509003
// 第一次访问耗时约8ms

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm
0.04973400 1258509015
0.04988900 1258509015
// 第二次访问相同的数据,耗时约1.5ms

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm
0.50933300 1258509019
0.50948600 1258509019

// 第二次访问相同的数据,耗时约1.5ms

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm 10
0.63028200 1258509035
0.63044500 1258509035
// 访问和上次稍有差异的数据,耗时约0.2ms,

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm 4096
0.89414900 1258509055
0.89434800 1258509055
// 访问和上次差异4k的数据,耗时约0.2ms
// 说明第一次系统缓存起来的数据不仅仅是我们访问的4k的数据,而是更多,具体多少,看内核源代码吧,似乎比测试更快、更准确一些

[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm 4096000
0.24825000 1258509072
0.25472600 1258509072
// 访问和原来差异4M的数据,耗时6ms,说明这部分数据没有被系统cache给缓存

[junjie2@bsso ~]$ mv MySQL-devel-community-5.1.40-0.rhel5.i386.rpm MySQL-devel-community-5.1.40-0.rhel5.i386.rpm2
[junjie2@bsso ~]$ php cache.php MySQL-devel-community-5.1.40-0.rhel5.i386.rpm2 4096000            
0.54055900 1258510429
0.54070300 1258510429
// 重命名文件后访问相同内容,耗时约0.2ms
// 说明文件重命名不影响系统cache,即系统cache和文件名没有关系; 大概是根据inode来cache的,因为重命名前后的inode是没有变化的,name仅仅是文件的一个属性罢了

[junjie2@bsso ~]$

遗留问题:
1. 系统cache的换出机制是怎样的?
2. 系统cache的具体内容能查看吗? 肯定能的,怎么查看呢?
3. 如何刷新系统cache? 上面的测试说明,至少重命名文件是不行的;

另外:
系统cache是系统级别的cache,和进程没有关系; 如mysqld因系统cache加快了访问的速度,这时,重启mysqld是没用的。

参考:
文件系统的cache可以通过/dev/mem 这个字符设备来查看,好像系统利用缓存时是根据inode来查询的,就像memcached那样,至少现在要枚举所有信息是比较麻烦的。

/dev/mem  与 /dev/kmem 的区别:
 原创内容,转载请标明来自http://lixings.cublog.cn

区别:

        

  1. /dev/mem: 物理内存的全镜像。可以用来访问物理内存。
  2.     

  3. /dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。

作用:

        

  1. 前者用来访问物理IO设备,比如X用来访问显卡的物理内存,或嵌入式中访问GPIO。用法一般就是open,然后mmap,接着可以使用map之后的地址来访问物理内存。这其实就是实现用户空间驱动的一种方法。
  2.     

  3. 后者一般可以用来查看kernel的变量,或者用作rootkit之类的。参考1和2描述了用来查看kernel变量这个问题。

参考:

        

  1. http://lwn.net/Articles/147902/
  2.     

  3. http://lkml.org/lkml/2005/8/11/301

rpmdb: Lock table is out of available locker entries…

如果运行一些程序后,远行yum命令出现

“rpmdb: Lock table is out of available locker entries…”的问题时, 你可以按照如下操作来修复它:

错误表现如下:
rpmdb: Lock table is out of available locker entries
error: db4 error(22) from db->close: Invalid argument
error: cannot open Packages index using db3 – Cannot allocate memory (12)
error: cannot open Packages database in /var/lib/rpm

操作之前请先备份 /var/lib/rpm :
tar cvzf rpmdb-backup.tar.gz /var/lib/rpm

去除rpm使用的BDB数据库:
rm /var/lib/rpm/__db.00*

重建立 rpm 使用的数据库,注意:此处可能需要一点时间:
rpm –rebuilddb

现在检查,看看 rpm 包是否一切正常:
rpm -qa | sort

为什么为发生这个问题呢?
其实当您用rpm命令后,rpm访问BDB数据库,首先会设置一个临时锁。如果在它运行时您用 control-c 中断它,或者是给它发了中断信号。那么rpm就很可能会出错误。因为这个临时锁还没有被释放呢。找到原因,相信您还可以找到其它的解决方法。

lftp 上传下载目录的方法

ftp IP
user USERNAME
PASSWORD

> open -u

下传目录 mirror DIRNAME
上传目录 mirror -R DIRNAME

下传目录
方法一 > mget -d DIRNAME/*
方法二 > mirror DIRNAME
上传目录
方法一 >mput -d DIRNAME/*
方法二 >mirror -R DIRNAME
下传普通文件
> get FILENAME
下传多个普通文件
> mget *
lftp搜索文件方法
ls -R | grep .iso
find -d 3 | grep .iso
在使用中,多看看帮助 help
bookmark 标签
cat/more/less 显示文件内容(用cat和more)
zcat/zmore/zless 显示文件内容(用zcat和zmore,区别是zcat和zmore只能直接操作.gz文件)
bzcat/bzmore/bzless 显示文件内容(用bzcat和bzmore,区别是bzcat和bzmore只能直接操作.bz2文件)
get/mget/pget 抓取文件
put/mput/ 上传文件
mirror (-R) 下载上传目录

修改浏览器的useragent

一些wap服务总是根据用户的浏览器类型来判断是否跳转到适合用户访问的页面,比如只有在手机上才能访问到wap页面; 但是我确实想用Firefox访问,这样更方便一些,于是就有了修改Firefox的useragent的需求,修改办法有二:
一、安装插件:
User Agent Switcher
二、通过about:config  修改,添加 general.useragent.override , 值就是你要使用的useragent


导数据中学到的几点东西

1. mysql 的查询速度可能受mysql qcache的影响,更可能受系统cache的影响,而且系统cache影响可能是200和6000的差别,不可小视啊。

2. 如果能批量处理就不要一条一条地处理,批量处理方法:

a.  mysqldump 工具
b.  select * into outfile "myfile" from atable;  // 这样将以tab分隔的方式将记录写到文件里面,对于反斜线会自动加反斜线转义的
c.  用loaddata将b的结果导入到目标表里面

3. mysql的瓶颈一般在磁盘IO上
4. 不要小看sql语句的优化,注意下面两条语句的区别

a.  select * from atable where akey = ‘avalue’;
b.  select count(*) from atable where akey = ‘avalue’;

这里不仅仅是结果集的数据量的差异,a语句除了访问索引文件,还要访问数据文件; 而b语句只访问索引文件就够了; 如果内存小的话,这里的差异就比较大了。

5.  mysql 需要修改表结构时的一些到数据的问题

a. 首先考虑上面提到的使用文件的方式处理
b. 如果需要逐条数据处理则可以将源数据导成文本文件,逐行处理; 注意: 数据要干净,否则就不要使用文件处理
c. 如果需要逐条处理,但是数据量很大,又不能一次查询获取,一般的做法为:
   按照主键排序,每次取一部分(如:1万条,和每条数据量大小有关,既不要频繁获取,又不要每次获取数据量太大,以至于占太多内存)
   这种做法似乎很常用,但是
   写程序并不简单,需要考虑的地方比较多;
   排序只是为了将数据分块,所以做了一些无用功;
   排序是一件很耗时的事情,如果数据量很大,非常不建议使用order by;
   排序还很可能出现[ERROR] /usr/local/mysql/libexec/mysqld: Sort aborted 的错误
   下面介绍一种比较简单有效的做法:
   思路是我们只是想把要取的数据分块儿,很自然就想到了hash,如果主键(的一)部分是数字,则可以根据数量的大小选取一个适当的素数,根据对该素数取模来分块; 将设有900万条数据,每次想取约1万条,则 900万/1万 = 100 ,则取100左右的素数就行,如:127;
   如果主键不是数字,也可以使用hash函数
   


6.  联合主键时的一些查询

mysql> select sql_no_cache count(*) into outfile "/tmp/a" from t_user_friend_list_52 group by user_
id;
Query OK, 878180 rows affected (6.65 sec)

mysql> select sql_no_cache count(*) into outfile "/tmp/b" from t_user_friend_list_52 group by user_
id,friend_id;
Query OK, 9511565 rows affected (10.48 sec)

mysql> select sql_no_cache * into outfile "/tmp/c" from t_user_friend_list_52 order by user_id,frie
nd_id limit 9000000,1000;         
Query OK, 1000 rows affected (31.82 sec)

mysql> select sql_no_cache * into outfile "/tmp/d" from t_user_friend_list_52 order by user_id limi
t 9000000,1000;         
Query OK, 1000 rows affected (31.96 sec)

前两行比较说明根据联合主键的第一列分组还是比根据整个联合主键分组要快一些,毕竟都是只访问索引文件
后两行比较说明根据联合主键的第一列排序还是比根据整个联合主键排序没有明显差别

javascript 中null与undefined到底有啥区别

在JavaScript开发中,被人问到:null与undefined到底有啥区别?

    一时间不好回答,特别是undefined,因为这涉及到undefined的实现原理。于是,细想之后,写下本文,请各位大侠拍砖。

    总所周知:null == undefined

    但是:null !== undefined

    那么这两者到底有啥区别呢?

    请听俺娓娓道来…

null

    这是一个对象,但是为空。因为是对象,所以 typeof null  返回 ‘object’ 。

    null 是 JavaScript 保留关键字。

    null 参与数值运算时其值会自动转换为 0 ,因此,下列表达式计算后会得到正确的数值:

    表达式:123 + null    结果值:123

    表达式:123 * null    结果值:0

undefined

  undefined是全局对象(window)的一个特殊属性,其值是未定义的。但 typeof undefined 返回 ‘undefined’ 。

      虽然undefined是有特殊含义的,但它确实是一个属性,而且是全局对象(window)的属性。请看下面的代码:

    alert(undefined in window);   //输出:true

    
var anObj = {};
     alert(
undefined in anObj);    //输出:false

 
从中可以看出,undefined是window对象的一个属性,但却不是anObj对象的一个属性。

  注意:尽管undefined是有特殊含义的属性,但却不是JavaScript的保留关键字。

  undefined参与任何数值计算时,其结果一定是NaN。

  随便说一下,NaN是全局对象(window)的另一个特殊属性,Infinity也是。这些特殊属性都不是JavaScript的保留关键字!

提高undefined性能

  当我们在程序中使用undefined值时,实际上使用的是window对象的undefined属性。

  同样,当我们定义一个变量但未赋予其初始值,例如:

    var aValue;

  这时,JavaScript在所谓的预编译时会将其初始值设置为对window.undefined属性的引用,

  于是,当我们将一个变量或值与undefined比较时,实际上是与window对象的undefined属性比较。这个比较过程中,JavaScript会搜索window对象名叫‘undefined’的属性,然后再比较两个操作数的引用指针是否相同。

  由于window对象的属性值是非常多的,在每一次与undefined的比较中,搜索window对象的undefined属性都会花费时 间。在需要频繁与undefined进行比较的函数中,这可能会是一个性能问题点。因此,在这种情况下,我们可以自行定义一个局部的undefined变 量,来加快对undefined的比较速度。例如:

    function anyFunc()
    {
        
var undefined;          //自定义局部undefined变量
        
        
if(x == undefined)      //作用域上的引用比较
        
        
        
while(y != undefined)   //作用域上的引用比较
        
    };

   其中,定义undefined局部变量时,其初始值会是对window.undefined属性值的引用。新定义的局部undefined变 量存在与该函数的作用域上。在随后的比较操作中,JavaScript代码的书写方式没有任何的改变,但比较速度却很快。因为作用域上的变量数量会远远少 于window对象的属性,搜索变量的速度会极大提高。

  这就是许多前端JS框架为什么常常要自己定义一个局部undefined变量的原因!

原著:李战(leadzen).杭州-阿里软件 2009-2-18