DHTCache.php params['dht_cache_conf'];if( empty($conf) ) {throw new \Exception('Error cache config!');}self::$_obj = new self($conf);}return self::$_obj;}private function __construct($conf) {$this-conf = $conf;$this-init();}/** * 初始化节
params['dht_cache_conf'];
if( empty($conf) ) {
throw new \Exception('Error cache config!');
}
self::$_obj = new self($conf);
}
return self::$_obj;
}
private function __construct($conf) {
$this->conf = $conf;
$this->init();
}
/**
* 初始化节点配置
* @return number
*/
protected function init() {
$incr = floor( $this->conf['hash_range'] / ( $this->conf['virtual_number']+1 ) );
foreach($this->conf['nodes'] as $key => $node) {
$this->conf['nodes'][$key]['hash'] = $this->getHash($node['host'].':'.$node['port']);
}
// 生成虚拟节点
$newNodes = $this->conf['nodes'];
for($i = 0; $i < $this->conf['virtual_number']; $i++) {
foreach($this->conf['nodes'] as $node) {
$hash = $incr * ( $i + 1 ) + $node['hash'];
$node['hash'] = $hash > $this->conf['hash_range'] ? $hash - $this->conf['hash_range'] : $hash;
$newNodes[] = $node;
}
}
usort($newNodes, function($a, $b) {
return $a['hash'] > $b['hash'] ? 1 : -1;
});
$this->conf['nodes'] = $newNodes;
}
/**
* 缓存
* @param Array|String $key 缓存键
* 若为数组,会自动使用 下划线(_) 连接各元素,将至转换为字符串
*
* @param Array $callback 获取数据回调函数配置
* 可以为:
* 1. 数组, [{Class},{Method}]
* 2. 匿名函数, function(){...}
* 3. 函数名, 'functionName'
*
* @param Array $params $callback所需要的参数
* @param number $expire 过期时间,单位秒(s)
* @return number
*/
public function run($key, $callback, $params = [], $expire = 0) {
is_array($key) && $key = implode('_', $key);
!$expire && $expire = $this->conf['default_expires'];
$cacheKey = $this->conf['key_prefix'] . $key;
$redis = $this->getNode($cacheKey);
$result = $redis->get($cacheKey);
// 是否需要更新缓存
$update = true;
$nowTime = time();
!empty($result) && $update = $result['expires'] < $nowTime;
if($update) {
$lock = new RedisLock($key, $redis);
if( $lock->begin() ) {
// 获取数据
try{
$data = call_user_func_array($callback, $params);
} catch(\Exception $e) {
$data = ['code'=>-500,'msg'=>$e->getMessage()];
}
// 数据过期时间, 需要根据这个时间, 判断是否更新缓存
$result = [
'expires'=>$nowTime + max($this->conf['min_expires'], (int)$expire),
'data'=> $data
];
// 长时间缓存数据, 在这个期间遇到争锁的情况, 也能正常返回数据
$res = $redis->setex($cacheKey, $this->conf['keep_time'], $result);
$lock->release();
}
}
return $result['data'];
}
/**
* 获取缓存节点
* @param String $cacheKey
*/
public function getNode($cacheKey) {
$hash = $this->getHash($cacheKey);
$conf = [];
foreach($this->conf['nodes'] as $node) {
if($hash <= $node['hash']) {
$conf = $node;
break;
}
}
empty($conf) && $conf = $this->conf['nodes'][0];
$conf = array_merge($this->conf['redis_conf'], $conf);
unset($conf['hash']);
return Yii::createObject($conf);
}
/**
* 计算字符串hash, 0 到 2^31 - 1之间的数
* @param String $string
* @return number
*/
protected function getHash($string) {
$val = base_convert( hash('fnv1a32', $string), 16, 10 ) / 2;
return (int)$val;
}
}
/**配置**/
return [
// redis 节点
'nodes'=>[
[
'host' => '10.71.182.79',
'port' => 6379,
'slaves'=>[
['host' => '10.71.182.79','port' => 6379]
],
]
],
'redis_conf'=>[
'class'=>'yii\common\components\Redis',
'serializer'=>true,
'timeout'=>2,
'database'=>0
],
// 统一缓存键前缀
'key_prefix'=>'cache_',
// 默认过期时间 秒(s)
'default_expires'=>30,
// 过期时间最小值 秒(s)
'min_expires'=> 1,
// 锁过期时间 秒(s)
'lock_expires'=>10,
// 缓存保留时间 秒(s)
// 缓存过期后,并不会直接删除
'keep_time'=>86400,
// 每个真实节点对应的虚拟节点数
'virtual_number'=>2,
// hash范围2^31 - 1
'hash_range'=> 2147483647,
];
/**使用**/
$data = DHTCache::instance($conf)->run('some_key', function($a) { return [...];}, [1], 60);
