swoole入門abc

1. 入門abc

1.1 github賬號添加

  • 第一步依然是配置git用戶名和郵箱
git config user.name "用戶名"
git config user.email "郵箱"
  • 生成ssh key時同時指定保存的文件名
ssh-keygen -t rsa -f ~/.ssh/id_rsa.github -C "email"
  • 新增并配置config文件

    touch ~/.ssh/config

在config文件里添加如下內容(User表示你的用戶名)

Host *.github.com
IdentityFile ~/.ssh/id_rsa.github
User test
  • 上傳key到github
  pbcopy < ~/.ssh/id_rsa.github.pub
  eval "$(ssh-agent -s)"
  ssh-add ~/.ssh/id_rsa.github
git remote add origin git@github.com:zhuanxuhit/laravel-doc.git
git push -u origin master

1.2 case:Rate Limit

Rate Limit

以rate limit為例來使用swoole開發。

1.2.1 最簡單的隔離算法

思想很簡單,計算相鄰兩次請求之間的間隔,當速率大于Rate的時候,就拒絕請求。

技能分析:

  1. swoole的swoole_http_server功能,監聽端口,等待客戶端請求
  2. 注冊回調,當請求到來的時候,處理請求

代碼示例:

<?php namespace Swoole\Rate;

class Simple {

    protected $http;

    protected $lastTime;

    protected $rate = 0.1;
    /**
     * Simple constructor.
     *
     * @param $port
     */
    public function __construct($port)
    {
        $this->http = new \swoole_http_server('0.0.0.0',$port);
        $this->http->on('request',array($this,'onRequest'));
    }

    public function onRequest( \swoole_http_request $request, \swoole_http_response $response )
    {
        $lastTime = $this->lastTime;
        $currentTime = microtime(true);

        if(($currentTime-$lastTime)<1/$this->rate){
           // deny
        }
        else {
            $this->lastTime = $currentTime;
            // access
        }
    }

    public function start()
    {
        $this->http->start();
    }
}

$simple = new Simple(9090);
$simple->start();

分析上面的代碼,我們發現會有什么問題?如果兩個請求同時進來,都讀到了lastTime,沒有被拒絕,但是這兩個請求本身是已經請求過快了。

這個疑問產生的原因是對于swoole的網絡處理模型不是很清晰,如果請求是串行處理的,那不會有什么問題?但是如果請求是并發處理,那多個請求可能讀到的是同一個時間戳,導致瞬間并發很大,出現問題。

首先來解決第一個問題:swoole是什么

swoole 是一個網絡通信框架,首要解決的問題是什么?通信問題,之后就是高性能這個話題了,高性能主要從3個方面考慮

高性能

1) I/O調度模型:同步阻塞I/O(BIO)還是非阻塞I/O(NIO)。

2) 序列化框架的選擇:文本協議、二進制協議或壓縮二進制協議。

3) 線程調度模型:串行調度還是并行調度,鎖競爭還是無鎖化算法。

swoole在IO模型上是使用異步阻塞IO實現,調度模型則是采用Reactor,簡單說就是有一個線程專門負責IO操作,當關心事件發生的時候,進行回調函數處理,具體分析見下一章。

通過修改代碼,使用ab test工具,我能夠簡單的模擬上面的討論到的并發問題:

public function onRequest( \swoole_http_request $request, \swoole_http_response $response )
    {
        $lastTime = $this->lastTime;
        $currentTime = microtime(true);
        $pid =($this->http->worker_pid);
        if(($currentTime-$lastTime)<1/$this->rate){
            
            echo "deny worker_pid: $pid lastTime:$lastTime currentTime:$currentTime\n";
        }
        else {
            $this->lastTime = $currentTime;
           
            echo "accept worker_pid: $pid lastTime:$lastTime currentTime:$currentTime\n";
        }
    }

測試腳本

ab -n10 -c5 http://0.0.0.0:9090/

測試結果:

accept worker_pid: 45674 lastTime: currentTime:1463470993.3306
accept worker_pid: 45675 lastTime: currentTime:1463470993.331
accept worker_pid: 45671 lastTime: currentTime:1463470993.3318
accept worker_pid: 45672 lastTime: currentTime:1463470993.3322
accept worker_pid: 45673 lastTime: currentTime:1463470993.333
deny worker_pid: 45674 lastTime:1463470993.3306 currentTime:1463470993.3344
deny worker_pid: 45673 lastTime:1463470993.333 currentTime:1463470993.3348
deny worker_pid: 45675 lastTime:1463470993.331 currentTime:1463470993.3351
deny worker_pid: 45671 lastTime:1463470993.3318 currentTime:1463470993.3352
deny worker_pid: 45672 lastTime:1463470993.3322 currentTime:1463470993.3354

可以很顯然的看到,并發請求來的時候,讀到的lastTime都是未設置過的

模擬出并發問題后,這個關于swoole中有進程模型也很好測試出來:

public function serverInfoDebug()
    {
        return json_encode(
            [
                'master_id' => $this->http->master_pid,//返回當前服務器主進程的PID。
                'manager_pid' => $this->http->manager_pid,//返回當前服務器管理進程的PID。
                'worker_id' => $this->http->worker_id,//得到當前Worker進程的編號,包括Task進程
                'worker_pid' => $this->http->worker_pid,//得到當前Worker進程的操作系統進程ID。與posix_getpid()的返回值相同。
            ]
        );
    }

啟動成功后會創建worker_num+2個進程。主進程+Manager進程+worker_num個Worker進程。

完整地址:
https://github.com/zhuanxuhit/php-recipes/blob/master/app/SwooleAbc/Rate/Simple.php

那回到上面的問題,怎么解決并發問題呢?在C++等語言中,很好解決這個問題,使用鎖,互斥訪問。

寫到這的時候,發現個問題,發現在回調中,每個worker在處理onRequest函數的時候,this都是一個新的,為什么呢?因為worker進程都是由Manager進程fork()出來的,自然數據是新的一份了。

現在要解決的問題變為:如何在swoole中實現多個進程的數據共享功能

可以看到https://github.com/swoole/swoole-src/issues/242
其中建議,可以通過使用swoole提供的swoole_table來做,代碼如下:

  public function onRequest( \swoole_http_request $request, \swoole_http_response $response )
    {
        $currentTime = microtime(true);
        $pid =($this->http->worker_pid);
        $this->table->lock();
        $lastTime = $this->table->get( 'lastTime' );
        $lastTime = $lastTime['lastTime'];
        if(($currentTime-$lastTime)<1/$this->rate){
            $this->table->unlock();
            //deny
        }
        else {
            $this->table->set( 'lastTime', [ 'lastTime' => $currentTime] );
            $this->table->unlock();
            // access
        }
    }

再次測試,能夠發現很好的滿足了要求。

1.2.2 最清晰的吊桶算法

隔離算法的問題很明顯,使用ab -n2 -c2 http://0.0.0.0:9090/,同時并發2個請求就被拒絕了,因此只計算了相鄰兩次的間隔,而沒有關注1s內的請求,因此一個改進思路就是以s為key,記錄時間戳下來。下面是實現

    public function onRequest( \swoole_http_request $request, \swoole_http_response $response )
    {
        $currentTime = time();
        $this->table->lock();
        $count = $this->table->get( (string)$currentTime );
//        (new Dumper)->dump($count);
        if($count){
            $count = $count['count'];
        }
        else {
            $count = 0;
        }

        if($count >$this->rate){
            $this->table->unlock();
            // deny
        }
        else {
            $this->table->set( (string)$currentTime, [ 'count' => $count + 1] );
            $this->table->unlock();
            //accept
        }
    }

由于每s的數據都記錄了,沒有過期,導致數據不斷增長,有問題,而且由于1s是分割的,不是連續的,必然會造成最開始腦圖中的bad case。

于是就有了下面的第三個方法:最精確的隊列算法

1.2.3 最精確的隊列算法

思路上就是將請求入隊,記錄請求的時間,這樣就可以判斷任意連續的多個請求,其是否是在1s之內了

首先看下這個算法思路:假設rate=5,當請求到來的時候,得到當前請求編號,然后減5得到index,然后判斷兩次請求之間的時間間隔,是否大于1s,如果大于則accept,否則deny

n-5 n-4 n-3 n-2 n-1 n n+1 n+2 n+3 n+4 n+5

現在來的請求是n,則去n-5,為什么是減5,因此rate是5,則當qps為6的時候就deny,因此需要判斷n-5到n這6個請求的間隔!

算法有了,下面就是在swoole中怎么實現隊列的問題了,這個隊列還需要在進程間共享。

我們可以使用swoole_table來實現,另外還需要一個計數器,給每個請求編號,實現如下:

// 每個請求過來后的是否判斷通過,這個操作必須要單點,串行,所以也就是說必須要加速
        $this->table->lock();
        $count = $this->counter->add(1);
        $bool = true;
        $currentCount = $count + 1;
        $previousCount = $count - $this->rate;
        if($currentCount<=$this->rate){
            $this->table->set( $count, [ 'timeStamp' => $currentTime ] );
            $this->table->unlock();
        }
        else {
            $previousTime = $this->table->get( $previousCount );
            if ( $currentTime - $previousTime['timeStamp'] > 1 ) {
                $this->table->set( $currentCount, [ 'timeStamp' => $currentTime ] );
                $this->table->unlock();
            } else {
                // 去除 deny
                $bool = false;
                $this->counter->sub( 1 );
                $this->table->unlock();
            }
        }

上面有一個核心點,之前一直沒有注意到的:對所有請求的處理都是需要互斥的,即是一個單點,處理完后才能轉發給真正的業務邏輯進行處理。

因此可以將Rate的邏輯抽離出來,作為一個服務提供,這個以后講服務化的時候再做的。

1.2.4 最傳統的令牌算法

令牌算法類似小米搶購,放量出來一定的票,當人想進來搶的時候,必須有F碼才能進行搶購,而票的放出是按一定速率產生的。

上面算法實現時,需要用到swoole的定時器功能,需要在OnWorkerStart的回調的時候使用

public function onWorkerStart(\swoole_server $server, $worker_id)
    {
        if($worker_id ==0){
            $server->tick( 1000/$this->rate, [$this,'addTicket'] );
        }
    }

而請求到來的時候,就是通過getTicket獲取資格,沒票的時候,直接返回false

完整的github地址:
https://github.com/zhuanxuhit/php-recipes/tree/master/app/SwooleAbc/Rate
Rate limit的介紹:http://www.bittiger.io/classpage/hfjPKuZaLxPLyL5iN
gitbook地址:
https://zhuanxuhit.gitbooks.io/swoole-abc/content/chapter1.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,890評論 18 139
  • 前文再續,就書接上一回,隨著與Server、TCP、Protocol的邂逅,Swoole終于迎來了自己的故事,今天...
    蝸牛淋雨閱讀 1,776評論 1 14
  • 在有了“自古套路留人心”一句話后,才詳細研究了網上針對渣男的慣用套路,招招全中,無一例外。他會翻看你的朋友圈,和你...
    木子小姐閱讀 282評論 0 0
  • 作者/凱勒漠 目錄:《狼鬼:覺醒》目錄 上一章:NO.8)塵封的記憶 血紅色的四周,偶爾飄著血紅色的“球”,上面是...
    初紀閱讀 390評論 0 1
  • 你看到了吧,我就是這么膽小的一個小女孩,看見游樂設施就渾身顫抖,懼怕打針所以抵觸所有的醫院和醫生,一只小蟲就足以把...
    高冷的小小櫻閱讀 276評論 0 1