最近在接入service mesh时,由于涉及底层接口高访问量的场景,考虑到性能问题需将mesh的地址缓存起来,首先想到了使用共享内存。
我们知道关于共享内存的操作,php有两套内置扩展函数shmop和sysvshm,一开始我采用了shmop扩展,业务在线上跑了半个多月均无任何异常出现,直到20天之后开始出现一些零星的异常,捕捉到的错误信息如下:
1
| PHP Warning 'yii\base\ErrorException' with message 'shmop_delete() expects parameter 1 to be long, string given' in test.php
|
其中写共享内存的php代码实现如下:
1 2 3 4 5 6 7
| public function write($data){ $data = pack('a*', $data); shmop_delete($this->shmId); shmop_close($this->shmId); $this->shmId = shmop_open($this->key, 'c', $this->perms, strlen($data)); return shmop_write($this->shmId, $data, 0); }
|
由于shmop扩展函数在插入、更新、读取等操作均需要自行规划和管理共享内存的存储结构,比如使用shmop开辟一块共享内存并写入内容:”abcdefghijklmn”,第二次写入:”123456”,则此时共享内存的内容为”123456ghijklmn”,第二次的写入并不会清空共享内存已有的内容,所以当初图方便在每次写入时先删掉原来的共享内存块,重新创建一块内存地址并写入新内容,这就为高并发场景下出现以上错误埋下了伏笔。
原因找到了,于是我想到了改成使用sysvshm那套扩展,具体实现如下:
1 2 3 4
| public function write($varKey, $data){ $data = pack('a*', $data); return shm_put_var($this->shmId, $varKey, $data); }
|
第二天凌晨,收到了一大波告警,捕捉到的异常信息如下:
1
| shm_put_var(): not enough shared memory left
|
并且php-fpm一直core,于是立马关掉使用共享内存的开关,经过两天的排查,终于在测试环境复现了问题。
复现方式,通过一个crontab开多个进程同时操作共享内存,一分钟之后,开始出现了同样的错误。于是停止脚本,发现无法通过shm_get_var()来读取共享内存的内容,于是我使用shmop_read读取到的内容如下图所示:
写的内容只有:{“zmq”:{“host”:”19.168.1.100”,”port”:”8899”,”createTime”:1542110838}},但是共享内存里面却是重复的内容,经过分析sysV的接口对于shareKey没有做去重处理,每次都写入了新的key,导致了共享内存的写入指针尽管是相同的shareKey但不断后移,最终导致共享内存被写爆。
于是在操作共享内存的地方都加上信号锁,经过验证再也不会出现以上问题。代码如下:
1 2 3 4 5 6 7 8
| public function write($varKey, $data){ $data = pack('a*', $data); $signal = sem_get($this->key); sem_acquire($signal); $result = shm_put_var($this->shmId, $varKey, $data); sem_release($signal); return $result; }
|
补充:
后来想起了补丁shmop版,其实可以自行进行存储空间管理,再加上信号锁,应该是可以完美解决问题的(以下代码未经过测试):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public function write($data){ $dataLength = strlen($data); $msg = pack('n', $dataLength); $msg .= pack('a', $data); $signal = sem_get($this->key); sem_acquire($signal); $result = shmop_write($this->shmId, $msg, 0); sem_release($signal); return $result; } public function read(){ try{ $size = shmop_size($this->shmId); $signal = sem_get($this->key); sem_acquire($signal); $shmContent = shmop_read($this->shmId, 0, $size); sem_release($signal); $dataInfo = unpack('n1dataLength', $shmContent); $data = unpack("n1dataLength/a{$dataInfo['dataLength']}data", $shmContent); return $data['data']; }catch(\Exception $e){ return null; } }
|
最后还是推荐使用sysvshm那套API,比shmop要好用一些。
感谢:https://www.jianshu.com/p/a182bc8b3f23