Files
sentcms/core/library/think/cache/driver/Secache.php
2016-06-21 17:12:08 +08:00

764 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\cache\driver;
use think\Cache;
/**
* Secache缓存驱动
* @author liu21st <liu21st@gmail.com>
*/
class Secache
{
protected $handler = null;
protected $options = [
'project' => '',
'path' => '',
'expire' => 0,
'prefix' => '',
];
/**
* 架构函数
* @param array $options 缓存参数
* @access public
*/
public function __construct($options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
if (substr($this->options['path'], -1) != '/') {
$this->options['path'] .= '/';
}
$this->handler = new SecacheClient;
$this->handler->workat($this->options['path'] . $this->options['project']);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @return mixed
*/
public function get($name)
{
$name = $this->options['prefix'] . $name;
$key = md5($name);
$this->handler->fetch($key, $return);
return $return;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer $expire 有效时间(秒)
* @return boolean
*/
public function set($name, $value)
{
$name = $this->options['prefix'] . $name;
$key = md5($name);
return $this->handler->store($key, $value);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return boolen
*/
public function rm($name)
{
$name = $this->options['prefix'] . $name;
$key = md5($name);
return $this->handler->delete($key);
}
/**
* 清除缓存
* @access public
* @return boolen
*/
public function clear()
{
return $this->handler->_format(true);
}
}
if (!defined('SECACHE_SIZE')) {
define('SECACHE_SIZE', '15M');
}
class SecacheClient
{
public $idx_node_size = 40;
public $data_base_pos = 262588; //40+20+24*16+16*16*16*16*4;
public $schema_item_size = 24;
public $header_padding = 20; //保留空间 放置php标记防止下载
public $info_size = 20; //保留空间 4+16 maxsize|ver
//40起 添加20字节保留区域
public $idx_seq_pos = 40; //id 计数器节点地址
public $dfile_cur_pos = 44; //id 计数器节点地址
public $idx_free_pos = 48; //id 空闲链表入口地址
public $idx_base_pos = 444; //40+20+24*16
public $min_size = 10240; //10M最小值
public $schema_struct = array('size', 'free', 'lru_head', 'lru_tail', 'hits', 'miss');
public $ver = '$Rev: 3 $';
public $name = '系统默认缓存(文件型)';
public function workat($file)
{
$this->_file = $file . '.php';
$this->_bsize_list = array(
512 => 10,
3 << 10 => 10,
8 << 10 => 10,
20 << 10 => 4,
30 << 10 => 2,
50 << 10 => 2,
80 << 10 => 2,
96 << 10 => 2,
128 << 10 => 2,
224 << 10 => 2,
256 << 10 => 2,
512 << 10 => 1,
1024 << 10 => 1,
);
$this->_node_struct = array(
'next' => array(0, 'V'),
'prev' => array(4, 'V'),
'data' => array(8, 'V'),
'size' => array(12, 'V'),
'lru_right' => array(16, 'V'),
'lru_left' => array(20, 'V'),
'key' => array(24, 'H*'),
);
if (!file_exists($this->_file)) {
$this->create();
} else {
$this->_rs = fopen($this->_file, 'rb+') || $this->triggerError('Can\'t open the cachefile: ' . realpath($this->_file), E_USER_ERROR);
$this->_seek($this->header_padding);
$info = unpack('V1max_size/a*ver', fread($this->_rs, $this->info_size));
if ($info['ver'] != $this->ver) {
$this->_format(true);
} else {
$this->max_size = $info['max_size'];
}
}
$this->idx_node_base = $this->data_base_pos + $this->max_size;
$this->_block_size_list = array_keys($this->_bsize_list);
sort($this->_block_size_list);
return true;
}
public function create()
{
$this->_rs = fopen($this->_file, 'wb+') || $this->triggerError('Can\'t open the cachefile: ' . realpath($this->_file), E_USER_ERROR);
fseek($this->_rs, 0);
fputs($this->_rs, '<' . '?php exit()?' . '>');
return $this->_format();
}
public function _puts($offset, $data)
{
if ($offset < $this->max_size * 1.5) {
$this->_seek($offset);
return fputs($this->_rs, $data);
} else {
$this->triggerError('Offset over quota:' . $offset, E_USER_ERROR);
}
}
public function _seek($offset)
{
return fseek($this->_rs, $offset);
}
public function clear()
{
return $this->_format(true);
}
public function fetch($key, &$return)
{
$locked = false;
if ($this->lock(false)) {
$locked = true;
}
if ($this->search($key, $offset)) {
$info = $this->_get_node($offset);
$schema_id = $this->_get_size_schema_id($info['size']);
if (false === $schema_id) {
if ($locked) {
$this->unlock();
}
return false;
}
$this->_seek($info['data']);
$data = fread($this->_rs, $info['size']);
$return = unserialize($data);
if (false === $return) {
if ($locked) {
$this->unlock();
}
return false;
}
if ($locked) {
$this->_lru_push($schema_id, $info['offset']);
$this->_set_schema($schema_id, 'hits', $this->_get_schema($schema_id, 'hits') + 1);
return $this->unlock();
} else {
return true;
}
} else {
if ($locked) {
$this->unlock();
}
return false;
}
}
/**
* lock
* 如果flock不管用请继承本类并重载此方法
*
* @param mixed $is_block 是否阻塞
* @access public
* @return bool
*/
public function lock($is_block, $whatever = false)
{
return flock($this->_rs, $is_block ? LOCK_EX : LOCK_EX + LOCK_NB);
}
/**
* unlock
* 如果flock不管用请继承本类并重载此方法
*
* @access public
* @return bool
*/
public function unlock()
{
return flock($this->_rs, LOCK_UN);
}
public function delete($key, $pos = false)
{
if ($pos || $this->search($key, $pos)) {
if ($info = $this->_get_node($pos)) {
//删除data区域
if ($info['prev']) {
$this->_set_node($info['prev'], 'next', $info['next']);
$this->_set_node($info['next'], 'prev', $info['prev']);
} else {
//改入口位置
$this->_set_node($info['next'], 'prev', 0);
$this->_set_node_root($key, $info['next']);
}
$this->_free_dspace($info['size'], $info['data']);
$this->_lru_delete($info);
$this->_free_node($pos);
return $info['prev'];
}
}
return false;
}
public function store($key, $value)
{
if ($this->lock(true)) {
//save data
$data = serialize($value);
$size = strlen($data);
//get list_idx
$has_key = $this->search($key, $list_idx_offset);
$schema_id = $this->_get_size_schema_id($size);
if (false === $schema_id) {
$this->unlock();
return false;
}
if ($has_key) {
$hdseq = $list_idx_offset;
$info = $this->_get_node($hdseq);
if ($this->_get_size_schema_id($info['size']) == $schema_id) {
$dataoffset = $info['data'];
} else {
//破掉原有lru
$this->_lru_delete($info);
if (!($dataoffset = $this->_dalloc($schema_id))) {
$this->unlock();
return false;
}
$this->_free_dspace($info['size'], $info['data']);
$this->_set_node($hdseq, 'lru_left', 0);
$this->_set_node($hdseq, 'lru_right', 0);
}
$this->_set_node($hdseq, 'size', $size);
$this->_set_node($hdseq, 'data', $dataoffset);
} else {
if (!($dataoffset = $this->_dalloc($schema_id))) {
$this->unlock();
return false;
}
$hdseq = $this->_alloc_idx(array(
'next' => 0,
'prev' => $list_idx_offset,
'data' => $dataoffset,
'size' => $size,
'lru_right' => 0,
'lru_left' => 0,
'key' => $key,
));
if ($list_idx_offset > 0) {
$this->_set_node($list_idx_offset, 'next', $hdseq);
} else {
$this->_set_node_root($key, $hdseq);
}
}
if ($dataoffset > $this->max_size) {
$this->triggerError('alloc datasize:' . $dataoffset, E_USER_WARNING);
return false;
}
$this->_puts($dataoffset, $data);
$this->_set_schema($schema_id, 'miss', $this->_get_schema($schema_id, 'miss') + 1);
$this->_lru_push($schema_id, $hdseq);
$this->unlock();
return true;
} else {
$this->triggerError("Couldn't lock the file !", E_USER_WARNING);
return false;
}
}
/**
* search
* 查找指定的key
* 如果找到节点则$pos=节点本身 返回true
* 否则 $pos=树的末端 返回false
*
* @param mixed $key
* @access public
* @return mixed
*/
public function search($key, &$pos)
{
return $this->_get_pos_by_key($this->_get_node_root($key), $key, $pos);
}
public function _get_size_schema_id($size)
{
foreach ($this->_block_size_list as $k => $block_size) {
if ($size <= $block_size) {
return $k;
}
}
return false;
}
public function _parse_str_size($str_size, $default)
{
if (preg_match('/^([0-9]+)\s*([gmk]|)$/i', $str_size, $match)) {
switch (strtolower($match[2])) {
case 'g':
if ($match[1] > 1) {
$this->triggerError('Max cache size 1G', E_USER_ERROR);
}
$size = $match[1] << 30;
break;
case 'm':
$size = $match[1] << 20;
break;
case 'k':
$size = $match[1] << 10;
break;
default:
$size = $match[1];
}
if ($size <= 0) {
$this->triggerError('Error cache size ' . $this->max_size, E_USER_ERROR);
return false;
} elseif ($size < 10485760) {
return 10485760;
} else {
return $size;
}
} else {
return $default;
}
}
public function _format($truncate = false)
{
if ($this->lock(true, true)) {
if ($truncate) {
$this->_seek(0);
ftruncate($this->_rs, $this->idx_node_base);
}
$this->max_size = $this->_parse_str_size(SECACHE_SIZE, 15728640); //default:15m
$this->_puts($this->header_padding, pack('V1a*', $this->max_size, $this->ver));
ksort($this->_bsize_list);
$ds_offset = $this->data_base_pos;
$i = 0;
foreach ($this->_bsize_list as $size => $count) {
//将预分配的空间注册到free链表里
$count *= min(3, floor($this->max_size / 10485760));
$next_free_node = 0;
for ($j = 0; $j < $count; $j++) {
$this->_puts($ds_offset, pack('V', $next_free_node));
$next_free_node = $ds_offset;
$ds_offset += intval($size);
}
$code = pack(str_repeat('V1', count($this->schema_struct)), $size, $next_free_node, 0, 0, 0, 0);
$this->_puts(60 + $i * $this->schema_item_size, $code);
$i++;
}
$this->_set_dcur_pos($ds_offset);
$this->_puts($this->idx_base_pos, str_repeat("\0", 262144));
$this->_puts($this->idx_seq_pos, pack('V', 1));
$this->unlock();
return true;
} else {
$this->triggerError("Couldn't lock the file !", E_USER_ERROR);
return false;
}
}
public function _get_node_root($key)
{
$this->_seek(hexdec(substr($key, 0, 4)) * 4 + $this->idx_base_pos);
$a = fread($this->_rs, 4);
list(, $offset) = unpack('V', $a);
return $offset;
}
public function _set_node_root($key, $value)
{
return $this->_puts(hexdec(substr($key, 0, 4)) * 4 + $this->idx_base_pos, pack('V', $value));
}
public function _set_node($pos, $key, $value)
{
if (!$pos) {
return false;
}
if (isset($this->_node_struct[$key])) {
return $this->_puts($pos * $this->idx_node_size + $this->idx_node_base + $this->_node_struct[$key][0], pack($this->_node_struct[$key][1], $value));
} else {
return false;
}
}
public function _get_pos_by_key($offset, $key, &$pos)
{
if (!$offset) {
$pos = 0;
return false;
}
$info = $this->_get_node($offset);
if ($info['key'] == $key) {
$pos = $info['offset'];
return true;
} elseif ($info['next'] && $info['next'] != $offset) {
return $this->_get_pos_by_key($info['next'], $key, $pos);
} else {
$pos = $offset;
return false;
}
}
public function _lru_delete($info)
{
if ($info['lru_right']) {
$this->_set_node($info['lru_right'], 'lru_left', $info['lru_left']);
} else {
$this->_set_schema($this->_get_size_schema_id($info['size']), 'lru_tail', $info['lru_left']);
}
if ($info['lru_left']) {
$this->_set_node($info['lru_left'], 'lru_right', $info['lru_right']);
} else {
$this->_set_schema($this->_get_size_schema_id($info['size']), 'lru_head', $info['lru_right']);
}
return true;
}
public function _lru_push($schema_id, $offset)
{
$lru_head = $this->_get_schema($schema_id, 'lru_head');
$lru_tail = $this->_get_schema($schema_id, 'lru_tail');
if ((!$offset) || ($lru_head == $offset)) {
return;
}
$info = $this->_get_node($offset);
$this->_set_node($info['lru_right'], 'lru_left', $info['lru_left']);
$this->_set_node($info['lru_left'], 'lru_right', $info['lru_right']);
$this->_set_node($offset, 'lru_right', $lru_head);
$this->_set_node($offset, 'lru_left', 0);
$this->_set_node($lru_head, 'lru_left', $offset);
$this->_set_schema($schema_id, 'lru_head', $offset);
if (0 == $lru_tail) {
$this->_set_schema($schema_id, 'lru_tail', $offset);
} elseif ($lru_tail == $offset && $info['lru_left']) {
$this->_set_schema($schema_id, 'lru_tail', $info['lru_left']);
}
return true;
}
public function _get_node($offset)
{
$this->_seek($offset * $this->idx_node_size + $this->idx_node_base);
$info = unpack('V1next/V1prev/V1data/V1size/V1lru_right/V1lru_left/H*key', fread($this->_rs, $this->idx_node_size));
$info['offset'] = $offset;
return $info;
}
public function _lru_pop($schema_id)
{
if ($node = $this->_get_schema($schema_id, 'lru_tail')) {
$info = $this->_get_node($node);
if (!$info['data']) {
return false;
}
$this->delete($info['key'], $info['offset']);
if (!$this->_get_schema($schema_id, 'free')) {
$this->triggerError('pop lru,But nothing free...', E_USER_ERROR);
}
return $info;
} else {
return false;
}
}
public function _dalloc($schema_id, $lru_freed = false)
{
if ($free = $this->_get_schema($schema_id, 'free')) {
//如果lru里有链表
$this->_seek($free);
list(, $next) = unpack('V', fread($this->_rs, 4));
$this->_set_schema($schema_id, 'free', $next);
return $free;
} elseif ($lru_freed) {
$this->triggerError('Bat lru poped freesize', E_USER_ERROR);
return false;
} else {
$ds_offset = $this->_get_dcur_pos();
$size = $this->_get_schema($schema_id, 'size');
if ($size + $ds_offset > $this->max_size) {
if ($info = $this->_lru_pop($schema_id)) {
return $this->_dalloc($schema_id, $info);
} else {
$this->triggerError('Can\'t alloc dataspace', E_USER_ERROR);
return false;
}
} else {
$this->_set_dcur_pos($ds_offset + $size);
return $ds_offset;
}
}
}
public function _get_dcur_pos()
{
$this->_seek($this->dfile_cur_pos);
list(, $ds_offset) = unpack('V', fread($this->_rs, 4));
return $ds_offset;
}
public function _set_dcur_pos($pos)
{
return $this->_puts($this->dfile_cur_pos, pack('V', $pos));
}
public function _free_dspace($size, $pos)
{
if ($pos > $this->max_size) {
$this->triggerError('free dspace over quota:' . $pos, E_USER_ERROR);
return false;
}
$schema_id = $this->_get_size_schema_id($size);
if ($free = $this->_get_schema($schema_id, 'free')) {
$this->_puts($free, pack('V1', $pos));
} else {
$this->_set_schema($schema_id, 'free', $pos);
}
$this->_puts($pos, pack('V1', 0));
}
public function _dfollow($pos, &$c)
{
$c++;
$this->_seek($pos);
list(, $next) = unpack('V1', fread($this->_rs, 4));
if ($next) {
return $this->_dfollow($next, $c);
} else {
return $pos;
}
}
public function _free_node($pos)
{
$this->_seek($this->idx_free_pos);
list(, $prev_free_node) = unpack('V', fread($this->_rs, 4));
$this->_puts($pos * $this->idx_node_size + $this->idx_node_base, pack('V', $prev_free_node) . str_repeat("\0", $this->idx_node_size - 4));
return $this->_puts($this->idx_free_pos, pack('V', $pos));
}
public function _alloc_idx($data)
{
$this->_seek($this->idx_free_pos);
list(, $list_pos) = unpack('V', fread($this->_rs, 4));
if ($list_pos) {
$this->_seek($list_pos * $this->idx_node_size + $this->idx_node_base);
list(, $prev_free_node) = unpack('V', fread($this->_rs, 4));
$this->_puts($this->idx_free_pos, pack('V', $prev_free_node));
} else {
$this->_seek($this->idx_seq_pos);
list(, $list_pos) = unpack('V', fread($this->_rs, 4));
$this->_puts($this->idx_seq_pos, pack('V', $list_pos + 1));
}
return $this->_create_node($list_pos, $data);
}
public function _create_node($pos, $data)
{
$this->_puts($pos * $this->idx_node_size + $this->idx_node_base
, pack('V1V1V1V1V1V1H*', $data['next'], $data['prev'], $data['data'], $data['size'], $data['lru_right'], $data['lru_left'], $data['key']));
return $pos;
}
public function _set_schema($schema_id, $key, $value)
{
$info = array_flip($this->schema_struct);
return $this->_puts(60 + $schema_id * $this->schema_item_size + $info[$key] * 4, pack('V', $value));
}
public function _get_schema($id, $key)
{
$info = array_flip($this->schema_struct);
$this->_seek(60 + $id * $this->schema_item_size);
unpack('V1' . implode('/V1', $this->schema_struct), fread($this->_rs, $this->schema_item_size));
$this->_seek(60 + $id * $this->schema_item_size + $info[$key] * 4);
list(, $value) = unpack('V', fread($this->_rs, 4));
return $value;
}
public function _all_schemas()
{
$schema = [];
for ($i = 0; $i < 16; $i++) {
$this->_seek(60 + $i * $this->schema_item_size);
$info = unpack('V1' . implode('/V1', $this->schema_struct), fread($this->_rs, $this->schema_item_size));
if ($info['size']) {
$info['id'] = $i;
$schema[$i] = $info;
} else {
return $schema;
}
}
}
public function schemaStatus()
{
$return = [];
foreach ($this->_all_schemas() as $k => $schemaItem) {
if ($schemaItem['free']) {
$this->_dfollow($schemaItem['free'], $schemaItem['freecount']);
}
$return[] = $schemaItem;
}
return $return;
}
public function status(&$curBytes, &$totalBytes)
{
$totalBytes = $curBytes = 0;
$hits = $miss = 0;
$schemaStatus = $this->schemaStatus();
$totalBytes = $this->max_size;
$freeBytes = $this->max_size - $this->_get_dcur_pos();
foreach ($schemaStatus as $schema) {
$freeBytes += $schema['freecount'] * $schema['size'];
$miss += $schema['miss'];
$hits += $schema['hits'];
}
$curBytes = $totalBytes - $freeBytes;
$return[] = array('name' => '缓存命中', 'value' => $hits);
$return[] = array('name' => '缓存未命中', 'value' => $miss);
return $return;
}
public function triggerError($errstr, $errno)
{
triggerError($errstr, $errno);
}
}