无限存储之胖文本数据库TTD(Thick Text Database)
阅读:
评论:
作者:
Rybby 日期:
来源:
rybby.com
所谓的“胖”就是多、大、丰富的意思,像我们平时看到的胖客户端、胖操作系统、胖文本编辑器等各种应用中的“胖”,就是指丰富多样的功能;这里的胖文本数据库的“胖”是指无限多、无限大的意思,结合数据库一起来说就是可以存储无限多数据的数据库。
说到数据库,想必大家都很熟悉了,但说到文本数据库未必所有人都熟悉。数据库就是存储数据的仓库,只要能存储数据的东西就可以理解为数据库,文本文档可以存储文本数据,所以它是数据库;图片可以存储图片数据,它也是数据库;mp3可以存储音乐数据,它也是数据库;可见任意类型的文件都是数据库。那如何将文本文档当成数据库来用呢?我们需要使用某种数据结构,通常,文本数据库都使用 json 结构来存储数据,而非文本数据库则使用行、字段的结构来存储数据,下面以存储三个会员的数据表来介绍两种结构:
行&字段结构
id name age sex birthday
1 张三 18 男 09-23
2 李四 25 男 02-15
3 王五 22 女 05-08
json 结构
[
{"id":"1", "name":"张三", "age":"18", "sex":"男", "birthday":"09-23"},
{"id":"2", "name":"李四", "age":"25", "sex":"男", "birthday":"02-15"},
{"id":"3", "name":"王五", "age":"22", "sex":"女", "birthday":"05-08"}
]
两种数据结构都有各自的优缺点,先来看 json 格式,很明显,它有很多重复的数据,如果一张表有100万、1000万、10亿行数据,那这些重复的属性名将会浪费掉非常多的空间!而行&字段的格式只需声明一次字段名,因此省下了很多空间,但是,它没有 json 格式灵活。比如我现在要添加两个新的字段 email、homepage,以后每次添加的行都会有这两个字段,而以前添加的三行数据是没有这两个字段的,所以在添加这两个字段时不得不修改以前的数据行,为它们添加两个值为空的字段,因为每个数据行的结构必须保持相同。现在只是修改三个数据行的数据而已,如果原来已经10亿行数据了再添加两个字段,那得修改多少数据行(不知道mysql等数据库实际操作是否是这样,锐某想象中的应该是这样,自己没研究过mysql的源码)!而 json 则灵活多了,它不需要为每行数据都保持相同的结构,它对每行的属性数量没有限制,一行数据可以有一个属性也可以有两个属性,也可能有很多,这样并不影响它的使用。如添加一个新行:{"id":"4", "name":"马六"},这行会员数据只有两个属性。
既然 json 这么浪费空间!为什么还要用 json?
太好了!会问这个问题证明你已经对数据库这玩意感兴趣了!
随着网络应用的数据量不断攀高,非文本数据库已经不能满足应用了,如 mysql 在数据量到达百万级就已经接近瓶颈了,如果索引优化得好一点的话还能到达千万级,而现在大多数网络应用都以亿为单位进行计算的,所以 mysql 早就应该被淘汰啦!但是 SUN 公司居然以10亿美元收购 mysql!Y?为了证明自己的财大气粗还是什么?在我看来,他们不是呆子就是疯子!而事实也很戏剧化!SUN 收购 mysql 后不久就面临倒闭,最后被 Oracle 收购了,真是螳螂捕蝉黄雀在后!貌似“收购”这个词近来热乎得很!经常看到某某公司拟定多少千万、多少亿对xx公司进行收购,是世界疯了还是人疯了!如果想证明自己比别人强大不至于要靠通过吞掉别人来炫耀吧!一个破玩意居然要花10亿去买!还是美元!随便丢个几十万向社会招揽一群苦逼的程序猿都能设计出比那个好n倍的产品,事实也有很多比 mysql 强n倍的开源产品,如:HBase、MongoDB、OceanBase(千亿级数据库)。如果以数据库的存储量来衡量价值的话,OceanBase 比 mysql 优秀千、万倍,那 Oracle 是不是要花1000个10亿美元来收购 OceanBase?
现在有个比 mysql 优秀1000倍的数据库,谁敢打赌明天不会出现一个比 OceanBase 优秀1000倍的数据库?作为挨踢(IT)民工,我们这些程序猿真的很幸福,因为不断有免费开源的产品供我们随意使用。如果你只想寻求一款好用、够用的数据库,读到这里就不用再往下看了,若你对数据库产生了浓厚的兴趣,也希望开发一款属于自己的数据库,请继续往下看。虽说锐某不知道真正的数据库是如何设计的,也没去欣赏过别人的数据库源码,感觉别人写的东西都很深奥!要完全读懂别人的程序不知要花多少时间,还是按自己的想法去设计程序好,虽说这样的程序不一定能用,但好歹是自己写的,引用某人说的一句话:没有成功,也没有失败,但有收获!以下是锐某开发的文本数据库的实现原理,算是抛砖引玉吧!又引用一下某人的一句话:我抛了一块砖,有玉的尽管砸过来!
为什么要自己开发数据库呢?嘛!这个要从三年前说起,那时锐某想自己开发一个垂直搜索引擎,因为 mysql 已经完全不能满足我的需求,很自然地就想要自己设计数据库了,早前接触 json 时就有想用 json 来设计数据库的冲动。因为要自己设计搜索引擎,所以就有非常正当的理由要自己设计数据库了。至于数据库的名字为什么叫胖文本数据库,嘛!这个是因为自己写文章的分类都是某种特定的格式,如AAB、ABB,锐某的享客分类有:上上游(游戏分享),下下问(问卷调查),左听听(音乐分享),右看看(电影分享)等等,而自己设计的开源程序也都取这种格式的命名:巴巴变(画客),牛牛汇(议客),窝窝谷(个人或中小企业网站程序,使用这个程序任何人都可以很轻松地在网上安个窝,原本打算将窝窝谷与巴巴变与TTD这三个程序进行捆绑的,开发TTD后又想到一种更好的运营模式代替窝窝谷,后面有说到)。so,为了使程序名与 AAB 或 ABB 类同,我给数据库取名叫 TTD(Thick Text Database中文译为胖文本数据库)。设计文本数据库我并没有用 json,因为它跟 xml 一样很垃圾!明眼一看就知道,很多符号都被它霸占了,要用这些符号必须转义。我用的是数组格式:name,value,name,value,name,value...用这种格式解析为对象比用 json 格式解析为对象快6倍,不明白的可以看看锐某写的那篇《编译语言 vs 解释语言》。
数据结构已经知道了,那如何读写数据?哈!终于到精彩的地方了,要知道怎样读写数据,必须搞清楚两个概念:定长与非定长。什么是定长?就是固定长度,即每行数据都是固定长度,如果每行数据的长度都是100字节,要读取第一行数据:(1-1)*100+100,(1-1)*100为找到数据指针(起点,因为指针从0开始,所以减1),+100为读取数据的长度(终点),要从打开的文件句柄中读取一段数据,首先要指定指针的起点和终点,这点很多编程语言都一样的,通过终点来计算长度(如果知道了长度就直接使用长度)。读取第二行数据:(2-1)*100+100,读取第三行数据:(3-1)*100+100...读取第1亿行数据:(100000000-1)*100+100。要读取固定长度的数据非常容易,只要知道行号,无论数据量有多大都可以直接定位进行读取,如果你的磁盘容量足够大,无论要存储多少亿笔数据都没问题,因为定长数据没有数量的概念。这是非常好的优点呀!但是从反正理论(不知道反正理论的可以看看锐某写的那篇《反正理论》)来说,任何事物都有正反两面,它的优点刚好是它的缺点。定长虽好,但我们的数据不可能都用定长,如一篇文章,有人只写了几十字,而有的人写了几千字,如果将一行数据固定为几千字节,那只有很少数据的行将会浪费很大的磁盘空间,另外,大于这个存储空间的数据将会被丢弃。为了节省磁盘空间,只好用非定长数据了(数据是变长的,即不固定长度),但是非定长数据却给读写带来了麻烦,因为我们无法通过行号算出数据行的起点。解决的办法还是有的,就是建立索引。要读写数据首先要知道数据的起点与终点(终点用来算出长度,通常存储时都直接用长度,免去一步计算),所以索引文件就是记录每行数据的起点和长度,当然这个索引文件的每行数据是定长的(方便定位指针),我们可以称这个索引文件为主键索引。下面用实例来说明:
数据文件:data.ttd(非定长)
第一行数据=100字节,
第二行数据=250字节,
第三行数据=1000字节
...
索引文件:key.ttd(定长,每行长20字节)
第一行数据的起点和长度:0,100(每行数据长20字节,不够的在后面加空格补足)
第二行数据的起点和长度:100,250
第三行数据的起点和长度:350,1000
...
通过索引,读数据就变得和定长一样简单了,只是多了一步打开索引文件的步骤,如果我要读取第二行的数据,先打开索引文件key.ttd,(2-1)*20+20="100,250".split(",");打开数据文件data.ttd,移动数据指针到 arr[0],读取长度 arr[1] 字节,完成。
索引文件的行长设置为多少字节才合适?那要看你的磁盘空间有多大与单个文件的体积有多大,磁盘空间影响着数据指针,文件体积影响数据长度。如:100,250,前面的数据代表指针位置,如果你的数据文件有几百TB(1BB=1*1000YB*1000ZB*1000EB*1000PB*1000TB*1000GB*1000MB*1000KB*1000B),那你的数据行在数据文件最后面时需要多少位数字来表示呢?看上面的公式,1TB有12个0加个1,共13位数字,1T=13,10T=14,100T=15,百泰级别要15位数字才能表示(即15字节)。索引数据后面部份代表数据长度,如果你的数据库还用来存储二进制文件(必须将二进制流转换成base64格式,因为里面可能有特殊符号),如电影,游戏之类,那就有可能几G,现在有的游戏有十几G,10-99吉的数据长度就要10位数字表示,15+10+1=26Byte,要根据磁盘空间与文件体积规划索引文件的行长。
上面只说了读数据,其实写数据(包括更新数据与添加数据,更新数据一定要用r+模式打开,w是覆盖写,a是追加写,两者都不能定位指针;添加新数据不用定位指针,直接用a模式打开在数据最后添加)也一样的。不过如果写的数据比原来的数据大就要注意了,因为更新的数据不能大于原来的数据,不然下一行的数据会被覆盖掉,必须是原来有多少字节就写入多少字节,不够的用空格补足。如果大于原来的数据就要用添加新数据的方式将更新的数据添加到最后面,然后修改索引文件中此行数据的起点与长度,再将原来的数据用空格替换掉,这样就会出现数据碎片(空白的一段数据),随着更新的次数增加,碎片会越来越多,要定期处理碎片。碎片处理起来其实很简单,每次读取指定长度的数据,如2MB,干掉里面的空格,重新连接数据内容,修改新内容的索引。
还是上个简单的更新数据的例子吧,不然有人要晕倒了。
修改前的数据内容:
id,1,name,张三,age,18,sex,男,birthday,09-23;id,2,name,李四,age,25,sex,男,birthday,02-15;id,3,name,王五,age,22,sex,女,birthday,05-08;
修改前的索引内容:
0,47 47,47 94,47
我现在要更新第二笔数据,即将“id,2,name,李四,age,25,sex,男,birthday,02-15;”修改为“id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;”
修改后的数据内容:
id,1,name,张三,age,18,sex,男,birthday,09-23; id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;
修改后的索引内容:
0,47 141,86 94,47
处理碎片:
data.ttd
arr = data.split(/; */);
newData = arr[0] + arr[1] + arr[2];
key.ttd
id1.start = 0;
id1.length = arr[0].lnegth(Byte);
id3.start = id1.length;
id3.length = arr[1].lnegth(Byte);
id2.start = id1.length + id3.length;
id2.length = arr[2].lnegth(Byte);
关于特殊字符,就是用来格式化数据内容的字符,如上面的用到三个字符“,”、“;”、“ ”,如果数据内容中有这样的字符就必须进行转义,不能直接加个反斜杠进行转义,应该将它们替换为其它的字符,如 html 实体字,其实上面只要两个字符就够了,将空格全替换为行末符“;”(不应该用换行符“\n”作为行末符,因为win与linx中是不一样的),如:id,1,name,张三,age,18,sex,男,birthday,09-23; id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;应该被替换为id,1,name,张三,age,18,sex,男,birthday,09-23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;切分字符串时就这样 arr = data.split(/;+/); 上面只是作为例子演示,如果实际编程中你用“,”与“;”作为格式化符号的话,证明你还嫩,应该尽量使用内容中不会出现的符号,如:“`”、“~”,这样可以减少转义所需的时间,而且尽可能在客户端完成这些操作,能偷客户端的资源就尽量偷,可以的话应该用客户端去采集数据,当然锐某不建议你去采别人的数据作为自己网站的内容,但像百毒这样的搜索引擎还是可以的哈!
如果你觉得上面的“name,value,name,value...”这种类似于 json 的格式太占磁盘空间(确实很占空间!!!),那你也可以用行&字段的形式来存储数据,即“value,value,value...”。这种格式如何通过属性名来取值呢?方法还是有的,用表的第一行来存放属性名,如:name,name,name...这种格式比较麻烦,属性名与属性值的顺序与位置必须一一对应,而且两者的数量也要相等,如果一个属性名对应的属性值没有值,可以用空值。下面用实例来说明:
table.ttd
id,name,age,sex,email;
1,张三,18,男,zs@qq.com;
2,李四,25,男,;
3,王五,22,,ww@qq.com;
json.ttd
id,1,name,张三,age,18,sex,男,email,zs@qq.com;
id,2,name,李四,age,25,sex,男;
id,3,name,王五,age,22,email,ww@qq.com;
对行&字段格式取值很简单,先取属性名这一行,将它转为数组,这个数组有几个单元,属性值这一行就应该有几个单元,无值的单元要以用空值替换。
对象化代码演示:
var o, k, v, l, z;
o = {};
k = "id,name,age,sex,email".split(',');
l = k.length;
v = "1,张三,18,男,zs@qq.com".split(',');
for(z = 0; z < l; z++) o[k[z]] = v[z] || '';
看到这里你应该能写一个有模有样的数据库了,随便一种支持文件操作的编程语言都可以,前提是你的数据不需要检索服务,如新闻系统。因为新闻系统只需要将最新的内容展现出来,通过主键索引从后面读数据就可以实现倒序排列,即将最新的新闻返回客户端。
性能测试
本来我想写入10亿笔数据进行测试,但我的磁盘空间不够,现在只写了5000笔会员数据,然后再写了1.2亿笔文件数据(耗时72分钟),文件数据只有两个属性,id与内容,内容为10个随机字符(这个要用来分词然后建立反向索引),fdada.ttd=3.6G,fkey.ttd=1.5G。PC配置:CPU:N270=1.6GHz,内存:1G,硬盘:SATA。如果排除磁盘寻址时间的话,从1.2亿笔数据中取一笔耗时为 0.4ms,这个是通过循环读取某个行号10000次求出的平均值,这个跟行号的大小没有关系,因为是重复读磁盘的某个位置,所以降低了寻址时间。随机读取性能,读10000笔求平均每笔耗时,这个与随机行号的范围有关,限定行号在100万以内耗时:头两次为 9ms、2ms,后面的都固定在 0.4ms,这个是不是 Node 缓存了结果?1000万耗时:18ms,1亿耗时:18ms。随着随机行号的范围越大耗时也稍微有所提升,这个应该是寻址范围增大了。如果使用连续行号读的话基本都保持在 1.5ms/笔,这个升序与倒序都一样的。以上是通过主键索引来读取数据的每笔耗时,我现在的数据量为1.2亿,随机读性能为 18ms,按我们平常一次读10笔来算那就是 180ms,有条件的网友可以测试一下几十亿、几百亿数据量的性能。我觉得都差不多在这个范围吧,唯一要考虑的是磁盘寻址时间,如果用固态硬盘再加 RAID 的话还会更快!
如果你希望开发一个多功能的数据库就接着往下看吧,锐某只说一下如何对数据库添加检索服务,其它的诸如权限、事务、排序之类的自己琢磨去吧。要对数据进行检索就要建立索引,平时我们常用的索引有以下这些:B树、B+树、B-树、B*树、R树、反向索引等等。我先来说反向索引(又称倒序索引、倒排索引)吧,要说明什么是反向索引那得先说明什么是正向索引。按我们一般的理解,查找一些包含某个关键词的文章,那就要对数据库里的文章一篇一篇地打开进行查找,当一个数据库的文章数非常非常庞大里,这样查找是非常耗时的!且先不说数据库,就算在一篇文章里查找某个关键词也很耗时,因为你不知道它在哪一段哪一句?但是,我们程序猿是一种聪明的物种,这点小事难不倒我们滴!于是,我们对每篇文章都建立索引,这就是正向索引。这有点像书本的目录,只记录一些主要的内容,这样就不用一页一页地进行翻阅了,只要看完目录页就知道这本书有没有我们需要的内容了。比如我们在网上发表博客时都可以添加一些 tag,这些 tag 表示这篇博文里有包含这个 tag 的词或与这个 tag 有关的东西,下面以一篇介绍笔记本电脑的文章为例,这篇文章里介绍三个品牌的电脑,这三个品牌是:联想、华硕、戴尔,文章的行号是:120,于是,我们可以这样建立正向索引:
index.ttd
120:联想,华硕,戴尔
当我们要检索包含有“联想”这个关键词的文章时,很容易就发现id为120的这篇文章,这样就不用一篇一篇地打开进行查找了,检索速度比以前没有建立索引时快多了。但是当数据库的文章数很多时,这种索引还是很慢!比如一个索引文件里有下面的内容:
120:联想,华硕,戴尔
121:华硕,戴尔
122:清华同方,戴尔
123:联想,华硕,明基
当我们打开这个索引文件找到120这一行时还不能停止,因为后面的索引可能还有包含“联想”这个关键词的文章,于是,不得不将整个索引文件查完。针对这种问题我们聪明绝顶的程序猿又想出一种完美方案来证明自己真的是一种聪明绝顶的物种!这就是大名鼎鼎的反向索引了!说它是反向的确实非常贴切!因为它就是反过来用的,如将上面的正向索引变为反向索引就是:
联想:120,123
华硕:120,121,123
戴尔:120,121,122
清华同方:122
明基:123
当我们检索“联想”这个关键词时找到id为120与123这两篇文章,而且也不需要再向下找了,因为包含这个关键词的文章都记录在这里,找到id就能直接算出文章在数据库的哪个地方。同样,检索“华硕”关键词可以找到三篇文章,分别是:120,121,123,可见反向索引要比正向索引要高效很多!目前的搜索引擎都是用反向索引的。
B tree,锐某对B树的具体运用不是很理解,网上介绍B树的资料虽然很多,但几乎没有实例,要知道,一个简单的实例胜过千言万语!而且很多文章基本都一样,也就是说很多网友老喜欢抄袭、复制别人的东西随手一转又是自己的了,还有很多自己都不是很理解也发出来给别人看。因为几乎所有的文件系统都是用B树进行检索文件的,我就简单说一下吧。这里的B是二的意思,B树就是指二叉树,即最多只有两个分枝,当然分枝底下还可以再分两枝,然后分枝又可以再分枝,但每个分枝最多只有两枝,最下面的就是叶子了(之所以说是最下面,因为用电脑屏幕来表示树形图都是倒过来的,即树根在最上面,树顶在最下面,html的DOM树也是这样表示的),中间的分枝可以理解为树枝。B树的分枝规则是,比当前节点小的往左边发芽,大的往右边发芽,下面附个简单的例子:
d
/ \
b e
/\ /\
a c g
/ \
f
上面的树形中,d为树根节点,a、f、g为叶子节点,b、c、e为树枝节点。每个节点都存放着其所对应的id号,B树的检索过程,先声明,锐某对B树的检索过程并不是很理解,以下只是根据自己的浅显见解而作的讲解,如有不对请指正。若我要查找关键词为“g”,先从根节点开始查找,先用“g”和“d”比较,“g”比“d”大,所以走右边到达“e”这个节点,再和“e”比较,还是比“e”大,再走右边到达“g”节点,“g”和“g”比较,相等,找到了g的id,根据id找到数据。下面找f,f>d -> e; f>e -> g;找到g已经没有分枝了,但还没找到f,怎么办?难道再返回从d往左边开始找吗?这个f在建立b树的时候是不是应该分在g的左边,因为f比e大所以应该被分到e的右边,但f出现时e的右边已经有g了,所以在g的下面再分枝,将f放在g的左边。这样的情形还很多很多,当这棵树已经非常庞大的时候,突然来了一个处在两个节点中间的节点,应该如何分,同时还要考虑树的左右平衡,因为如果这棵树单独向某一边成长的话就会变成检索整棵树了,性能是非常低的。而且更新索引时还要考虑重新分裂,就算这棵树能保持左右平衡,但当这棵树的节点非常非常多的时候,比如几亿个,那我要找到叶子节点是不是要经过几亿次查找才能找到呢?所以我觉得B树是种很垃圾的东西,但是这么垃圾的东西居然所有的文件系统(ntfs,ext2,ext3,ext4,btrfs,zfs等等)都在用它,而且还用了这么多年!实在令我想不通!
为什么要用B树?可以用反向索引呀!确实,反向索引很优秀,但它也不是万能的,因为数据量一大都会带来麻烦。就拿搜索引擎来说吧,它到网络上抓取一篇文章,然后对文章进行分词再将这些词建立反向索引指向这篇文章。假如一篇有1000字的文章可以分出5000个关键词,随着抓取的文章数量不断增加,关键词的数量会越来越庞大!当然某些文章可能会出现很多重复的关键词,即使如此也还是会有很大的语汇量,简单算一下都知道,将二进制的两个字符0和1进行组合而成一个词,两位二进制数就是2的2次方,有4个词汇,三位二进制数就是2的3次方,有8个词汇,,四位二进制数就是2的4次方,有16词汇...知道 unicode 定义的字符有多少吗?仅仅中文就7万多,加上其它国家的文字总共9万多,将近10万的字符量,将这10万个字符每两个组成一个词汇就有100000*100000=10000000000,100亿呀!!!这还仅仅是两位数的词语,如果再有三位数、四位数的词...当然生活中不会出现这么多词汇,但是,假如有10亿个词语,这些词语都作为关键词建立了反向索引,每个索引都包含有几百到几万篇文章,那我们如何找到这些词在哪个位置?因为要找到这个词索引的文章id就得先找到这个词,这就像叫你在10亿个人中找到一个叫“魂淡”的家伙,你一个一个去问:“A,你是不是叫魂淡?”如果运气好可能在前100个人当中就找到了,若运气不好那可能要问8、9亿才能找到:“可恶的魂淡!我终于找到你了!!!!!!!!!”
当然可以为这个索引建立索引,就是索引中的索引,即使如此也要用10亿个位置来放这些词语,无论这些词语是以单个文件存在磁盘上还是全部写进一个文件中,要找到其中某个词语都要一个一个进行查找,即使用B树也要比较数以亿计的次数。刚才就说了,B树是种很垃圾的东西,所以我们应该寻找某种比B树更优秀的结构。这个问题一直困扰我很久,每当想不到方案时我都会将它丢到一边,过些时候再捡回来继续想,就在最近,终于让我想到了方法,我不知道目前到底有没有人用这种方法,如果没有,那么这种方法就是我锐拜发明的,我暂且将它命名为C树吧(Ctree),暂时不打算公开它的原理,因为我怕别人拿去申请专利,然后通过收费方式供他人使用,我宁愿自己申请专利然后免费给别人使用。我觉得专利制度这种东西应该废除,因为它与科学背道而驰,技术是用来共享的而不是独享,更何况,连我一个没文化的农民工都能想出来的东西能算是技术吗?Ctree 的C 并不是指3,如果以比较次数来衡量它的性能的话,它至少比B树快几百万、几千万甚至几亿倍!我有预感,这种树将会对IT界带来巨变!很多领域都急需这种技术,如:AI、检索程序、云计算、文件系统等等。锐某开发的胖文本数据库就是基于Ctree,我会将胖文本数据库作为“优优”的记忆库以及作为“胖胖堂”的文件系统。
胖胖堂是我用来取代窝窝谷的网络服务,在完成胖文本数据库的开发后就想到了这种更好的运营模式取代窝窝谷。胖胖堂是整合巴巴变(在线版网页设计应用,使用SVG或VML在网页上进行实时绘图,真正的所见即所得,通过JS控制DOM树实现效果实时展现)与网络文件系统的网络桌面应用,网络桌面应用是指“桌面网站”,桌面网站的概念我在早前写的《桌面网站与网页大革命》有提到过,就是一个网站只有一个页面,这个页面全面启用了滑动系统、预载系统、按键系统、历史系统、检索系统、广告系统、多媒体系统等等。而且现在很多浏览器都可以实现真全屏模式浏览,所谓的“真全屏”是指浏览器的标题栏、菜单栏、工具栏、状态栏等全部隐藏掉,甚至连操作系统的任务栏也隐藏掉,整个桌面就是网页的内容区。要使用真全屏模式很简单,IE与Opera(欧朋)只需在快捷方式后面加个参数“-k”,古狗的酷容(Chrome)在快捷方式后加参数“-kiosk”,Firefox(火狐)使用扩展“r-kiosk”(使用r-kiosk要注意,因为r-kiosk禁用了浏览器所有的快捷键,而且我发现自己使用的便携版火狐无法从安全模式-safe-mode启动,导致无法禁用r-kiosk,解决的方法还是有的,将扩展文件夹里对应的r-kiosk文件夹改名就可以禁用r-kiosk,如不知道r-kiosk的id号可以查看它的xpi文件,里面有;当然也可以修改扩展文件夹里r-kiosk的xul文件内容来启用所有快捷键),Safari(远征者)用插件“saft”。至于“网络文件系统”就是胖文本数据库,锐某现在很犹豫,是将它命名为胖文本数据库(TTD)还是胖文件系统(FFS/Fat File System),比起数据库,它更像文件系统。因为我去掉了数据库与表的概念,任何用户只要通过客户端的浏览器就可以往存储系统里存储任何文件,可以为这些文件添加任何属性以及索引,这与传统的数据库是不一样的,传统的数据库是由网站管理员进行管理的,而胖胖堂则将这种权限赋予用户,用户可以在上面添加、删除、复制、移动等各种文件操作。显然,它很像我们平时用的文件系统,只不过它是在线版的。此存储系统只有两种文件,一种普通文件,一种特殊文件,即分类,也可以认为是目录,分类有公共分类与私有分类,公共分类是对所有人开放的,任何人都可以在里面分享自己的所有东西,比如添加新分类或新文件,私有分类则属于个人的,只允许个人在此分类下添加新文件(文件是指分类或普通文件,比如发表一篇博文就是添加一个网页文件),但别人可以浏览与发表评论。个人用户如需创建公共分类则必须向服务商提交申请,经审核批准后方可对外公开此分类。
胖胖堂提供了如下一些一级公共分类(当然还可以无限添加):
电影,音乐,图片,新闻,电脑,手机,数码,汽车,旅行,美食,游戏,编程,爱情,校园,购物,聚会,调查,房产,服装,工业,电气,企业,美容,美发,漫画,动漫,摄影,写真...
一级分类下面还有子分类,子分类又有子分类,如果我想发表一篇关于游戏经验分享的文章,那我以“Rybby”为用户名进行注册,登录后进入到“游戏”这一公共分类,然后向服务器提交添加新文件操作,这个文件有很多属性,如文件类型(这里为网页)、标题、内容、作者、时间、索引、权限等等,大多数操作都与传统网站的发表文章没什么区别,这里有个权限的属性,我稍微说一下。权限的形式与linux系统的类似,用3位数字表示3种不同的用户群,如677,第一位数字代表访客权限,第二位数字代表用户权限,第三位数字代表用户组权限。每个用户群都有读、写、执行等权限,分别用4、2、1表示各种权限,读、写权限在在所有的用户组都相同,就是浏览与发表评论的权限(分类的写权限是指添加新文件,如该分类为公共权限,则对应的权限码为2,表示任何人都可以在此分类下添加新文件)。执行权限在不同的用户群有不同的操作,在访客群里代表下载操作(下载只针对普通文件,不包括分类文件),代码为0表示禁止下载,1表示允许下载,当然下载可能有积分制度。在用户群与用户组群里,执行表示:添加、删除、修改、复制、移动等操作,如果此文件属于某个组或用户的,则该组内的用户或某个单独用户则有相应的权限,如下有两个文件:
a.html(权限码:677,用户id:12,用户组id:5)
b.html(权限码:476,用户id:1,用户组id:3)
以我的uid=1,gid=3为例,对于a.html文件,我有浏览与发表评论的权限(4+2=6),这个文件属于uid=12这个用户的,他有读、写、执行等操作权限,他所在的用户组也有读、写、执行等操作。而对于b.html文件,访客只有浏览权限,没有发表评论与下载权限;这个文件属于我自己的,我有读、写、执行等操作,而我所在的用户组成员只有读与写操作。
上面我发表了一篇关于游戏经验分享的文章,如果操作成功的话,任何访客都可以在“游戏”这一公共分类检索到这篇文章,我也可以在此分类下添加游戏图片文件、游戏录像文件,如果我不想将它们放在游戏分类下,可以进行下面的操作,将游戏图片移到“图片”分类,将游戏录像移到“电影”分类。
添加自己的分类属于私有分类,别人对这个分类只有读权限没有写权限(添加新文件),对该分类里的文件可以有读、写权限,但我可以为这个分类申请一个用户组,然后为该组内的成员提供写权限。如我为了增加自己账号的访问量,添加一个游戏评测的分类,付费请一些员工为该分类写一些高质量的文章来吸引访客,然后通过该分类来进行营利。这跟传统的博客网站不同,他们为用户开通的博客平台只属于用户一个人的,而胖胖堂则可以在这个平台组建小型工作室,另外还可以申请公共分类,下面说一下申请公共分类。
申请公共分类,你可以理解为,在胖胖堂开通自己的网站。与传统的网站相比,胖胖堂为用户省去了购买服务器、请网站管理员、请网页美工等等系列繁杂的工作,通过胖胖堂的服务平台,用户可以直接开通应用服务。申请开通公共分类后,此分类的日常操作管理和运营维护与传统网站的操作管理与运营维护没有什么区别,唯一的区别是数据的存储与检索全由胖胖堂代劳,而且胖胖堂所提供的所有服务都是免费的。
关于广告权限,胖胖堂广告展示范围仅限于公共分类、会员管理界面以及官方活动区,其余的广告展示权均属于用户。通过胖胖堂的广告系统,用户可以很方便地对自己的广告进行控制,用户的广告收入完全归用户所有。
针对胖胖堂的分享系统,我称之为享客,即任何人都可以在上面分享任何东西。
说到数据库,想必大家都很熟悉了,但说到文本数据库未必所有人都熟悉。数据库就是存储数据的仓库,只要能存储数据的东西就可以理解为数据库,文本文档可以存储文本数据,所以它是数据库;图片可以存储图片数据,它也是数据库;mp3可以存储音乐数据,它也是数据库;可见任意类型的文件都是数据库。那如何将文本文档当成数据库来用呢?我们需要使用某种数据结构,通常,文本数据库都使用 json 结构来存储数据,而非文本数据库则使用行、字段的结构来存储数据,下面以存储三个会员的数据表来介绍两种结构:
行&字段结构
id name age sex birthday
1 张三 18 男 09-23
2 李四 25 男 02-15
3 王五 22 女 05-08
json 结构
[
{"id":"1", "name":"张三", "age":"18", "sex":"男", "birthday":"09-23"},
{"id":"2", "name":"李四", "age":"25", "sex":"男", "birthday":"02-15"},
{"id":"3", "name":"王五", "age":"22", "sex":"女", "birthday":"05-08"}
]
两种数据结构都有各自的优缺点,先来看 json 格式,很明显,它有很多重复的数据,如果一张表有100万、1000万、10亿行数据,那这些重复的属性名将会浪费掉非常多的空间!而行&字段的格式只需声明一次字段名,因此省下了很多空间,但是,它没有 json 格式灵活。比如我现在要添加两个新的字段 email、homepage,以后每次添加的行都会有这两个字段,而以前添加的三行数据是没有这两个字段的,所以在添加这两个字段时不得不修改以前的数据行,为它们添加两个值为空的字段,因为每个数据行的结构必须保持相同。现在只是修改三个数据行的数据而已,如果原来已经10亿行数据了再添加两个字段,那得修改多少数据行(不知道mysql等数据库实际操作是否是这样,锐某想象中的应该是这样,自己没研究过mysql的源码)!而 json 则灵活多了,它不需要为每行数据都保持相同的结构,它对每行的属性数量没有限制,一行数据可以有一个属性也可以有两个属性,也可能有很多,这样并不影响它的使用。如添加一个新行:{"id":"4", "name":"马六"},这行会员数据只有两个属性。
既然 json 这么浪费空间!为什么还要用 json?
太好了!会问这个问题证明你已经对数据库这玩意感兴趣了!
随着网络应用的数据量不断攀高,非文本数据库已经不能满足应用了,如 mysql 在数据量到达百万级就已经接近瓶颈了,如果索引优化得好一点的话还能到达千万级,而现在大多数网络应用都以亿为单位进行计算的,所以 mysql 早就应该被淘汰啦!但是 SUN 公司居然以10亿美元收购 mysql!Y?为了证明自己的财大气粗还是什么?在我看来,他们不是呆子就是疯子!而事实也很戏剧化!SUN 收购 mysql 后不久就面临倒闭,最后被 Oracle 收购了,真是螳螂捕蝉黄雀在后!貌似“收购”这个词近来热乎得很!经常看到某某公司拟定多少千万、多少亿对xx公司进行收购,是世界疯了还是人疯了!如果想证明自己比别人强大不至于要靠通过吞掉别人来炫耀吧!一个破玩意居然要花10亿去买!还是美元!随便丢个几十万向社会招揽一群苦逼的程序猿都能设计出比那个好n倍的产品,事实也有很多比 mysql 强n倍的开源产品,如:HBase、MongoDB、OceanBase(千亿级数据库)。如果以数据库的存储量来衡量价值的话,OceanBase 比 mysql 优秀千、万倍,那 Oracle 是不是要花1000个10亿美元来收购 OceanBase?
现在有个比 mysql 优秀1000倍的数据库,谁敢打赌明天不会出现一个比 OceanBase 优秀1000倍的数据库?作为挨踢(IT)民工,我们这些程序猿真的很幸福,因为不断有免费开源的产品供我们随意使用。如果你只想寻求一款好用、够用的数据库,读到这里就不用再往下看了,若你对数据库产生了浓厚的兴趣,也希望开发一款属于自己的数据库,请继续往下看。虽说锐某不知道真正的数据库是如何设计的,也没去欣赏过别人的数据库源码,感觉别人写的东西都很深奥!要完全读懂别人的程序不知要花多少时间,还是按自己的想法去设计程序好,虽说这样的程序不一定能用,但好歹是自己写的,引用某人说的一句话:没有成功,也没有失败,但有收获!以下是锐某开发的文本数据库的实现原理,算是抛砖引玉吧!又引用一下某人的一句话:我抛了一块砖,有玉的尽管砸过来!
为什么要自己开发数据库呢?嘛!这个要从三年前说起,那时锐某想自己开发一个垂直搜索引擎,因为 mysql 已经完全不能满足我的需求,很自然地就想要自己设计数据库了,早前接触 json 时就有想用 json 来设计数据库的冲动。因为要自己设计搜索引擎,所以就有非常正当的理由要自己设计数据库了。至于数据库的名字为什么叫胖文本数据库,嘛!这个是因为自己写文章的分类都是某种特定的格式,如AAB、ABB,锐某的享客分类有:上上游(游戏分享),下下问(问卷调查),左听听(音乐分享),右看看(电影分享)等等,而自己设计的开源程序也都取这种格式的命名:巴巴变(画客),牛牛汇(议客),窝窝谷(个人或中小企业网站程序,使用这个程序任何人都可以很轻松地在网上安个窝,原本打算将窝窝谷与巴巴变与TTD这三个程序进行捆绑的,开发TTD后又想到一种更好的运营模式代替窝窝谷,后面有说到)。so,为了使程序名与 AAB 或 ABB 类同,我给数据库取名叫 TTD(Thick Text Database中文译为胖文本数据库)。设计文本数据库我并没有用 json,因为它跟 xml 一样很垃圾!明眼一看就知道,很多符号都被它霸占了,要用这些符号必须转义。我用的是数组格式:name,value,name,value,name,value...用这种格式解析为对象比用 json 格式解析为对象快6倍,不明白的可以看看锐某写的那篇《编译语言 vs 解释语言》。
数据结构已经知道了,那如何读写数据?哈!终于到精彩的地方了,要知道怎样读写数据,必须搞清楚两个概念:定长与非定长。什么是定长?就是固定长度,即每行数据都是固定长度,如果每行数据的长度都是100字节,要读取第一行数据:(1-1)*100+100,(1-1)*100为找到数据指针(起点,因为指针从0开始,所以减1),+100为读取数据的长度(终点),要从打开的文件句柄中读取一段数据,首先要指定指针的起点和终点,这点很多编程语言都一样的,通过终点来计算长度(如果知道了长度就直接使用长度)。读取第二行数据:(2-1)*100+100,读取第三行数据:(3-1)*100+100...读取第1亿行数据:(100000000-1)*100+100。要读取固定长度的数据非常容易,只要知道行号,无论数据量有多大都可以直接定位进行读取,如果你的磁盘容量足够大,无论要存储多少亿笔数据都没问题,因为定长数据没有数量的概念。这是非常好的优点呀!但是从反正理论(不知道反正理论的可以看看锐某写的那篇《反正理论》)来说,任何事物都有正反两面,它的优点刚好是它的缺点。定长虽好,但我们的数据不可能都用定长,如一篇文章,有人只写了几十字,而有的人写了几千字,如果将一行数据固定为几千字节,那只有很少数据的行将会浪费很大的磁盘空间,另外,大于这个存储空间的数据将会被丢弃。为了节省磁盘空间,只好用非定长数据了(数据是变长的,即不固定长度),但是非定长数据却给读写带来了麻烦,因为我们无法通过行号算出数据行的起点。解决的办法还是有的,就是建立索引。要读写数据首先要知道数据的起点与终点(终点用来算出长度,通常存储时都直接用长度,免去一步计算),所以索引文件就是记录每行数据的起点和长度,当然这个索引文件的每行数据是定长的(方便定位指针),我们可以称这个索引文件为主键索引。下面用实例来说明:
数据文件:data.ttd(非定长)
第一行数据=100字节,
第二行数据=250字节,
第三行数据=1000字节
...
索引文件:key.ttd(定长,每行长20字节)
第一行数据的起点和长度:0,100(每行数据长20字节,不够的在后面加空格补足)
第二行数据的起点和长度:100,250
第三行数据的起点和长度:350,1000
...
通过索引,读数据就变得和定长一样简单了,只是多了一步打开索引文件的步骤,如果我要读取第二行的数据,先打开索引文件key.ttd,(2-1)*20+20="100,250".split(",");打开数据文件data.ttd,移动数据指针到 arr[0],读取长度 arr[1] 字节,完成。
索引文件的行长设置为多少字节才合适?那要看你的磁盘空间有多大与单个文件的体积有多大,磁盘空间影响着数据指针,文件体积影响数据长度。如:100,250,前面的数据代表指针位置,如果你的数据文件有几百TB(1BB=1*1000YB*1000ZB*1000EB*1000PB*1000TB*1000GB*1000MB*1000KB*1000B),那你的数据行在数据文件最后面时需要多少位数字来表示呢?看上面的公式,1TB有12个0加个1,共13位数字,1T=13,10T=14,100T=15,百泰级别要15位数字才能表示(即15字节)。索引数据后面部份代表数据长度,如果你的数据库还用来存储二进制文件(必须将二进制流转换成base64格式,因为里面可能有特殊符号),如电影,游戏之类,那就有可能几G,现在有的游戏有十几G,10-99吉的数据长度就要10位数字表示,15+10+1=26Byte,要根据磁盘空间与文件体积规划索引文件的行长。
上面只说了读数据,其实写数据(包括更新数据与添加数据,更新数据一定要用r+模式打开,w是覆盖写,a是追加写,两者都不能定位指针;添加新数据不用定位指针,直接用a模式打开在数据最后添加)也一样的。不过如果写的数据比原来的数据大就要注意了,因为更新的数据不能大于原来的数据,不然下一行的数据会被覆盖掉,必须是原来有多少字节就写入多少字节,不够的用空格补足。如果大于原来的数据就要用添加新数据的方式将更新的数据添加到最后面,然后修改索引文件中此行数据的起点与长度,再将原来的数据用空格替换掉,这样就会出现数据碎片(空白的一段数据),随着更新的次数增加,碎片会越来越多,要定期处理碎片。碎片处理起来其实很简单,每次读取指定长度的数据,如2MB,干掉里面的空格,重新连接数据内容,修改新内容的索引。
还是上个简单的更新数据的例子吧,不然有人要晕倒了。
修改前的数据内容:
id,1,name,张三,age,18,sex,男,birthday,09-23;id,2,name,李四,age,25,sex,男,birthday,02-15;id,3,name,王五,age,22,sex,女,birthday,05-08;
修改前的索引内容:
0,47 47,47 94,47
我现在要更新第二笔数据,即将“id,2,name,李四,age,25,sex,男,birthday,02-15;”修改为“id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;”
修改后的数据内容:
id,1,name,张三,age,18,sex,男,birthday,09-23; id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;
修改后的索引内容:
0,47 141,86 94,47
处理碎片:
data.ttd
arr = data.split(/; */);
newData = arr[0] + arr[1] + arr[2];
key.ttd
id1.start = 0;
id1.length = arr[0].lnegth(Byte);
id3.start = id1.length;
id3.length = arr[1].lnegth(Byte);
id2.start = id1.length + id3.length;
id2.length = arr[2].lnegth(Byte);
关于特殊字符,就是用来格式化数据内容的字符,如上面的用到三个字符“,”、“;”、“ ”,如果数据内容中有这样的字符就必须进行转义,不能直接加个反斜杠进行转义,应该将它们替换为其它的字符,如 html 实体字,其实上面只要两个字符就够了,将空格全替换为行末符“;”(不应该用换行符“\n”作为行末符,因为win与linx中是不一样的),如:id,1,name,张三,age,18,sex,男,birthday,09-23; id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;应该被替换为id,1,name,张三,age,18,sex,男,birthday,09-23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;id,3,name,王五,age,22,sex,女,birthday,05-08;id,2,name,李四,age,25,sex,男,birthday,02-15,email,lishi.163.com,homepage,lishi.com;切分字符串时就这样 arr = data.split(/;+/); 上面只是作为例子演示,如果实际编程中你用“,”与“;”作为格式化符号的话,证明你还嫩,应该尽量使用内容中不会出现的符号,如:“`”、“~”,这样可以减少转义所需的时间,而且尽可能在客户端完成这些操作,能偷客户端的资源就尽量偷,可以的话应该用客户端去采集数据,当然锐某不建议你去采别人的数据作为自己网站的内容,但像百毒这样的搜索引擎还是可以的哈!
如果你觉得上面的“name,value,name,value...”这种类似于 json 的格式太占磁盘空间(确实很占空间!!!),那你也可以用行&字段的形式来存储数据,即“value,value,value...”。这种格式如何通过属性名来取值呢?方法还是有的,用表的第一行来存放属性名,如:name,name,name...这种格式比较麻烦,属性名与属性值的顺序与位置必须一一对应,而且两者的数量也要相等,如果一个属性名对应的属性值没有值,可以用空值。下面用实例来说明:
table.ttd
id,name,age,sex,email;
1,张三,18,男,zs@qq.com;
2,李四,25,男,;
3,王五,22,,ww@qq.com;
json.ttd
id,1,name,张三,age,18,sex,男,email,zs@qq.com;
id,2,name,李四,age,25,sex,男;
id,3,name,王五,age,22,email,ww@qq.com;
对行&字段格式取值很简单,先取属性名这一行,将它转为数组,这个数组有几个单元,属性值这一行就应该有几个单元,无值的单元要以用空值替换。
对象化代码演示:
var o, k, v, l, z;
o = {};
k = "id,name,age,sex,email".split(',');
l = k.length;
v = "1,张三,18,男,zs@qq.com".split(',');
for(z = 0; z < l; z++) o[k[z]] = v[z] || '';
看到这里你应该能写一个有模有样的数据库了,随便一种支持文件操作的编程语言都可以,前提是你的数据不需要检索服务,如新闻系统。因为新闻系统只需要将最新的内容展现出来,通过主键索引从后面读数据就可以实现倒序排列,即将最新的新闻返回客户端。
性能测试
本来我想写入10亿笔数据进行测试,但我的磁盘空间不够,现在只写了5000笔会员数据,然后再写了1.2亿笔文件数据(耗时72分钟),文件数据只有两个属性,id与内容,内容为10个随机字符(这个要用来分词然后建立反向索引),fdada.ttd=3.6G,fkey.ttd=1.5G。PC配置:CPU:N270=1.6GHz,内存:1G,硬盘:SATA。如果排除磁盘寻址时间的话,从1.2亿笔数据中取一笔耗时为 0.4ms,这个是通过循环读取某个行号10000次求出的平均值,这个跟行号的大小没有关系,因为是重复读磁盘的某个位置,所以降低了寻址时间。随机读取性能,读10000笔求平均每笔耗时,这个与随机行号的范围有关,限定行号在100万以内耗时:头两次为 9ms、2ms,后面的都固定在 0.4ms,这个是不是 Node 缓存了结果?1000万耗时:18ms,1亿耗时:18ms。随着随机行号的范围越大耗时也稍微有所提升,这个应该是寻址范围增大了。如果使用连续行号读的话基本都保持在 1.5ms/笔,这个升序与倒序都一样的。以上是通过主键索引来读取数据的每笔耗时,我现在的数据量为1.2亿,随机读性能为 18ms,按我们平常一次读10笔来算那就是 180ms,有条件的网友可以测试一下几十亿、几百亿数据量的性能。我觉得都差不多在这个范围吧,唯一要考虑的是磁盘寻址时间,如果用固态硬盘再加 RAID 的话还会更快!
如果你希望开发一个多功能的数据库就接着往下看吧,锐某只说一下如何对数据库添加检索服务,其它的诸如权限、事务、排序之类的自己琢磨去吧。要对数据进行检索就要建立索引,平时我们常用的索引有以下这些:B树、B+树、B-树、B*树、R树、反向索引等等。我先来说反向索引(又称倒序索引、倒排索引)吧,要说明什么是反向索引那得先说明什么是正向索引。按我们一般的理解,查找一些包含某个关键词的文章,那就要对数据库里的文章一篇一篇地打开进行查找,当一个数据库的文章数非常非常庞大里,这样查找是非常耗时的!且先不说数据库,就算在一篇文章里查找某个关键词也很耗时,因为你不知道它在哪一段哪一句?但是,我们程序猿是一种聪明的物种,这点小事难不倒我们滴!于是,我们对每篇文章都建立索引,这就是正向索引。这有点像书本的目录,只记录一些主要的内容,这样就不用一页一页地进行翻阅了,只要看完目录页就知道这本书有没有我们需要的内容了。比如我们在网上发表博客时都可以添加一些 tag,这些 tag 表示这篇博文里有包含这个 tag 的词或与这个 tag 有关的东西,下面以一篇介绍笔记本电脑的文章为例,这篇文章里介绍三个品牌的电脑,这三个品牌是:联想、华硕、戴尔,文章的行号是:120,于是,我们可以这样建立正向索引:
index.ttd
120:联想,华硕,戴尔
当我们要检索包含有“联想”这个关键词的文章时,很容易就发现id为120的这篇文章,这样就不用一篇一篇地打开进行查找了,检索速度比以前没有建立索引时快多了。但是当数据库的文章数很多时,这种索引还是很慢!比如一个索引文件里有下面的内容:
120:联想,华硕,戴尔
121:华硕,戴尔
122:清华同方,戴尔
123:联想,华硕,明基
当我们打开这个索引文件找到120这一行时还不能停止,因为后面的索引可能还有包含“联想”这个关键词的文章,于是,不得不将整个索引文件查完。针对这种问题我们聪明绝顶的程序猿又想出一种完美方案来证明自己真的是一种聪明绝顶的物种!这就是大名鼎鼎的反向索引了!说它是反向的确实非常贴切!因为它就是反过来用的,如将上面的正向索引变为反向索引就是:
联想:120,123
华硕:120,121,123
戴尔:120,121,122
清华同方:122
明基:123
当我们检索“联想”这个关键词时找到id为120与123这两篇文章,而且也不需要再向下找了,因为包含这个关键词的文章都记录在这里,找到id就能直接算出文章在数据库的哪个地方。同样,检索“华硕”关键词可以找到三篇文章,分别是:120,121,123,可见反向索引要比正向索引要高效很多!目前的搜索引擎都是用反向索引的。
B tree,锐某对B树的具体运用不是很理解,网上介绍B树的资料虽然很多,但几乎没有实例,要知道,一个简单的实例胜过千言万语!而且很多文章基本都一样,也就是说很多网友老喜欢抄袭、复制别人的东西随手一转又是自己的了,还有很多自己都不是很理解也发出来给别人看。因为几乎所有的文件系统都是用B树进行检索文件的,我就简单说一下吧。这里的B是二的意思,B树就是指二叉树,即最多只有两个分枝,当然分枝底下还可以再分两枝,然后分枝又可以再分枝,但每个分枝最多只有两枝,最下面的就是叶子了(之所以说是最下面,因为用电脑屏幕来表示树形图都是倒过来的,即树根在最上面,树顶在最下面,html的DOM树也是这样表示的),中间的分枝可以理解为树枝。B树的分枝规则是,比当前节点小的往左边发芽,大的往右边发芽,下面附个简单的例子:
d
/ \
b e
/\ /\
a c g
/ \
f
上面的树形中,d为树根节点,a、f、g为叶子节点,b、c、e为树枝节点。每个节点都存放着其所对应的id号,B树的检索过程,先声明,锐某对B树的检索过程并不是很理解,以下只是根据自己的浅显见解而作的讲解,如有不对请指正。若我要查找关键词为“g”,先从根节点开始查找,先用“g”和“d”比较,“g”比“d”大,所以走右边到达“e”这个节点,再和“e”比较,还是比“e”大,再走右边到达“g”节点,“g”和“g”比较,相等,找到了g的id,根据id找到数据。下面找f,f>d -> e; f>e -> g;找到g已经没有分枝了,但还没找到f,怎么办?难道再返回从d往左边开始找吗?这个f在建立b树的时候是不是应该分在g的左边,因为f比e大所以应该被分到e的右边,但f出现时e的右边已经有g了,所以在g的下面再分枝,将f放在g的左边。这样的情形还很多很多,当这棵树已经非常庞大的时候,突然来了一个处在两个节点中间的节点,应该如何分,同时还要考虑树的左右平衡,因为如果这棵树单独向某一边成长的话就会变成检索整棵树了,性能是非常低的。而且更新索引时还要考虑重新分裂,就算这棵树能保持左右平衡,但当这棵树的节点非常非常多的时候,比如几亿个,那我要找到叶子节点是不是要经过几亿次查找才能找到呢?所以我觉得B树是种很垃圾的东西,但是这么垃圾的东西居然所有的文件系统(ntfs,ext2,ext3,ext4,btrfs,zfs等等)都在用它,而且还用了这么多年!实在令我想不通!
为什么要用B树?可以用反向索引呀!确实,反向索引很优秀,但它也不是万能的,因为数据量一大都会带来麻烦。就拿搜索引擎来说吧,它到网络上抓取一篇文章,然后对文章进行分词再将这些词建立反向索引指向这篇文章。假如一篇有1000字的文章可以分出5000个关键词,随着抓取的文章数量不断增加,关键词的数量会越来越庞大!当然某些文章可能会出现很多重复的关键词,即使如此也还是会有很大的语汇量,简单算一下都知道,将二进制的两个字符0和1进行组合而成一个词,两位二进制数就是2的2次方,有4个词汇,三位二进制数就是2的3次方,有8个词汇,,四位二进制数就是2的4次方,有16词汇...知道 unicode 定义的字符有多少吗?仅仅中文就7万多,加上其它国家的文字总共9万多,将近10万的字符量,将这10万个字符每两个组成一个词汇就有100000*100000=10000000000,100亿呀!!!这还仅仅是两位数的词语,如果再有三位数、四位数的词...当然生活中不会出现这么多词汇,但是,假如有10亿个词语,这些词语都作为关键词建立了反向索引,每个索引都包含有几百到几万篇文章,那我们如何找到这些词在哪个位置?因为要找到这个词索引的文章id就得先找到这个词,这就像叫你在10亿个人中找到一个叫“魂淡”的家伙,你一个一个去问:“A,你是不是叫魂淡?”如果运气好可能在前100个人当中就找到了,若运气不好那可能要问8、9亿才能找到:“可恶的魂淡!我终于找到你了!!!!!!!!!”
当然可以为这个索引建立索引,就是索引中的索引,即使如此也要用10亿个位置来放这些词语,无论这些词语是以单个文件存在磁盘上还是全部写进一个文件中,要找到其中某个词语都要一个一个进行查找,即使用B树也要比较数以亿计的次数。刚才就说了,B树是种很垃圾的东西,所以我们应该寻找某种比B树更优秀的结构。这个问题一直困扰我很久,每当想不到方案时我都会将它丢到一边,过些时候再捡回来继续想,就在最近,终于让我想到了方法,我不知道目前到底有没有人用这种方法,如果没有,那么这种方法就是我锐拜发明的,我暂且将它命名为C树吧(Ctree),暂时不打算公开它的原理,因为我怕别人拿去申请专利,然后通过收费方式供他人使用,我宁愿自己申请专利然后免费给别人使用。我觉得专利制度这种东西应该废除,因为它与科学背道而驰,技术是用来共享的而不是独享,更何况,连我一个没文化的农民工都能想出来的东西能算是技术吗?Ctree 的C 并不是指3,如果以比较次数来衡量它的性能的话,它至少比B树快几百万、几千万甚至几亿倍!我有预感,这种树将会对IT界带来巨变!很多领域都急需这种技术,如:AI、检索程序、云计算、文件系统等等。锐某开发的胖文本数据库就是基于Ctree,我会将胖文本数据库作为“优优”的记忆库以及作为“胖胖堂”的文件系统。
胖胖堂是我用来取代窝窝谷的网络服务,在完成胖文本数据库的开发后就想到了这种更好的运营模式取代窝窝谷。胖胖堂是整合巴巴变(在线版网页设计应用,使用SVG或VML在网页上进行实时绘图,真正的所见即所得,通过JS控制DOM树实现效果实时展现)与网络文件系统的网络桌面应用,网络桌面应用是指“桌面网站”,桌面网站的概念我在早前写的《桌面网站与网页大革命》有提到过,就是一个网站只有一个页面,这个页面全面启用了滑动系统、预载系统、按键系统、历史系统、检索系统、广告系统、多媒体系统等等。而且现在很多浏览器都可以实现真全屏模式浏览,所谓的“真全屏”是指浏览器的标题栏、菜单栏、工具栏、状态栏等全部隐藏掉,甚至连操作系统的任务栏也隐藏掉,整个桌面就是网页的内容区。要使用真全屏模式很简单,IE与Opera(欧朋)只需在快捷方式后面加个参数“-k”,古狗的酷容(Chrome)在快捷方式后加参数“-kiosk”,Firefox(火狐)使用扩展“r-kiosk”(使用r-kiosk要注意,因为r-kiosk禁用了浏览器所有的快捷键,而且我发现自己使用的便携版火狐无法从安全模式-safe-mode启动,导致无法禁用r-kiosk,解决的方法还是有的,将扩展文件夹里对应的r-kiosk文件夹改名就可以禁用r-kiosk,如不知道r-kiosk的id号可以查看它的xpi文件,里面有;当然也可以修改扩展文件夹里r-kiosk的xul文件内容来启用所有快捷键),Safari(远征者)用插件“saft”。至于“网络文件系统”就是胖文本数据库,锐某现在很犹豫,是将它命名为胖文本数据库(TTD)还是胖文件系统(FFS/Fat File System),比起数据库,它更像文件系统。因为我去掉了数据库与表的概念,任何用户只要通过客户端的浏览器就可以往存储系统里存储任何文件,可以为这些文件添加任何属性以及索引,这与传统的数据库是不一样的,传统的数据库是由网站管理员进行管理的,而胖胖堂则将这种权限赋予用户,用户可以在上面添加、删除、复制、移动等各种文件操作。显然,它很像我们平时用的文件系统,只不过它是在线版的。此存储系统只有两种文件,一种普通文件,一种特殊文件,即分类,也可以认为是目录,分类有公共分类与私有分类,公共分类是对所有人开放的,任何人都可以在里面分享自己的所有东西,比如添加新分类或新文件,私有分类则属于个人的,只允许个人在此分类下添加新文件(文件是指分类或普通文件,比如发表一篇博文就是添加一个网页文件),但别人可以浏览与发表评论。个人用户如需创建公共分类则必须向服务商提交申请,经审核批准后方可对外公开此分类。
胖胖堂提供了如下一些一级公共分类(当然还可以无限添加):
电影,音乐,图片,新闻,电脑,手机,数码,汽车,旅行,美食,游戏,编程,爱情,校园,购物,聚会,调查,房产,服装,工业,电气,企业,美容,美发,漫画,动漫,摄影,写真...
一级分类下面还有子分类,子分类又有子分类,如果我想发表一篇关于游戏经验分享的文章,那我以“Rybby”为用户名进行注册,登录后进入到“游戏”这一公共分类,然后向服务器提交添加新文件操作,这个文件有很多属性,如文件类型(这里为网页)、标题、内容、作者、时间、索引、权限等等,大多数操作都与传统网站的发表文章没什么区别,这里有个权限的属性,我稍微说一下。权限的形式与linux系统的类似,用3位数字表示3种不同的用户群,如677,第一位数字代表访客权限,第二位数字代表用户权限,第三位数字代表用户组权限。每个用户群都有读、写、执行等权限,分别用4、2、1表示各种权限,读、写权限在在所有的用户组都相同,就是浏览与发表评论的权限(分类的写权限是指添加新文件,如该分类为公共权限,则对应的权限码为2,表示任何人都可以在此分类下添加新文件)。执行权限在不同的用户群有不同的操作,在访客群里代表下载操作(下载只针对普通文件,不包括分类文件),代码为0表示禁止下载,1表示允许下载,当然下载可能有积分制度。在用户群与用户组群里,执行表示:添加、删除、修改、复制、移动等操作,如果此文件属于某个组或用户的,则该组内的用户或某个单独用户则有相应的权限,如下有两个文件:
a.html(权限码:677,用户id:12,用户组id:5)
b.html(权限码:476,用户id:1,用户组id:3)
以我的uid=1,gid=3为例,对于a.html文件,我有浏览与发表评论的权限(4+2=6),这个文件属于uid=12这个用户的,他有读、写、执行等操作权限,他所在的用户组也有读、写、执行等操作。而对于b.html文件,访客只有浏览权限,没有发表评论与下载权限;这个文件属于我自己的,我有读、写、执行等操作,而我所在的用户组成员只有读与写操作。
上面我发表了一篇关于游戏经验分享的文章,如果操作成功的话,任何访客都可以在“游戏”这一公共分类检索到这篇文章,我也可以在此分类下添加游戏图片文件、游戏录像文件,如果我不想将它们放在游戏分类下,可以进行下面的操作,将游戏图片移到“图片”分类,将游戏录像移到“电影”分类。
添加自己的分类属于私有分类,别人对这个分类只有读权限没有写权限(添加新文件),对该分类里的文件可以有读、写权限,但我可以为这个分类申请一个用户组,然后为该组内的成员提供写权限。如我为了增加自己账号的访问量,添加一个游戏评测的分类,付费请一些员工为该分类写一些高质量的文章来吸引访客,然后通过该分类来进行营利。这跟传统的博客网站不同,他们为用户开通的博客平台只属于用户一个人的,而胖胖堂则可以在这个平台组建小型工作室,另外还可以申请公共分类,下面说一下申请公共分类。
申请公共分类,你可以理解为,在胖胖堂开通自己的网站。与传统的网站相比,胖胖堂为用户省去了购买服务器、请网站管理员、请网页美工等等系列繁杂的工作,通过胖胖堂的服务平台,用户可以直接开通应用服务。申请开通公共分类后,此分类的日常操作管理和运营维护与传统网站的操作管理与运营维护没有什么区别,唯一的区别是数据的存储与检索全由胖胖堂代劳,而且胖胖堂所提供的所有服务都是免费的。
关于广告权限,胖胖堂广告展示范围仅限于公共分类、会员管理界面以及官方活动区,其余的广告展示权均属于用户。通过胖胖堂的广告系统,用户可以很方便地对自己的广告进行控制,用户的广告收入完全归用户所有。
针对胖胖堂的分享系统,我称之为享客,即任何人都可以在上面分享任何东西。
锐拜十大博:
网易博客:http://rybby.blog.163.com/
新浪博客:http://blog.sina.com.cn/rybby/
搜狐博客:http://rybby.blog.sohu.com/
和讯博客:http://rybby.blog.hexun.com/
CSDN博客:http://blog.csdn.net/rybby/
ITeye博客:http://rybby.iteye.com/
博客园:http://www.cnblogs.com/rybby/
博客大巴:http://rybby.blogbus.com/
百度空间:http://hi.baidu.com/rybby/
QQ空间:http://user.qzone.qq.com/898056025/
网易博客:http://rybby.blog.163.com/
新浪博客:http://blog.sina.com.cn/rybby/
搜狐博客:http://rybby.blog.sohu.com/
和讯博客:http://rybby.blog.hexun.com/
CSDN博客:http://blog.csdn.net/rybby/
ITeye博客:http://rybby.iteye.com/
博客园:http://www.cnblogs.com/rybby/
博客大巴:http://rybby.blogbus.com/
百度空间:http://hi.baidu.com/rybby/
QQ空间:http://user.qzone.qq.com/898056025/