手机之家(imobile.com.cn)是一个旨在提供全方位的手机相关服务的资讯类网站。在7年的时间里,手机之家从无到有,已经发展成为极具人气、最受关注的手机产品资讯网站。先看一些统计数据,为后面的架构设计提供数据需求基础:已有1000W+用户,3000W+帖子,1.1TB+附件,780W+的每日PV,5~15W在线用户/15分钟)。在最新的系统设计中,已经有很多轮子已成通用轮子,但还需要不断完善已有轮子,同时还会有新轮子出现(如,在表现层引入Widget,更好的存储系统)。这篇文章我是从《beta技术沙龙:手机之家新系统介绍及架构分享》中整理出来的,最后关于DAL的设计和实现,还存在一些疑问,需要再寻找相关的资料。不过本文已经提供了一些很好的系统演化的实例,非常具有参考价值。

老系统的存在问题

老系统的基本是出于无设计的状态,主要存在三方面问题:(1)由于系统分层比较模糊,导致问题极难排查,到底是哪个层面(页面相关、访问数据、处理逻辑)出现错误?(2)由于Web Server可以直接访问DB Server,在高峰阶段,并发量比较大,导致DB Server经常Down掉。(3)数据表结构设计不当,导致无法很简单地(甚至不可能)进行竖直和水平方向的伸缩。

纯PHP分层方案

上图1是方案1的架构,基本解决了前面说的问题。但是开发一个功能,要写很多代码,重复性极高,代码复用非常少(特别是数据访问层代码)。而且,程序员需要自己控制缓存,这使得缓存代码渗透到业务逻辑的每一个角度,造成代码维护成本上升;程序员根据自己的喜好来控制缓存的Key/Value,造成缓存混乱;程序员既要负责业务逻辑的编写,还要负责缓存管理,造成编程复杂度提高,开发效率低下。

面对这些问题,我们寻找了改进缓存的解决方案。对数据库记录的缓存的访问做了一定的抽象处理,开发出了Cache处理器。所有的数据访问都经过了Cache处理器。这样,系统代替程序员接管了缓存的存取访问。缓存的Key/Value由系统处理,从而避免了冲突和混乱。Cache处理器的引入,减少了40%的数据访问层代码。并且,我们采用了Namespace的方法使得缓存能自动消除。改进缓存后的架构如图2所示。

但是问题又出现了,此时系统响应速度变得很慢,原因在于Namespace是借助PHP fcgi进程+SQLite来实现的。这促使我们对采用纯PHP方案的可行性产生了怀疑。

PHP+JAVA提升性能

由于Memcached不支持namespace,也不支持遍历。所以,我们基于之前开发的monkey(Java nio框架)实现了一个支持namespace遍历功能的内存缓存程序(jmcd),采用的是简单高效的STP协议。同时改进了Session的实现方式:在monkey和jmcl的基础上开发出session server。这次响应时间提升了80毫秒以上!这基本上达到了性能预期:平均每个页面响应时间低于20毫秒。架构如图3所示。

DA引入

Cache处理器在缓存方面有了很大的进步,但是由于接口众多,使用起来不太方便,而且还要编写大量的重复代码。为此,我们重构了这部分代码,吸收了其他框架的优点,开发出了Data Accessor(简称DA)。如图4所示。

DA标准化了调用接口,简化了编程,使程序员在业务逻辑层面不再关心缓存与切库分表,极大地提高了生产力(代码量继续减少,易用性提高)。但是它仍然有很多不足。DA直接和数据库打交道,在这一点上,可以说是又回到了起点。不过,从逻辑上来说,DA已经是之后构想的Data Access Layer的雏形,经过4个月的运行测试,现在,我们把[原DA]称为DAL1.0,而把[原DA]中包含的客户端程序仍然称为DA。

DAL1.0有很多不足,要弥补这些不足,必须从根本上进行重新设计。如果说DAL1.0是为易用性而设计,那么DAL2.0就是为性能、可用性等等而设计。

DAL设计概览

DAL设计的目标是让程序员不再关心分库切表问题,经DBA配置,有DAL处理;程序员不再关心缓存的存取和清理问题,有DAL处理。新的架构如图5所示。

关于分库切表,可以分为垂直切分(按功能分,分到不同的数据库或服务器)和水平切分(对数据进行水平分割)。水平切分最好分到同一个数据库。一种已经证明是切实可行的方案:主表+辅表,分为3种类型,主表不打散、主表打散无辅表、主表打散有辅表。对程序员来说,看到的只是一张表,不妨称之为虚表(逻辑表),这张虚表实际上可能是有N张实表(物理表)组成的。

对于缓存不应该紧耦合于业务逻辑,慎用XC/APC缓存,一般情况下,mc/jmc这种远程缓存已经够用了。缓存的最佳实践是缓存单个条目,根据id list(unique key list)找列表。缓存能自动清理就自动清理,不能自动清理的想办法自动清理。

DA的用法示例

1)增:DataAccessor::insert()
->table('imobile.post.db_post')
->data(array('post_id'=>1, ...))
->dup()
->execute();

2)删:DataAccessor::delete()
->table('imobile.post.db_post')
->where('post_id', 'IN', array(1, 2, 3))
->limit(2)
->execute();

3)改:DataAccessor::update()
->table('imobile.post.db_post')
->data(array('level'=>0))
->where('user_id', '<', 1000)
->limit(100)
->execute();

4)查:DataAccessor::select()
->table('imobile.post.db_post')
->columns('post_id')
->where(array('thread_id'=>1, 'forum_id'=>2))
->getPage();

DAL Server 2.0设计目标

一是提高性能(连接池,并行查询、合并结果,更好的缓存处理、包括过期策略可配置、缓存提供者可配置等等)。而是提高可用性(多个DAL实例,引入单数据库事务)。三是更智能的Where分析,表的状态监控,DAL更具可扩展性。

DAL的可扩展性方面,总体上设计成(core+plugins)的形式。core负责一些不可插件化(或难以插件化)的组件,plugins则是那些可插件化的组件。如配置处理,查询分析,选路策略,数据处理等等就是属于core的内容。而CacheProvider,ConnectionProvider...就是一些可插件化的例子。

DAL Server 2.0示意图,如图6所示:

扩展DAL概念

访问数据,可以是访问数据库中的数据,也可以是MC/JMCD当中的数据,那为什么不可以是lucene索引中的数据?例如:

5)搜:DataAccessor::search()
->index('imobile.thread.db_thread')
->fields('title')
->key('三星')
->where(array('forum_id'=>2))
->andWhere('date', 'BETWEEN', array(20090305, 20090310))
->getPage();

这样,扩展后的DAL概念,如图7所示:

posted on 2010-11-21 22:54  菊次郎  阅读(1167)  评论(0编辑  收藏  举报