配置系统的思考

配置系统在整个软件的生命周期里,有着很重要的角色。例如:

在新公司上班,第一天需要配置开发环境。。。

多环境部署,开发、测试、预发布、生产不同环境的配置。。。

项目的数据库,缓存,负载均衡,Nginx各种服务的配置。。。

做这么多年的开发,说来好笑,一直在跟配置系统打交道,但却没有花时间思考配置系统存在的意义。那么如何在目前Linux+Nginx+MySQL+PHP的构架下,尝试合适自己的配置系统呢?

常见的配置系统

既然不知道什么样的配置系统适合自己,那么我们可以看看别人家,是如何使用配置系统的。

Phalcon

Phalcon框架是我使用时间较长的开发框架,先看看它内部的配置如何实现。

<?php

use Phalcon\Config;

$config = new Config(
    [
        'test' => [
            'parent' => [
                'property'  => 1,
                'property2' => 'yeah',
            ],
        ],
    ]
);

echo $config->get('test')->get('parent')->get('property');  // displays 1
echo $config->test->parent->property;                       // displays 1
echo $config->path('test.parent.property');                 // displays 1

#甚至你可以注入配置到di
use Phalcon\Di\FactoryDefault;
use Phalcon\Config;

// Create a DI
$di = new FactoryDefault();

$di->set(
    'config',
    function () {
        $configData = require 'config/config.php';

        return new Config($configData);
    }
);

实现的原理是根据传入的数组转换Phlacon\Config对象的属性。支持不同类型配置器,例如ini、Json、Php、yaml等,你可以理解为Phlacon\Config是一个工厂模式,生产不同的类型的配置实例。

Magento

这是一个流行的开源电子商务框架。在1.x的框架版本都是采用xml去实现的。那时候发现xml做配置,非常“灵活”。注意,我这里用一个非常的形容词,这意味调试开发成本会比较高。那时候我的时间大部分都花费在xml文件里查找对应的配置。幸好,官方终于在2.x版本优化过来了,终于可以解脱了。

The Magento 2 deployment configuration replaces local.xml in Magento 1.x.

config.php 代码实例:

return array (
  'modules' =>
  array (
    'Magento_Core' => 1,
    'Magento_Store' => 1,
    'Magento_Theme' => 1,
    'Magento_Authorization' => 1,
    'Magento_Directory' => 1,
    'Magento_Backend' => 1,
    'Magento_Backup' => 1,
    'Magento_Eav' => 1,
    'Magento_Customer' => 1,
...
  ),
);

json

JSON文件格式是一个伟大的发明。简单的key和value的形式,就解决大部分配置文件,主要应用在不需要复杂逻辑配置,比如Composer、NPM等。

# composer.json
{
    "config": {
        "preferred-install": {
            "my-organization/stable-package": "dist",
            "my-organization/*": "source",
            "partner-organization/*": "auto",
            "*": "dist"
        }
    }
}
# package.josn

{ "name": "ethopia-waza",
  "description": "a delightfully fruity coffee varietal",
  "version": "1.2.3",
  "devDependencies": {
    "coffee-script": "~1.6.3"
  },
  "scripts": {
    "prepare": "coffee -o lib/ -c src/waza.coffee"
  },
  "main": "lib/waza.js"
}

YAML

在GitLab中CI构建配置文件是采用YML。原因是在构建的过程需要存储复杂业务流程,整体逻辑较多,YML适合应用于这类场景。

# see https://gitlab.com/help/ci/yaml/README.md#jobs
# Select image
image: geekwho/code:php
stages:
  - phplint
  - phpfixer
  - build
  - test
  - deploy

# php lint
phplint:
  stage: phplint
  script:
  - bash bin/phplint.sh
  only:
  - master
  - prelease
  - dev

# php code style
phpfixer:
  stage: phpfixer
  script:
  - bash bin/phpfixer.sh
  only:
  - master
  - prelease
  - dev

# unit test
test:
  stage: test
  script:
  - bash bin/test.sh
  only:
  - master
  - prelease
  - dev
  allow_failure: true

PHP数组

最简单的配置系统可以直接采用PHP自带的数组。数组的实现本身也是hash table 。

<?php
// 直接返回数组元素
return [
    'database' => [
      'adapter'     => 'Mysql',
      'host'        => 'localhost',
      'username'    => 'root',
      'password'    => '',
      'dbname'      => '@@name@@',
      'charset'     => 'utf8',
    ],
    'version' => '1.0',

    /**
     * if true, then we print a new line at the end of each execution
     *
     * If we dont print a new line,
     * then the next command prompt will be placed directly on the left of the output
     * and it is less readable.
     *
     * You can disable this behaviour if the output of your application needs to don't have a new line at end
     */
    'printNewLine' => true,
];
// 可以在配置里做一些逻辑判断
<?php

$dsn = 'mysql:host=localhost;dbname=yii2basic';
return [
    'class' => 'yii\db\Connection',
    'dsn' => $dsn,
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
];
// 甚至还可以使用静态变量
<?php

class Config {
    public static $db = 'mysql';
    public static $user = 'root';
    public static $passwd = '';
    public static $host = 'locahost';

}

Bash

甚至有时候在Bash环境也需要添加部分文件,我是这样做的。

# @Author: geekwho
# @Date:   2018-09-18 11:11:11
# @Last Modified by:   geekwho
# @Last Modified time: 2018-09-17 11:11:11
# 配置文件
project="wiki"

domain=".xbc.me"

user="geekwho"

host="your host"

port="your port"

dest=""

# 然后在你需要使用变量的地方进行加载
source ./.config.sh

环境变量

很多时候我们需要判断的环境变量,比如dev、stg、pre、pro等不同环境。

项目目录

这类方式就是直接在当前项目下建一个隐藏文件,类似 .env.dev 代表当前的环境。不好的地方,因为在当前项目下,容易被误修改或删除,导致环境判断异常。

php.ini

直接在php.ini添加自定义的环境变量,例如env=dev。那么在bash环境下,还需要单独去做一次环境变量的解析。

/etc

直接在系统文件下建立一个环境变量的文件。例如/etc/.env.dev。这类操作需要运维配置设置对应环境的变量文件。

两者结合

如何把环境变量和配置系统结合起来呢?

根据环境变量独立配置文件

推荐的配置目录结构如下:

app
    config
        dev
            config.php
        test
            config.php
        pre
            config.php
        prd
            config.php
// 实例
<?php

class Config {
    public static $db = 'mysql';
    public static $user = 'root';
    public static $passwd = '';
    public static $host = 'locahost';

}

All in ONE

全部环境变量全部都写同一个文件里。

#app/config/db.php
<?php

class Config {
    public static $db = [
      'dev' => 'mysql',
      'test' => 'mysql',
      'pre' => 'mysql',
      'prd' => 'mysql',
    ];
    public static $user_dev = [
      'dev' => 'root',
      'test' => 'root',
      'pre' => 'root',
      'prd' => 'root',
    ];
    public static $passwd = [
      'dev' => '',
      'test' => '',
      'pre' => '',
      'prd' => '',
    ]
    public static $hos = [
      'dev' => 'localhost',
      'test' => 'localhost',
      'pre' => 'localhost',
      'prd' => 'localhost',
    ]

}

参考链接

  1. phalcon config
  2. magento config
  3. composer config
  4. npm config
  5. GitLabv CI YAML
  6. PHP Array
  7. LaraDock

堆排序

具体实现

GitHub版本库的堆排序。

算法实现

实现

该算法按照二叉树的原理。
完全二叉树:深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中,序号为1至n的节点对应时,称之为完全二叉树

 原数组 52,85,6,3,57,27,77,37,96,52
        0   1 2 3 4  5  6  7  8  9
              52
            /   \
           /     \
          85      6
         / \     / \
        /   \   /   \
        3   57 27   77
       / \  /
      6  3 25
 实现堆的排序,在起始数组为0的情况下:
 1. 数组下标i的左子节点在位置  (2*i+1)
 2. 数组下标i的右节点在位置    (2*i+2)
 3. 数组下标i的父节点在位置    floor(i-1)/2

 最大堆
               96
             /    \
            /      \
           85      77
          / \      / \
         /   \    /   \
         57  52  37   27
        / \  /
       25 6  3

 最小堆
               3
             /    \
            /      \
           6       25
          / \      / \
         /   \    /   \
        27   37  52   57
       / \   /
      77 85 96

难点

  1. 数组元素个数为n,则从最后一个父元素(最后一个非叶子节点)开始访问,即$i = floor($n/2)-1
  2. 从根元素开始访问,再访问左节点,再到右结点,称为先序遍历。
  3. 最大的堆,完成排序之后,是从小到大的数组。因为建立最大堆,是最大的在堆顶,但是第二次循环之后,
  4. 最大的被置换到数组最后一个数组。
  5. 最小堆,完成排序的数组是从大到小的数组。

耗时分析

机器配置 ThinkPad T440s CPU i5-4300U Memory 8GB
maxheap num 1000 sort cost time is 0.011452198028564 s
minheap num 1000 sort cost time is 0.0060770511627197 s
maxheap num 10000 sort cost time is 0.092055082321167 s
minheap num 10000 sort cost time is 0.090849161148071 s
maxheap num 100000 sort cost time is 1.2427570819855 s
minheap num 100000 sort cost time is 1.1724309921265 s

算法分析

最坏时间复杂度 О(nlog n)
最优时间复杂度 О(nlog n)
平均时间复杂度 О(nlog n)
空间复杂度   О(n)

参考链接

  1. 堆排序
  2. 堆排序(Heap Sort)算法学习

归并排序

具体实现

GitHub版本库的归并排序。

算法实现

采用2层循环遍历,实现两个已排序的数组合并为一个排序数组。
1. 第一层循环,从一个元素开始到最后一个元素为止。
2. 选择每次循环的第一个元素为基准。
3. 第二层循环,以第一层循环的第二个数开始,到未排序的数组末尾。
4. 与第二个元素比较,遇到比第一个元素小的数,交换位置。

耗时分析

机器配置 ThinkPad T440s CPU i5-4300U Memory 8GB
num 1000 sort cost time is 0.0038759708404541 s
num 10000 sort cost time is 0.28233289718628 s
num 100000 sort cost time is 36.840610027313 s

算法分析

最坏时间复杂度 О(nlog n)
最优时间复杂度 О(nlog n)
平均时间复杂度 О(nlog n)
空间复杂度   О(n)

参考链接

  1. 归并排序

选择排序

具体实现

GitHub版本库的选择排序。

算法实现

采用2层循环遍历实现。
1. 第一层循环,从一个元素开始到最后一个元素为止。
2. 选择每次循环的第一个元素为基准。
3. 第二层循环,以第一层循环的第二个数开始,到未排序的数组末尾。
4. 与第二个元素比较,遇到比第一个元素小的数,交换位置。

耗时分析

机器配置 ThinkPad T440s CPU i5-4300U Memory 8GB
num 1000 sort cost time is 0.012492895126343 s
num 10000 sort cost time is 1.2727150917053 s
num 100000 sort cost time is 146.14201211929 s

算法分析

最坏时间复杂度 О(n²)
最优时间复杂度 О(n²)
平均时间复杂度 О(n²)
空间复杂度   О(n) total, O(1) auxiliary

参考链接

  1. 选择排序

快速排序

具体实现

GitHub版本库的选择排序。

算法实现

采用2层循环遍历实现。
1. 第一层循环,从一个元素开始到最后一个元素为止。
2. 选择每次循环的第一个元素为基准。
3. 第二层循环,以第一层循环的第二个数开始,到未排序的数组末尾。
4. 与第二个元素比较,遇到比第一个元素小的数,交换位置。

耗时分析

机器配置 ThinkPad T440s CPU i5-4300U Memory 8GB
num 1000 sort cost time is 0.012492895126343 s
num 10000 sort cost time is 1.2727150917053 s
num 100000 sort cost time is 146.14201211929 s

算法分析

最坏时间复杂度 О(n²)
最优时间复杂度 О(n²)
平均时间复杂度 О(n²)
空间复杂度   О(n) total, O(1) auxiliary

参考链接

  1. 选择排序