使用場景
監控匯總
目前正在用的一個場景,針對某一台機器上的錯誤進行匯總並報警,我們把一分鍾之內的相同報警合並成一條,用共享內存來暫存,非常實用且高效。
PHP SESSION
如果你是單機的服務,且又啟用了session,那么可以把session換成共享內存的來存儲,會比文件要快上不少,這里還要強調是單機,這是最大的軟肋,但就功能上來講沒有memcache方便。
什么是共享內存
共享內存是一種在同一台機器的不同進程(應用程序)之間交換數據的方式。一個進程可創建一個可供其他進程訪問的內存段,並賦予它相應的權限。每個內存段擁有一個惟一的ID,我們通常稱之為shmid,這個ID指向一個物理內存區域,其他進程可通過此ID來操作這塊內存, 包擴讀取、寫入以及刪除。
共享內存的使用是一種在進程之間交換數據的快速方法,主要因為在創建內存段之后傳遞數據,不會涉及內核。這種方法常常稱為進程間通信 (IPC)。其他 IPC 方法包括管道、消息隊列、RPC 和套接字。
PHP 中幾種常見的共享內存使用方式
APC 可以緩存 PHP 的 opcode 提高應用的性能,可以在同個 PHP-FPM 進程池的進程間共享數據,常用功能如下:
- apc_store
- apc_fetch
- apc_add
- apc_delete
- apcinc apcdec
- apc_cas
- apcclearcache
- apcsmainfo
Shmop Unix 系統共享內存使用接口常用功能:
- shmop_open
- shmop_close
- shmop_read
- shmop_write
- shmop_delete
ipcs -m 查看本機共享內存的狀態和統計。
ipcrm -m shmid 或 ipcrm -M shmkey 清除共享內存中的數據。
SystemV Shm常用功能:
- ftok
- shm_attach
- shm_detach
- shmputvar
- shmgetvar
- shmremovevar
使用共享內存需要考慮操作的原子性和鎖、並行和互斥。
sem 信號量相關函數:
* semget * semremove * semacquire * semrelease
PHP 提供的 IPC 機制。
- --enable-shmop 共享內存,只能按字節操作
- --enable-sysvsem 信號量
- --enable-sysvshm 共享內存,和 shmop 的差別是提供的操作函數不同,支持 key、value操作
- --enable-sysvmsg 消息隊列
本文主講
如何使用 PHP shmop 創建和操作共享內存段,使用它們存儲可供其他應用程序使用的數據。
1. 創建內存段
共享內存函數類似於文件操作函數,但無需處理一個流,您將處理一個共享內存訪問 ID。第一個示例就是 shmopopen 函數,它允許您打開一個現有的內存段或創建一個新內存段。此函數非常類似於經典的 fopen 函數,后者打開用於文件操作的流,返回一個資源供其他希望讀取或寫入該打開的流的函數使用。讓我們看看 shmopopen的用法:
<?php
$key = ftok(__FILE__, 'h');
$mode = 'c';
$permissions = 0644;
$size = 1024;
$shmid = shmop_open($key, $mode, $permissions, $size);
?>
第一個參數($key):
系統建立IPC通訊 (消息隊列、信號量和共享內存) 時必須指定一個key值。通常情況下,該key值通過ftok函數得到, * *key是一個我們邏輯上表示共享內存段的標識。不同進程只要選擇同一個Key值就可以共享同一段存儲段。
第二個參數($mode):
訪問模式,它類似於fopen的訪問模式,有以下幾種
- 模式 “a”,它允許您訪問只讀內存段
- 模式 “w”,它允許您訪問可讀寫的內存段
- 模式 “c”,它創建一個新內存段,或者如果該內存段已存在,嘗試打開它進行讀寫 *模式 “n”,它創建一個新內存段,如果該內存段已存在,則會失敗,返回 false,並伴隨有warning: unable to attach or create shared memory segment
第三個參數($permissions):
內存段的權限。您必須在這里提供一個八進制值,它類似於UNIX操作系統文件和目錄的操作權限。
第四個參數($size):
內存段大小,以字節為單位。在寫入一個內存段之前,您必須在它之上分配適當的字節數。
返回結果:
此函數返回一個 ID 編號,其他函數可使用該 ID 編號操作該共享內存段。這個 ID 是共享內存訪問 ID,與系統 ID 不同,它以參數的形式傳遞。請注意不要混淆這兩者。如果失敗,shmop_open 將返回 FALSE。
shmop_open成功后,使用ipcs -m, 可以查看到剛剛創建的內存段,注意 申請的內存段有嚴格的權限,比如用root用戶申請的,普通用戶就無權訪問
2. 向內存段寫入數據
使用 shmop_write 函數向共享內存塊寫入數據。此函數的使用很簡單,它僅接受 3 個參數,如下所示。
<?php
//這里shmid可以延用上一段代碼返回的shmid
$shmid = shmop_open(ftok(__FILE__,'h'), 'c', 0644, 1024);
shmop_write($shmid, "Hello World!", 0);
?>
這個函數類似於 fwrite 函數, 在這里有三個參數。 * 第一個參數($shmid):是 shmop_open 返回的 ID,它識別您操作的共享內存塊。 * 第二個參數($data):是您希望存儲的數據。 * 第三個參數($offset):是您希望開始寫入的位置。默認情況下,我們始終使用 0 來表示開始寫入的位置。
返回結果:此函數在失敗時會返回 FALSE,在成功時會返回寫入的字節數。
3. 從內存段讀取數據
從共享內存段讀取數據很簡單。您只需要一個打開的內存段和 shmop_read 函數,它接受三個參數,如下所示:
<?php
$shmid = shmop_open(ftok(\__FILE_\_,'h'), 'c', 0644, 1024);
shmop_write($shmid, "Hello World\!", 0);
var_dump(shmop_read($shmid, 0, 11));
?>
- 第一個參數($shmid):是 shmop_open 返回的 ID,它識別您操作的共享內存塊。
- 第二個參數($start):是您希望從內存段讀取的位置,這個參數可以始終為0, 表示數據的開頭
- 第三個參數($count):是您希望讀取的字節數。一般情況下我們用shmop_size($shmid),以便完整的讀取它。
4. 刪除內存段
shmop_delete 該函數只接收一個參數,如下所示:
<?php
$shmid = shmop_open(ftok(\__FILE_\_,'h'), 'c', 0644, 1024);
shmop_delete($shmid);
?>
其實這個函數不會實際刪除該內存段。它將該內存段標記為刪除狀態,因為共享內存段在有其他進程正在使用它時無法被刪除。shmop_delete 函數將該內存段標記為刪除,阻止任何其他進程打開它。要刪除它,我們需要關閉該內存段。
5. 關閉內存段
打開一個共享內存段會 “附加” 到它。附加該內存段之后,我們可在其中進行讀取和寫入,但完成操作后,我們必須從它解除。
<?php
$shmid = shmop_open(ftok(\__FILE_\_,'h'), 'c', 0644, 1024);
shmop_write($shmid, "Hello World\!", 0);
shmop_delete($shmid); shmop_close($shmid);
?>
共享內存的原子操作 - 信號控制
針對共享內存的寫操作本身不是原子性的,那么當我們大量並發進行讀寫的時候,怎么保證原子性呢,這里要引入信號量進行控制。
PHP 也提供了內置擴展 sysvsem ,其實我們在看sysvsem 提供的一系列sem*的方法的時候,就會想到,這和上面提到的shmop*有什么區別呢,我們來看官房文檔中的這一個解釋:PHP already had a shared memory extension (sysvshm) written by Christian Cartus cartus@atrior.de, unfortunately this extension was designed with PHP only in mind and offers high level features which are extremely bothersome for basic SHM we had in mind.
也就是說:sysvshm 擴展提供的方法在存儲之前對用戶的數據進行serialize處理,這里就導致這個存儲的數據是無法與其它語言共享的,這一系列方法是php only的方法。
引入信號控制之后的示例:
<?php
$key = ftok(_FILE_, 'h') $mode = "c";
$permissions = 0755;
$size = 1024; // 內存段的大小,單位是字節
$semid = sem_get($key); # 請求信號控制權
if (sem_acquire($semid)) {
$shmid = shmop_open($key, 'c', 0644, 1024); # 讀取並寫入數據
shmop_write($shmid, '13800138000', 0); # 關閉內存塊
shmop_close($shmid); # 釋放信號 sem_release($semid);
}
共享內存的操作是非常快的,在本地想要模擬實現寫入沖突是非常困難的,但是本地想模擬實現寫入沖突實際上是非常難的(考慮到計算機的執行速度)。在本地測試中,使用 for 循環操作時如果不使用shmop_close 關閉資源會出現無法打開共享內存的錯誤警告。這應該是因為正在共享內存被上一次操作占用中還沒有釋放導致。
共享內存,memcache,文件的讀寫速度對比。
以下是同時讀寫1k的數據讀寫100000次的時間對比:
讀(s) | 寫(s) | |
memcache | 7.8 | 8.11 |
file | 2.6 | 3.2 |
shm | 0.1 | 0.07 |