git config user.name "用戶名" git config user.email "郵箱"
ssh-keygen -t rsa -f ~/.ssh/id_rsa.github -C "email"
新增並配置config文件php
touch ~/.ssh/confightml
在config文件裏添加以下內容(User表示你的用戶名)laravel
Host *.github.com IdentityFile ~/.ssh/id_rsa.github User test
pbcopy < ~/.ssh/id_rsa.github.pub eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_rsa.github
測試ssh key是否配置成功git
ssh -T git@github.comgithub
添加文件算法
git remote add origin git@github.com:zhuanxuhit/laravel-doc.git git push -u origin master
以rate limit爲例來使用swoole開發。json
思想很簡單,計算相鄰兩次請求之間的間隔,當速率大於Rate的時候,就拒絕請求。服務器
技能分析:swoole
代碼示例:網絡
<?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 } }
再次測試,可以發現很好的知足了要求。
隔離算法的問題很明顯,使用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。
因而就有了下面的第三個方法:最精確的隊列算法
思路上就是將請求入隊,記錄請求的時間,這樣就能夠判斷任意連續的多個請求,其是不是在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的邏輯抽離出來,做爲一個服務提供,這個之後講服務化的時候再作的。
令牌算法相似小米搶購,放量出來必定的票,當人想進來搶的時候,必須有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
原文地址:https://www.jianshu.com/p/7c16cb61b59d