From 271dc11d913bc6bee293ea9e48684e66832e7c99 Mon Sep 17 00:00:00 2001 From: Aleksandr Denisyuk Date: Thu, 28 Dec 2023 00:51:15 +0300 Subject: [PATCH] Upgrade package --- .github/workflows/ci.yml | 24 +- Dockerfile | 11 + LICENSE | 2 +- Makefile | 5 + README.md | 81 ++-- benchmarks/FrequencyRandomBench.php | 13 +- benchmarks/RandomBench.php | 13 +- benchmarks/RoundRobinBench.php | 17 +- benchmarks/SmoothWeightedRoundRobinBench.php | 13 +- benchmarks/WeightedRandomBench.php | 13 +- benchmarks/WeightedRoundRobinBench.php | 17 +- composer.json | 16 +- docs/index.md | 409 ++++++++---------- helpers.php | 21 + psalm-baseline.xml | 80 +--- src/{Collection => Cluster}/Cluster.php | 8 +- .../ClusterInterface.php | 5 +- src/Cluster/ClusterPool.php | 56 +++ src/Cluster/ClusterSet.php | 19 + src/Collection/Asc.php | 13 - src/Collection/Collection.php | 92 ---- src/Collection/CollectionInterface.php | 14 +- src/Collection/Desc.php | 13 - .../Exception/EmptyCollectionException.php | 13 - .../UnweightedCollectionException.php | 13 - src/Collection/InMemoryCollection.php | 105 +++++ src/Collection/Node.php | 29 +- src/Collection/NodeInterface.php | 17 + src/Collection/Sort/Asc.php | 15 + src/Collection/Sort/Desc.php | 15 + src/Collection/Sorter.php | 21 - src/Counter/CounterInterface.php | 10 + src/Counter/InMemoryCounter.php | 25 ++ src/FrequencyRandomThrottler.php | 32 ++ src/MultipleThrottler.php | 40 ++ src/RandomThrottler.php | 22 + src/RoundRobinThrottler.php | 29 ++ src/SmoothWeightedRoundRobinThrottler.php | 50 +++ src/Strategy/ClusterDetermineStrategy.php | 51 --- src/Strategy/ClusterSet.php | 20 - src/Strategy/CounterInterface.php | 10 - src/Strategy/FrequencyRandomStrategy.php | 38 -- src/Strategy/GcdCalculator.php | 19 - src/Strategy/InMemoryCounter.php | 27 -- src/Strategy/MultipleDynamicStrategy.php | 43 -- src/Strategy/RandomStrategy.php | 23 - src/Strategy/RoundRobinStrategy.php | 28 -- .../SmoothWeightedRoundRobinStrategy.php | 68 --- src/Strategy/StrategyInterface.php | 13 - src/Strategy/WeightedRandomStrategy.php | 57 --- src/Strategy/WeightedRoundRobinStrategy.php | 85 ---- src/Throttler.php | 22 - src/ThrottlerInterface.php | 4 +- src/WeightedRandomThrottler.php | 36 ++ src/WeightedRoundRobinThrottler.php | 56 +++ tests/.gitkeep | 0 tests/Collection/ClusterTest.php | 35 -- tests/Collection/CollectionTest.php | 171 -------- tests/Collection/SorterTest.php | 53 --- .../Strategy/ClusterDetermineStrategyTest.php | 37 -- .../Strategy/FrequencyRandomStrategyTest.php | 48 -- tests/Strategy/GcdCalculatorTest.php | 18 - tests/Strategy/InMemoryCounterTest.php | 21 - .../Strategy/MultipleDynamicStrategyTest.php | 36 -- tests/Strategy/RandomStrategyTest.php | 46 -- tests/Strategy/RoundRobinStrategyTest.php | 55 --- .../SmoothWeightedRoundRobinStrategyTest.php | 54 --- tests/Strategy/WeightedRandomStrategyTest.php | 48 -- .../WeightedRoundRobinStrategyTest.php | 56 --- tests/ThrottlerTest.php | 29 -- 70 files changed, 886 insertions(+), 1812 deletions(-) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 helpers.php rename src/{Collection => Cluster}/Cluster.php (66%) rename src/{Collection => Cluster}/ClusterInterface.php (58%) create mode 100644 src/Cluster/ClusterPool.php create mode 100644 src/Cluster/ClusterSet.php delete mode 100644 src/Collection/Asc.php delete mode 100644 src/Collection/Collection.php delete mode 100644 src/Collection/Desc.php delete mode 100644 src/Collection/Exception/EmptyCollectionException.php delete mode 100644 src/Collection/Exception/UnweightedCollectionException.php create mode 100644 src/Collection/InMemoryCollection.php create mode 100644 src/Collection/NodeInterface.php create mode 100644 src/Collection/Sort/Asc.php create mode 100644 src/Collection/Sort/Desc.php delete mode 100644 src/Collection/Sorter.php create mode 100644 src/Counter/CounterInterface.php create mode 100644 src/Counter/InMemoryCounter.php create mode 100644 src/FrequencyRandomThrottler.php create mode 100644 src/MultipleThrottler.php create mode 100644 src/RandomThrottler.php create mode 100644 src/RoundRobinThrottler.php create mode 100644 src/SmoothWeightedRoundRobinThrottler.php delete mode 100644 src/Strategy/ClusterDetermineStrategy.php delete mode 100644 src/Strategy/ClusterSet.php delete mode 100644 src/Strategy/CounterInterface.php delete mode 100644 src/Strategy/FrequencyRandomStrategy.php delete mode 100644 src/Strategy/GcdCalculator.php delete mode 100644 src/Strategy/InMemoryCounter.php delete mode 100644 src/Strategy/MultipleDynamicStrategy.php delete mode 100644 src/Strategy/RandomStrategy.php delete mode 100644 src/Strategy/RoundRobinStrategy.php delete mode 100644 src/Strategy/SmoothWeightedRoundRobinStrategy.php delete mode 100644 src/Strategy/StrategyInterface.php delete mode 100644 src/Strategy/WeightedRandomStrategy.php delete mode 100644 src/Strategy/WeightedRoundRobinStrategy.php delete mode 100644 src/Throttler.php create mode 100644 src/WeightedRandomThrottler.php create mode 100644 src/WeightedRoundRobinThrottler.php create mode 100644 tests/.gitkeep delete mode 100644 tests/Collection/ClusterTest.php delete mode 100644 tests/Collection/CollectionTest.php delete mode 100644 tests/Collection/SorterTest.php delete mode 100644 tests/Strategy/ClusterDetermineStrategyTest.php delete mode 100644 tests/Strategy/FrequencyRandomStrategyTest.php delete mode 100644 tests/Strategy/GcdCalculatorTest.php delete mode 100644 tests/Strategy/InMemoryCounterTest.php delete mode 100644 tests/Strategy/MultipleDynamicStrategyTest.php delete mode 100644 tests/Strategy/RandomStrategyTest.php delete mode 100644 tests/Strategy/RoundRobinStrategyTest.php delete mode 100644 tests/Strategy/SmoothWeightedRoundRobinStrategyTest.php delete mode 100644 tests/Strategy/WeightedRandomStrategyTest.php delete mode 100644 tests/Strategy/WeightedRoundRobinStrategyTest.php delete mode 100644 tests/ThrottlerTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdf2f73..6bb085d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,60 +24,48 @@ jobs: parallel-lint: name: 'ParallelLint' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run ParallelLint run: composer parallel-lint -- --no-progress --ignore-fails psalm: name: 'Psalm' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run Psalm run: composer psalm -- --no-progress --no-cache --output-format=github php-cs-fixer: name: 'PHPCsFixer' runs-on: 'ubuntu-latest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 - - name: Run PHPCsFixer run: composer php-cs-fixer:diff -- --no-interaction --using-cache=no @@ -85,7 +73,6 @@ jobs: name: 'PHPUnit' needs: ['parallel-lint', 'psalm', 'php-cs-fixer'] runs-on: ${{ matrix.operating-system }} - strategy: fail-fast: false matrix: @@ -94,24 +81,21 @@ jobs: - 'windows-latest' php-version: - '8.1' + - '8.2' composer-dependency: - 'lowest' - 'highest' - steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} coverage: none - - name: Install dependencies uses: ramsey/composer-install@v2 with: dependency-versions: ${{ matrix.composer-dependency }} - - name: Run PHPUnit run: composer phpunit -- --no-interaction --do-not-cache-result diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6541a1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM php:8.2-cli + +RUN \ + apt-get update ; \ + apt-get install -y unzip ; \ + pecl install pcov ; \ + docker-php-ext-enable pcov ; + +COPY --from=composer:2.4 /usr/bin/composer /usr/local/bin/composer + +WORKDIR /usr/local/packages/throttler/ diff --git a/LICENSE b/LICENSE index 880306b..cb8d327 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Orangesoft +Copyright (c) 2021 Orangesoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..42539b6 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +init: + docker build -t throttler:8.2 ./ + +exec: + docker run --name throttler --rm --interactive --tty --volume ${PWD}:/usr/local/packages/throttler/ throttler:8.2 /bin/bash diff --git a/README.md b/README.md index 4344d00..02e9ea6 100644 --- a/README.md +++ b/README.md @@ -20,38 +20,60 @@ This package requires PHP 8.1 or later. ## Quick usage -Configure Throttler as below: +Configure `Orangesoft\Throttler\WeightedRoundRobinThrottler::class` as below and set weight for each node as the second argument in constructor if you are using weighted strategies: ```php pick($collection); - + // ... } ``` -Set weight for Node as the second argument in constructor if you are using weighted-strategies. +As a result, the strategy will go through all the nodes and return the appropriate one like below: + +```text ++-------------+ +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.2 | +| 192.168.0.3 | +| etc. | ++-------------+ +``` + +The following load balancing strategies are available: + +- [Orangesoft\Throttler\RandomThrottler](../src/RandomThrottler.php) +- [Orangesoft\Throttler\WeightedRandomThrottler](../src/WeightedRandomThrottler.php) +- [Orangesoft\Throttler\FrequencyRandomThrottler](../src/FrequencyRandomThrottler.php) +- [Orangesoft\Throttler\RoundRobinThrottler](../src/RoundRobinThrottler.php) +- [Orangesoft\Throttler\WeightedRoundRobinThrottler](../src/WeightedRoundRobinThrottler.php) +- [Orangesoft\Throttler\SmoothWeightedRoundRobinThrottler](../src/SmoothWeightedRoundRobinThrottler.php) ## Benchmarks @@ -61,25 +83,26 @@ Run `composer phpbench` to check out benchmarks: +-------------------------------+------+-----+----------+----------+----------+---------+ | benchmark | revs | its | mean | best | worst | stdev | +-------------------------------+------+-----+----------+----------+----------+---------+ -| FrequencyRandomBench | 1000 | 5 | 6.074μs | 5.924μs | 6.242μs | 0.139μs | | RandomBench | 1000 | 5 | 4.002μs | 3.880μs | 4.097μs | 0.073μs | -| RoundRobinBench | 1000 | 5 | 4.060μs | 3.888μs | 4.363μs | 0.171μs | -| SmoothWeightedRoundRobinBench | 1000 | 5 | 6.888μs | 6.707μs | 7.102μs | 0.130μs | | WeightedRandomBench | 1000 | 5 | 11.660μs | 11.533μs | 11.797μs | 0.094μs | +| FrequencyRandomBench | 1000 | 5 | 6.074μs | 5.924μs | 6.242μs | 0.139μs | +| RoundRobinBench | 1000 | 5 | 4.060μs | 3.888μs | 4.363μs | 0.171μs | | WeightedRoundRobinBench | 1000 | 5 | 10.778μs | 10.655μs | 10.919μs | 0.115μs | +| SmoothWeightedRoundRobinBench | 1000 | 5 | 6.888μs | 6.707μs | 7.102μs | 0.130μs | +-------------------------------+------+-----+----------+----------+----------+---------+ ``` -The report is based on measuring the speed. Check `best` column to find out which strategy is the fastest. You can see that the fastest strategies are Random and RoundRobin. +The report is based on measuring the speed. Check `best` column to find out which strategy is the fastest. ## Documentation -- [Configure Throttler](docs/index.md#configure-throttler) -- [Available strategies](docs/index.md#available-strategies) -- [Sort nodes](docs/index.md#sort-nodes) -- [Keep counter](docs/index.md#keep-counter) -- [Serialize strategies](docs/index.md#serialize-strategies) -- [Dynamically change strategy](docs/index.md#dynamically-change-strategy) -- [Balance cluster](docs/index.md#balance-cluster) +- [How it works](docs/index.md##how-it-works) +- [Available strategies](docs/index.md##available-strategies) +- [Keep states](docs/index.md##keep-states) + - [Counting](docs/index.md##counting) + - [Serialization](docs/index.md##serialization) +- [Choice from multiple](docs/index.md##choice-from-multiple) +- [Balance cluster](docs/index.md##balance-cluster) +- [Production example](docs/index.md##production-example) -Read more about usage on [Orangesoft Tech](https://orangesoft.co/blog/how-to-make-proxy-balancing-in-guzzle). +Read more about [Load Balancing](https://samwho.dev/load-balancing/). diff --git a/benchmarks/FrequencyRandomBench.php b/benchmarks/FrequencyRandomBench.php index 4b7e47b..0c8a545 100644 --- a/benchmarks/FrequencyRandomBench.php +++ b/benchmarks/FrequencyRandomBench.php @@ -4,29 +4,26 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\FrequencyRandomStrategy; -use Orangesoft\Throttler\Throttler; +use Orangesoft\Throttler\FrequencyRandomThrottler; use Orangesoft\Throttler\ThrottlerInterface; -class FrequencyRandomBench +final class FrequencyRandomBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1'), new Node('node2'), new Node('node3'), ]); - $this->throttler = new Throttler( - new FrequencyRandomStrategy(), - ); + $this->throttler = new FrequencyRandomThrottler(); } /** diff --git a/benchmarks/RandomBench.php b/benchmarks/RandomBench.php index 8ea5b62..1fb934d 100644 --- a/benchmarks/RandomBench.php +++ b/benchmarks/RandomBench.php @@ -4,29 +4,26 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\RandomStrategy; -use Orangesoft\Throttler\Throttler; +use Orangesoft\Throttler\RandomThrottler; use Orangesoft\Throttler\ThrottlerInterface; -class RandomBench +final class RandomBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1'), new Node('node2'), new Node('node3'), ]); - $this->throttler = new Throttler( - new RandomStrategy(), - ); + $this->throttler = new RandomThrottler(); } /** diff --git a/benchmarks/RoundRobinBench.php b/benchmarks/RoundRobinBench.php index a6deaa8..02ad98a 100644 --- a/benchmarks/RoundRobinBench.php +++ b/benchmarks/RoundRobinBench.php @@ -4,31 +4,28 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\InMemoryCounter; -use Orangesoft\Throttler\Strategy\RoundRobinStrategy; -use Orangesoft\Throttler\Throttler; +use Orangesoft\Throttler\Counter\InMemoryCounter; +use Orangesoft\Throttler\RoundRobinThrottler; use Orangesoft\Throttler\ThrottlerInterface; -class RoundRobinBench +final class RoundRobinBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1'), new Node('node2'), new Node('node3'), ]); - $this->throttler = new Throttler( - new RoundRobinStrategy( - new InMemoryCounter(start: 0), - ) + $this->throttler = new RoundRobinThrottler( + new InMemoryCounter(), ); } diff --git a/benchmarks/SmoothWeightedRoundRobinBench.php b/benchmarks/SmoothWeightedRoundRobinBench.php index 896d7dc..649295b 100644 --- a/benchmarks/SmoothWeightedRoundRobinBench.php +++ b/benchmarks/SmoothWeightedRoundRobinBench.php @@ -4,29 +4,26 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\SmoothWeightedRoundRobinStrategy; -use Orangesoft\Throttler\Throttler; +use Orangesoft\Throttler\SmoothWeightedRoundRobinThrottler; use Orangesoft\Throttler\ThrottlerInterface; -class SmoothWeightedRoundRobinBench +final class SmoothWeightedRoundRobinBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1', 5), new Node('node2', 1), new Node('node3', 1), ]); - $this->throttler = new Throttler( - new SmoothWeightedRoundRobinStrategy(), - ); + $this->throttler = new SmoothWeightedRoundRobinThrottler(); } /** diff --git a/benchmarks/WeightedRandomBench.php b/benchmarks/WeightedRandomBench.php index ac0a0a1..8927cde 100644 --- a/benchmarks/WeightedRandomBench.php +++ b/benchmarks/WeightedRandomBench.php @@ -4,29 +4,26 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\WeightedRandomStrategy; -use Orangesoft\Throttler\Throttler; use Orangesoft\Throttler\ThrottlerInterface; +use Orangesoft\Throttler\WeightedRandomThrottler; -class WeightedRandomBench +final class WeightedRandomBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1', 5), new Node('node2', 1), new Node('node3', 1), ]); - $this->throttler = new Throttler( - new WeightedRandomStrategy(), - ); + $this->throttler = new WeightedRandomThrottler(); } /** diff --git a/benchmarks/WeightedRoundRobinBench.php b/benchmarks/WeightedRoundRobinBench.php index 9a2ee6e..ed7a067 100644 --- a/benchmarks/WeightedRoundRobinBench.php +++ b/benchmarks/WeightedRoundRobinBench.php @@ -4,31 +4,28 @@ namespace Orangesoft\Throttler\Benchmarks; -use Orangesoft\Throttler\Collection\Collection; use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\InMemoryCollection; use Orangesoft\Throttler\Collection\Node; -use Orangesoft\Throttler\Strategy\InMemoryCounter; -use Orangesoft\Throttler\Strategy\WeightedRoundRobinStrategy; -use Orangesoft\Throttler\Throttler; +use Orangesoft\Throttler\Counter\InMemoryCounter; use Orangesoft\Throttler\ThrottlerInterface; +use Orangesoft\Throttler\WeightedRoundRobinThrottler; -class WeightedRoundRobinBench +final class WeightedRoundRobinBench { private CollectionInterface $collection; private ThrottlerInterface $throttler; public function __construct() { - $this->collection = new Collection([ + $this->collection = new InMemoryCollection([ new Node('node1', 5), new Node('node2', 1), new Node('node3', 1), ]); - $this->throttler = new Throttler( - new WeightedRoundRobinStrategy( - new InMemoryCounter(start: 0), - ) + $this->throttler = new WeightedRoundRobinThrottler( + new InMemoryCounter(), ); } diff --git a/composer.json b/composer.json index eb0a3d6..52ec204 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,10 @@ "autoload": { "psr-4": { "Orangesoft\\Throttler\\": "./src/" - } + }, + "files": [ + "./helpers.php" + ] }, "autoload-dev": { "psr-4": { @@ -47,17 +50,20 @@ } }, "scripts": { - "phpbench": "vendor/bin/phpbench run --report=throttler --ansi", + "phpbench": "./vendor/bin/phpbench run --report=throttler --ansi", "phpunit": "./vendor/bin/phpunit --verbose --colors=always --no-coverage", + "phpunit:clear-cache": "rm ./build/cache/phpunit.cache", "phpunit-coverage": "./vendor/bin/phpunit --verbose --colors=always --coverage-text", "phpunit-coverage-html": "./vendor/bin/phpunit --verbose --colors=always --coverage-html ./build/logs/phpunit-coverage/", "parallel-lint": "./vendor/bin/parallel-lint --colors ./src/ ./tests/ ./benchmarks/", "php-cs-fixer:fix": "./vendor/bin/php-cs-fixer fix --verbose --ansi --show-progress=dots", "php-cs-fixer:diff": "./vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --diff", + "php-cs-fixer:clear-cache": "rm ./build/cache/php-cs-fixer.cache", "psalm": "./vendor/bin/psalm --show-info=true", - "psalm:set-baseline": "@psalm --set-baseline=./psalm-baseline.xml", - "psalm:update-baseline": "@psalm --update-baseline", - "psalm:ignore-baseline": "@psalm --ignore-baseline", + "psalm:clear-cache": "rm -rf ./build/cache/psalm/", + "psalm:set-baseline": "@psalm --set-baseline=./psalm-baseline.xml --no-cache", + "psalm:update-baseline": "@psalm --update-baseline --no-cache", + "psalm:ignore-baseline": "@psalm --ignore-baseline --no-cache", "test": [ "@parallel-lint", "@psalm", diff --git a/docs/index.md b/docs/index.md index e07aded..a7191aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,201 +1,75 @@ # Documentation -- [Configure Throttler](#configure-throttler) +- [How it works](#how-it-works) - [Available strategies](#available-strategies) -- [Sort nodes](#sort-nodes) -- [Keep counter](#keep-counter) -- [Serialize strategies](#serialize-strategies) -- [Dynamically change strategy](#dynamically-change-strategy) +- [Keep states](#keep-states) + - [Counting](#counting) + - [Serialization](#serialization) +- [Choice from multiple](#choice-from-multiple) - [Balance cluster](#balance-cluster) +- [Production example](#production-example) -## Configure Throttler +## How it works -You need to choose a strategy and configure it: - -```php -pick($collection); - - // ... -} -``` - -As a result, you will see the following distribution of nodes: - -```text -+-------+ -| node1 | -| node1 | -| node1 | -| node1 | -| node1 | -| node2 | -| node3 | -| etc. | -+-------+ -``` - -Result of Throttler depends on the chosen strategy. +[...] ## Available strategies -Strategies are divided into two types: random and round-robin. The following strategies are available: - -- [RandomStrategy](../src/Strategy/RandomStrategy.php) -- [WeightedRandomStrategy](../src/Strategy/WeightedRandomStrategy.php) -- [FrequencyRandomStrategy](../src/Strategy/FrequencyRandomStrategy.php) -- [RoundRobinStrategy](../src/Strategy/RoundRobinStrategy.php) -- [WeightedRoundRobinStrategy](../src/Strategy/WeightedRoundRobinStrategy.php) -- [SmoothWeightedRoundRobinStrategy](../src/Strategy/SmoothWeightedRoundRobinStrategy.php) -- [MultipleDynamicStrategy](../src/Strategy/MultipleDynamicStrategy.php) -- [ClusterDetermineStrategy](../src/Strategy/ClusterDetermineStrategy.php) - -## Sort nodes - -For some strategies, such as [FrequencyRandomStrategy](../src/Strategy/FrequencyRandomStrategy.php), it might be necessary to adjust the order of nodes by their weight. This can be done with Sorter: - -```php -sort($collection, new Desc()); -``` +## Keep states -The nodes at the top of the list will be used more often. You can manage sorting using [Asc](../src/Collection/Asc.php) and [Desc](../src/Collection/Desc.php) comparators. Example for the Desc direction: +[...] ```text -+--------+--------+ -| name | weight | -+--------+--------+ -| node7 | 2048 | -| node4 | 1024 | -| node3 | 512 | -| node8 | 256 | -| node5 | 128 | -| node6 | 64 | -| node1 | 32 | -| node2 | 16 | -| node9 | 8 | -| node10 | 4 | -+--------+--------+ ++--------------------------+---------------+ +| Throttler | Method | ++--------------------------+---------------+ +| Random | [x] | +| WeightedRandom | [x] | +| FrequencyRandom | [x] | +| RoundRobin | counting | +| WeightedRoundRobin | counting | +| SmoothWeightedRoundRobin | serialization | ++--------------------------+---------------+ ``` -FrequencyRandomStrategy has 2 not required options: frequency and depth. Frequency is probability to choose nodes from a first group in percent. Depth is length the first group from the list in percent. By default, frequency is 0.8 and depth is 0.2: +[...] -```php -$throttler = new Throttler( - new FrequencyRandomStrategy(frequency: 0.8, depth: 0.2), -); - -/** @var Node $node */ -$node = $throttler->pick($collection); -``` +### Counting -The probability of choosing nodes for FrequencyRandomStrategy can be visualized as follows: +[...] ```text -+--------+--------+ -| nodes | chance | -+--------+--------+ -| node7 | 40% | -| node4 | 40% | -+--------+--------+ -| node3 | 2.5% | -| node8 | 2.5% | -| node5 | 2.5% | -| node6 | 2.5% | -| node1 | 2.5% | -| node2 | 2.5% | -| node9 | 2.5% | -| node10 | 2.5% | -+--------+--------+ +composer require predis/predis ``` -If you need the reverse order of the nodes use Asc direction. - -## Keep counter - -For strategies are [RoundRobinStrategy](../src/Strategy/RoundRobinStrategy.php) and [WeightedRoundRobinStrategy](../src/Strategy/WeightedRoundRobinStrategy.php) you must use InMemoryCounter to remember order of nodes. A counter is not needed for round-robin strategies: +[...] ```php client->exists($name)) { - $this->client->set($name, -1); + $this->client->set($name, $start - 1); } return $this->client->incr($name); @@ -203,127 +77,200 @@ class RedisCounter implements CounterInterface } ``` -In the example above, we wrote the counter with Redis. +[...] ```php /** @var Predis\Client $client */ +$client = new Client('tcp://127.0.0.1:6379'); -$strategy = new WeightedRoundRobinStrategy( +$throttler = new WeightedRoundRobinThrottler( new RedisCounter($client), ); ``` -Now Throttler will resume work from the last node according to the chosen strategy. +[...] -## Serialize strategies +### Serialization -[SmoothWeightedRoundRobinStrategy](../src/Strategy/SmoothWeightedRoundRobinStrategy.php) does not support counters. Instead it you can serialize and unserialize this strategy to keep last state: +[...] + + + +## Choice from multiple + +[...] ```php pick($collection, [ + 'throttler' => RoundRobinStrategy::class, +]); ``` -This way you can preserve the order of nodes for a given strategy between PHP calls. +[...] -## Dynamically change strategy +## Balance cluster -You can dynamically change the strategy from the client code. To do this, configure the [MultipleDynamicStrategy](../src/Strategy/MultipleDynamicStrategy.php) with the strategies you need: +[...] ```php pick($collection, [ - 'strategy_name' => RoundRobinStrategy::class, -]); +/** @var NodeInterface $node */ +$node = $cluster->balance($pool); ``` -The advantage of this method is that you do not need to create many instances of the balancer. +[...] -## Balance cluster +## Production example + +[...] -You can divide the nodes into clusters and set a specific balancing strategy for each cluster. To do this, configure the [ClusterDetermineStrategy](../src/Strategy/ClusterDetermineStrategy.php) as shown below: +```text +composer require \ + && orangesoft/throttler \ + && guzzlehttp/guzzle \ + && psr/http-message +``` + +[...] ```php $context + */ + public function __construct( + private readonly ThrottlerInterface $throttler, + private readonly CollectionInterface $collection, + private array $context = [], + ) { + } -$throttler = new Throttler( - new ClusterDetermineStrategy( - new ClusterSet(new RoundRobinStrategy(new InMemoryCounter(start: 0)), ['cluster1']), - new ClusterSet(new RandomStrategy(), ['cluster2', 'cluster3']), - ) -); + public function __invoke(callable $handler): \Closure + { + return function (RequestInterface $request, array $options) use ($handler): ResponseInterface { + /** @var NodeInterface $node */ + $node = $this->throttler->pick($this->collection, $this->context); + $options['proxy'] = $node->getName(); + + return $handler($request, $options); + }; + } +} ``` -Create clusters from nodes: +[...] ```php -$collection = new Collection([ - new Node('node1'), - new Node('node2'), - new Node('node3'), +$throttler = new WeightedRoundRobinThrottler( + new InMemoryCounter(), +); + +$collection = new InMemoryCollection([ + new Node('user:pass@192.168.0.1', 5), + new Node('user:pass@192.168.0.2', 1), + new Node('user:pass@192.168.0.3', 1), ]); -$cluster = new Cluster('cluster1', $collection); +$stack = HandlerStack::create(); +$stack->push(new ProxyMiddleware($throttler, $collection)); +$client = new Client(['handler' => $stack]); ``` -Using the `balance()` method, force your cluster to balance: +[...] ```php -/** @var Node $node */ -$node = $cluster->balance($throttler); +while (true) { + /** @var ResponseInterface $response */ + $response = $client->get('https://httpbin.org/ip'); + + // ... +} +``` + +[...] + +```text ++-------------+ +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.1 | +| 192.168.0.2 | +| 192.168.0.3 | +| etc. | ++-------------+ ``` -This method is well suited in cases where the nodes can be divided according to a specific criterion and each cluster needs its own balancing strategy. +[...] diff --git a/helpers.php b/helpers.php new file mode 100644 index 0000000..da32a72 --- /dev/null +++ b/helpers.php @@ -0,0 +1,21 @@ + - - - - iterator_to_array($this->nodes, false) - - - $node - - - $node - - - Node[] - - - - - $callback - - - - - $context['cluster_name'] - - - $this->clusterNames[$context['cluster_name']] - $this->clusterNames[$context['cluster_name']] - - - $this->clusterNames - $this->strategies - - - StrategyInterface - - - - - $context['strategy_name'] - - - StrategyInterface - - - - - $context['counter_name'] ?? self::class - - - - - $this->currentWeights - - - getNode - - - - - getNode - - - Node - - - $collection->getNode($index) - - - - - $context['counter_name'] ?? self::class - - - - - assertIsIterable - - - + diff --git a/src/Collection/Cluster.php b/src/Cluster/Cluster.php similarity index 66% rename from src/Collection/Cluster.php rename to src/Cluster/Cluster.php index b4497fd..7699cdf 100644 --- a/src/Collection/Cluster.php +++ b/src/Cluster/Cluster.php @@ -2,8 +2,10 @@ declare(strict_types=1); -namespace Orangesoft\Throttler\Collection; +namespace Orangesoft\Throttler\Cluster; +use Orangesoft\Throttler\Collection\CollectionInterface; +use Orangesoft\Throttler\Collection\NodeInterface; use Orangesoft\Throttler\ThrottlerInterface; final class Cluster implements ClusterInterface @@ -14,10 +16,10 @@ public function __construct( ) { } - public function balance(ThrottlerInterface $throttler, array $context = []): Node + public function balance(ThrottlerInterface $throttler, array $context = []): NodeInterface { return $throttler->pick($this->collection, array_merge($context, [ - 'cluster_name' => $this->name, + 'cluster' => $this->name, ])); } } diff --git a/src/Collection/ClusterInterface.php b/src/Cluster/ClusterInterface.php similarity index 58% rename from src/Collection/ClusterInterface.php rename to src/Cluster/ClusterInterface.php index 1cd6a41..2141b40 100644 --- a/src/Collection/ClusterInterface.php +++ b/src/Cluster/ClusterInterface.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Orangesoft\Throttler\Collection; +namespace Orangesoft\Throttler\Cluster; +use Orangesoft\Throttler\Collection\NodeInterface; use Orangesoft\Throttler\ThrottlerInterface; interface ClusterInterface { - public function balance(ThrottlerInterface $throttler, array $context = []): Node; + public function balance(ThrottlerInterface $throttler, array $context = []): NodeInterface; } diff --git a/src/Cluster/ClusterPool.php b/src/Cluster/ClusterPool.php new file mode 100644 index 0000000..f9c243b --- /dev/null +++ b/src/Cluster/ClusterPool.php @@ -0,0 +1,56 @@ + + */ + private array $throttlers = []; + /** + * @var array + */ + private array $clusterNames = []; + + public function __construct(ClusterSet ...$clusterSets) + { + foreach ($clusterSets as $clusterSet) { + $id = $clusterSet->throttler::class; + $this->throttlers[$id] = $clusterSet->throttler; + + foreach ($clusterSet->clusterNames as $clusterName) { + if (isset($this->clusterNames[$clusterName])) { + throw new \UnexpectedValueException(sprintf('Cluster "%s" has already been added.', $clusterName)); + } + + $this->clusterNames[$clusterName] = $id; + } + } + } + + public function pick(CollectionInterface $collection, array $context = []): NodeInterface + { + if (!isset($context['cluster'])) { + throw new \RuntimeException('The parameter "cluster" is required.'); + } + + if (!\is_string($context['cluster'])) { + throw new \RuntimeException('The parameter "cluster" must be as a string.'); + } + + if (!isset($this->clusterNames[$context['cluster']])) { + throw new \RuntimeException(sprintf('The cluster "%s" is undefined.', $context['cluster'])); + } + + $throttler = $this->throttlers[$this->clusterNames[$context['cluster']]]; + + return $throttler->pick($collection, $context); + } +} diff --git a/src/Cluster/ClusterSet.php b/src/Cluster/ClusterSet.php new file mode 100644 index 0000000..b5197f8 --- /dev/null +++ b/src/Cluster/ClusterSet.php @@ -0,0 +1,19 @@ +weight - $b->weight; - } -} diff --git a/src/Collection/Collection.php b/src/Collection/Collection.php deleted file mode 100644 index e735159..0000000 --- a/src/Collection/Collection.php +++ /dev/null @@ -1,92 +0,0 @@ -nodes = new \SplObjectStorage(); - - foreach ($nodes as $node) { - $this->addNode($node); - } - } - - public function addNode(Node $node): self - { - if (0 >= $node->weight) { - $this->isWeighted = false; - } - - $this->nodes->attach($node); - - return $this; - } - - public function getNode(int $index): Node - { - if ($index > $this->nodes->count()) { - throw new \InvalidArgumentException(sprintf('Cannot find node at index "%d".', $index)); - } - - $this->nodes->rewind(); - - while ($index--) { - $this->nodes->next(); - } - - /** @var Node $node */ - $node = $this->nodes->current(); - - return $node; - } - - public function hasNode(Node $node): bool - { - return $this->nodes->contains($node); - } - - public function removeNode(Node $node): void - { - $this->nodes->detach($node); - } - - public function purge(): void - { - $this->nodes->removeAll($this->nodes); - } - - public function isWeighted(): bool - { - return $this->isWeighted; - } - - public function isEmpty(): bool - { - return 0 === $this->nodes->count(); - } - - public function count(): int - { - return $this->nodes->count(); - } - - /** - * @return Node[] - */ - public function toArray(): array - { - return iterator_to_array($this->nodes, false); - } - - public function getIterator(): \Traversable - { - return $this->nodes; - } -} diff --git a/src/Collection/CollectionInterface.php b/src/Collection/CollectionInterface.php index aaec000..2dfba96 100644 --- a/src/Collection/CollectionInterface.php +++ b/src/Collection/CollectionInterface.php @@ -6,22 +6,20 @@ interface CollectionInterface extends \Countable, \IteratorAggregate { - public function addNode(Node $node): self; + public function add(NodeInterface $node): self; - public function getNode(int $index): Node; + public function get(int $key): NodeInterface; - public function hasNode(Node $node): bool; + public function has(NodeInterface $node): bool; - public function removeNode(Node $node): void; + public function remove(NodeInterface $node): self; - public function purge(): void; - - public function isWeighted(): bool; + public function sort(callable $callback): self; public function isEmpty(): bool; /** - * @return Node[] + * @return NodeInterface[] */ public function toArray(): array; } diff --git a/src/Collection/Desc.php b/src/Collection/Desc.php deleted file mode 100644 index 7fb7083..0000000 --- a/src/Collection/Desc.php +++ /dev/null @@ -1,13 +0,0 @@ -weight - $a->weight; - } -} diff --git a/src/Collection/Exception/EmptyCollectionException.php b/src/Collection/Exception/EmptyCollectionException.php deleted file mode 100644 index 95c9df3..0000000 --- a/src/Collection/Exception/EmptyCollectionException.php +++ /dev/null @@ -1,13 +0,0 @@ - + */ + private array $nodes; + /** + * @var array + */ + private array $keys; + + /** + * @param NodeInterface[] $nodes + */ + public function __construct(array $nodes = []) + { + $this->nodes = []; + $this->keys = []; + + foreach ($nodes as $node) { + if ($this->has($node)) { + throw new \InvalidArgumentException(sprintf('All nodes must be unique, "%s" given as duplicate.', $node->getName())); + } + + $this->nodes[] = $node; + $this->keys[$node->getName()] = array_key_last($this->nodes); + } + } + + public function add(NodeInterface $node): self + { + if ($this->has($node)) { + throw new \UnexpectedValueException(sprintf('The node "%s" has been already added.', $node->getName())); + } + + $self = clone $this; + $self->nodes[] = $node; + $self->keys[$node->getName()] = array_key_last($self->nodes); + + return $self; + } + + public function get(int $key): NodeInterface + { + if (!isset($this->nodes[$key])) { + throw new \OutOfRangeException(sprintf('Can\'t get node at key "%d".', $key)); + } + + return $this->nodes[$key]; + } + + public function has(NodeInterface $node): bool + { + return \array_key_exists($node->getName(), $this->keys); + } + + public function remove(NodeInterface $node): self + { + if (!$this->has($node)) { + throw new \UnexpectedValueException(sprintf('The node "%s" hasn\'t been already added.', $node->getName())); + } + + $self = clone $this; + unset($self->nodes[$self->keys[$node->getName()]]); + + return new self($self->toArray()); + } + + public function sort(callable $callback): self + { + $nodes = $this->toArray(); + usort($nodes, $callback); + + return new self($nodes); + } + + public function isEmpty(): bool + { + return 0 == $this->count(); + } + + public function count(): int + { + return \count($this->nodes); + } + + /** + * @return NodeInterface[] + */ + public function toArray(): array + { + return $this->nodes; + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->nodes); + } +} diff --git a/src/Collection/Node.php b/src/Collection/Node.php index 96dccd0..52d2d82 100644 --- a/src/Collection/Node.php +++ b/src/Collection/Node.php @@ -4,12 +4,33 @@ namespace Orangesoft\Throttler\Collection; -final class Node +final class Node implements NodeInterface { + /** + * @param array $payload + */ public function __construct( - public readonly string $name, - public readonly int $weight = 0, - public readonly array $info = [], + private string $name, + private int $weight = 0, + private array $payload = [], ) { } + + public function getName(): string + { + return $this->name; + } + + public function getWeight(): int + { + return $this->weight; + } + + /** + * @return array + */ + public function getPayload(): array + { + return $this->payload; + } } diff --git a/src/Collection/NodeInterface.php b/src/Collection/NodeInterface.php new file mode 100644 index 0000000..682956c --- /dev/null +++ b/src/Collection/NodeInterface.php @@ -0,0 +1,17 @@ + + */ + public function getPayload(): array; +} diff --git a/src/Collection/Sort/Asc.php b/src/Collection/Sort/Asc.php new file mode 100644 index 0000000..431032d --- /dev/null +++ b/src/Collection/Sort/Asc.php @@ -0,0 +1,15 @@ +getWeight() - $b->getWeight(); + } +} diff --git a/src/Collection/Sort/Desc.php b/src/Collection/Sort/Desc.php new file mode 100644 index 0000000..b00f40f --- /dev/null +++ b/src/Collection/Sort/Desc.php @@ -0,0 +1,15 @@ +getWeight() - $a->getWeight(); + } +} diff --git a/src/Collection/Sorter.php b/src/Collection/Sorter.php deleted file mode 100644 index 87e73a8..0000000 --- a/src/Collection/Sorter.php +++ /dev/null @@ -1,21 +0,0 @@ -toArray(); - - usort($nodes, $callback); - - $collection->purge(); - - foreach ($nodes as $node) { - $collection->addNode($node); - } - } -} diff --git a/src/Counter/CounterInterface.php b/src/Counter/CounterInterface.php new file mode 100644 index 0000000..62c3705 --- /dev/null +++ b/src/Counter/CounterInterface.php @@ -0,0 +1,10 @@ + + */ + private array $counter = []; + + public function next(string $name = 'default', int $start = 0): int + { + if (!isset($this->counter[$name])) { + $this->counter[$name] = $start; + } + + $next = $this->counter[$name]; + ++$this->counter[$name]; + + return $next; + } +} diff --git a/src/FrequencyRandomThrottler.php b/src/FrequencyRandomThrottler.php new file mode 100644 index 0000000..07dadf1 --- /dev/null +++ b/src/FrequencyRandomThrottler.php @@ -0,0 +1,32 @@ +isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $total = \count($collection); + $lowerKey = (int) ceil($this->threshold * $total); + $higherKey = $lowerKey + (1 < $total ? 1 : 0); + $probability = mt_rand() / mt_getrandmax(); + $key = $this->frequency >= $probability ? mt_rand(1, $lowerKey) : mt_rand($higherKey, $total); + + return $collection->get($key - 1); + } +} diff --git a/src/MultipleThrottler.php b/src/MultipleThrottler.php new file mode 100644 index 0000000..427d8f6 --- /dev/null +++ b/src/MultipleThrottler.php @@ -0,0 +1,40 @@ +throttlers[$throttler::class] = $throttler; + } + } + + public function pick(CollectionInterface $collection, array $context = []): NodeInterface + { + if (!isset($context['throttler'])) { + throw new \RuntimeException('Required parameter "throttler" is missing.'); + } + + if (!isset($this->throttlers[$context['throttler']])) { + throw new \RuntimeException(sprintf('Throttler "%s" is undefined.', $context['throttler'])); + } + + if (!class_exists($context['throttler']) || !is_a($context['throttler'], ThrottlerInterface::class, true)) { + throw new \UnexpectedValueException(sprintf('Throttler must be a class that exists and implements "%s" interface, "%s" given.', ThrottlerInterface::class, get_debug_type($context['throttler']))); + } + + /** @var ThrottlerInterface $throttler */ + $throttler = $this->throttlers[$context['throttler']]; + + return $throttler->pick($collection, $context); + } +} diff --git a/src/RandomThrottler.php b/src/RandomThrottler.php new file mode 100644 index 0000000..ee593fe --- /dev/null +++ b/src/RandomThrottler.php @@ -0,0 +1,22 @@ +isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $key = mt_rand(0, \count($collection) - 1); + + return $collection->get($key); + } +} diff --git a/src/RoundRobinThrottler.php b/src/RoundRobinThrottler.php new file mode 100644 index 0000000..b270ed9 --- /dev/null +++ b/src/RoundRobinThrottler.php @@ -0,0 +1,29 @@ +isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $counter = $context['counter'] ?? spl_object_hash($collection); + $key = $this->counter->next($counter) % \count($collection); + + return $collection->get($key); + } +} diff --git a/src/SmoothWeightedRoundRobinThrottler.php b/src/SmoothWeightedRoundRobinThrottler.php new file mode 100644 index 0000000..5d7feb4 --- /dev/null +++ b/src/SmoothWeightedRoundRobinThrottler.php @@ -0,0 +1,50 @@ +> + */ + private array $weights = []; + /** + * @var array> + */ + private array $currentWeights = []; + + public function pick(CollectionInterface $collection, array $context = []): NodeInterface + { + if ($collection->isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $counter = $context['counter'] ?? spl_object_hash($collection); + + if (!isset($this->weights[$counter]) || !isset($this->currentWeights[$counter])) { + foreach ($collection as $key => $node) { + if (0 == $node->getWeight()) { + throw new \RuntimeException('All nodes in the collection must be weighted.'); + } + + $this->weights[$counter][$key] = $this->currentWeights[$counter][$key] = $node->getWeight(); + } + } + + uasort($this->currentWeights[$counter], static fn (int $a, int $b): int => $a <=> $b); + $sumWeights = array_sum($this->weights[$counter]); + $maxCurrentWeightKey = array_key_last($this->currentWeights[$counter]); + $this->currentWeights[$counter][$maxCurrentWeightKey] -= $sumWeights; + + foreach ($this->weights[$counter] as $key => $weight) { + $this->currentWeights[$counter][$key] += $weight; + } + + return $collection->get($maxCurrentWeightKey); + } +} diff --git a/src/Strategy/ClusterDetermineStrategy.php b/src/Strategy/ClusterDetermineStrategy.php deleted file mode 100644 index b902779..0000000 --- a/src/Strategy/ClusterDetermineStrategy.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ - private array $strategies = []; - /** - * @var array - */ - private array $clusterNames = []; - - public function __construct(ClusterSet ...$clusterSets) - { - foreach ($clusterSets as $key => $clusterSet) { - $this->strategies[$key] = $clusterSet->strategy; - - foreach ($clusterSet->clusterNames as $clusterName) { - if (isset($this->clusterNames[$clusterName])) { - throw new \InvalidArgumentException(sprintf('Cluster "%s" has already been added.', $clusterName)); - } - - $this->clusterNames[$clusterName] = $key; - } - } - } - - public function getNode(CollectionInterface $collection, array $context = []): Node - { - if (!isset($context['cluster_name'])) { - throw new \LogicException('Required parameter "cluster_name" is missing.'); - } - - if (!isset($this->clusterNames[$context['cluster_name']])) { - throw new \LogicException(sprintf('Cluster name "%s" is undefined.', $context['cluster_name'])); - } - - /** @var StrategyInterface $strategy */ - $strategy = $this->strategies[$this->clusterNames[$context['cluster_name']]]; - - return $strategy->getNode($collection, $context); - } -} diff --git a/src/Strategy/ClusterSet.php b/src/Strategy/ClusterSet.php deleted file mode 100644 index ca6b7b2..0000000 --- a/src/Strategy/ClusterSet.php +++ /dev/null @@ -1,20 +0,0 @@ -isEmpty()) { - throw new EmptyCollectionException(); - } - - $total = \count($collection); - $low = (int) ceil($this->depth * $total); - $high = $low + ((1 < $total) ? 1 : 0); - - $index = $this->isChance($this->frequency) ? mt_rand(1, $low) : mt_rand($high, $total); - - return $collection->getNode($index - 1); - } - - private function isChance(float $frequency): bool - { - return $frequency * 100 >= mt_rand(1, 100); - } -} diff --git a/src/Strategy/GcdCalculator.php b/src/Strategy/GcdCalculator.php deleted file mode 100644 index b17ab27..0000000 --- a/src/Strategy/GcdCalculator.php +++ /dev/null @@ -1,19 +0,0 @@ - - */ - private array $counter = []; - - public function __construct( - private int $start = 0, - ) { - } - - public function next(string $name = 'default'): int - { - if (!isset($this->counter[$name])) { - $this->counter[$name] = $this->start; - } - - return $this->counter[$name]++; - } -} diff --git a/src/Strategy/MultipleDynamicStrategy.php b/src/Strategy/MultipleDynamicStrategy.php deleted file mode 100644 index b6580f2..0000000 --- a/src/Strategy/MultipleDynamicStrategy.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ - private array $pool = []; - - public function __construct(StrategyInterface ...$strategies) - { - foreach ($strategies as $strategy) { - $this->pool[$strategy::class] = $strategy; - } - } - - public function getNode(CollectionInterface $collection, array $context = []): Node - { - if (!isset($context['strategy_name'])) { - throw new \LogicException('Required parameter "strategy_name" is missing.'); - } - - if (!class_exists($context['strategy_name']) || !is_a($context['strategy_name'], StrategyInterface::class, true)) { - throw new \LogicException(sprintf('Strategy must be a class that exists and implements "%s" interface, "%s" given.', StrategyInterface::class, get_debug_type($context['strategy_name']))); - } - - if (!isset($this->pool[$context['strategy_name']])) { - throw new \LogicException(sprintf('Strategy "%s" is undefined.', $context['strategy_name'])); - } - - /** @var StrategyInterface $strategy */ - $strategy = $this->pool[$context['strategy_name']]; - - return $strategy->getNode($collection, $context); - } -} diff --git a/src/Strategy/RandomStrategy.php b/src/Strategy/RandomStrategy.php deleted file mode 100644 index ff47033..0000000 --- a/src/Strategy/RandomStrategy.php +++ /dev/null @@ -1,23 +0,0 @@ -isEmpty()) { - throw new EmptyCollectionException(); - } - - $index = mt_rand(0, \count($collection) - 1); - - return $collection->getNode($index); - } -} diff --git a/src/Strategy/RoundRobinStrategy.php b/src/Strategy/RoundRobinStrategy.php deleted file mode 100644 index f7ec867..0000000 --- a/src/Strategy/RoundRobinStrategy.php +++ /dev/null @@ -1,28 +0,0 @@ -isEmpty()) { - throw new EmptyCollectionException(); - } - - $index = $this->counter->next($context['counter_name'] ?? self::class) % \count($collection); - - return $collection->getNode($index); - } -} diff --git a/src/Strategy/SmoothWeightedRoundRobinStrategy.php b/src/Strategy/SmoothWeightedRoundRobinStrategy.php deleted file mode 100644 index c31446e..0000000 --- a/src/Strategy/SmoothWeightedRoundRobinStrategy.php +++ /dev/null @@ -1,68 +0,0 @@ - - */ - private array $weights = []; - /** - * @var array - */ - private array $currentWeights = []; - - public function getNode(CollectionInterface $collection, array $context = []): Node - { - if ($collection->isEmpty()) { - throw new EmptyCollectionException(); - } - - if (!$collection->isWeighted()) { - throw new UnweightedCollectionException(); - } - - if (0 === \count($this->weights) || 0 === \count($this->currentWeights)) { - /** @var array $collection */ - foreach ($collection as $index => $node) { - $this->weights[$index] = $this->currentWeights[$index] = $node->weight; - } - } - - $maxCurrentWeightIndex = $this->getMaxCurrentWeightIndex(); - - $this->recalculateCurrentWeights($maxCurrentWeightIndex); - - return $collection->getNode($maxCurrentWeightIndex); - } - - private function getMaxCurrentWeightIndex(): int - { - $maxCurrentWeight = max($this->currentWeights); - - if (false === $index = array_search($maxCurrentWeight, $this->currentWeights, true)) { - throw new \LogicException('Cannot find max current weight index.'); - } - - return $index; - } - - private function recalculateCurrentWeights(int $maxCurrentWeightIndex): void - { - $recalculatedCurrentWeight = $this->currentWeights[$maxCurrentWeightIndex] - array_sum($this->weights); - - $this->currentWeights[$maxCurrentWeightIndex] = $recalculatedCurrentWeight; - - foreach ($this->weights as $index => $weight) { - $this->currentWeights[$index] += $weight; - } - } -} diff --git a/src/Strategy/StrategyInterface.php b/src/Strategy/StrategyInterface.php deleted file mode 100644 index 1b11174..0000000 --- a/src/Strategy/StrategyInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -isEmpty()) { - throw new EmptyCollectionException(); - } - - if (!$collection->isWeighted()) { - throw new UnweightedCollectionException(); - } - - $currentWeight = 0; - - if (0 === $this->sumWeight) { - $this->sumWeight = $this->calculateSumWeight($collection); - } - - $randomWeight = mt_rand(1, $this->sumWeight); - - /** @var array $collection */ - foreach ($collection as $index => $node) { - $currentWeight += $node->weight; - - if ($randomWeight <= $currentWeight) { - return $collection->getNode($index); - } - } - - throw new \RuntimeException('You never will catch this exception.'); - } - - private function calculateSumWeight(CollectionInterface $collection): int - { - $sumWeight = 0; - - /** @var Node $node */ - foreach ($collection as $node) { - $sumWeight += $node->weight; - } - - return $sumWeight; - } -} diff --git a/src/Strategy/WeightedRoundRobinStrategy.php b/src/Strategy/WeightedRoundRobinStrategy.php deleted file mode 100644 index 60ebfb7..0000000 --- a/src/Strategy/WeightedRoundRobinStrategy.php +++ /dev/null @@ -1,85 +0,0 @@ -isEmpty()) { - throw new EmptyCollectionException(); - } - - if (!$collection->isWeighted()) { - throw new UnweightedCollectionException(); - } - - if (0 === $this->gcd) { - $this->gcd = $this->calculateGcd($collection); - } - - if (0 === $this->maxWeight) { - $this->maxWeight = $this->calculateMaxWeight($collection); - } - - while (true) { - $index = $this->counter->next($context['counter_name'] ?? self::class) % \count($collection); - - if (0 === $index) { - $this->currentWeight -= $this->gcd; - - if (0 >= $this->currentWeight) { - $this->currentWeight = $this->maxWeight; - } - } - - $node = $collection->getNode($index); - - if ($node->weight >= $this->currentWeight) { - return $node; - } - } - } - - private function calculateGcd(CollectionInterface $collection): int - { - $gcd = 0; - - /** @var Node $node */ - foreach ($collection as $node) { - $gcd = GcdCalculator::calculate($gcd, $node->weight); - } - - return $gcd; - } - - private function calculateMaxWeight(CollectionInterface $collection): int - { - $maxWeight = 0; - - /** @var Node $node */ - foreach ($collection as $node) { - if ($node->weight >= $maxWeight) { - $maxWeight = $node->weight; - } - } - - return $maxWeight; - } -} diff --git a/src/Throttler.php b/src/Throttler.php deleted file mode 100644 index c3430c3..0000000 --- a/src/Throttler.php +++ /dev/null @@ -1,22 +0,0 @@ -strategy->getNode($collection, $context); - } -} diff --git a/src/ThrottlerInterface.php b/src/ThrottlerInterface.php index 4e81068..0cd3393 100644 --- a/src/ThrottlerInterface.php +++ b/src/ThrottlerInterface.php @@ -5,9 +5,9 @@ namespace Orangesoft\Throttler; use Orangesoft\Throttler\Collection\CollectionInterface; -use Orangesoft\Throttler\Collection\Node; +use Orangesoft\Throttler\Collection\NodeInterface; interface ThrottlerInterface { - public function pick(CollectionInterface $collection, array $context = []): Node; + public function pick(CollectionInterface $collection, array $context = []): NodeInterface; } diff --git a/src/WeightedRandomThrottler.php b/src/WeightedRandomThrottler.php new file mode 100644 index 0000000..e23c2da --- /dev/null +++ b/src/WeightedRandomThrottler.php @@ -0,0 +1,36 @@ +isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $currentWeight = 0; + $sumWeight = array_sum(array_map(static fn (NodeInterface $node): int => $node->getWeight(), $collection->toArray())); + $randomWeight = mt_rand(1, $sumWeight); + + foreach ($collection as $node) { + if (0 == $node->getWeight()) { + throw new \RuntimeException('All nodes in the collection must be weighted.'); + } + + $currentWeight += $node->getWeight(); + + if ($randomWeight <= $currentWeight) { + return $node; + } + } + + throw new \RuntimeException('You never will catch this exception.'); + } +} diff --git a/src/WeightedRoundRobinThrottler.php b/src/WeightedRoundRobinThrottler.php new file mode 100644 index 0000000..eba560f --- /dev/null +++ b/src/WeightedRoundRobinThrottler.php @@ -0,0 +1,56 @@ +isEmpty()) { + throw new \RuntimeException('Collection of nodes mustn\'t be empty.'); + } + + $gcdWeight = 0; + $maxWeight = 0; + $currentWeight = 0; + + foreach ($collection as $node) { + if (0 == $node->getWeight()) { + throw new \RuntimeException('All nodes in the collection must be weighted.'); + } + + $gcdWeight = gcd($gcdWeight, $node->getWeight()); + $maxWeight = max($maxWeight, $node->getWeight()); + } + + while (true) { + $counter = $context['counter'] ?? spl_object_hash($collection); + $key = $this->counter->next($counter) % \count($collection); + + if (0 == $key) { + $currentWeight -= $gcdWeight; + + if (0 >= $currentWeight) { + $currentWeight = $maxWeight; + } + } + + $node = $collection->get($key); + + if ($node->getWeight() >= $currentWeight) { + return $node; + } + } + } +} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/Collection/ClusterTest.php b/tests/Collection/ClusterTest.php deleted file mode 100644 index 4c65ecf..0000000 --- a/tests/Collection/ClusterTest.php +++ /dev/null @@ -1,35 +0,0 @@ -assertSame($node, $cluster->balance($throttler)); - } -} diff --git a/tests/Collection/CollectionTest.php b/tests/Collection/CollectionTest.php deleted file mode 100644 index 0b26efb..0000000 --- a/tests/Collection/CollectionTest.php +++ /dev/null @@ -1,171 +0,0 @@ -assertCount(4, $collection); - } - - public function testGetNode(): void - { - $nodes = [ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]; - - $collection = new Collection($nodes); - - $this->assertSame($nodes[2], $collection->getNode(2)); - } - - public function testHasNode(): void - { - $node = new Node('node1'); - - $collection = new Collection([ - $node, - ]); - - $this->assertTrue($collection->hasNode($node)); - } - - public function testRemoveNode(): void - { - $node = new Node('node1'); - - $collection = new Collection([ - $node, - ]); - - $collection->removeNode($node); - - $this->assertFalse($collection->hasNode($node)); - } - - public function testReindex(): void - { - $nodes = [ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]; - - $collection = new Collection($nodes); - - $collection->removeNode($nodes[0]); - - $this->assertSame($nodes[1], $collection->getNode(0)); - } - - public function testPurge(): void - { - $collection = new Collection([ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]); - - $collection->purge(); - - $this->assertCount(0, $collection); - } - - public function testWeightedCollection(): void - { - $collection = new Collection([ - new Node('node1', 5), - new Node('node2', 1), - new Node('node3', 1), - ]); - - $this->assertTrue($collection->isWeighted()); - } - - public function testUnweightedCollection(): void - { - $collection = new Collection([ - new Node('node1', 5), - new Node('node2', 0), - new Node('node3', 0), - ]); - - $this->assertFalse($collection->isWeighted()); - } - - public function testEmpty(): void - { - $collection = new Collection(); - - $this->assertTrue($collection->isEmpty()); - } - - public function testNotEmpty(): void - { - $collection = new Collection([ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]); - - $this->assertFalse($collection->isEmpty()); - } - - public function testToArray(): void - { - $collection = new Collection([ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]); - - $this->assertIsArray($collection->toArray()); - $this->assertCount(3, $collection->toArray()); - } - - public function testCountable(): void - { - $collection = new Collection([ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]); - - $this->assertCount(3, $collection); - } - - public function testIterable(): void - { - $expectedNodes = [ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]; - - $collection = new Collection($expectedNodes); - - $this->assertIsIterable($collection); - - $actualNodes = iterator_to_array($collection); - - $this->assertSame($expectedNodes[0], $actualNodes[0]); - $this->assertSame($expectedNodes[1], $actualNodes[1]); - $this->assertSame($expectedNodes[2], $actualNodes[2]); - } -} diff --git a/tests/Collection/SorterTest.php b/tests/Collection/SorterTest.php deleted file mode 100644 index b5a28a8..0000000 --- a/tests/Collection/SorterTest.php +++ /dev/null @@ -1,53 +0,0 @@ -sort($collection, new Asc()); - - $this->assertSame('node1', $collection->getNode(0)->name); - $this->assertSame('node2', $collection->getNode(1)->name); - $this->assertSame('node3', $collection->getNode(2)->name); - $this->assertSame('node4', $collection->getNode(3)->name); - } - - public function testSortDesc(): void - { - $collection = new Collection([ - new Node('node2', 8), - new Node('node3', 16), - new Node('node1', 4), - new Node('node4', 32), - ]); - - $sorter = new Sorter(); - - $sorter->sort($collection, new Desc()); - - $this->assertSame('node4', $collection->getNode(0)->name); - $this->assertSame('node3', $collection->getNode(1)->name); - $this->assertSame('node2', $collection->getNode(2)->name); - $this->assertSame('node1', $collection->getNode(3)->name); - } -} diff --git a/tests/Strategy/ClusterDetermineStrategyTest.php b/tests/Strategy/ClusterDetermineStrategyTest.php deleted file mode 100644 index f300eb6..0000000 --- a/tests/Strategy/ClusterDetermineStrategyTest.php +++ /dev/null @@ -1,37 +0,0 @@ -assertSame($expectedNodes[0], $strategy->getNode($collection, ['cluster_name' => 'cluster1'])); - $this->assertSame($expectedNodes[1], $strategy->getNode($collection, ['cluster_name' => 'cluster1'])); - $this->assertSame($expectedNodes[2], $strategy->getNode($collection, ['cluster_name' => 'cluster1'])); - } -} diff --git a/tests/Strategy/FrequencyRandomStrategyTest.php b/tests/Strategy/FrequencyRandomStrategyTest.php deleted file mode 100644 index c09c760..0000000 --- a/tests/Strategy/FrequencyRandomStrategyTest.php +++ /dev/null @@ -1,48 +0,0 @@ - new Node('node1'), - 'node2' => new Node('node2'), - 'node3' => new Node('node3'), - ]; - - $collection = new Collection($nodes); - - $strategy = new FrequencyRandomStrategy(frequency: 0.8, depth: 0.2); - - $indexes = []; - - for ($i = 0; $i < 1000; ++$i) { - $node = $strategy->getNode($collection); - - if (!isset($indexes[$node->name])) { - $indexes[$node->name] = 0; - } - - ++$indexes[$node->name]; - } - - $this->assertCount(3, $indexes); - - foreach ($indexes as $count) { - $this->assertGreaterThan(0, $count); - } - - $this->assertEquals(1000, array_sum($indexes)); - $this->assertGreaterThan($indexes['node2'], $indexes['node1']); - $this->assertGreaterThan($indexes['node3'], $indexes['node1']); - } -} diff --git a/tests/Strategy/GcdCalculatorTest.php b/tests/Strategy/GcdCalculatorTest.php deleted file mode 100644 index 0ee8db7..0000000 --- a/tests/Strategy/GcdCalculatorTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertEquals(3, $gcd); - } -} diff --git a/tests/Strategy/InMemoryCounterTest.php b/tests/Strategy/InMemoryCounterTest.php deleted file mode 100644 index 093a2e2..0000000 --- a/tests/Strategy/InMemoryCounterTest.php +++ /dev/null @@ -1,21 +0,0 @@ -assertEquals(5, $counter->next('a')); - $this->assertEquals(5, $counter->next('b')); - $this->assertEquals(6, $counter->next('a')); - $this->assertEquals(6, $counter->next('b')); - } -} diff --git a/tests/Strategy/MultipleDynamicStrategyTest.php b/tests/Strategy/MultipleDynamicStrategyTest.php deleted file mode 100644 index 33e1c58..0000000 --- a/tests/Strategy/MultipleDynamicStrategyTest.php +++ /dev/null @@ -1,36 +0,0 @@ -assertSame($expectedNodes[0], $strategy->getNode($collection, ['strategy_name' => RoundRobinStrategy::class])); - $this->assertSame($expectedNodes[1], $strategy->getNode($collection, ['strategy_name' => RoundRobinStrategy::class])); - $this->assertSame($expectedNodes[2], $strategy->getNode($collection, ['strategy_name' => RoundRobinStrategy::class])); - } -} diff --git a/tests/Strategy/RandomStrategyTest.php b/tests/Strategy/RandomStrategyTest.php deleted file mode 100644 index c306bed..0000000 --- a/tests/Strategy/RandomStrategyTest.php +++ /dev/null @@ -1,46 +0,0 @@ - new Node('node1'), - 'node2' => new Node('node2'), - 'node3' => new Node('node3'), - ]; - - $collection = new Collection($nodes); - - $strategy = new RandomStrategy(); - - $indexes = []; - - for ($i = 0; $i < 1000; ++$i) { - $node = $strategy->getNode($collection); - - if (!isset($indexes[$node->name])) { - $indexes[$node->name] = 0; - } - - ++$indexes[$node->name]; - } - - $this->assertCount(3, $indexes); - - foreach ($indexes as $count) { - $this->assertGreaterThan(0, $count); - } - - $this->assertEquals(1000, array_sum($indexes)); - } -} diff --git a/tests/Strategy/RoundRobinStrategyTest.php b/tests/Strategy/RoundRobinStrategyTest.php deleted file mode 100644 index a54659c..0000000 --- a/tests/Strategy/RoundRobinStrategyTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ - private array $expectedNodes; - - protected function setUp(): void - { - $this->expectedNodes = [ - new Node('node1'), - new Node('node2'), - new Node('node3'), - ]; - } - - public function testRoundRobin(): InMemoryCounter - { - $counter = new InMemoryCounter(start: 0); - $strategy = new RoundRobinStrategy($counter); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[1], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[2], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - - return $counter; - } - - /** - * @depends testRoundRobin - */ - public function testRoundRobinRestart(InMemoryCounter $counter): void - { - $strategy = new RoundRobinStrategy($counter); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[1], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[2], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - } -} diff --git a/tests/Strategy/SmoothWeightedRoundRobinStrategyTest.php b/tests/Strategy/SmoothWeightedRoundRobinStrategyTest.php deleted file mode 100644 index 744e09b..0000000 --- a/tests/Strategy/SmoothWeightedRoundRobinStrategyTest.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ - private array $expectedNodes; - - protected function setUp(): void - { - $this->expectedNodes = [ - new Node('node1', 5), - new Node('node2', 1), - new Node('node3', 1), - ]; - } - - public function testSmoothWeightedRoundRobin(): string - { - $strategy = new SmoothWeightedRoundRobinStrategy(); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[1], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - - return serialize($strategy); - } - - /** - * @depends testSmoothWeightedRoundRobin - */ - public function testRestartSmoothWeightedRoundRobin(string $serializedStrategy): void - { - /** @var SmoothWeightedRoundRobinStrategy $strategy */ - $strategy = unserialize($serializedStrategy); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[2], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - } -} diff --git a/tests/Strategy/WeightedRandomStrategyTest.php b/tests/Strategy/WeightedRandomStrategyTest.php deleted file mode 100644 index b4b67fc..0000000 --- a/tests/Strategy/WeightedRandomStrategyTest.php +++ /dev/null @@ -1,48 +0,0 @@ - new Node('node1', 10), - 'node2' => new Node('node2', 5), - 'node3' => new Node('node3', 1), - ]; - - $collection = new Collection($nodes); - - $strategy = new WeightedRandomStrategy(); - - $indexes = []; - - for ($i = 0; $i < 1000; ++$i) { - $node = $strategy->getNode($collection); - - if (!isset($indexes[$node->name])) { - $indexes[$node->name] = 0; - } - - ++$indexes[$node->name]; - } - - $this->assertCount(3, $indexes); - - foreach ($indexes as $count) { - $this->assertGreaterThan(0, $count); - } - - $this->assertEquals(1000, array_sum($indexes)); - $this->assertGreaterThan($indexes['node2'], $indexes['node1']); - $this->assertGreaterThan($indexes['node3'], $indexes['node2']); - } -} diff --git a/tests/Strategy/WeightedRoundRobinStrategyTest.php b/tests/Strategy/WeightedRoundRobinStrategyTest.php deleted file mode 100644 index 185cde6..0000000 --- a/tests/Strategy/WeightedRoundRobinStrategyTest.php +++ /dev/null @@ -1,56 +0,0 @@ - - */ - private array $expectedNodes; - - protected function setUp(): void - { - $this->expectedNodes = [ - new Node('node1', 5), - new Node('node2', 1), - new Node('node3', 1), - ]; - } - - public function testWeightedRoundRobin(): InMemoryCounter - { - $counter = new InMemoryCounter(start: 0); - $strategy = new WeightedRoundRobinStrategy($counter); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - - return $counter; - } - - /** - * @depends testWeightedRoundRobin - */ - public function testRestartWeightedRoundRobin(InMemoryCounter $counter): void - { - $strategy = new WeightedRoundRobinStrategy($counter); - $collection = new Collection($this->expectedNodes); - - $this->assertSame($this->expectedNodes[1], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[2], $strategy->getNode($collection)); - $this->assertSame($this->expectedNodes[0], $strategy->getNode($collection)); - } -} diff --git a/tests/ThrottlerTest.php b/tests/ThrottlerTest.php deleted file mode 100644 index 5161751..0000000 --- a/tests/ThrottlerTest.php +++ /dev/null @@ -1,29 +0,0 @@ -assertSame($node, $throttler->pick($collection)); - } -}