定制 letscodehu/php-circuit-breaker 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

letscodehu/php-circuit-breaker

Composer 安装命令:

composer require letscodehu/php-circuit-breaker

包简介

PHP Circuit Breaker component

README 文档

README

Build Status

A component helping you gracefully handle outages and timeouts of external services (usually remote, 3rd party services).

It is a library providing extremely easy to use circuit breaker component. It does not require external dependencies and it has default storage implementations for APC and Memcached but can be extended multiple ways.

Frameworks support

This library does not require any particular PHP framework, all you need is PHP 5.4 or higher.

Symfony 2

If you are using Symfony 2 framework you should use php-circuit-breaker-bundle. It is a bundle i have created to wrap up php-circuit-breaker and integrate it with Symfony 2 components and Dependency Injection.

Other Frameworks

If you are using other frameworks and you would like to use php-circuit-breaker please let me know and we can try to build up an open source integration just like the one for Symfony 2.

Motivation & Benefits

  • Allow application to detect failures and adapt its behaviour without human intervention.
  • Increase robustness of services by addinf fail-safe functionality into modules.

Installation

You can download sources and use them with your autoloader or you can use composer in which case all you nees is a require like this:

"require": {
    "ejsmont-artur/php-circuit-breaker": "*"
},

After that you should update composer dependencies and you are good to go.

Use Case - Non-Critical Feature

  • Your application has an Non-Critical Feature like: user tracking, stats, recommendations etc
  • The optional feature uses remote service that causes outages of your application.
  • You want to keep applicaton and core processes available when "Non-Critical Feature" fails.

Code of your application could look something like:

    $factory = new Ejsmont\CircuitBreaker\Factory();
    $circuitBreaker = $factory->getSingleApcInstance(30, 300);

    $userProfile = null;
    if( $circuitBreaker->isAvailable("UserProfileService") ){
        try{
            $userProfile = $userProfileService->loadProfileOrWhatever();
            $circuitBreaker->reportSuccess("UserProfileService");
        }catch( UserProfileServiceConnectionException $e ){
            // network failed - report it as failure
            $circuitBreaker->reportFailure("UserProfileService");
        }catch( Exception $e ){
            // something went wrong but it is not service's fault, dont report as failure
        }
    }
    if( $userProfile === null ){
        // for example, show 'System maintenance, you cant login now.' message
        // but still let people buy as logged out customers.
    }

Use Case - Payment Gateway

  • Web application depends on third party service (for example a payment gateway).
  • Web application needs to keep track when 3rd party service is unavailable.
  • Application can not become slow/unavailable, it has to tell user that features are limited or just hide them.
  • Application uses circuit breaker before checkout page rendering and if particular payment gateway is unavailable payment option is hidden from the user.

As you can see that is a very powerful concept of selectively disabling feautres at runtime but still allowing the core business processes to be uninterrupted.

Backend talking to the payment service could look like this:

    $factory = new Ejsmont\CircuitBreaker\Factory();
    $circuitBreaker = $factory->getSingleApcInstance(30, 300);

    try{
        // try to process the payment
        // then tell circuit breaker that it went well
        $circuitBreaker->reportSuccess("PaymentOptionOne");
    }catch( SomePaymentConnectionException $e ){
        // If you get network error report it as failure
        $circuitBreaker->reportFailure("PaymentOptionOne");
    }catch( Exception $e ){
        // in case of your own error handle it however it makes sense but
        // dont tell circuit breaker it was 3rd party service failure
    }

Since you are recording failed and successful operations you can now use them in the front end as well to hide payment options that are failing.

Frontend rendering the available payment options could look like this:

    $factory = new Ejsmont\CircuitBreaker\Factory();
    $circuitBreaker = $factory->getSingleApcInstance(30, 300);

    if ($circuitBreaker->isAvailable("PaymentOptionOne")) {
        // display the option
    }

Dynamic proxy

In order to easily wrap a service class and protect it with circuit breaker there is a factory class:

$client = new Predis\Client("tcp://127.0.0.1:6379?read_write_timeout=0");
$factory = new Ejsmont\CircuitBreaker\Factory();
$cb = $factory->getRedisInstance($client, 30, 3600);

$proxy = CircuitBreakerProxyFactory::create('SomeSlowService', $circuitBreaker, ["constructor", "arguments"]);

$proxy->remoteCalls(); // the call is protected by the circuit breaker

the default wrapping functionality is as follows:

        $oldTimeout = ini_get("default_socket_timeout"); // as we are talking about remote calls, we get the old timeout

        if ($this->circuitBreaker->isAvailable($this->serviceName)) { // we are checking if the service is available
            try {
                ini_set("default_socket_timeout", $this->timeout); // override the timeout
                $returnValue = $method->invokeArgs($proxy, $args); // the original methodcall
                $this->circuitBreaker->reportSuccess($this->serviceName); // at the happy path we report the success
            } catch(\Exception $e) {
                $this->circuitBreaker->reportFailure($this->serviceName); // or we report the error
            }

        }
        ini_set("default_socket_timeout", $oldTimeout); // and set the timeout to the original value

You may override this by providing a 4th parameter to the create factory method:

$proxy = CircuitBreakerProxyFactory::create('SomeSlowService', $circuitBreaker, ["constructor", "arguments"], $ownMethodHook);

The 4th parameter must implement the 'guymers\proxy\MethodHook' interface.

Features

  • Track multiple services through a single Circuit Breaker instance.
  • Pluggable backend adapters, provided APC, Redis and Memcached by default.
  • Customisable service thresholds. You can define how many failures are necessary for service to be considered down.
  • Customisable retry timeout. You do not want to disable the service forever. After provided timeout circuit breaker will allow a single process to attempt
  • Create a dynamic proxy wrapping around a service class with circuit breaker.

Performance Impact

Overhead of the Circuit Breaker is negligible.

APC implementation takes roughly 0.0002s to perform isAvailable() and then reportSuccess() or reportFailure().

Memcache adapter is in range of 0.0005s when talking to the local memcached process.

Redis adapter is in range of 0.0002s when talking to the local Redis.

The only potential performance impact is network connection time. If you chose to use remote memcached server or implement your own custom StorageAdapter.

The proxy creation takes roughly 0.0007s. The further calls have no performance impact.

Running tests

  • Tests are run via PHPUnit It is assumed to be installed via PEAR.
  • Tests can be ran using phpunit alone or via ant build targets.
  • The "ci" target generate code coverage repor, "phpunit" target does not.

You can run all tests by any of the following:

ant
ant phpunit
ant ci

You can run selected test case by running:

cd tests
phpunit Unit/Ejsmont/CircuitBreaker/Storage/Adapter/DummyAdapterTest.php

Details

Before documentation gets updated you can read more on the concept of circuit breaker and its implementation on my blog http://artur.ejsmont.org/blog/circuit-breaker

Some implementation details has changed but the core logic is still the same.

Author

letscodehu/php-circuit-breaker 适用场景与选型建议

letscodehu/php-circuit-breaker 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 36 次下载、GitHub Stars 达 0, 最近一次更新时间为 2017 年 02 月 18 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「error handling」 「circuit breaker」 「graceful」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 letscodehu/php-circuit-breaker 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 letscodehu/php-circuit-breaker 我们能提供哪些服务?
定制开发 / 二次开发

基于 letscodehu/php-circuit-breaker 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 36
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 12
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 28
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-02-18