使用場景
監控彙總
目前正在用的一個場景,針對某一臺機器上的錯誤進行彙總並報警,咱們把一分鐘以內的相同報警合併成一條,用共享內存來暫存,很是實用且高效。
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):是shmopopen返回的ID,它識別您操做的共享內存塊。∗第二個參數(shmid):是shmopopen返回的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):是您希望讀取的字節數。一般情況下我們用shmopsize(count):是您但願讀取的字節數。通常狀況下咱們用shmopsize(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 |
<?php
//$key = ftok(_FILE_, 'h'); //$key = ftok('/tmp/', 'h'); $key = 0xff4; $permissions = 0755; $size = 1024; // 內存段的大小,單位是字節 $semid = sem_get($key); # 請求信號控制權 if (sem_acquire($semid)) { $content = file_get_contents(__FILE__); $shmid = shmop_open($key, 'c', 0644, strlen($content)); # 讀取並寫入數據 shmop_write($shmid, $content, 0); # 關閉內存塊 shmop_close($shmid); # 釋放信號 sem_release($semid); }
<?php /** * @param $fn */ function profile(callable $fn) { $t = microtime(1); $i = 0; while ($i < 999) { $fn(); ++$i; } var_dump('takes ', microtime(1) - $t); } $fnIO = function (){ file_get_contents(__DIR__."/chmop_create.php"); }; $fn = function (){ //$key = ftok('chmop', 'h'); //$key = ftok('/tmp/', 'h'); $key = 0xff4; $permissions = 0755; $size = 1024; // 內存段的大小,單位是字節 $semid = sem_get($key); # 請求信號控制權 if (sem_acquire($semid)) { $shmid = shmop_open($key, 'a', 0644, 1024); # 讀取並寫入數據 $out = shmop_read($shmid, 0, shmop_size($shmid)); # 關閉內存塊 // var_dump($out); // echo $out; shmop_close($shmid); # 釋放信號 sem_release($semid); } }; profile($fn); profile($fnIO);
這是Mac Pro 2014上的結果:
這是ali ECS的結果 :
可見,現代磁盤優化比我們老觀念中的性能要好些了~
但利用內存依然是個不錯的玩法~