如何使用 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 除此方法外,還提供了更多的使用方法。

官方連接

相關文章
相關標籤/搜索