php| php 微服务之旅: 配置中心

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 这篇我们来撸配置中心.为啥要用配置中心呢? 我用个讨巧的方式来回答这个问题:

这篇我们来撸配置中心.

为啥要用配置中心呢? 我用个讨巧的方式来回答这个问题:

  • 携程配置中心 apollo
  • 360配置中心 QConf
  • aliyun 应用配置管理 acm

这是调研并在框架层适配过的 3 个配置中心, 在他们的文档里, 你都可以找到使用配置中心的理由, 有些理由(或者说场景), 也许正好直击你的痛点.

也许仅仅只是为了 更改配置不用发版, 也应该尝试一下配置中心试试看.

使用配置中心, 远远没有我们想的那么复杂, 在做选型的时候, 往往会被这个工具或者那个工具新增的一些 定义 绕进去. 那么, 我们从自己使用的框架开始, 先看我们使用配置的需求, 然后再来看配置中心 怎样满足我们的需求.

PHPer 项目中使用的配置

以 PHPer 为例, 项目中通常有 2 类配置:

  • env 环境相关的配置

以下面的 db 配置为例, 不同环境需要使用不同的 db 配置, 从 .env 配置文件中获取

return [
    'db' => [
        'driver' => 'mysql',
        'host' => env('DB_HOST'),
        'database' => 'test',
        'username' => env('DB_USER'),
        'password' => env('DB_PWD'),
    ],
];
  • config 项目中所有配置
$container = ApplicationContext::getContainer();
/** @var ConfigInterface $config */
$config = $container->get(ConfigInterface::class);
$config->get('a.b.c', 'default');

这里有记住 2 个原则(约定大于配置):

  • env 只在配置文件中使用, 所有配置使用的地方, 都使用 config
  • 所有的配置都可以通过 $config 对象来获取, 所有配置都由 $config 来管理
明白了这点以后, 无论什么配置中心, 都只需要增加一个 package 来适配, 最终将配置更新到 $config 中即可

以适配 aliyun 应用配置管理(acm) 为例

我们新增一个 hyperf/config-aliyun-acm 的 package, 用来适配 aliyun 应用配置管理(acm). 这是个免费的服务, 适合用来体验配置中心, 少了一部配置中心服务运维管理的步骤.

总共只有 2 步:

  • 从配置中心获取最新配置
// vendor/hyperf/config-aliyun-acm/src/Client.php
<?php

declare(strict_types=1);

namespace Hyperf\ConfigAliyunAcm;

use Closure;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Guzzle\ClientFactory as GuzzleClientFactory;
use Psr\Container\ContainerInterface;
use RuntimeException;

class Client implements ClientInterface
{
    /**
     * @var array
     */
    public $fetchConfig;

    /**
     * @var Closure
     */
    private $client;

    /**
     * @var ConfigInterface
     */
    private $config;

    /**
     * @var array
     */
    private $servers;

    public function __construct(ContainerInterface $container)
    {
        $this->client = $container->get(GuzzleClientFactory::class)->create();
        $this->config = $container->get(ConfigInterface::class);
    }

    public function pull(): array
    {
        $client = $this->client;
        if (! $client instanceof \GuzzleHttp\Client) {
            throw new RuntimeException('aliyun acm: Invalid http client.');
        }

        // ACM config
        $endpoint = $this->config->get('aliyun_acm.endpoint', 'acm.aliyun.com');
        $namespace = $this->config->get('aliyun_acm.namespace', '');
        $dataId = $this->config->get('aliyun_acm.data_id', '');
        $group = $this->config->get('aliyun_acm.group', 'DEFAULT_GROUP');
        $accessKey = $this->config->get('aliyun_acm.access_key', '');
        $secretKey = $this->config->get('aliyun_acm.secret_key', '');

        // Sign
        $timestamp = round(microtime(true) * 1000);
        $sign = base64_encode(hash_hmac('sha1', "{$namespace}+{$group}+{$timestamp}", $secretKey, true));

        if (! $this->servers) {
            // server list
            $response = $client->get("http://{$endpoint}:8080/diamond-server/diamond");
            if ($response->getStatusCode() !== 200) {
                throw new RuntimeException('Get server list failed from Aliyun ACM.');
            }
            $this->servers = array_filter(explode("\n", $response->getBody()->getContents()));
        }
        $server = $this->servers[array_rand($this->servers)];

        // Get config
        $response = $client->get("http://{$server}:8080/diamond-server/config.co", [
            'headers' => [
                'Spas-AccessKey' => $accessKey,
                'timeStamp' => $timestamp,
                'Spas-Signature' => $sign,
            ],
            'query' => [
                'tenant' => $namespace,
                'dataId' => $dataId,
                'group' => $group,
            ],
        ]);
        if ($response->getStatusCode() !== 200) {
            throw new RuntimeException('Get config failed from Aliyun ACM.');
        }
        return json_decode($response->getBody()->getContents(), true);
    }
}
  • 更新到 $config 对象中
// vendor/hyperf/config-aliyun-acm/src/Process/ConfigFetcherProcess.php
<?php

declare(strict_types=1);

namespace Hyperf\ConfigAliyunAcm\Process;

use Hyperf\ConfigAliyunAcm\ClientInterface;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Process\AbstractProcess;
use Hyperf\Process\Annotation\Process;
use Psr\Container\ContainerInterface;
use Swoole\Server;

/**
 * @Process(name="aliyun-acm-config-fetcher")
 */
class ConfigFetcherProcess extends AbstractProcess
{
    /**
     * @var Server
     */
    private $server;

    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var ConfigInterface
     */
    private $config;

    /**
     * @var string
     */
    private $cacheConfig;

    public function __construct(ContainerInterface $container)
    {
        parent::__construct($container);
        $this->client = $container->get(ClientInterface::class);
        $this->config = $container->get(ConfigInterface::class);
    }

    public function bind(Server $server): void
    {
        $this->server = $server;
        parent::bind($server);
    }

    public function isEnable(): bool
    {
        return $this->config->get('aliyun_acm.enable', false);
    }

    public function handle(): void
    {
        while (true) {
            $config = $this->client->pull();
            if ($config !== $this->cacheConfig) {
                if ($this->cacheConfig !== null) {
                    $diff = array_diff($this->cacheConfig ?? [], $config);
                } else {
                    $diff = $config;
                }
                $this->cacheConfig = $config;
                $workerCount = $this->server->setting['worker_num'] + $this->server->setting['task_worker_num'] - 1;
                // 通过进程间通信, 投递配置信息到每一个启动的进程
                for ($workerId = 0; $workerId <= $workerCount; ++$workerId) {
                    $this->server->sendMessage($diff, $workerId);
                }
            }
            sleep($this->config->get('aliyun_acm.interval', 5));
        }
    }
}

以 apollo 为例

代码适配其实和上面类似, 也是通过调用 apollo 提供的 api 去获取最新的配置, 然后进行更新, 不同的是, 你得部署一套 apollo 配置中心的环境. 以本地开发测试为例:

version: '3.1'
services:
    apollo: # https://github.com/ctripcorp/apollo/tree/master/scripts/docker-quick-start
        image: nobodyiam/apollo-quick-start
        ports:
            - "8080:8080"
            - "8070:8070"
        links:
            - mysql:apollo-db
        tty: true
    mysql:
        image: mysql:5.7.26
        restart: always
        volumes:
            - ./config/my.cnf:/etc/mysql/conf.d/my.cnf
            - ./config/sql:/docker-entrypoint-initdb.d
            - ./data/mysql:/var/lib/mysql
        ports:
            - "3306:3306"
        environment:
            TZ: Asia/Shanghai
            MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

这只是本地测试, 所以只启动了一个配置中心, 如果是线上环境, 要做集群化处理, 配置中心这样重要的服务, 要确保高可用.

写在最后

QConf 的理念和部署又是另一个样子了, 使用了 zookeeper 来确保服务的可用.

但是无论是使用 apollo QConf 还是 acm, 在框架层其实只需要使用 composer require 增加相应的适配包即可, 项目中的代码完全不用修改.

配置中心可以改进的部分:

  • 目前使用 http 轮询的方式, 部分配置中心提供了长连接, 可以进行适配
  • 将配置中心注册到 服务注册发现 服务中, 统一从 服务注册发现服务中获取服务信息
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
存储 消息中间件 缓存
微服务配置中心功能实现
【2月更文挑战第9天】配置中心的思路就是把服务的各种配置,如代码里配置的各种参数、服务降级的开关甚至依赖的资源等都在一个地方统一进行管理。
|
3月前
|
关系型数据库 MySQL API
|
3月前
|
Prometheus Cloud Native Java
微服务框架(二十三)Prometheus + Grafana 安装、配置及使用
此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。 本文为Prometheus + Grafana 安装、配置及使用 本系列文章中所使用的框架版本为Spring ...
|
5月前
|
PHP
windows11 vscode 配置 php环境
windows11 vscode 配置 php环境
43 0
|
6月前
|
PHP Apache
PHP环境搭建(配置php模块到apache服务器)
PHP环境搭建(配置php模块到apache服务器)
64 0
|
5月前
|
Java 开发工具 Maven
微服务技术系列教程(24) - SpringCloud- 分布式配置中心
微服务技术系列教程(24) - SpringCloud- 分布式配置中心
44 0
|
15天前
|
SpringCloudAlibaba Java Nacos
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
|
25天前
|
应用服务中间件 Linux PHP
Linux下安装php环境并且配置Nginx支持php-fpm模块
Linux下安装php环境并且配置Nginx支持php-fpm模块
22 0
|
1月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
782 0
|
1月前
|
微服务
SpringCloud-Config服务端微服务从自己的Gitee上获取配置内容配置读取规则
SpringCloud-Config服务端微服务从自己的Gitee上获取配置内容配置读取规则
17 0