用文件的方式读写,一个文件是索引文件,另一个文件是真实的数据文件。
索引文件分为2部分,第一部分是所有的指针,记录第二部分的位置;第二部分是索引记录。所有的索引指针:是记录所有相同Hash值的key的指针,它是一个链表结构,记录在数据文件的位置和同key的下一个值。
索引记录中:每条记录有四部分,第一部分4个字节,是下一条索引的偏移量;第二部分是该记录的key,128字节;第三部分是数据偏移量,4个字节;第四部分是数据记录长度,4个字节。
我们设定文件的存储上限为262144个。

查找流程如下:
1、根据key算出hash值,获取该hash值的链表在索引文件的第一部分(所有指针区)的位置。
2、根据步骤一的位置,获取值,时间复杂度O(1);
2、根据步骤一中的值,找到索引文件中第二部分(索引记录)的位置,也就是和key相同hash值的所有指针的链表。顺着链表查找该key,获取该key在链表中存放的数据,数据只包含该key在索引文件中的位置,时间复杂度为O(n);
3、根据步骤二所获取的key在索引文件位置,得到索引文件中存放该key的信息。信息包含在真实数据文件中存放真实数据的位置。
4、根据步骤三所获取的位置,在真实数据文件中获取数据,并返回给应用程序。

测试结果:插入10000条耗时:793ms。查找10000条耗时:149ms。虽然这效率只有Redis的十分之一。。。但是请不要在意这些细节。。。

代码做了注释,上述文字有些乱。代码只实现三个方法,一个插入(如果存在则跳过),一个是查找,一个是删除。

思路来源:《PHP核心技术与最佳实践》一书。尊重作者,转载请保留该书名。

<?php
//Hash表中的元素指针个数,每个指针都是int,存储hash链表的文件偏移量
define('DB_BUCKET_SIZE', 262144);
//每条记录的key的长度
define('DB_KEY_SIZE', 128);
//一条索引记录的长度
define('DB_INDEX_SIZE', DB_KEY_SIZE + 12);

//成功-返回码
define('DB_SUCCESS', 1);
//失败-返回码
define('DB_FAILURE', -1);
//key重复-返回码
define('DB_KEY_EXISTS', -2);

class DB{
    private $idx_fp;
    private $dat_fp;
    private $closed;

    /**
     * Description: 打开数据库
     * @param $pathName 数据文件的存放路径
     * @return mixed
     */
    public function open($pathName){
        $idx_path = $pathName . '.idx';
        $dat_path = $pathName . '.dat';
        if(!file_exists($idx_path)){
            $init = true;
            $mode = "w+b";
        }else{
            $init = false;
            $mode = 'r+b';
        }
        $this->idx_fp = fopen($idx_path, $mode);
        if(!$this->idx_fp){
            return DB_FAILURE;
        }
        if($init){
            //把0x00000000转换成无符号长整型的二进制
            $elem = pack('L', 0x00000000);
            for($i=0; $i< DB_BUCKET_SIZE; $i++){
                fwrite($this->idx_fp, $elem, 4);
            }
        }
        $this->dat_fp = fopen($dat_path, $mode);
        if(!$this->dat_fp){
            return DB_FAILURE;
        }

        return DB_SUCCESS;
    }

    /**
     * Description: Times33 Hash算法
     * @param $key
     * @return int
     */
    private function times33Hash($key){
        $len = 8;
        $key = substr(md5($key), 0, $len);
        $hash = 0;
        for($i=0; $i<$len; $i++){
            $hash += 33 * $hash + ord($key[$i]);
        }
        //0x7FFFFFFF:一个十六进制的数是4bit,8个就是32位,就是4字节,和一个int一样大。而F是1111,7是0111,那么这个十六进制的数就是头为0,其余为1的,首位是符号位,也就是说7fffffff是最大的整数。
        //& 0x7fffffff 可以保证返回的数是正整数
        return $hash & 0x7FFFFFFF;
    }

    /**
     * Description: 插入记录
     * @param $key
     * @param $value
     */
    public function add($key, $value){
        $offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;

        $idxoff = fstat($this->idx_fp);
        $idxoff = intval($idxoff['size']);

        $datoff = fstat($this->dat_fp);
        $datoff = intval($datoff['size']);

        $keylen = strlen($key);
        $vallen = strlen($value);
        if($keylen > DB_KEY_SIZE){
            return DB_FAILURE;
        }
        //0表示这是最后一个记录,该链再无其他记录。
        $block = pack('L', 0x00000000);
        //键值
        $block .= $key;
        //如果键值的长度没有达到最大长度,则用0填充
        $space = DB_KEY_SIZE - $keylen;
        for($i=0; $i<$space; $i++){
            $block .= pack('C', 0x00);
        }
        //数据所在文件的偏移量
        $block .= pack('L', $datoff);
        //数据记录的长度
        $block .= pack('L', $vallen);
        //尽管SEEK_SET是默认值,但是显式声明了就不怕以后官方会改变了-.-
        fseek($this->idx_fp, $offset, SEEK_SET);
        //检测该key所对应的hash值是否存在了
        $pos = @unpack('L', fread($this->idx_fp, 4));
        $pos = $pos[1];
        //如果key不存在
        if($pos == 0){
            fseek($this->idx_fp, $offset, SEEK_SET);
            fwrite($this->idx_fp, pack('L', $idxoff), 4);

            fseek($this->idx_fp, 0, SEEK_END);
            fwrite($this->idx_fp, $block, DB_INDEX_SIZE);

            fseek($this->dat_fp, 0, SEEK_END);
            fwrite($this->dat_fp, $value, $vallen);

            return DB_SUCCESS;
        }
        //如果key存在
        $found = false;
        while($pos){
            fseek($this->idx_fp, $pos, SEEK_SET);
            $tmp_block = fread($this->idx_fp, DB_INDEX_SIZE);
            $cpkey = substr($tmp_block, 4, DB_KEY_SIZE);
            //$cpkey==$key时返回0,小于返回负数,大于返回正数
            if(!strncmp($cpkey, $key, $keylen)){
                $dataoff = unpack('L', substr($tmp_block, DB_KEY_SIZE + 4, 4));
                $dataoff = $dataoff[1];
                $datalen = unpack('L', substr($tmp_block, DB_KEY_SIZE + 8, 4));
                $datalen = $datalen[1];
                $found = true;
                break;
            }
            $prev = $pos;
            $pos = @unpack('L', substr($tmp_block, 0, 4));
            $pos = $pos[1];
        }

        if($found){
            return DB_KEY_EXISTS;
        }
        fseek($this->idx_fp, $prev, SEEK_SET);
        fwrite($this->idx_fp, pack('L', $idxoff), 4);
        fseek($this->idx_fp, 0, SEEK_END);
        fwrite($this->idx_fp, $block, DB_INDEX_SIZE);
        fseek($this->dat_fp, 0, SEEK_END);
        fwrite($this->dat_fp, $value, $vallen);
        return DB_SUCCESS;
    }

    /**
     * Description: 查询一条记录
     * @param $key
     */
    public function get($key){
        //计算偏移量,key的hash值对索引文件的大小求模,再乘4。因为每个链表指针大小为4
        $offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;
        //SEEK_SET是默认的
        fseek($this->idx_fp, $offset, SEEK_SET);
        $pos = unpack('L', fread($this->idx_fp, 4));
        $pos = $pos[1];

        $found = false;
        while($pos){
            fseek($this->idx_fp, $pos, SEEK_SET);
            $block = fread($this->idx_fp, DB_INDEX_SIZE);
            $cpkey = substr($block, 4, DB_KEY_SIZE);

            if(!strncmp($key, $cpkey, strlen($key))){
                $dataoff = unpack('L', substr($block, DB_KEY_SIZE + 4, 4));
                $dataoff = $dataoff[1];

                $datalen = unpack('L', substr($block, DB_KEY_SIZE + 8, 4));
                $datalen = $datalen[1];

                $found = true;
                break;
            }
            $pos = unpack('L', substr($block, 0, 4));
            $pos = $pos[1];
        }
        if(!$found){
            return null;
        }
        fseek($this->dat_fp, $dataoff, SEEK_SET);
        $data = fread($this->dat_fp, $datalen);
        return $data;
    }

    /**
     * Description: 删除
     * @param $key
     */
    public function delete($key){
        $offset = ($this->times33Hash($key) % DB_BUCKET_SIZE) * 4;
        fseek($this->idx_fp, $offset, SEEK_SET);
        $head = unpack('L', fread($this->idx_fp, 4));
        $head = $head[1];
        $curr = $head;
        $prev = 0;
        $found = false;
        while($curr){
            fseek($this->idx_fp, $curr, SEEK_SET);
            $block = fread($this->idx_fp, DB_INDEX_SIZE);

            $next = unpack('L', substr($block, 0, 4));
            $next = $next[1];

            $cpkey = substr($block, 4, DB_KEY_SIZE);
            if(!strncmp($key, $cpkey, strlen($key))){
                $found = true;
                break;
            }
            $prev = $curr;
            $curr = $next;
        }
        if(!$found){
            return DB_FAILURE;
        }
        //删除索引文件。
        if($prev == 0){
            fseek($this->idx_fp, $offset, SEEK_SET);
            fwrite($this->idx_fp, pack('L', $next), 4);
        }else{
            fseek($this->idx_fp, $prev, SEEK_SET);
            fwrite($this->idx_fp, pack('L', $next), 4);
        }
        return DB_SUCCESS;
    }

    public function close(){
        if(!$this->closed){
            fclose($this->idx_fp);
            fclose($this->dat_fp);
            $this->closed = true;
        }
    }
}
?>

测试,测试添加一万条和查找一万条:

<?php
//先include上面的类。。如果在同一个文件中就不用了。
//测试
$db = new DB();
$db->open('/var/www/data/');

$startTime = microtime(true);

//插入测试...插入10000条:成功,耗时: 793.48206520081ms
//for($i=0; $i<10000; $i++){
//    $db->add('key'.$i, 'value'.$i);
//}

//查找测试...查找10000条:成功,耗时: 149.08313751221ms
for($i=0; $i<10000; $i++){
    $db->get('key'.$i);
}

$endTime = microtime(true);
echo '成功,耗时: ' . (($endTime - $startTime)*1000) . 'ms';
$db->close();
?>

Hash表作为最重要的数据结构之一,也叫做散列表。使用PHP实现Hash表的功能。PHP可以模拟实现Hash表的增删改查。通过对key的映射到数组中的一个位置来访问。映射函数叫做Hash函数,存放记录的数组称为Hash表。
Hash函数把任意长度的和类型的key转换成固定长度输出。不同的key可能拥有相同的hash。
Hash表的时间复杂度为O(1)

<?php
/**
 * hash表类
 * Class HashTable
 * Auth Lane
 * Mail lixuan868686@163.com
 * Blog http://www.lanecn.com
 */
class HashTable{
    private $arr = array();
    private $size = 10;
    public function __construct(){
        //SplFixedArray创建的数组比一般的Array()效率更高,因为更接近C的数组。创建时需要指定尺寸
        $this->arr = new SplFixedArray($this->size);
    }

    /**
     * Description: 简单hash算法。输入key,输出hash后的整数
     * @param $key
     * @return int
     */
    private function simpleHash($key){
        $len = strlen($key);
        //key中每个字符所对应的ASCII的值
        $asciiTotal = 0;
        for($i=0; $i<$len; $i++){
            $asciiTotal += ord($key[$i]);
        }
        return $asciiTotal % $this->size;
    }

    /**
     * Description: 赋值
     * @param $key
     * @param $value
     * @return bool
     */
    public function set($key, $value){
        $hash = $this->simpleHash($key);
        $this->arr[$hash] = $value;
        return true;
    }

    /**
     * Description: 取值
     * @param $key
     * @return mixed
     */
    public function get($key){
        $hash = $this->simpleHash($key);
        return $this->arr[$hash];
    }

    public function getList(){
        return $this->arr;
    }

    public function editSize($size){
        $this->size = $size;
        $this->arr->setSize($size);
    }
}
?>

下面对我们的HashTable进行测试。

<?php
//测试1
$arr = new HashTable();
for($i=0; $i<15; $i++){
    $arr->set('key'.$i, 'value'.$i);
}
print_r($arr->getList());
//SplFixedArray Object
//(
//    [0] => value14
//    [1] => value4
//    [2] => value5
//    [3] => value6
//    [4] => value7
//    [5] => value8
//    [6] => value10
//    [7] => value11
//    [8] => value12
//    [9] => value13
//)
//不同的key可能产生相同的hash值,那么赋值的时候后操作会覆盖前操作。

//测试2
$arr->editSize(15);
for($i=0; $i<15; $i++){
    $arr->set('key'.$i, 'value'.$i);
}
print_r($arr->getList());
//SplFixedArray Object
//(
//    [0] => value14
//    [1] => value4
//    [2] => value0
//    [3] => value1
//    [4] => value2
//    [5] => value3
//    [6] => value10
//    [7] => value11
//    [8] => value12
//    [9] => value13
//    [10] => value14
//    [11] => value9
//    [12] =>
//    [13] =>
//    [14] =>
//)
?>

改变了值之后可以存放更多的元素。但是仍然存在不同的key可能产生相同的hash值,那么赋值的时候后操作会覆盖前操作的问题。这种冲突的问题我们来用拉链法解决。

拉链法解决冲突。拉链法解决冲突的做法是将所有的相同Hash值的key放在一个链表中,比如key3和key14在hash之后都是0,那么在数组的键为0的地方存储这两个值,形式是链表。如果不能理解我的文字,请看下面的示例,看一下打印信息就明白了。拉链法是什么,就是链表。
创建一个HashNode类,用来存储key和value的值,并且存储相同hash的另一个元素。在同一条链上,查找越后的元素越费时。时间复杂度为O(n).

<?php
class HashNode{
    public $key;
    public $value;
    public $nextNode;
    public function __construct($key, $value, $nextNode=Null){
        $this->key = $key;
        $this->value = $value;
        $this->nextNode = $nextNode;
    }
}
class NewHashTable{
    private $arr;
    private $size = 10;
    public function __construct(){
        $this->arr = new SplFixedArray($this->size);
    }
    private function simpleHash($key){
        $asciiTotal = 0;
        $len = strlen($key);
        for($i=0; $i<$len; $i++){
            $asciiTotal += ord($key[$i]);
        }
        return $asciiTotal % $this->size;
    }
    public function set($key, $value){
        $hash = $this->simpleHash($key);
        if(isset($this->arr[$hash])){
            $newNode = new HashNode($key, $value, $this->arr[$hash]);
        }else{
            $newNode = new HashNode($key, $value, null);
        }
        $this->arr[$hash] = $newNode;
        return true;
    }
    public function get($key){
        $hash = $this->simpleHash($key);
        $current = $this->arr[$hash];
        while(!empty($current)){
            if($current->key == $key){
                return $current->value;
            }
            $current = $current->nextNode;
        }
        return NULL;
    }
    public function getList(){
        return $this->arr;
    }
}
?>

对我们新的HashTable进行测试

<?php
//测试1
$newArr = new NewHashTable();
for($i=0; $i<30; $i++){
    $newArr->set('key'.$i, 'value'.$i);
}
print_r($newArr->getList());
var_dump($newArr->get('key3'));
//SplFixedArray Object
//(
//    [0] => HashNode Object
//(
//    [key] => key23
//            [value] => value23
//            [nextNode] => HashNode Object
//(
//    [key] => key14
//                    [value] => value14
//                    [nextNode] => HashNode Object
//(
//    [key] => key3
//                            [value] => value3
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [1] => HashNode Object
//(
//    [key] => key24
//            [value] => value24
//            [nextNode] => HashNode Object
//(
//    [key] => key15
//                    [value] => value15
//                    [nextNode] => HashNode Object
//(
//    [key] => key4
//                            [value] => value4
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [2] => HashNode Object
//(
//    [key] => key25
//            [value] => value25
//            [nextNode] => HashNode Object
//(
//    [key] => key16
//                    [value] => value16
//                    [nextNode] => HashNode Object
//(
//    [key] => key5
//                            [value] => value5
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [3] => HashNode Object
//(
//    [key] => key26
//            [value] => value26
//            [nextNode] => HashNode Object
//(
//    [key] => key17
//                    [value] => value17
//                    [nextNode] => HashNode Object
//(
//    [key] => key6
//                            [value] => value6
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [4] => HashNode Object
//(
//    [key] => key27
//            [value] => value27
//            [nextNode] => HashNode Object
//(
//    [key] => key18
//                    [value] => value18
//                    [nextNode] => HashNode Object
//(
//    [key] => key7
//                            [value] => value7
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [5] => HashNode Object
//(
//    [key] => key28
//            [value] => value28
//            [nextNode] => HashNode Object
//(
//    [key] => key19
//                    [value] => value19
//                    [nextNode] => HashNode Object
//(
//    [key] => key8
//                            [value] => value8
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [6] => HashNode Object
//(
//    [key] => key29
//            [value] => value29
//            [nextNode] => HashNode Object
//(
//    [key] => key10
//                    [value] => value10
//                    [nextNode] => HashNode Object
//(
//    [key] => key9
//                            [value] => value9
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [7] => HashNode Object
//(
//    [key] => key20
//            [value] => value20
//            [nextNode] => HashNode Object
//(
//    [key] => key11
//                    [value] => value11
//                    [nextNode] => HashNode Object
//(
//    [key] => key0
//                            [value] => value0
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [8] => HashNode Object
//(
//    [key] => key21
//            [value] => value21
//            [nextNode] => HashNode Object
//(
//    [key] => key12
//                    [value] => value12
//                    [nextNode] => HashNode Object
//(
//    [key] => key1
//                            [value] => value1
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//    [9] => HashNode Object
//(
//    [key] => key22
//            [value] => value22
//            [nextNode] => HashNode Object
//(
//    [key] => key13
//                    [value] => value13
//                    [nextNode] => HashNode Object
//(
//    [key] => key2
//                            [value] => value2
//                            [nextNode] =>
//                        )
//
//                )
//
//        )
//
//)
//string(6) "value3"
?>

高性能网站架构方案,本文谈了七点网站架构方案,用以优化网站响应时间,实现大型网站技术架构方案。无论是电子商务或者其他网站且可使用。
一、优化网站响应时间的架构方案:
网站能不能留的住用户,一方面是看内容,另一方面是看响应时间。通常有以下几个方式来降低网站响应时间:
1、减少HTTP请求。包括合并css和javascript。减少图片数量,比如利用css的偏移技术来在一个图片中选择不同的位置内容。利用浏览器的Cache功能,我们可以在头中声明是否被浏览器缓存。
2、动态内容静态化。比如永久生成HTML文件。生成静态文件并设定生存时间,到期后查询新的动态内容进行替换。
3、优化数据库。数据库的性能对于项目整体性能中是重中之重。设计良好的Mysql比乱糟糟的Mysql性能高出N个数量级,更别论再引入NOSQL了,比如Redis,MongoDB。
4、使用负载均衡。将请求合理的分发到更多服务器。
5、使用缓存。把花费时间和资源成本高昂的计算结果取出缓存起来,避免重复计算。比如在Mysql前面挡一层Memcached。比如生成一个文件,使用的时候include进来。再比如PHP中的OPCACHE等。

二、压力测试的架构方案:
吞吐率是指单位时间内处理的请求数,单位reqs/s。最大吞吐率是指单位时间内能够处理的最大请求出。模拟足够多的人数和并发请求来测试最大吞吐率的方法叫做压力测试。比如Apache自带的ab(Apache Bench)。ab的参数很多,常用的有请求数(-n),并发用户数(-c),超时时间(-t),长连接(-k),附件一个Cookie(-c name=value)

$ab -c 10 -n 1000 http://localhost/

三、长连接的架构方案:
每次请求都需要TCP的三次握手,握手完比表示连接正式联通,之后再发送数据。那么,把N个请求,就需要3N次握手,传递N次数据,得到N次响应,总共5N。如果把N个请求合成一个请求,就是3次握手,1次传递数据,1次返回响应,共5次。但是,有时候我们需要上一次响应的返回结果来发送新一轮的请求,在这个时候,合并请求并不好实现,这就需要长连接。使用起来很简单,在头中包含如下:

Connection: Keep-Alive

客户端和服务器端都可以设置长连接的最大时间,当两者不统一时以小的一方为准。开启长连接后进行压力测试:

$ab -c 10 -n 1000 http://localhost/

发现提升不止三五倍。本机是提升了8倍的性能。

四、提高Mysql的响应速度的架构方案:
Handlerocker是日本的一位架构师开发。Mysql的一种插件。Handlerocker实现了绕过Mysql的SQL解析层。在Mysql5.1以上版本可以使用,详情可以查看Mysql手册。这里就不在阐述。

五、Mysql主从复制的架构方案:
在分布式部署中,1台主库,N台从库。主库只写,从库只查。主库从库数据需要实现统一,这就是主从复制。优点是:
1、从库备份时,主库可以继续处理更新。
2、优化响应时间。
3、增加健壮性。主库挂了可以切换到从库作为备份。
主从复制的实现过程有三步,1个在主库,2个在从库:
1、主库服务器将用户对数据库更新的操作以二进制格式保存到Binary Log日志文件。然后Binlog Dump线程将Binary Log日志文件传输给从库服务器。
2、从库服务器通过一个I/O线程将主库服务器的Binary Log日志文件中的更新操作复制到一个叫做Relay Log中的中继日志文件中。
3、从库服务器通过另一个SQL线程Relay Log中继日志文件中的操作依次在本地执行,从而实现主从数据库之间数据的同步。
本篇只是简单的列出方案,详细的配置和实现步骤将在另一篇中写到。

六、代理的架构方案:
读取内存的速度是读取硬盘的100000-1000000倍。把访问过的页面缓存在内存中,下次直接从内存中读取,可以有效加速。
1、传统代理。客户端发送请求给代理服务器,代理服务器向WEB服务器取到数据并返回给浏览器。代理服务器就是一个有大的存储空间的Cache。
2、反向代理。和传统代理原理类似,只是使用对象不同。传统代理的使用对象是客户端,反向代理的使用对象是服务器。用户通过反向代理访问Web服务器,Web服务器是隐藏起来的。不过用户不关心这些,权把代理服务器当作真实的Web服务器。反向代理有Vamish。

七、异步计算的架构方案:
比较耗时的比如将用户上传的文件分发到多台机器,比如裁剪图片,视频转码等。可以使用异步方案。让用户无须等待计算结束而是先行返回结果。代表产品有和Memcache同一家的Gearman。关于Gearman的使用可以查看PHP手册。

一台Memcache通常不能满足我们的需求,这就需要分布式部署。Memcached分布式部署方案通常会采用两种方式,一种是普通Hash分布,一种是一致性Hash分布。本篇将以PHP作为客户端,来分析两种方案。
一、普通Hash分布:

<?php
function test($key='name'){
    $md5 = substr(md5($key), 0, 8);
    $seed = 31;
    $hash = 0;
    for($i=0; $i<8; $i++){
        $hash = $hash * $seed + ord($md5[$i]);
    }
    return $hash & 0x7FFFFFFF;
}

$memcacheList = array(
        array('host'=>'192.168.1.2', 'port'=>6379),
        array('host'=>'192.168.1.3', 'port'=>6379),
        array('host'=>'192.168.1.4', 'port'=>6379),
        array('host'=>'192.168.1.5', 'port'=>6379),
);
$key = 'username';
$value = 'lane';
//根据KEY获取hash
$hash = $this->test($key);
$count = count($memcacheList);
$memcache = $memcacheList[$hash % $count];
$mc = new Memcached($memcache);
$mc->set($key, $value);
?>

代码很简单,一个Hash函数,根据所需要的key,将他md5后取前8位,然后经过Hash算法返回一个整数。将这个整数对服务器总数求模。得到的就是服务器列表的编号。这种方式的缺点是服务器数量改变后,同一个key不同hash,将取不到值了。

二、一致性Hash分布
一致性Hash尽管也会造成数据的丢失,但是损失是最小的。
将2的32次方-1想象成一个圆环,服务器列表在上面排列。根据key通过hash算法求得在圆环上的位置,那么所需要的服务器的位置在key的位置前面最近的一个(顺时针)。

<?php
class FlexiHash{
    //服务器列表
    private $serverList = array();
    //是否排序
    private $isSort = false;

    /**
     * Description: Hash函数,将传入的key以整数形式返回
     * @param string $key
     * @return int
     */
    private function myHash($key){
        $md5 = substr(md5($key), 0, 8);
        $seed = 31;
        $hash = 0;
        for($i=0; $i<8; $i++){
            $hash = $hash * $seed + ord($md5[$i]);
        }
        return $hash & 0x7FFFFFFF;
    }

    /**
     * Description: 添加新服务器
     * @param $server
     */
    public function addServer($server){
        $hash = $this->myHash($server);
        if(!isset($this->serverList[$hash])){
            $this->serverList[$hash] = $server;
        }
        $this->isSort = false;
        return true;
    }

    /**
     * Description: 删除指定服务器
     * @param $server
     * @return bool
     */
    public function removeServer($server){
        $hash = $this->myHash($server);
        if(isset($this->serverList[$hash])){
            unset($this->serverList[$hash]);
        }
        $this->isSort = false;
        return true;
    }

    /**
     * Description: 根据要操作的KEY返回一个操作的服务器信息
     * @param $key
     * @return mixed
     */
    public function lookup($key){
        //将指定的KEYhash出一个整数
        $hash = $this->myHash($key);
        if($this->isSort !== true){
            krsort($this->serverList);
            $this->isSort = false;
        }
        foreach($this->serverList as $key=>$server){
            if($key <= $hash){
                return $server;
            }
        }
        return array_pop($this->serverList);
    }
}
//使用方法
$mc = new FlexiHash();
$mc->addServer('192.168.1.2');
$mc->addServer('192.168.1.3');
$mc->addServer('192.168.1.4');
$mc->addServer('192.168.1.5');

echo 'KEY=key1时,操作的服务器为:'.$mc->lookup('key1').'<br>';
echo 'KEY=key1时,操作的服务器为:'.$mc->lookup('key2').'<br>';
echo 'KEY=key1时,操作的服务器为:'.$mc->lookup('key3').'<br>';
echo 'KEY=key1时,操作的服务器为:'.$mc->lookup('key4').'<br>';
echo 'KEY=key1时,操作的服务器为:'.$mc->lookup('key5').'<br>';
?>

Redis是实时广播推送的一把好手。Redis提供了发布-订阅者模式。发布消息是PUBLISH 频道名 内容的格式。如:

PUBLISH fm97 hello world

这样,所有订阅fm7频道的用户就可以收到hello world了。PUBLISH返回值是收到的订阅者个数。订阅命令是SUBSCRIBE。如:

SUBSCRIBE fm97

在输入Redis订阅命令之后,值可以输入SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE/PUNSUBSCRIBE这四个命令,不然会报错。在SUBSCRIBE模式下,收到的消息第一行是subscribe。第二行是频道名称fm97,第三行是当前的订阅数量。也可能是收到的消息第一行是message。第二行是频道名称fm97,第三行是广播内容hello world。
退定则是UNSUBSCRIBE,如

UNSUBSCRIBE fm97

PSUBSCRIBE 通过通配符来进行订阅,如:

PSUBSCRIBE fm?*

这就订阅了fm开头的所有频道,但不会订阅fm这个频道。
PUNSUBSCRIBE同理。不说啦~