php yaf框架扩展实践五——数据层

2018-03-19 18:34:55 查看 1424 回复 0

从狭义角度上来理解数据层就是数据库,比较广义的理解来看数据库、远程数据、文件等都可以看做数据层。项目初期的时候一般单一的数据库就可以了,随着流量的增大就要对数据层做很多的改进,例如增加从库分散读压力,使用kv缓存增加系统性能,又或者使用分布式服务这样就会涉及到到远程数据调用。这么多东西该怎么整呢?项目好像越来越乱了。

当涉及的东西多了,如果没有良好的项目结构就会导致项目层次越来越乱,很容易出问题。下面就分享一下在yaf中数据层设计经验。分为如下:

  • 数据抽象层DAO
  • 数据库Mysql
  • KV缓存Redis
  • 远程调用Http

yaf扩展数据层

数据抽象层DAO

DAO全称就是Data Access Object,通俗的讲就是数据访问对象。该层提供统一对外的数据访问接口,具体是要调用数据库、http、redis的数据由DAO决定处理。DAO层保存在目录:application/models/DAO

例如我们需要根据用户编号获取用户信息,可以在DAO目录下新建一个User.php文件,其中有一个find方法:

public function find($userId) {
    $mysql = \Mysql\UserModel::getInstance();
    return $mysql->find($userId);
}

这里就是直接从数据库里进行读取。当这个方法调用很频繁,数据库负载上来时,我们考虑使用kv缓存来缓解数据库压力:

$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:

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方法:

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这个库:

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/