Files
OMS/app/base/lib/cache/secache/model.php
2026-01-04 19:08:31 +08:00

679 lines
21 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
/**
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* fcache
* 最大bs 16m
* 最多16个类型
*
* @package
* @version $Id: secache.php 34670 2009-10-28 10:42:14Z ever $
* @copyright 2003-2007 ShopEx
* @author Wanglei <flaboy@shopex.cn>
* @license Commercial
* [20][20][384][16*16*16*4]
*/
if(!defined('SECACHE_SIZE')){
define('SECACHE_SIZE','100M');
}
class base_cache_secache_model extends base_cache_abstract
{
var $idx_node_size = 40;
var $data_core_pos = 262588; //40+20+24*16+16*16*16*16*4;
var $schema_item_size = 24;
var $header_padding = 20; //保留空间 放置php标记防止下载
var $info_size = 20; //保留空间 4+16 maxsize|ver
//40起 添加20字节保留区域
var $idx_seq_pos = 40; //id 计数器节点地址
var $dfile_cur_pos = 44; //id 计数器节点地址
var $idx_free_pos = 48; //id 空闲链表入口地址
var $idx_core_pos = 444; //40+20+24*16
var $min_size = 10240; //10M最小值
var $schema_struct = array('size','free','lru_head','lru_tail','hits','miss');
var $ver = '$Rev: 34670 $';
var $name = 'secache';
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+') or $this->trigger_error('Can\'t open the cachefile: '.realpath($this->_file),E_USER_ERROR);
$this->_seek($this->header_padding);
if (version_compare(phpversion(), '5.5', '>=')){
$unpack_key = 'V1max_size/Z*ver';
}else{
$unpack_key = 'V1max_size/a*ver';
}
$info = unpack($unpack_key,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_core_pos+$this->max_size;
$this->_block_size_list = array_keys($this->_bsize_list);
sort($this->_block_size_list);
return true;
}
function create(){
$this->_rs = fopen($this->_file,'wb+') or $this->trigger_error('Can\'t open the cachefile: '.realpath($this->_file),E_USER_ERROR);;
if(!is_writable($this->_file) || !is_readable($this->_file)){
chmod($this->_file,0666);
}
fseek($this->_rs,0);
fputs($this->_rs,'<'.'?php exit()?'.'>');
return $this->_format();
}
function _puts($offset,$data){
if($offset < $this->max_size*1.5){
$this->_seek($offset);
return fputs($this->_rs,$data);
}else{
$this->trigger_error('Offset over quota:'.$offset,E_USER_ERROR);
}
}
function _seek($offset){
return fseek($this->_rs,$offset);
}
function clear(){
return $this->_format(true);
}
function fetch($key,&$return){
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($schema_id===false){
if($locked) $this->unlock();
return false;
}
$this->_seek($info['data']);
$data = fread($this->_rs,$info['size']);
$return = unserialize($data);
if($return===false){
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 void
*/
function lock($is_block,$whatever=false){
ignore_user_abort(true);
return flock($this->_rs, $is_block?LOCK_EX:LOCK_EX+LOCK_NB);
}
/**
* unlock
* 如果flock不管用请继承本类并重载此方法
*
* @access public
* @return void
*/
function unlock(){
ignore_user_abort(false);
return flock($this->_rs, LOCK_UN);
}
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;
}
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($schema_id===false){
$this->unlock();
return false;
}
if($has_key){
$hdseq = $list_idx_offset;
$info = $this->_get_node($hdseq);
if($schema_id == $this->_get_size_schema_id($info['size'])){
$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->trigger_error('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->trigger_error("Couldn't lock the file !",E_USER_WARNING);
return false;
}
}
/**
* search
* 查找指定的key
* 如果找到节点则$pos=节点本身 返回true
* 否则 $pos=树的末端 返回false
*
* @param mixed $key
* @access public
* @return void
*/
function search($key,&$pos){
return $this->_get_pos_by_key($this->_get_node_root($key),$key,$pos);
}
function _get_size_schema_id($size){
foreach($this->_block_size_list as $k=>$block_size){
if($size <= $block_size){
return $k;
}
}
return false;
}
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->trigger_error('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->trigger_error('Error cache size '.$this->max_size,E_USER_ERROR);
return false;
}elseif($size<10485760){
return 10485760;
}else{
return $size;
}
}else{
return $default;
}
}
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);
if (version_compare(phpversion(), '5.5', '>=')){
$pack_key = 'V1Z*';
}else{
$pack_key = 'V1a*';
}
$this->_puts($this->header_padding,pack($pack_key,$this->max_size,$this->ver));
ksort($this->_bsize_list);
$ds_offset = $this->data_core_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_core_pos,str_repeat("\0",262144));
$this->_puts($this->idx_seq_pos,pack('V',1));
$this->unlock();
return true;
}else{
$this->trigger_error("Couldn't lock the file !",E_USER_ERROR);
return false;
}
}
function _get_node_root($key){
$this->_seek(hexdec(substr($key,0,4))*4+$this->idx_core_pos);
$a= fread($this->_rs,4);
list(,$offset) = unpack('V',$a);
return $offset;
}
function _set_node_root($key,$value){
return $this->_puts(hexdec(substr($key,0,4))*4+$this->idx_core_pos,pack('V',$value));
}
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;
}
}
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;
}
}
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;
}
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($lru_tail==0){
$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;
}
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;
}
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->trigger_error('pop lru,But nothing free...',E_USER_ERROR);
}
return $info;
}else{
return false;
}
}
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->trigger_error('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->trigger_error('Can\'t alloc dataspace',E_USER_ERROR);
return false;
}
}else{
$this->_set_dcur_pos($ds_offset+$size);
return $ds_offset;
}
}
}
function _get_dcur_pos(){
$this->_seek($this->dfile_cur_pos);
list(,$ds_offset) = unpack('V',fread($this->_rs,4));
return $ds_offset;
}
function _set_dcur_pos($pos){
return $this->_puts($this->dfile_cur_pos,pack('V',$pos));
}
function _free_dspace($size,$pos){
if($pos>$this->max_size){
$this->trigger_error('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));
}
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;
}
}
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));
}
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);
}
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;
}
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));
}
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;
}
function _all_schemas(){
$schema = array();
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;
}
}
}
function schemaStatus(){
$return = array();
foreach($this->_all_schemas() as $k=>$schemaItem){
if($schemaItem['free']){
$this->_dfollow($schemaItem['free'],$schemaItem['freecount']);
}
$return[] = $schemaItem;
}
return $return;
}
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'=>app::get('base')->_('缓存命中'),'value'=>$hits);
$return[] = array('name'=>app::get('base')->_('缓存未命中'),'value'=>$miss);
return $return;
}
function trigger_error($errstr,$errno){
if($errno==E_USER_ERROR){
if(!$this->_in_fatal_error){
$this->_in_fatal_error = true;
$this->_format(true);
}
if(function_exists('debug_print_backtrace')){
echo '<h1>'.$errstr.'</h1><hr />';
echo '<pre>';
debug_print_backtrace();
echo '</pre>';
exit;
}else{
trigger_error($errstr,$errno);
}
}else{
trigger_error($errstr,$errno);
}
}
}