php yaf框架扩展实践五——数据层
从狭义角度上来理解数据层就是数据库,比较广义的理解来看数据库、远程数据、文件等都可以看做数据层。项目初期的时候一般单一的数据库就可以了,随着流量的增大就要对数据层做很多的改进,例如增加从库分散读压力,使用kv缓存增加系统性能,又或者使用分布式服务这样就会涉及到到远程数据调用。这么多东西该怎么整呢?项目好像越来越乱了。
当涉及的东西多了,如果没有良好的项目结构就会导致项目层次越来越乱,很容易出问题。下面就分享一下在yaf中数据层设计经验。分为如下:
- 数据抽象层DAO
- 数据库Mysql
- KV缓存Redis
- 远程调用Http
数据抽象层DAO
DAO全称就是Data Access Object,通俗的讲就是数据访问对象。该层提供统一对外的数据访问接口,具体是要调用数据库、http、redis的数据由DAO决定处理。DAO层保存在目录:application/models/DAO
例如我们需要根据用户编号获取用户信息,可以在DAO目录下新建一个User.php文件,其中有一个find方法:
1
2
3
4
|
public function find( $userId ) { $mysql = \Mysql\UserModel::getInstance(); return $mysql ->find( $userId ); } |
这里就是直接从数据库里进行读取。当这个方法调用很频繁,数据库负载上来时,我们考虑使用kv缓存来缓解数据库压力:
1
2
3
4
5
6
7
8
9
10
|
$redis = \Redis\Db0\UserModel::getInstance(); $user = $redis ->find( $userId ); if (! $user ) { $mysql = \Mysql\UserModel::getInstance(); $user = $mysql ->find( $userId ); if ( $user ) { $redis ->update( $userId , $user ); } } return $user ; |
这里先从redis内读取用户信息,如果没有则从mysql里读取。因为使用了redis缓存,如果数据有更新的时候需要同步更新缓存里的内容,这个在DAO层内就很容易做到了。
此外在DAO层的抽象方法里实现了一个方法,当访问的方法不存在的时候会自动调用数据库mysql对应的同名方法,这个在项目前期可以大大提高开发效率,缺点就是当用ide调用的时候没有代码提示。
数据库Mysql
yaf没有提供数据库操作的封装,这里使用了zend framework2的db类库进行数据库的操作。zend framework虽然整体性能慢,但是类库非常齐全、封装的也非常好,这里直接使用zend framework的db库进行操作避免重复造车轮子。mysql的保存目录:application/models/Mysql
创建数据表操作类
mysql层有一个抽象类,定义了表名和表主键属性,文件建议以表名进行命名而后继承mysql的抽象类,在类中指明相应的表和主键就可以了。如用户表文件User.php:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class UserModel extends \Mysql\AbstractModel { /** * 表名 * * @var string */ protected $_tableName = 'user' ; /** * 主键 * * @var string */ protected $_primaryKey = 'user_id' ; .... } |
在mysql的抽象类里封装了find、fetchAll、insert、update、remove增删改查的方法,可以直接使用User的类实例进行调用,就像上面讲DAO时直接调用find方法。这些方法基本可以满足80%-90%的数据查询需求,倘使需要更加复杂的查询,可以调用父类的_getDbSelect方法获取\Zend\Db\Sql\Select对象自定义查询,关于\Zend\Db\Sql\Select可以参考zend framework手册。
使用从库
如果需要使用从库来进行均衡读负载,可以将从库的操作在mysql/Slave目录,Slave目录下有一个单独的抽象类,因为从库一般用来读,所以这里只封装了find、fetchAll方法。在需要定位到从库时的地方重写相应的find、fetchAll方法就可以了。例如读取用户列表数据可以考虑从从库读取,重写\Mysql\UserModel类的fetchAll方法:
1
2
3
4
|
public function fetchAll( $columns = null, $where = null, $order = null, $count = null, $offset = null, $group = null) { $slave = \Mysql\Slave\UserModel::getInstance(); return $slave ->fetchAll( $columns , $where , $order , $count , $offset , $group ); } |
Tips:使用从库进行数据读取需要注意数据实时性的问题,虽然从库的数据同步一般都在毫秒级,但是在程序操作中可能出现主库已经插入数据,但是从库读取不到的问题。建议数据实时性要求不高的才从库进行读取。
多个库
有时一个项目涉及到要连接多个数据库,这时候可以考虑在mysql目录下新建目录,目录名使用数据库名进行命名。每个库内的抽象类文件可以考虑继承通用的抽象文件,重写其中的_getAdapter就可以了。
KV缓存Redis
Redis存放在:application/models/Redis目录。同mysql一样,这里也有一个抽象类文件,相应的文件继承该文件就可以了。下面讲下和mysql的不同点。
redis多库
一个redis实例总共有16个库,从db0~db15,在项目中以Db0、Db1这样的目录进行区分,每个db下的抽象类继承默认抽象类,重写$_db属性为相应的库就可以了。例如Db0这个库:
1
2
3
4
5
6
7
8
9
10
|
class AbstractModel extends \Redis\AbstractModel { /** * 连接的库 * * @var int */ protected $_db = 0; .... } |
在开发中不建议将所有的缓存都放在一个库下,有的时候我们需要使用keys命令来查找到相应的key,并进行数据更新。如果该库下缓存太多会导致性能很低。建议按照数据的重要性和时效性进行分布存储。
缓存设计思想
kv缓存并没有表的概念,但是为了更好的对应用的整个缓存系统进行操作,这里抽象出表的概念。这里的表相当于key中的一个前缀,通过和id组合成实际的key可以很好的避免key值出现重复。例如上面DAO层从redis读取用户数据,抽象出了一个用户表。
Tips:表名和id是通过一个分隔符号组成,这里是使用横线”-”进行拼接,可以根据实际需要进行修改,只需要保证最后组合成的key是唯一的就OK了。
远程调用Http
项目越来越多、访问量也越来越大,这时候我们考虑将通用的服务提取出来,部署到另外的服务器上。这时候就需要通过网络进行远程调用了,有一个专业术语叫RPC(Remote Procedure Call Protocol)。这里我们使用的http协议,方便在各个语言之间进行通讯, 当然也可以使用一些rpc框架来实现。
Http存放在:application/models/Http目录。抽象类封装了request请求方法。建议以不同的应用进行目录划分,例如我们将用户中心分离出来之后,用户中心的数据可以通过http进行调用。可以在http目录下新建User目录,User目录下的抽象类继承默认抽象类,重新定义host就可以了。
一般来讲一个应用的接口都有统一的数据格式定义,在实际的开发中通常会在User的抽象类中重写父类的request方法,并统一处理相应的数据格式。
小结
这里只列举了mysql、redis等库,类似文件存储、mongodb、memcache、sql server等都可以按照这样的思想进行处理。项目到后期基本上瓶颈都会在数据层上,只要在数据层方面处理妥当了,便能够很好的实施扩展以应对一些高并发场景的情况。
http://www.01happy.com/php-yaf-ext-data/