Experience by itself teaches nothing… Without theory, experience has no meaning. Without theory, one has no questions to ask. Hence, without theory, there is no learning. – Edwards Deming

1.16.1 配置的简单读取

通常,我们会有以下三个配置文件:

  1. dogstar@ubuntu:Config$ tree
  2. .
  3. ├── app.php
  4. ├── dbs.php
  5. └── sys.php

其中app.php为项目应用配置;dbs.php为分布式存储的数据库配置;sys.php为不同环境下的系统配置。

在入口文件,注册配置组件服务后:

  1. //$ vim ./Public/init.php
  2. //配置
  3. DI()->config = new PhalApi_Config_File(API_ROOT . '/Config');

假设有这样的app.php配置:

  1. return array(
  2. 'version' => '1.1.1',
  3. 'email' => array(
  4. 'address' => 'chanzonghuang@gmail.com',
  5. );
  6. );

我们就可以分别这样根据需要获取配置:

  1. //app.php里面的全部配置
  2. DI()->config->get('app');
  3. //app.php里面的单个配置
  4. DI()->config->get('app.version'); //返回:1.1.1
  5. //app.php里面的多级配置
  6. DI()->config->get('app.email.address'); //返回:'chanzonghuang@gmail.com'

其他配置文件类似,你也可以随意添加新的配置文件。

1.16.2 内外环境配置管理的策略

可以说,在项目开发中,除了我们的代码、数据和文档外,配置也是一块相当重要的组成部分,而且占据着非常重要的位置。最为明显的是,如果配置一旦出错,就很可能影响到整个系统的运行,并且远比修改代码再上线发布速度快(假设我们不用文件存储配置而是以分布式存储在各服务器内存中)。

但这里将讨论另外一个同样重要的问题,即: 不同环境下不同配置的管理和切换

现在将不同的策略分说如下。

(1)支持整包发布的环境变量配置

此种策略的主要方法就是在PHP代码中读取php-fpm中的ENV环境配置,再对应到linux环境下的profile环境变量,即:

  1. PHP代码 --> $_ENV环境配置 --> Linux服务器环境变量/etc/profile

例如:

  1. <?php
  2. return array(
  3. /**
  4. * DB数据库服务器集群
  5. */
  6. 'servers' => array(
  7. 'db_demo' => array( //服务器标记
  8. 'host' => $_ENV['PHALPI_DB_HOST'], //数据库域名
  9. 'name' => $_ENV['PHALPI_DB_NAME'], //数据库名字
  10. 'user' => $_ENV['PHALPI_DB_USER'], //数据库用户名
  11. 'password' => $_ENV['PHALPI_DB_PASSWORD'], //数据库密码
  12. 'port' => $_ENV['PHALPI_DB_PORT'], //数据库端口
  13. 'charset' => $_ENV['PHALPI_DB_CHARSET'], //数据库字符集
  14. ),
  15. ),
  16. );

接着,在php-fpm.ini文件中,添加相应的配置:

  1. env[PHALPI_DB_HOST]='localhost'
  2. env[PHALPI_DB_NAME]='phalapi'
  3. env[PHALPI_DB_USER]='root'
  4. env[PHALPI_DB_PASSWORD]=''
  5. env[PHALPI_DB_PORT]='3306'
  6. env[PHALPI_DB_CHARSET]='UTF8'

最后,重启php-fpm即可。

这样的好处莫过于可以支持项目代码的整包发布,而不需要在各个环境(开发环境、测试环境、回归环境、预发布环境、生产环境)来回修改切换配置,同时运维可以更好地保护服务器的账号和密码而不让开发知道。

而这样的不足则是,在对项目进行初次部署时,需要添加以上一系列的配置,而且后期维护也比较复杂麻烦,特别当机器多时。这时可以通过pupple/stacksalt这些运维工具进行自动化管理。但对于开发来说,依然会觉得有点烦锁。

(2)不同环境,不同入口

当服务器的账号和密码也是由开发来掌控时,则可以使用这种在代码层次控制的策略。

我们可以在Public下提供不同的入口,一如:

  1. $ mkdir ./Public/myapi
  2. $ myapi$ tree
  3. .
  4. ├── index.php
  5. └── test.php

我们有这样不同的入口,客户端在测试时,只需要将入口路径改成:/myapi/test.php?service=XXX.XXX,而在打包发布时只需要将入口路径改成:/myapi/?service=XXX.XXX 即可。也就是将test.php去掉。

而在服务端,仅需要在这些不同的入口文件,修改一下配置文件目录路径即可:

  1. //$ vim ./Public/myapi/test.php
  2. DI()->config = new PhalApi_Config_File(API_ROOT . '/ConfigTest');

另外,也可以通过客户端在请求时带参数来作区分,如带上&env=test或者&env=prod。

(3)持续集成下的配置管理

但个人最为建议的还是在 持续集成 下作配置管理。

因为首先,持续集成中的发布应该是经常性的,且应该是自动化的。所以,既然有自动化的支持,我们也应该及早地将配置纳入其中管理。

配置文件不同只要是环境不同,而环境不同所影响的配置文件通常只有sys.php和dbs.php;为此,我们为测试环境和生产环境准备了各自的配置文件,而在发布时自动选择所需要的配置文件。一般地,我们建议生产环境的配置文件以 .prod 结尾。所以,这时我们的配置文件可能会是这样:

  1. dogstar@ubuntu:Config$ tree
  2. .
  3. ├── app.php
  4. ├── dbs.php
  5. ├── dbs.php.prod
  6. ├── sys.php
  7. └── sys.php.prod

多了生产环境的配置文件:dbs.php.prod和sys.php.prod。

再通过发布工具,我们就可以对不同环境的配置文件进行快速选择了。这里以phing为例,说明一下相关的配置和效果。

如下,在Phing的配置文件build.xml中,在生产环境发布过程中,我们将配置文件进行了替换。

  1. //$ vim ./build.xml
  2. <copy
  3. file="./Config/dbs.php.prod"
  4. tofile="./Config/dbs.php"
  5. overwrite="true" />
  6. <copy
  7. file="./Config/sys.php.prod"
  8. tofile="./Config/sys.php"
  9. overwrite="true" />

执行phing发布后,将会看到对应的这样提示:

  1. [copy] Copying 1 file to /home/phapapi/Config
  2. [copy] Copying 1 file to /home/phapapi/Config

(4) 更灵活的配置覆盖管理方式

还有一种相当于.env形式的配置管理方式。即添加一个dbs.php.prod文件,但不需要复制全部的配置,只需要配置待覆盖的配置即可。例如,不同环境下,有不同的数据库配置。可以:

  1. // $ vim ./Config/dbs.php.prod
  2. <?php
  3. return array(
  4. /**
  5. * DB数据库服务器集群
  6. */
  7. 'servers' => array(
  8. 'db_demo' => array( //服务器标记
  9. 'host' => '192.168.1.101', //数据库域名(仅数据库HOST、用户名、密码不同)
  10. 'user' => 'apps', //数据库用户名
  11. 'password' => '123456', //数据库密码
  12. ),
  13. ),
  14. );

然后,在dbs.php文件添加上面配置并覆盖原来的即可,如:

  1. <?php
  2. $rs = array(
  3. // 原来的配置...
  4. );
  5. if (file_exists(dirname(__FILE__) . '/dbs.php.prod')) {
  6. $rs = array_replace_recursive($rs, include(dirname(__FILE__) . '/dbs.php.prod')));
  7. }
  8. return $rs;

这样,就可以通过新增一个dbs.php.prod文件,并且此文件是不纳入代码版本管理了,便可实现部署时的配置管理。

1.16.3 主从数据库的配置

通常一般而言,数据库支持分表分库配置,且只有一份,即:

  1. ./Config/dbs.php

然后,我们这样指定数据库的配置:

  1. DI()->notorm = function() {
  2. $debug = !empty($_GET['__sql__']) ? true : false;
  3. return new PhalApi_DB_NotORM(DI()->config->get('dbs'), $debug);
  4. };

如果需要从库时,可以参考./Config/dbs.php,创建一个从库的配置拷贝,即:

  1. cp ./Config/dbs.php ./Config/dbs_slave.php

然后,注册一个从库的notorm:

  1. DI()->notormSlave = function() {
  2. $debug = !empty($_GET['__sql__']) ? true : false;
  3. return new PhalApi_DB_NotORM(DI()->config->get('dbs_slave'), $debug); //注意:配置不同
  4. };

最后使用此从库的服务DI()->notormSlave即可完成对从库的读取操作,用法同DI()->notorm,这里不再赘述。

1.16.4 扩展你的配置读取

虽然上面有不同的配置文件管理策略,但很多实际情况下,我们的配置需要可以随时更改、下发和调整。并且在海量访问,性能要求高的情况下快速读取配置。

这就要求我们的项目既可以方便维护即时修改,又需要能够快速同步一致更新下发和读取。这样就涉及到了配置更高层的管理: 统一集中式的配置管理,还是分布式的配置管理? 文件存储,还是DB存储,还是MC缓存,还是进驻内存?

个人认为,性能最优的莫过于配置进驻内存并分布式存储。毕竟作为前端机的PHP内存这块还是相对充裕且可以利用的。但需要把这一条线打通,有点难度。

这里不过多地谈论配置更多的内容,但在PhalApi框架中,当你根据自己项目的需要实现了自己的配置读取方式后,再次简单的在入口文件重新注册即可。

1.16.5 使用Yaconf扩展快速读取配置

首先,非常感谢 @豹 同学的提议:使用Yaconf扩展快速读取配置。

虽然现在是冬天,有点冷(特别是在北京的同学),而且现在还是凌晨1点25分。但我们希望能继续将开源的热情继续下去。

以下是刚刚这几个小时努力的成果。

(1)前期准备:PHP7 的安装

具体可以参考:Linux环境PHP7.0安装

(2)前期准备:Yaconf扩展的安装

具体可以参考:laruence/yaconf

(3)使用

和正常的配置一样,简单如下:

  1. DI()->config = new PhalApi_Config_Yaconf();
  2. var_dump(DI()->config->get('foo')); //相当于var_dump(Yaconf::get("foo"));
  3. var_dump(DI()->config->has('foo')); //相当于var_dump(Yaconf::has("foo"));

以上,假设我们已经有了这样的配置文件:

  1. $ vim ./test.ini
  2. name="PhalApi"
  3. version="1.3.1"

需要注意的是,如果需要使用Yaconf扩展,需要PHP7的版本支持,但使用基本上和原来PhalApi的思路是完全一样的。但还有所区别的是,配置文件的目录路径(当然也可以配置成和PhalApi的配置目录一致)、以及配置文件的格式。

Just have fun!

原文: https://www.phalapi.net/wikis/1-16.html