并发下常见的加锁及锁的PHP具体实现-转载

并发下常见的加锁及锁的PHP具体实现

http://www.cnblogs.com/scotoma/archive/2010/09/26/1836312.html

在最近的项目中有这样的场景

1.生成文件的时候,由于多用户都有权限进行生成,防止并发下,导致生成的结果出现错误,需要对生成的过程进行加锁,只容许一个用户在一个时间内进行操作,这个时候就需要用到锁了,将这个操作过程锁起来.

2.在用了cache的时候,cache失效可能导致瞬间的多数并发请求穿透到数据库此时也可以得需要用锁在同一并发的过程中将这个操作锁定.

 

针对以上的2种情况,现在的解决方法是对处理过程进行锁机制,通过PHP实现如下

用到了Eaccelerator的内存锁 和 文件锁,原理如下

判断系统中是否安了EAccelerator 如果有则使用内存锁,如果不存在,则进行文件锁

根据带入的key的不同可以实现多个锁直接的并行处理,类似Innodb的行级锁

 

使用如下:

$lock = new CacheLock('key_name');

$lock->lock();

//logic here

$lock->unlock();

//使用过程中需要注意下文件锁所在路径需要有写权限.

具体类如下:

复制代码
<?php
/**
 * CacheLock 进程锁,主要用来进行cache失效时的单进程cache获取,防止过多的SQL请求穿透到数据库
 * 用于解决PHP在并发时候的锁控制,通过文件/eaccelerator进行进程间锁定
 * 如果没有使用eaccelerator则进行进行文件锁处理,会做对应目录下产生对应粒度的锁
 * 使用了eaccelerator则在内存中处理,性能相对较高
 * 不同的锁之间并行执行,类似mysql innodb的行级锁
 * 本类在sunli的phplock的基础上做了少许修改  http://code.google.com/p/phplock 
 * @author yangxinqi
 *
 */
class CacheLock
{
    //文件锁存放路径
    private $path = null;
    //文件句柄
    private $fp = null;
    //锁粒度,设置越大粒度越小
    private $hashNum = 100;
    //cache key 
    private $name;
    //是否存在eaccelerator标志
    private  $eAccelerator = false;
     
    /**
     * 构造函数
     * 传入锁的存放路径,及cache key的名称,这样可以进行并发
     * @param string $path 锁的存放目录,以"/"结尾
     * @param string $name cache key
     */
    public function __construct($name,$path='lock\\')
    {
        //判断是否存在eAccelerator,这里启用了eAccelerator之后可以进行内存锁提高效率
        $this->eAccelerator = function_exists("eaccelerator_lock");
        if(!$this->eAccelerator)
        {
            $this->path = $path.($this->_mycrc32($name) % $this->hashNum).'.txt';
        }
        $this->name = $name;
    }
     
    /**
     * crc32
     * crc32封装
     * @param int $string
     * @return int
     */
    private function _mycrc32($string)
    {
        $crc = abs (crc32($string));
        if ($crc & 0x80000000) {
            $crc ^= 0xffffffff;
            $crc += 1;
        }
        return $crc;
    }
    /**
     * 加锁
     * Enter description here ...
     */
    public function lock()
    {
        //如果无法开启ea内存锁,则开启文件锁
        if(!$this->eAccelerator)
        {
            //配置目录权限可写
            $this->fp = fopen($this->path, 'w+');
            if($this->fp === false)
            {
                return false;
            }
            return flock($this->fp, LOCK_EX);
        }else{
            return eaccelerator_lock($this->name);
        }
    }
     
    /**
     * 解锁
     * Enter description here ...
     */
    public function unlock()
    {
        if(!$this->eAccelerator)
        {
            if($this->fp !== false)
            {
                flock($this->fp, LOCK_UN);
                clearstatcache();
            }
            //进行关闭
            fclose($this->fp);
        }else{
            return eaccelerator_unlock($this->name);
        }
    }
}
复制代码

本类在孙立同学的类的基础上做了小点改进的了.具体可以看 http://code.google.com/p/phplock  感谢孙同学的分享精神!


Apache + PHP 的并发访问

文章地址:http://www.cnblogs.com/WestContinent/archive/2013/03/25/2981667.html
1.书写例程:

做成一个测试用的PHP文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
//为了测试是否多个用户访问的时候这个值是公用的,即:是否临界资源
$count = 0;
 
//循环十次,消耗十秒,模拟一个费时操作
for ($i=1; $i<=10; $i++,$count++) {
   
    echo getTime()." i is $i and count is $count.</br>";
    sleep(1);
}
/**
 * 获得当前时间,返回字符串,包含毫秒数
 * 格式:yyyy-MM-dd HH:mm:ss.fff
 */
function getTime()
{
    $currentTime Date('Y-m-d H:i:s');//Get currentTime str
    $milisecond = microtime();
    $splitmiliTime explode('.'$milisecond);
    $milisecond $splitmiliTime[1];
    $milisecond substr($milisecond, 0,3);
    $currentTime $currentTime.'.'.$milisecond;
    return $currentTime;
}
?>

 2.把上面做成的文件Copy到Apache的发布目录(一般是Htdocs,但是可以配置)

3.打开两个浏览器同时访问发布PHP页面

分别得到如下结果

浏览器1

2013-03-25 14:55:42.128 i is 1 and count is 0.
2013-03-25 14:55:43.138 i is 2 and count is 1.
2013-03-25 14:55:44.152 i is 3 and count is 2.
2013-03-25 14:55:45.169 i is 4 and count is 3.
2013-03-25 14:55:46.180 i is 5 and count is 4.
2013-03-25 14:55:47.194 i is 6 and count is 5.
2013-03-25 14:55:48.208 i is 7 and count is 6.
2013-03-25 14:55:49.222 i is 8 and count is 7.
2013-03-25 14:55:50.236 i is 9 and count is 8.
2013-03-25 14:55:51.250 i is 10 and count is 9.

 浏览器2

2013-03-25 14:55:41.286 i is 1 and count is 0.
2013-03-25 14:55:42.296 i is 2 and count is 1.
2013-03-25 14:55:43.310 i is 3 and count is 2.
2013-03-25 14:55:44.324 i is 4 and count is 3.
2013-03-25 14:55:45.339 i is 5 and count is 4.
2013-03-25 14:55:46.355 i is 6 and count is 5.
2013-03-25 14:55:47.367 i is 7 and count is 6.
2013-03-25 14:55:48.402 i is 8 and count is 7.
2013-03-25 14:55:49.645 i is 9 and count is 8.
2013-03-25 14:55:50.658 i is 10 and count is 9.

 4.结论

从上面的试验结果可以得到如下结论,因为两组测试数据中的时间犬牙交错,两个用户在耗时操作中并没有出现某一个用户长时间占用执行时间片的情况。说明Apache+Php(loadmodule)是支持多用户并行操作的。另外全局变量Count在两个用户同时访问的时候都是以全新的状态出现的,因此Apache+Php(loadmodule)不支持内存缓存数据,也就是说在多用户并发访问的情况下每次访问都会开辟新的内存。那么如果需要对多用户的操作做同步,只能使用文件锁的方式来实现了。


加锁解锁PHP实现

文章地址:http://blog.csdn.net/topasstem8/article/details/6735240

PHP并没有完善的线程支持,甚至部署到基于线程模型的httpd服务器都会产生一些问题,但即使是多进程模型下的PHP,也难免出现多进程共同访问同一 资源的情况。比如整个程序共享的数据缓存,或者因为资源受限而必须对特定处理过程进行排队,以及针对每个用户生成唯一的某种标识的情形。PHP语言自身没 有提供进程互斥和锁定机制,因而使得在这些情况下的编程遇到了困难,目前了解到的可选的办法有以下这些:

1、利用MySQL的锁定机制来实现互斥。缺点是增大了数据库服务器的连接负担,并且使得程序依赖于数据库服务才能正常工作。
2、利用文件锁机制。也就是利用flock函数通过文件实现锁定和互斥机制,来模拟通用编程模型下的锁定原语的工作方式。这种方式在以前以纯文本文件为存储引擎的时代成为保护数据完整性的必备元素,现在在使用文本文件作为缓存媒介的场合也相当常见。PmWiki应该也是使用了这个机制来对多人同时编辑一个页面的情形进行提醒。不过文件锁机制多少会调用到宿主操作系统上的文件锁特性,因此在使用时一定要检查服务器操作系统是否为PHP环境提供了完善可靠的文件锁机制。
3、利用共享内存空间计数。PHP可以利用shmop_open函数开辟一块内存空间,在服务进程之间共享数据,为了保证共享数据的互斥安全访问,可以使用 sem_get、sem_acquire和sem_release这组函数实现共享计数锁定机制。这种办法在后台实际是调用了系统的ipc 服务来实现。


用 PHP 编写支持高并发的网站,需要做什么处理?

文章地址:http://www.zhihu.com/question/20049768

1、Webserver (Nginx) :这一层是可以轻松分布式部署的,结合智能DNS解析可以简易地防止单点故障、实现区域访问加速,结合LVS很容易实现负载均衡。这一层主要是负责处理静态请求和转发PHP请求至第二层的PHP处理节点,至于静态资源地址(http://misc.xxxx.com)可以单独拿出来部署,或者直接使用商用的云存储服务(国内七牛不错,国外有Amazon S3)
2、PHP 处理节点:一个节点其实就是一个监听特定端口的系统进程,webserver的请求通过负载均衡器(我用的AWS的loadbalancer)进行分发, 很好实现分布式和负载均衡。我现在用的还是php自带的php-fpm,其实facebook出的hhvm性能非常强悍,但是还不能100%通过我项目的 单元测试,等hhvm成熟过后可以平滑替换
3、高速缓存:用的memcached,这一层的作用主要是减轻数据库IO和加快热数据 访问,缓存策略与程序耦合度较高,不赘述,但简单地说有两种方式,一种是在程序的全局层面加一个缓存处理,这种方法代码耦合度低,但是有效命中率不高,有 些项目不一定适应,另一种是在具体的数据存取处加缓存处理,这种办法程序耦合度较高,但是缓存命中率非常高,几乎没有无效缓存存在,我用的是这种。
4、数据库 :我现在的项目数据规模不大,暂时只用了单台数据库,但是程序逻辑上已做好了数据库线性扩展的准备。其实数据库层的扩展是老生常谈了,常用手段是分库分 表,这一块需要在前期的代码就打下基础,另外更平滑地手段是使用中间件,比如360的Atlas,阿里巴巴的cobar,淘宝的TDDL,中间件可以在不 大范围变更代码的情况下扩展,但是具体的使用场景还是有限的,具体项目还需单独考察。
5、其 他:根据不同的项目,架构还可以选择性地使用队列,我现在用的beantalkd,Redis也是一个很好的选择。队列常用的使用环境是邮件发送和站内消息推送上面,但是在某些场景下也可以作为核心数据库的缓冲,对应对大并发或者突发性流量也是不错的选择

posted @ 2016-11-02 22:56  yubolin  阅读(986)  评论(0编辑  收藏  举报