如何使用 PHP 实现微服务

Swoft

为何要说服务治理

随着互联网浏览愈来愈大. 传统的 MVC 单一架构随着应用规模的不断扩大,应用模块不断增长,整个应用也显得愈来愈臃肿,维护起来也更加困难.php

咱们必须采起措施,按应用拆分,就是把原来的应用按照业务特色拆分红多个应用。好比一个大型电商系统可能包含用户系统、商品系统、订单系统、评价系统等等,咱们能够把他们独立出来造成一个个单独的应用。多应用架构的特色是应用之间各自独立 ,不相互调用。html

多应用虽然解决了应用臃肿问题,但应用之间相互独立,有些共同的业务或代码没法复用。前端

单一应用的解决方案

对于一个大型的互联网系统,通常会包含多个应用,并且应用之间每每还存在共同的业务,而且应用之间还存在调用关系。除此以外 ,对于大型的互联网系统还有一些其它的挑战,好比如何应对急剧增加的用户,如何管理好研发团队快速迭代产品研发,如何保持产品升级更加稳定等等 。git

所以,为了使业务获得很好的复用,模块更加容易拓展和维护,咱们但愿业务与应用分离,某个业务再也不属于一个应用,而是做为一个独立的服务单独进行维护。应用自己再也不是一个臃肿的模块堆积,而是由一个个模块化的服务组件组合而成。github

服务化

特色

那么采用服务化给有那些亮点的特点呢 ?算法

  • 应用按业务拆分红服务
  • 各个服务都可独立部署
  • 服务可被多个应用共享
  • 服务之间能够通讯
  • 架构上系统更加清晰
  • 核心模块稳定,以服务组件为单位进行升级,避免了频繁发布带来的风险
  • 开发管理方便
  • 单独团队维护、工做分明,职责清晰
  • 业务复用、代码复用
  • 很是容易拓展

服务化面临的挑战

系统服务化以后, 增长了依赖关系复杂, 也会增长服务与服务之间交互的次数. 在 fpm 的开发模式下. 由于没法常驻内存给咱们带来了, 每一次请求都要从零开始加载到退出进程, 增长了不少无用的开销, 数据库链接没法复用也得不到保护, 因为fpm是以进程为单位的fpm的进程数也决定了并发数, 这也是是fpm开发简单给咱们带来的问题. 因此说为何如今互联网平台Java比较流行了,.NETPHP在这方面都不行。PHP非内存常驻的就不用说了。除此以外,还有不少其余问题须要解决。数据库

  • 服务愈来愈多,配置管理复杂
  • 服务间依赖关系复杂
  • 服务之间的负载均衡
  • 服务的拓展
  • 服务监控
  • 服务降级
  • 服务鉴权
  • 服务上线与下线
  • 服务文档 ......

你能够想象一下常驻内存给咱们带来的好处 好比express

  • 只启动框架初始化 若是常驻内存咱们只是在启动的时候处理化框架初始化在内存中,专心处理请求后端

  • 链接复用,有些工程师并不能特别理解,若是不用链接池,来一个请求就发一个链接怎么样?这样就会致使后端资源链接过多。对一些基础服务来讲,好比 Redis,数据库,链接是个昂贵的消耗。安全

那么有没有好的方案呢?答案是有的,并且不少人都在用这个框架,它就是-SwoftSwoft就是一个带有服务治理功能的RPC框架。Swoft是首个 PHP常驻内存协程全栈框架, 基于 Spring Boot提出的约定大于配置的核心理念

Swoft 提供了相似 Dubbo 更为优雅的方式使用 RPC 服务, Swoft 性能是很是棒的有着相似Golang性能, 下面是个人PCSwoft 性能的压测状况.

Swoft

ab压力测试处理速度十分惊人, 在 i78代CPU, 16GB 内存100000万个请求只用了5s时间在fpm开发模式下基本不可能达到. 这也足以证实Swoft` 的高性能和稳定性,

优雅的服务治理

服务注册与发现

微服务治理过程当中,常常会涉及注册启动的服务到第三方集群,好比 consul / etcd 等等,本章以 Swoft 框架中使用 swoft-consul 组件,实现服务注册与发现为例。

Swoft

实现逻辑

<?php declare(strict_types=1);


namespace App\Common;


use ReflectionException;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Bean\Exception\ContainerException;
use Swoft\Consul\Agent;
use Swoft\Consul\Exception\ClientException;
use Swoft\Consul\Exception\ServerException;
use Swoft\Rpc\Client\Client;
use Swoft\Rpc\Client\Contract\ProviderInterface;

/** * Class RpcProvider * * @since 2.0 * * @Bean() */
class RpcProvider implements ProviderInterface {
    /** * @Inject() * * @var Agent */
    private $agent;

    /** * @param Client $client * * @return array * @throws ReflectionException * @throws ContainerException * @throws ClientException * @throws ServerException * @example * [ * 'host:port', * 'host:port', * 'host:port', * ] */
    public function getList(Client $client): array {
        // Get health service from consul
        $services = $this->agent->services();

        $services = [
        
        ];

        return $services;
    }
}
复制代码

服务熔断

在分布式环境下,特别是微服务结构的分布式系统中, 一个软件系统调用另一个远程系统是很是广泛的。这种远程调用的被调用方多是另一个进程,或者是跨网路的另一台主机, 这种远程的调用和进程的内部调用最大的区别是,远程调用可能会失败,或者挂起而没有任何回应,直到超时。更坏的状况是, 若是有多个调用者对同一个挂起的服务进行调用,那么就颇有可能的是一个服务的超时等待迅速蔓延到整个分布式系统,引发连锁反应, 从而消耗掉整个分布式系统大量资源。最终可能致使系统瘫痪。

断路器(Circuit Breaker)模式就是为了防止在分布式系统中出现这种瀑布似的连锁反应致使的灾难。

基本的断路器模式下,保证了断路器在open状态时,保护supplier不会被调用, 但咱们还须要额外的措施能够在supplier恢复服务后,能够重置断路器。一种可行的办法是断路器按期探测supplier的服务是否恢复, 一但恢复, 就将状态设置成close。断路器进行重试时的状态为半开(half-open)状态。

熔断器的使用想到简单且功能强大,使用一个 @Breaker 注解便可,Swoft 的熔断器能够用于任何场景, 例如 服务调用的时候使用, 请求第三方的时候均可以对它进行熔断降级

<?php declare(strict_types=1);


namespace App\Model\Logic;

use Exception;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Breaker\Annotation\Mapping\Breaker;

/** * Class BreakerLogic * * @since 2.0 * * @Bean() */
class BreakerLogic {
    /** * @Breaker(fallback="loopFallback") * * @return string * @throws Exception */
    public function loop(): string {
        // Do something
        throw new Exception('Breaker exception');
    }

    /** * @return string * @throws Exception */
    public function loopFallback(): string {
        // Do something
    }
}
复制代码

服务限流

限流、熔断、降级这个强调多少遍都不过度,由于确实很重要。服务不行的时候必定要熔断。限流是一个保护本身最大的利器,若是没有自我保护机制,无论有多少链接都会接收,若是后端处理不过来,前端流量又很大的时候确定就挂了。

限流是对稀缺资源访问时,好比秒杀,抢购的商品时,来限制并发和请求的数量,从而有效的进行削峰并使得流量曲线平滑。限流的目的是对并发访问和并发请求进行限速,或者一个时间窗口内请求进行限速从而来保护系统,一旦达到或超过限制速率就能够拒绝服务,或者进行排队等待等。

Swoft 限流器底层采用的是令牌桶算法,底层依赖于 Redis 实现分布式限流。

Swoft 限速器不只能够限流控制器,也能够限制任何 bean 里面的方法,能够控制方法的访问速率。这里如下面使用示例详解

<?php declare(strict_types=1);

namespace App\Model\Logic;

use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Limiter\Annotation\Mapping\RateLimiter;

/** * Class LimiterLogic * * @since 2.0 * * @Bean() */
class LimiterLogic {
    /** * @RequestMapping() * @RateLimiter(rate=20, fallback="limiterFallback") * * @param Request $request * * @return array */
    public function requestLimiter2(Request $request): array {
        $uri = $request->getUriPath();
        return ['requestLimiter2', $uri];
    }
    
    /** * @param Request $request * * @return array */
    public function limiterFallback(Request $request): array {
        $uri = $request->getUriPath();
        return ['limiterFallback', $uri];
    }
}
复制代码

key 这里支持 symfony/expression-language 表达式, 若是被限速会调用 fallback中定义的limiterFallback 方法

配置中心

提及配置中心前咱们先说说配置文件,咱们并不陌生,它提供咱们能够动态修改程序运行能力。引用别人的一句话就是:

系统运行时(runtime)飞行姿态的动态调整!

我能够把咱们的工做称之为在快速飞行的飞机上修理零件。咱们人类老是没法掌控和预知一切。对于咱们系统来讲,咱们老是须要预留一些控制线条,以便在咱们须要的时候作出调整,控制系统方向(如灰度控制、限流调整),这对于拥抱变化的互联网行业尤其重要。

对于单机版,咱们称之为配置(文件);对于分布式集群系统,咱们称之为配置中心(系统);

到底什么是分布式配置中心

随着业务的发展、微服务架构的升级,服务的数量、程序的配置日益增多(各类微服务、各类服务器地址、各类参数),传统的配置文件方式和数据库的方式已没法知足开发人员对配置管理的要求:

  • 安全性:配置跟随源代码保存在代码库中,容易形成配置泄漏;
  • 时效性:修改配置,须要重启服务才能生效;
  • 局限性:没法支持动态调整:例如日志开关、功能开关;

所以,咱们须要配置中心来统一管理配置!把业务开发者从复杂以及繁琐的配置中解脱出来,只需专一于业务代码自己,从而可以显著提高开发以及运维效率。同时将配置和发布包解藕也进一步提高发布的成功率,并为运维的细力度管控、应急处理等提供强有力的支持。

关于分布式配置中心,网上已经有不少开源的解决方案,例如:

Apollo是携程框架部门研发的分布式配置中心,可以集中化管理应用不一样环境、不一样集群的配置,配置修改后可以实时推送到应用端,而且具有规范的权限、流程治理等特性,适用于微服务配置管理场景。

本章以Apollo 为例,从远端配置中心拉取配置以及安全重启服务。若是对 Apollo 不熟悉,能够先看Swoft 扩展 Apollo 组件以及阅读 Apollo 官方文档。

本章以 Swoft 中使用 Apollo 为例,当 Apollo 配置变动后,重启服务(http-server / rpc-server/ ws-server)。以下是一个 agent 例子:

<?php declare(strict_types=1);


namespace App\Model\Logic;

use Swoft\Apollo\Config;
use Swoft\Apollo\Exception\ApolloException;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;

/** * Class ApolloLogic * * @since 2.0 * * @Bean() */
class ApolloLogic {
    /** * @Inject() * * @var Config */
    private $config;

    /** * @throws ApolloException */
    public function pull(): void {
        $data = $this->config->pull('application');
        
        // Print data
        var_dump($data);
    }
}
复制代码

以上就是一个简单的 Apollo 配置拉取,Swoft-Apollo 除此方法外,还提供了更多的使用方法。

官方连接

相关文章
相关标签/搜索