[转]解决 textdb 核心问题:超负载与稳定性1

作者:    提交时间:2020-03-01    点击:1497    TAGS:负载 稳定

先申明.以下内容完全由fenyu和王学集研究成果

下面主要讲解ofstar----textdb实现数据的负载能力.与稳定性的实现! 

时间问题.代码没有做详细的解释.如果您对其中的细节问题不解或是有好的建议.或可以联系ofstar 团队管理人王学集.QQ:280205859 .当然在大框架不变的前提下.我们以后还会继续在效率上下工夫.其中一些算法细节可能还有所变化!

(一)数据库模型
数据库是一种信息的集合,每个集合都包含一条或多条形式统一的记录,记录由字段构成.通常将集合称为表,将记录称为表中的行.
数据库一般分为:平面文件数据库(TEXT),层次化数据库,网络数据库,关系数据库(mysql),对象与对象关系型数据库!

关系型数据库是程序员开发软件的解放

由于平件文件数据库,必须直接与操作系统联系,当应用程序需要编辑这种信息时,必须确保字段与有正确的形式.这种形式的接口代价昂贵,因为她需要一种第3代语言(3GL),需要有掌握高度技能的人员进行长时间的开发!比如程序员必须考虑数据的

逻辑和物理表示,比如要从文件里读取一个整数,程序员需要知道它是写成文本形式还是二进制形式。数据在文件和应用程序之间流动时,必须遵循一定的约定。所以平面文件数据库程序的高效性一般取决于程序员的辛勤与水平!相对于MYSQL由于实现了数据的独立性,也实现了结构的独立性!因此程序员无须承受数据管理的负担!抛开了很多数据库处理的内部细节!这便是RDBMS产生最重要的理由!也是她普及的理由!

优秀的程序--对象关系数据库模型

数据库模型里,比如MYSQL是一种关系型数据库,但是关系模型并不是MYSQL的专利,数据库理论的革新正在继续,先进技术的一些基本原理也可以用于关系模型,如果你是精通3GL的程序员,使用平面文件数据库不仅可以开发出优秀的程序!而且在基层数据处理,编程控制,资源利用上,都有很大的优势! 如果需要对大量数据进行频繁的读写!,可以使用定位指针读取指定的数据,定长索引, 所谓的负载问题,迎刃而解,控制起来也非常灵活方便,层次化与网络模型的并发性原理都可以利用!当然最重要的还是程序员的敏觉性,设计完善的索引表,是置关重要的!

下面我将主要结合ofstar论坛程序讨论textdb--平面文件数据库在高负载下的稳定与快速.textdb的安全接口问题.整体效率的实现.数据检索等....


(二)textdb高负载的前提实现数据的快速处理

(1)textdb传统不足: 
有过开发经历的朋友一定有遇到.大数据量的操作简直就是噩梦.先不说大数据量下.操作数据的稳定性!单是那简单的添加删除以及修改所涉及的数据不得不让大部分的程序员望而却步....大量的数据处理与服务器并发性冲突造成的数据的不准确和不稳定现象.更是让广大用户抛弃甚至鄙视着textdb....
于是问题便产生了.如何解决对大量数据进行快速稳定地实现添加删除.修改等一系列数据操作!? 如何保证在高负荷下对数据进行频繁更改时.能保证整体数据的稳定性.和被更改数据的准确性呢!!?? 第2条将给出详细解决方案

(2)解决大数据量数据更新的快速与非溢出性 
[1]textdb文件基本常识:
顺序文件.索引文件.关键字文件.等
文件还可按记录的另一特性分成定长记录文件和不定长记录文件! 下面主要分析这些文件的特性.并将各类数据各得其所.充分利用3GL的轻型代码的优势
[2]顺序文件
非定长的顺序文件----这是传统的数据处理方法.也是广大朋友学习中最常用的数据存储容器. 它是有记录按其在文件中的逻辑顺序依次进入存储介质而建立的.

它的特点是:
存取第i个记录,必须先搜索在它之前的i-1个记录.
插入新的记录时只能在文件的末尾.如果需要顺序存储则必须对文件整体进行修改
若要更新文件中的某个记录.则必须将整个文件进行复制
所以.当文件数据越来越大时.添加删除修改.任务便变得非常烦琐.

定长的顺序文件-----用做数据的检索与修改.其中牵涉到两个内容
1)与数据结构B+树原理类似.采用索引集.顺序集.和数据集.顺序集和索引集一起构成一棵B+树的索引部分.分别存储.指向控制行的指针.和索引关键字! 这样在查找或修改时可以采用顺序存取.又可从最高层的索引出发进行按关键字存取.

利用这一思想将举3个例子(都是针对超大型的数据文件):
1,需要进行频繁数据处理的文件.且检索浏览频率大于写频率.建议思想实现采用各行定长顺序索引思想.进行倒排各行
以下程序来自ofstar里的版块索引代码
添加时只要将最新的数据使用 (require/postnew.php)

$newdb="│││$ofstarid││$tid│0│$threadnew│0││";
$newlist=str_pad($newdb,$db_linesize)."\n";
writeover("$dbpath/$fid/list.php",$newlist,"ab");


修改删除时进行倒序查找关键字代码如下(require/dbmodify.php):

function readsearch($filename,$tid,$db_linesize)
{
$size=5000;
$step=0;$end=0;$readsize=$db_linesize+1;$scharray=array();
if($fp=@fopen($filename,"rb+")){
flock($fp,LOCK_EX);
while(!feof($fp)){
$step++;
$offset=-$readsize*$step;
fseek($fp,$offset,SEEK_END);
$line=fread($fp,$readsize);
if(strpos($line,"││$tid│")!==false){
$detail=explode("│",$line);
break;
}elseif($step<$size){
$scharray[]=$line;
}else{
$fastwrite='Y'
}
}
fseek($fp,$offset,SEEK_END);
return array($fp,array_reverse($scharray),$detail,$fastwrite);
}
}



删除时的算法实现:

function write_del($fp,$writearray,$fastwrite)
{
global $db_linesize;
if($fastwrite!="Y"){
$writedb=implode("",$writearray);
fputs($fp,$writedb);
ftruncate($fp,ftell($fp));
}else{
fputs($fp,str_pad(' ',$db_linesize)."\n");
}
}



修改时的算法实现:

function write_alt($fp,$writearray,$fastwrite,$inline)
{
global $db_linesize;
if($fastwrite!="Y"){
array_push($writearray,$inline);
$writedb=implode("",$writearray);
fputs($fp,$writedb);
}else{
fputs($fp,str_pad(' ',$db_linesize)."\n");
fseek($fp,0,SEEK_END);
fputs($fp,$inline);
}
}



检索浏览的算法实现:

$total=$page*$db_perpage;
$linestart=min($total,$statusdetail[7]);
$pernum=$linestart-$total+$db_perpage;
$articleshow=readfrombot($filename,$linestart,$pernum,$db_linesize);
if($page==1){
$threadtop=gettoparray($filename,$db_linesize);
$articleshow=array_merge($threadtop,$articleshow);
}
function readfrombot($filename,$linestart,$linenum,$db_linesize)
{
$offset=max(-($db_linesize+1)*$linestart,-(filesize($filename)-($db_linesize+1)*11));
$num=0;$readb=array();
if($fp=fopen($filename,"rb")){
flock($fp,LOCK_SH);
fseek($fp,$offset,SEEK_END);
while(!feof($fp)&&$num<$linenum){
$readb[]=fgets($fp,100);
$num++;
}
fclose($fp);
}
return array_reverse($readb);
}



以上全部采用倒排数据区域. 对数据的处理只更新需要更改的数据.并非全部写入与读取!
2,针对频繁性添加修改和删除.并每个数据区域含有多个关键字.并对数据进行特定约束的索引表文件
/*在索引中找到最后的所有空行并删除之.将索引中找到一记录不符合条件的以空行填充.并进行在线会员数缓存*/

function substrnbsp($filename)
{
global $db_olsize,$timestamp,$db_onlinetime,$guestinbbs,$userinbbs;
$addnbsp=str_pad(" ",$db_olsize)."\n";
$addfb=str_pad("<?die;?>",$db_olsize)."\n";
$cutsize=$db_olsize+1;$step=$olnum=0;$onlinetime=$timestamp-$db_onlinetime;
$fp=fopen($filename,"rb+");
flock($fp,LOCK_EX);
fputs($fp,$addfb);
fseek($fp,0,SEEK_END);
while(ftell($fp)>$cutsize &&$step<20000){//ftell未测试速度 可以用filesize代替 哨兵$step
$step++;
$offset=-($cutsize*$step);
fseek($fp,$offset,SEEK_END);
$line=fread($fp,28);//读取28个字节.已经包含时间
if(empty($end)){
if(strpos($line,"│")!==false ││ ftell($fp)<=$cutsize){
$end=$offset;//break
}
}
if(strpos($line,"│")!==false){
$detail=explode("│",$line);
if($detail[1]<$onlinetime){
fseek($fp,$offset,SEEK_END);fputs($fp,$addnbsp);
}else{
$olnum++;/*解决在线数目不准确问题*/
}
}
}
if(isset($end)) ftruncate($fp,filesize($filename)+$end+$cutsize);
fclose($fp);
@include 'bbsdata/olcache.php'
if($filename=="bbsdata/guest.php"){$guestinbbs=$olnum;$userinbbs++;}else{$userinbbs=$olnum;$guestinbbs++;}
$olcache="<?php\n\$userinbbs=$userinbbs;\n\$guestinbbs=$guestinbbs;\n?>";
writeover('bbsdata/olcache.php',$olcache);
}



3,针对非频繁性修改.却是频繁性检索与浏览.且记录长度为无规律性
数据结构思想分析:记录将不采用统一的定长.而在控制区中除了存放记录本身以外.再存放每个记录的长度.
比如:

<?die;?>│关键字####,100,│││││这些放数据││││││││││*******************
<?die;?>│关键字####,200,│││││这些放数据││││││││││*******************


含义是第一行为100字节定长.第二行为200定行.以此类推
关键字也采用定长.可以通过关键字索引.
不过这一数据结构对空间的利用率太低.只适合后台管理的大数据量处理程序!!!


并在数据物理存储上采用pack与unpack 对数据的检索会更快.代码如:
/*采用位字符串写入*/

for ($i=0;$i<strlen($code1);$i++) 
{ 
$write=array_pop(unpack("c",substr($code1,$i,1)));
if(strlen($write)==2) $write=?'.$write;
fwrite($fp, $write,3);
}
fclose($fp);



/*采用位字符串读取*/

$fp=fopen("test1.php","rb");
@flock($fp,LOCK_SH);
$code1=fread($fp,filesize("test1.php")); 
fclose($fp); 
for ($i=0;$i<strlen($code1)/3;$i++) 
{
$write.=pack("c",substr($code1,$i*3,3));
}
echo $write;



[3]索引文件
准确的说这是包含文件数据区和索引表两大部分的文件.ofstar 今日到访会员表采用的就是这一典型的索引文件
算法实现采用的是:双向线性链表.每行数据段采用定长! (require/today.php job.php) 
对n-->无穷大 对数据的修改与添加只有 3-5项.大大减少了系统的负担!
添加数据如果是双向链表.只需要对3个数据区域进行修改.分别是 头结点区域.头结点数据存放区域.和添加区域
修改数据则需要进行5个数据段分别在添加的基础上加上.修改前一结点的后趋和修改后一结点的前趋
添加算法实现:

$offset=filesize($filename);
if($fp=@fopen($filename,"rb+")){
flock($fp,LOCK_EX);
list($node,$yestime)=nodeinfo($fp,$dbtdsize,$offset);
if($node!=''){/*修改头结点*/
$nowfp=$offset/($dbtdsize+1);
if($node!='NULL') {
fputin($fp,$node,$dbtdsize,$nowfp);
}
if($node!=$nowfp) fputin($fp,$nowfp,$dbtdsize,'node',$node,Y);/*添加数据*/
}
fclose($fp);
}



修改的算法实现是:

if($offset=strpos($todaydata,"\n".$ofstarid.'│')){/*使用精确匹配 必须是"\n".$ofstarid.'│'*/
$offset+=1;
if($fp=@fopen($filename,"rb+")){
flock($fp,LOCK_EX);
list($node,$yestime)=nodeinfo($fp,$dbtdsize,$offset);/*修改头结点*/
$nowfp=$offset/($dbtdsize+1);
if("$nowfp"!=$node && $node!=''){
fputin($fp,$node,$dbtdsize,$nowfp);/*修改头结点指向的数据段*/
list($oldprior,$oldnext)=fputin($fp,$nowfp,$dbtdsize,'node',$node);/*修改需要更新的数据*/
if($oldprior!='node'){
fputin($fp,$oldprior,$dbtdsize,'M',$oldnext);/*修改前一结点的后趋*/
}
if($oldnext!='NULL' && $oldprior!='node'){
fputin($fp,$oldnext,$dbtdsize,$oldprior);/*修改后一结点的前趋*/
}
}
fclose($fp);
}
}



检索的算法实现:

$filename='bbsdata/today.php'
$dbtdsize=100+1;
$seed=$page*$db_perpage;$count=0;
if($fp=@fopen($filename,"rb")){
flock($fp,LOCK_SH);
$node=fread($fp,$dbtdsize);
$nodedb=explode("│",$node);/*头结点在第二个数据段*/
$nodefp=$dbtdsize*$nodedb[1];
fseek($fp,$nodefp,SEEK_SET);
$todayshow=fseeks($fp,$dbtdsize,$seed);/*传回数组*/
fseek($fp,0,SEEK_END);
$count=floor(ftell($fp)/$dbtdsize)-1;
fclose($fp);
}



以下是上面几个算法的主要函数:

function fputin($fp,$offset,$dbtdsize,$prior='M',$next='M',$ifadd='N')
{
$offset=$offset*($dbtdsize+1);/*将行数转换成指针偏移量*/
fseek($fp,$offset,SEEK_SET);
if($ifadd=='N'){
$iddata=fread($fp,$dbtdsize);
$idarray=explode("│",$iddata);
fseek($fp,$offset,SEEK_SET);
}
if($next!='M' && $prior!='M'){/*说明这一数据是被更改的数据段.需要对其他辅助信息进行更改*/
global $ofstarid,$timestamp,$onlineip,$ofstardb;
$idarray[0]=$ofstarid;$idarray[3]=$ofstardb[8];
if($ifadd!='N') $idarray[4]=$timestamp;
$idarray[5]=$timestamp;$idarray[6]=$onlineip;$idarray[7]=$ofstardb[16];$idarray[8]=$ofstardb[17];
}
if($prior=='M') $prior=$idarray[1];
if($next=='M') $next=$idarray[2];
$data="$idarray[0]│$prior│$next│$idarray[3]│$idarray[4]│$idarray[5]│$idarray[6]│$idarray[7]│$idarray[8]│";
$data=str_pad($data,$dbtdsize)."\n";/*定长写入*/
fputs($fp,$data);
return array($idarray[1],$idarray[2]);/*传回数据更新前的上一结点和下一结点*/
}


function nodeinfo($fp,$dbtdsize,$offset)
{
$offset=$offset/($dbtdsize+1);
$node=fread($fp,$dbtdsize);
$nodedb=explode("│",$node);/*头结点在第二个数据段*/
if(is_int($offset)){
$nodedata=str_pad("<?die;?>│$offset│$nodedb[2]│",$dbtdsize)."\n";
fseek($fp,0,SEEK_SET);/*将指针放于文件开头*/
fputs($fp,$nodedata);
return array($nodedb[1],$nodedb[2]);
}else{
return ''
}
}



function fseeks($fp,$dbtdsize,$seed)
{
$num=0;
while($break!=1 && $num<$seed){
$num++;
$sdata=fread($fp,$dbtdsize);
$sdb=explode("│",$sdata);
$sdbnext=$sdb[2]*$dbtdsize;
if($sdbnext!='NULL'){
fseek($fp,$sdbnext,SEEK_SET);
}else{
$break=1;
}
$todayshow[]=$sdata;
}
return $todayshow;
}