日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > php >内容正文

php

php windows共享内存,关于php的共享内存的使用和研究之由起

發布時間:2023/12/4 php 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 php windows共享内存,关于php的共享内存的使用和研究之由起 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近遇到一個場景,服務尋址的時候,需要請求遠程的服務,獲取一批可用的ip和端口地址及其權重。根據權重和隨機算法選擇最合適的一個服務地址,進行請求。由于服務地址在短時間之內不會發生變化,因此為了避免無限制的進行尋址的請求,有必要將地址緩存至本地。

對于php而言,說到用戶數據緩存本地,第一反應出來的就是APC。但是APC首先被創建出來是給php做內部緩存的,其次才是提供給用戶態使用的。根據laruence在博客的說法,opcache出現了之后,對zend編譯的opcode做了緩存,實際上解決了apc被創建出來想要解決的問題。因此現在APC已經處于不再更新維護的狀態了。

對于想使用opcache,又要使用用戶態的APC的同學,就需要額外的配置,同時性能上也會比原來的APC要差,差不多相當于本機的memcache。這顯然就無法達到本機內存訪問的效率了,因此需要尋求其他的解決方案。

php的共享內存API

隨后我就想到了使用php的共享內存API,反正只是緩存非常少的路由信息,加在一起不超過1k,盡管是多讀多寫的場景,但是覆蓋了也沒關系,出于這種出發點,我就開始了對php的共享內存API的研究。

php中操作共享內存的方式一共有兩組:

System V IPC

編譯增加 --enable-sysvshm

Shared Memory

--enable-shmop

先來看一個shmop的例子:

// 從系統獲取一個共享內存的id

$key = ftok(__FILE__, 'test');

$size = 1024;

// 打開1024字節的共享內存(如果不存在則申請)

$shm_h = @shmop_open($key, 'c', 0644, $size);

if($shm_h === false) {

echo "shmop open failed";

exit;

}

// 讀取共享內存中的數據

$data = shmop_read($shm_h, 0, $size);

// 對讀取的數據進行反序列化

$data = unserialize($data);

//如果沒有數據則寫入

if(empty($data)) {

echo "there is no data";

$data = "imdonkey";

//所有寫入的數據,都必須提前序列化

$write_size = shmop_write($shm_h, serialize($data), 0);

if($write_size === false) echo "shmop write failed!";

}

//如果有,顯示出來,之后刪掉

else {

echo "shared memory data: ";

print_r($data);

shmop_delete($shm_h);

}

shmop_close($shm_h);

?>

使用shmop擴展,必須要注意數據的大小,以及讀寫時候的偏移量。同時,不管你寫入的是什么數據類型,都必須進行序列化和反序列化。

再看一下SysV的例子:

// 從系統獲取一個共享內存的id

$shm_key = ftok(__FILE__, 'test');

// 獲取此共享內存資源的操作句柄

$memsize = 1024;

$shm_h = shm_attach($shm_key, $memsize, 0644);

if($shm_h === false) {

echo "shmop open failed";

exit;

}

// 獲取共享內存中key=222時的內容

$var_key = 222;

$data = @shm_get_var($shm_h, $var_key);

if(empty($data)) {

$data = ['test'=>'here'];

echo "there is no data, insert $data.\n";

// 如果數據不存在,寫入數據,可以是任意類型,無需初始化

shm_put_var($shm_h, $var_key, $data);

} else {

// 否則,輸出數據,并清理相關內存

echo "find data: $data\n";

shm_remove_var($shm_h, $var_key);

}

// 斷開資源的鏈接

shm_detach($shm_h);

?>

原理上來講并無不同,只是SysV做了更多的封裝,讓你使用起來更加方便一些。不用自己控制偏移量,也不用進行序列化和反序列化。同時對于每個數據,都設置了對應的var_key, 這樣在同一個區域可以保存多個數據,而無需再次申請另一片共享內存。

業務中的使用

在使用兩者的時候,都要注意對數據大小的估算。否則很容易出現共享內存溢出的情況。而我在使用的時候,充分評估了要存儲的數據結構的大小,我需要存儲的內容是:

ip(15個字節以內)+port(8字節以內)+timestamp(15字節以內)+分隔符(3字節)=41字節

假設我調用100個后端服務。那么最高需要存儲的路由信息就是4.1k大小。

出于這種考慮,我申請了1M的內存,覺得應該是夠夠的了。就這么悠哉哉的在線上跑了一個星期左右,有天沒事到線上看了下php的錯誤日志,結果一臉懵逼:

屏幕快照 2016-12-25 下午2.51.26.png

什么情況,調用的后端服務一共才5個,共享內存這么快就寫滿了??經過一個初步的判斷之后,我得出的結論是:sysV的接口能力太差,對于shareKey沒有做去重處理,而是每次都寫入了新的key,這樣就導致了共享內存的寫入指針盡管是相同的shareKey,但是卻不斷的后移,最終導致共享內存被寫爆,而尋址的請求全部都打到了尋址服務,還好它比較健壯,也有短時的緩存,才沒有產生運營事故。

在得出了這么個結論之后,我修改了我的代碼,在每次完成對shareKey內容的獲取之后,增加了一行

shm_remove_var($shareKey)

同時寫了一個腳本,把原有的共享內存id對應的內容清空,經過手工處理十臺機器之后,再全量替換一把代碼,打卡下班,感覺自己棒棒噠。

沒想到,這才是悲劇的開始。就在當周的周六,吃著火鍋,突然就有一臺線上機器罷工了。機器服務狂core不止,打開系統配置的core文件輸出之后,迅速占滿磁盤,無奈之下,先讓運維把機器摘掉,再進一步的分析。其他機器也出現了不同程度的core,線上失敗率直線上升。

屏幕快照 2016-12-25 下午3.08.52.png

再把機器摘下來之后,看了一眼core文件,就發現,哎呀,闖禍了。

屏幕快照 2016-12-25 下午3.18.50.png

趕快恢復到沒有remove的版本,至少還能撐一個星期,不至于程序core掉。

踩坑與解決

接下來開始仔細分析源碼,發現sysV的擴展中,remove_var實現如下:

PHP_FUNCTION(shm_remove_var)

{

zval *shm_id;

long shm_key, shm_varpos;

sysvshm_shm *shm_list_ptr;

// 讀取輸入參數

if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &shm_id, &shm_key)) {

return;

}

SHM_FETCH_RESOURCE(shm_list_ptr, shm_id);

// 檢查sharekey在共享內存中是否存在

shm_varpos = php_check_shm_data((shm_list_ptr->ptr), shm_key);

// 如果不存在,返回錯誤

if (shm_varpos < 0) {

php_error_docref(NULL TSRMLS_CC, E_WARNING, "variable key %ld doesn't exist", shm_key);

RETURN_FALSE;

}

// 如果存在,刪除共享內存

php_remove_shm_data((shm_list_ptr->ptr), shm_varpos);

RETURN_TRUE;

}

咋一看沒啥問題,但是深入看一下php_check_shm_data,發現有問題:

// ptr為整個共享內存區塊的頭指針

static long php_check_shm_data(sysvshm_chunk_head *ptr, long key)

{

long pos;

sysvshm_chunk *shm_var;

// 從頭開始尋找

pos = ptr->start;

for (;;) {

// 找到最后了返回

if (pos >= ptr->end) {

return -1;

}

// 向前進一個內存區塊,由當前區塊的next指針決定

shm_var = (sysvshm_chunk*) ((char *) ptr + pos);

if (shm_var->key == key) {

return pos;

}

pos += shm_var->next;

if (shm_var->next <= 0 || pos < ptr->start) {

return -1;

}

}

return -1;

}

這個根本就是線程不安全的版本額,在高并發的場景下,非常有可能出現,對一個shareKey內是否存在數據的錯誤判斷,根據swoole的多進程模型,進程A進行尋址,查看共享內存,發現shareKey對應的區塊無數據,所以他準備進行寫入,同時進程B之前已經檢查了shareKey數據,發現shareKey數據已經過期,執行了remove操作。這時候進程A再想去寫入的時候,就會發生不可避免的segmentation fault。

發現了這個問題之后,反過來去想當時為什么共享內存會被寫滿,也是一樣的問題,都怪php_check_shm_data對key的判斷線程不安全,所以不可避免的,高并發下一直會用重復的key不停的向前寫入。當時申請了 12M的內存, 每秒500請求,swoole開了24個進程,假設碰撞概率是1/(24*500)=1/12000。每次寫入的大小是4k*3(四個服務尋址),程序設計的是五分鐘進行一次put。

那么12M共享內存被寫滿的時間應該是12M/12k/(60min/5min)/24h = 3.6天左右。基本上只能撐個這么久。

所以呢,解決方向有兩個:

實現一個有鎖的共享內存API版本

另辟蹊徑,使用別的本地內存存儲方案

權衡之下,準備采取第二種做法,預知后事如何,且看下回分解~

總結

以上是生活随笔為你收集整理的php windows共享内存,关于php的共享内存的使用和研究之由起的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。