以前看《代码大全2》时看到了如下一句经典台词:
“any problem in computer science can be sloved by another layer of indirecition”
“计算机科学领域的任何问题都可以通过增加一个中间层来解决”
当时只是觉得好就永远记住了,几年的编码经验,越来越感受到这句话的经典所在,下面来举几个例子说一下我对它的理解:
1.操作系统的缓存:
比较典型的用于阐述这句话的缓存实例,当属内存(包括swap、buffer、内存的cache)和cpu的Cache(有三个等级),
操作系统中数据总体读取流向:硬盘》内存》cpu。
swap:
当有应用需要读写磁盘数据时,由系统把相关数据从磁盘读取到内存,如果物理内存不够,则把内存中的部分数据导入到磁盘,从而把磁盘的部分空间当作虚拟内存(也就是swap)来使用。换句简单的话说,swap就是当内存不够时从硬盘中开辟的一块空间。
buffer:
当一个应用程序在内存中修改过数据后,因为写入磁盘速度相对较低,在有空闲内存的情况下,这些数据先存入内存的一块空闲空间(即buffer),在以后某个时间再写入磁盘,从而应用程序可以继续后面的操作,而不必等待这些数据写入磁盘的操作完成。简单说,buffer 是为了提高内存和硬盘(或其他I/O设备)之间的数据交换的速度而从内存中开辟的一块空间。
内存的cache: 从磁盘读取到内存的数据在被相关应用程序读取后,如果有剩余内存,则这部分数据会存入内存的一块空闲空间(内存的cache),以备第2次读取时,避免重新读取磁盘。
cpu的Cache:
Cache:高速缓存,是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,这样就减少了CPU的等待时间,提高了系统的效率。
又上可见,操作系统的缓存就是硬盘与内存之间,内存与硬盘之间的各种中间层,用于解决操作系统内部数据交互速度不一致的问题。
2.消息对列:
当系统中出现“生产“和“消费“的速度或稳定性等因素不一致的时候,就需要消息队列,作为抽象层,弥合双方的差异。“ 消息 ”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。消息被发送到队列中,“ 消息队列 ”是在消息的传输过程中保存消息的容器 。
常用的业务场景如下:
1)业务系统触发短信发送申请,但短信发送模块速度跟不上,需要将来不及处理的消息暂存一下,缓冲压力。就可以把短信发送申请丢到消息队列,直接返回用户成功,短信发送模块再可以慢慢去消息队列中取消息进行处理。
2)调远程系统下订单成本较高,且因为网络等因素,不稳定,攒一批一起发送。
3)任务处理类的系统,先把用户发起的任务请求接收过来存到消息队列中,然后后端开启多个应用程序从队列中取任务进行处理。
4)在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,比如说,大量的insert,update之类的请求同时到达MySQL,直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。通过使用消息队列,我们可以异步处理请求,从而缓解系统的压力。
消息对列就相当于两个应用之间的中间层,用于解决生产和消费的速度不一致的问题,能够提高系统的响应速度和系统的稳定性。
3.中间表:
具体应用比如闭包表、从属表等。
闭包表:用于分级存储的一个简单而优雅的解决方案,它记录了树种所有节点间的关系,而不仅仅只有那些直接的父子关系。
比如一个评论表,一般建表如下:
CREATE TABLE Comments (
comment_id bigint unsigned not null auto_increment primary key,
parent_id bigint unsigned not null,
comment text not null,
foreign key (parent_id) references Comments(comment_id)
)
这样建设虽然简单,但是当一个评论下面有很多分支的时候,无论你是用程序还是用sql语句去处理这种层级关系 ,都是比较低效的。
一种很好的解决方案技术增加一个中间表,用于存储各个评论之间的关系(包括自己与自己,自己与子孙之间的关系),将刚才的表拆分为两张表如下 :
CREATE TABLE Comments (
comment_id bigint unsigned not null auto_increment primary key,
comment text not null
);
CREATE TABLE TreePaths(
ancestor_id bigint unsigned not null,
descendant_id bigint unsigned not nul,
primary key(ancestor_id , descendant_id),
foreign key (ancestor_id) references Comments(comment_id),
foreign key (descendant_id ) references Comments(comment_id)
);
这样增加一个中间表后既使关系很清晰,在查询、做统计分析和增删改的时候也简单多了。
从属表:仅用一列来存储多值属性,将多个值存在一张单独表的多行中而不是一张表的多列中。
比如创建一个有多种属性值的表,一般建表如下
CREATE TABLE Bug(
bug_id int unsigned not null auto_increment primary key,
description varchar(1000),
tag varchar(500) //用json格式存储
)
或者
CREATE TABLE Bug(
bug_id int unsigned not null auto_increment primary key,
description varchar(1000),
tag1 varchar(20),
tag2 varchar(20),
tag3 varchar(20),
tag4 varchar(20),
)
这样建设虽然可以,但不优雅,在做统计查询或者增加一种属性时就比较费力了,这是可以增加一张中间表(从属表)单独用来存储bug的属性,具体建设如下
CREATE TABLE Bug(
bug_id int unsigned not null auto_increment primary key,
description varchar(1000)
)
CREATE TABLE Tag(
bug_id int unsigned not null,
tag varchar(20) not null,
primary key (bug_id,tag),
foreign key (bug_id) references Bug(bug_id)
)
这样修改后的表在做查询和统计的时候比较高效,在增加和删除的时候级联关系也很严格。
主键的约束能够保证不会有重复的记录出现,一个给定的标签只能和一个给定的Bug关联一次。
每个Bug也不在只限于几个标签,可以很方便的扩展成tagN 的列。
表建设过程中增加一个中间表非常适用于“有关系”的字段,既方便扩展也方便做普通查询和统计查询,严格的关系也可以通过外键加以控制,只是在查询的时候多了几个join(通过索引并不影响查询效率),这么好的想法,那就赶快在新项目中尝试吧。
以上只是通过操作系统的缓存、消息队列、中间表这三个实例阐述了我对“any problem in computer science can be sloved by another layer of indirecition”这句话的理解,这句话不仅适用于计算机领域,其他工程领域比如工程建造、机械建设、系统流程也是适用,所以经典之所以经典,是因为经典的东西不仅能帮你解决手中现有问题还能帮你解决以后可能出现的问题,还很富有哲学,能应用到各个领域。
写在最后的话:
“书中自有黄金屋 书中自有颜如玉”,也许你现在读的不一定现在就懂,等你经历的多了或者读得多了再回头看看,也许你会微微一笑,虽不至于倾城倾国,倾自己倒是绝对可以的。骚年们,当你走的路不多时,那就多读书吧。
参考博客:
http://blog.csdn.net/bingqingsuimeng/article/details/51858858
http://www.cnblogs.com/xuyatao/p/6864109.html
参考书籍:
《代码大全2》、《SQL反模式》