项目中有一个需求是统计视频的观看人数,这个是一定会涉及到并发问题,从而导致数据不准确,目前我了解到的 PHP 处理并发的方法有文件锁、MySQL 的乐观锁和悲观锁,以及 redis 队列。因为我每天的统计数据是存在 redis 中,凌晨的时候集中汇总,并发时也就涉及不到 MySQL 了,因此,统计时的并发问题,我选择用文件锁来解决。
锁机制简介
PHP文件锁有两种形式:共享锁(读锁)和 独占锁(写锁、排他锁)。
共享锁是为了保证写入程序使用独占锁对文件进行写入的时候,对该文件进行读取时,避免读到脏数据。
独占锁是在上锁之后,一直到 unlock 之前都不允许其他程序进行任何操作。
所以,在这个需求里,我要用的是排他锁。
参数介绍
flock ( resource $handle , int $operation [, int &$wouldblock ] )
|
handle
文件系统指针,是典型地由 fopen() 创建的 resource (资源)。
operation
operation 可以是以下值之一:
- LOCK_SH 取得共享锁定(读取的程序)。
- LOCK_EX 取得独占锁定(写入的程序)。
- LOCK_UN 释放锁定(无论共享或独占)。
- 如果不希望 flock() 在锁定时堵塞,则是 LOCK_NB(Windows 上还不支持)。
wouldblock
如果锁定会堵塞的话(EWOULDBLOCK 错误码情况下),可选的第三个参数会被设置为 TRUE。(Windows 上不支持)
代码实现
//获取指针
|
因为我没有对文件本身进行读写,并且我需要后面的程序堵塞,等待本次操作完成,才能获取文件锁,继续操作,保证数据不丢,所以我没有用到 LOCK_NB。另,我们的服务器是两台负载均衡,所以即使上了文件锁,在理论上也可能会稍有误差,因为这个数据并不需要那么精确,所以有一点误差是可以接受的。
注意
如果是要对文件本身进行操作的话,写锁要用 LOCK_EX,在写的同时其他程序进行读取的时候要用 LOCK_SH,如果不用共享锁来读的话,可能会读到 dirty 数据。
但是什么时候使用 lock_ex 什么时候使用 lock_sh 呢?
读的时候
如果不想出现 dirty 数据,那么最好使用 lock_sh 共享锁。可以考虑以下三种情况:
- 如果读的时候没有加共享锁,那么其他程序要写的话(不管这个写是加锁还是不加锁)都会立即写成功。如果正好读了一半,然后被其他程序给写了,那么读的后一半就有可能跟前一半对不上(前一半是修改前的,后一半是修改后的)。
- 如果读的时候加上了共享锁(因为只是读,没有必要使用排他锁),这个时候,其他程序开始写,这个写程序没有使用锁,那么写程序会直接修改这个文件,也会导致前面一样的问题。
- 最理想的情况是,读的时候加锁( lock_sh ),写的时候也进行加锁(lock_ex),这样写程序会等着读程序完成之后才进行操作,而不会出现贸然操作的情况。
写的时候
如果多个写程序不加锁同时对文件进行操作,那么最后的数据有可能一部分是 a 程序写的,一部分是 b 程序写的。如果写的时候加锁了,这个时候有其他的程序来读,那么他会读到什么东西呢?
- 如果读程序没有申请共享锁,那么他会读到 dirty 的数据。比如写程序要写 a,b,c 三部分,写完 a,这时候读读到的是 a,继续写 b,这时候读读到的是 ab,然后写c,这时候读到的是 abc。
- 如果读程序在之前申请了共享锁,那么读程序会等写程序将 abc 写完并释放锁之后才进行读。
浙公网安备 33010602011771号