Laravel中配置缓存以及使用memcached扩展遇到的坑
缘由
起因
最近遇到了南方电信idc访问北方redis从库速度慢的情况,公司动态平台无法根据南北机房配置不同的redis环境变量,所以由领导决定,直接采用动态平台默认提供的memcache服务器进行缓存配置。
Laravel
Laravel做配置缓存相对简单,运行php artisan config:cache
就可以生成一个config_cache.php
的文件,里面包含需要的所有配置信息,所以线上发布的时候,我们会在线下生成缓存文件,然后上传分发到动态平台的前端机。
Memcached变量
动态平台前端机在fpm的配置文件中给予了变量SINASRV_MEMCACHED_SERVERS
,使用$_SERVER["SINASRV_MEMCACHED_SERVERS"]
可以获取单台机器的配置,大抵是这个样子:10.13.32.21:7801 10.13.32.22:7801 10.13.32.105:7801
。
坑1
Laravel中Memcache缓存配置是类似一个数组的样子,如下:
<?php
return [
'stores' => [
'memcached' => [
'driver' => 'memcached',
'servers' =>
[
[
'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100
],
],
],
],
];
我们这里需要的配置项是需要从$_SERVER
变量中动态获取的,每个机房的值不可控。
由于配置文件最终走的是我们缓存的config_cache.php
文件,该文件是框架生成缓存之后使用函数var_export
导出来的,所以考虑这里使用一个对象进行缓存。
<?php
return [
'stores' => [
'memcached' => [
'driver' => 'memcached',
'servers' =>
[
new \Sina\Config\Cache\MemcacheCollection(),
],
],
],
];
类需要实现__set_state
静态方法,并且返回值类似原始配置中的数组样式,最终的代码如下所示:
<?php
/*
* Copyright (C) 2017 SINA Corporation
*
*
*
* This script is firstly created at 2017-03-06.
*
* To see more infomation,
* visit our official website http://app.finance.sina.com.cn/.
*/
namespace Sina\Config\Cache;
use Iterator;
use ArrayAccess;
/**
* Description of MemcacheCollection
*
* @encoding UTF-8
* @author jiaojie <jiaojie@staff.sina.com.cn>
* @since 2017-03-06 16:58 (CST)
* @version 0.1
* @description
*/
class MemcacheCollection implements Iterator, ArrayAccess
{
protected $container = [];
protected $position = 0;
protected static $reserved = "10.13.32.21:7801 10.13.32.22:7801 10.13.32.105:7801 10.13.32.106:7801 10.13.32.147:7801";
public function __construct($string = "")
{
$this->fillContainer($string);
}
public function getContainer() {
return $this->container;
}
protected function fillContainer($string = "")
{
if (empty($string)) {
$string = self::$reserved;
}
$arr = explode(" ", $string);
foreach ($arr as $k => $v) {
list($this->container[$k]["host"], $this->container[$k]["port"]) = explode(":", $v);
$this->container[$k]["weight"] = 0;
}
}
public static function __set_state($arr)
{
$string = env("SINASRV_MEMCACHED_SERVERS");
$setting = new self($string);
return $setting->getContainer();
}
public function current()
{
return $this->container[$this->position];
}
public function key()
{
return $this->position;
}
public function next()
{
++$this->position;
}
public function rewind()
{
$this->position = 0;
}
public function valid()
{
return isset($this->container[$this->position]);
}
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->container[$offset] : null;
}
public function offsetSet($offset, $value)
{
$this->container[$offset] = $value;
}
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
unset($this->container[$offset]);
}
}
}
由于框架中对memcache缓存的配置项加载时强制了array
,所以需要对此处进行修改:
<?php namespace Illuminate\Cache;
use Memcached;
use RuntimeException;
class MemcachedConnector {
/**
* Create a new Memcached connection.
*
* @param array $servers
* @return \Memcached
*
* @throws \RuntimeException
*/
public function connect(array $servers) { // 去掉array
// bla bla
}
}
如此,动态加载配置项就完成了。
正式环境上线后没遇到问题,此时放心地回家了。
坑2
今天上午在测试环境代码的时候,出现了新的问题:
exception 'ErrorException' with message 'Memcached::get(): could not unserialize value, no igbinary support' in /data1/projects/test/app.finance.sina.com.cn/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php:42
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError(2, 'Memcached::get(...', '/data1/projects...', 42, Array)
#1 /data1/projects/test/app.finance.sina.com.cn/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php(42): Memcached->get('app.finance.sin...')
经过google查询,看到了一篇相关的文章(可能要翻墙)
原來是因為,當程式將物件傳入memcache時,我們所使用的memcached會自動將物件做serialize。
memcached Runtime Configuration中提到,memcached有個memcached.serializer參數可控制寫入memcache的格式,目前有以下四種
json
json_array
php
igbinary
預設採用igbinary。但是,當主機上沒有安裝igbinary時,則會改用php模式(standard PHP serializer)。
知道可能的原因,就好處理了~比對了log,以及主機上所安裝的套件,果然是因為該系統的眾多主機中,有台主機沒有安裝Igbinary。因此,當該主機的程式由memcache取回資料時,因無法使用Igbinary而發生錯誤。
最後提一下,原本是打算將該主機上安裝Igbinary以解決這問題。但是考慮未來打算升級到PHP7,而Igbinary目前僅相容PHP5(Compatible with PHP 5.2 – 5.6)。因此,最後統一改為使用php模式(standard PHP serializer)來解決。
然后重新编译了memcached扩展,重启fpm,一切搞定。