在以往的項目中,遇到高併發大流量需求做併發控制的時候一般都使用redis分佈式鎖或者mysql加鎖處理高併發情況。最近遇到一個php項目,沒有安裝redis,由於某種原因也不考慮使用mysql加鎖控制併發,所以採用文件鎖的方式控制併發,整理了下代碼
php版本
class FileLock
{
/** @var string 鎖名稱 唯一性 */
private string $key;
/** @var string 鎖文件 */
private string $file = "";
/** @var 文件資源 */
private $fp = null;
public function __construct(string $key)
{
if (empty($key)) {
throw new \InvalidArgumentException("key 不能為空");
}
$this->key = $key;
}
/**
* 加鎖
*/
public function lock(): bool
{
// 文件路徑
$file = "lock_{$this->key}.txt";
$this->file = $file;
$fp = fopen($file, "w+");
if (is_resource($fp)) {
$this->fp = $fp;
} else {
return false;
}
return flock($fp, LOCK_EX);
}
public function unlock(): bool
{
if (is_resource($this->fp)) {
return flock($this->fp, LOCK_UN); // 釋放鎖
}
return false;
}
public function __destruct()
{
if (is_resource($this->fp)) {
@flock($this->fp, LOCK_UN);
}
if (is_file($this->file)) {
@unlink($this->file);
}
}
}
php代碼
go版本
同時整理go版本實現
package main
import (
"fmt"
"os"
"syscall"
"time"
)
// FileLock 實現文件鎖定
type FileLock struct {
file string
f *os.File
how int
}
func (l *FileLock) How(how int) {
l.how = how
}
func New(file string) *FileLock {
return &FileLock{
file: file,
how: syscall.LOCK_EX | syscall.LOCK_NB, // 默認非阻塞
}
}
// Lock 加鎖
func (l *FileLock) Lock() error {
f, err := os.Create(l.file)
if err != nil {
return err
}
l.f = f
// syscall.LOCK_NB 非阻塞
if err := syscall.Flock(int(f.Fd()), l.how); err != nil {
return err
}
return nil
}
// Unlock 釋放鎖
func (l *FileLock) Unlock() error {
defer l.f.Close()
if err := syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN); err != nil {
// 可以增加報警 或者 直接刪除文件
return err
}
return nil
}
func main() {
work()
}
func work() {
// 給該方法加鎖
lockedFile := "/tmp/my_lock.lock"
flock := New(lockedFile)
//flock.How(syscall.LOCK_EX) 阻塞
err := flock.Lock()
if err != nil {
// 獲取鎖失敗
fmt.Println(err.Error())
return
}
defer flock.Unlock()
fmt.Println("執行業務代碼,模擬長時間")
time.Sleep(time.Second * 50)
}
go代碼