爲速度而生的 Laravel 框架
官網的介紹很簡潔,並且 lumen 確實也很簡單,我在調研了 lumen 相關組件(好比緩存,隊列,校驗,路由,中間件和最重要的容器)以後認爲已經可以知足我目前這個微服務的需求了。php
由於業務需求,須要在內網服務B中獲取到公網服務A中的數據,可是B服務並不能直接對接公網,因而須要開發一個relay 中起色來完成數據轉存和交互。css
安裝composerhtml
https://getcomposer.org/downl...java
# 注意php的環境變量 php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');" mv composer.phar /usr/local/bin/composer
安裝lumenmysql
配置 .envnginx
配置 Lumen 框架全部的配置信息都是存在 .env 文件中。一旦 Lumen 成功安裝,你同時也要 配置本地環境。 應用程序密鑰 在你安裝完 Lumen 後,首先須要作的事情是設置一個隨機字符串到應用程序密鑰。一般這個密鑰會有 32 字符長。 這個密鑰能夠被設置在 .env 配置文件中。若是你還沒將 .env.example 文件重命名爲 .env,那麼你如今應該 去設置下。若是應用程序密鑰沒有被設置的話,你的用戶 Session 和其它的加密數據都是不安全的!
配置nginx 和 php-fpmlaravel
配置nginx的servergit
server { listen 8080; server_name localhost; index index.php index.html index.htm; root /home/work/YOURPROJECT/public; error_page 404 /404.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { root /home/work/YOURPROJECT/public; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; #include fastcgi.conf; } }
初始化核心容器是 bootstrap/app.php 它作了幾件很是重要的事情github
$app->register(App\Providers\AppServiceProvider::class); $app->register(App\Providers\AuthServiceProvider::class); $app->register(App\Providers\EventServiceProvider::class); 在AppServiceProvider 裏還能一塊兒註冊多個provider // JWT $this->app->register(\Tymon\JWTAuth\Providers\LumenServiceProvider::class); // redis $this->app->register(\Illuminate\Redis\RedisServiceProvider::class); // 方便IDE追蹤代碼的Helper,由於laravel使用了大量的魔術方法和call方法以致於,對IDE的支持並不友好,強烈推薦開發環境安裝 $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class); // sentry $this->app->register(\Sentry\SentryLaravel\SentryLumenServiceProvider::class);
//localhost:8080/test 調用app/Http/Controllers/Controller.php的 test方法 $router->get("/test", ['uses' => "Controller@test"]); // 使用中間件進行用戶校驗 $router->group(['middleware' => 'auth:api'], function () use ($router) { $router->get('/auth/show', 'AuthController@getUser'); });
$app->configureMonologUsing(function(Monolog\Logger $monoLog) use ($app){ // 設置processor的extra日誌信息等級爲WARNING以上,而且不展現Facade類的相關信息 $monoLog->pushProcessor(new \Monolog\Processor\IntrospectionProcessor(Monolog\Logger::WARNING, ['Facade'])); // monolog 日誌發送到sentry $client = new Raven_Client(env('SENTRY_LARAVEL_DSN')); $handler = new Monolog\Handler\RavenHandler($client); $handler->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true, true)); $monoLog->pushHandler($handler); // 設置monolog 的日誌處理handler return $monoLog->pushHandler( (new Monolog\Handler\RotatingFileHandler( env('APP_LOG_PATH') ?: storage_path('logs/lumen.log'), 90, env('APP_LOG_LEVEL') ?: Monolog\Logger::DEBUG) )->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true)) ); });
由於業務中包含部分敏感數據,因此,數據在傳輸過程當中須要加密傳輸。選用了RSA非對稱加密。web
若是選擇密鑰是1024bit長的(openssl genrsa -out rsa_private_key.pem 1024),那麼支持加密的明文長度字節最多隻能是1024/8=128byte;
若是加密的padding填充方式選擇的是OPENSSL_PKCS1_PADDING(這個要佔用11個字節),那麼明文長度最多隻能就是128-11=117字節。若是超出,那麼這些openssl加解密函數會返回false。
openssl genrsa -out rsa_private_key.pem 1024
//生成原始 RSA私鑰文件
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem
//將原始 RSA私鑰轉換爲 pkcs8格式
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
<?php namespace App\Lib; class Rsa { private static $PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY----- xxxxxxxxxxxxx完整複製過來xxxxxxxxxxxxxxxxxxx -----END RSA PRIVATE KEY-----'; private static $PUBLIC_KEY = '-----BEGIN PUBLIC KEY----- xxxxxxxxxxxxx完整複製過來xxxxxxxxxxxxxxxxxxx -----END PUBLIC KEY-----'; /** * 獲取私鑰 * @return bool|resource */ private static function getPrivateKey() { $privateKey = self::$PRIVATE_KEY; return openssl_pkey_get_private($privateKey); } /** * 獲取公鑰 * @return bool|resource */ private static function getPublicKey() { $publicKey = self::$PUBLIC_KEY; return openssl_pkey_get_public($publicKey); } /** * 私鑰加密 * @param string $data * @return null|string */ public static function privateEncrypt($data = '') { if (!is_string($data)) { return null; } $EncryptStr = ''; foreach (str_split($data, 117) as $chunk) { openssl_private_encrypt($chunk, $encryptData, self::getPrivateKey()); $EncryptStr .= $encryptData; } return base64_encode($EncryptStr); } /** * 公鑰加密 * @param string $data * @return null|string */ public static function publicEncrypt($data = '') { if (!is_string($data)) { return null; } return openssl_public_encrypt($data,$encrypted,self::getPublicKey()) ? base64_encode($encrypted) : null; } /** * 私鑰解密 * @param string $encrypted * @return null */ public static function privateDecrypt($encrypted = '') { $DecryptStr = ''; foreach (str_split(base64_decode($encrypted), 128) as $chunk) { openssl_private_decrypt($chunk, $decryptData, self::getPrivateKey()); $DecryptStr .= $decryptData; } return $DecryptStr; } /** * 公鑰解密 * @param string $encrypted * @return null */ public static function publicDecrypt($encrypted = '') { if (!is_string($encrypted)) { return null; } return (openssl_public_decrypt(base64_decode($encrypted), $decrypted, self::getPublicKey())) ? $decrypted : null; } }
// 私鑰加密則公鑰解密,反之亦然 $data = \GuzzleHttp\json_encode($data); $EncryptData = Rsa::privateEncrypt($data); $data = Rsa::publicDecrypt($EncryptData);
use GuzzleHttp\Client; $client = new Client(); // 發送 post 請求 $response = $client->request( 'POST', $this->queryUrl, [ 'form_params' => [ 'req' => $EncryptData ] ]); $callback = $response->getBody()->getContents(); $callback = json_decode($callback, true);
// Send an asynchronous request. $request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org'); $promise = $client->sendAsync($request)->then(function ($response) { echo 'I completed! ' . $response->getBody(); }); $promise->wait();
它在guzzle的基礎上作了封裝,採用鏈式調用
$response = Zttp::withHeaders(['Fancy' => 'Pants'])->post($url, [ 'foo' => 'bar', 'baz' => 'qux', ]); $response->json(); // => [ // 'whatever' => 'was returned', // ]; $response->status(); // int $response->isOk(); // true / false #若是是guzzle 則須要更多的代碼 $client = new Client(); $response = $client->request('POST', $url, [ 'headers' => [ 'Fancy' => 'Pants', ], 'form_params' => [ 'foo' => 'bar', 'baz' => 'qux', ] ]); json_decode($response->getBody());
/** * Register container bindings for the application. * * @return void */ protected function registerLogBindings() { $this->singleton('Psr\Log\LoggerInterface', function () { // monologConfigurator 咱們在 bootstrap/app.php中已經初始化了 if ($this->monologConfigurator) { return call_user_func($this->monologConfigurator, new Logger('lumen')); } else { // 這裏new的 Logger 就是 Monolog 類 return new Logger('lumen', [$this->getMonologHandler()]); } }); }
$app->configureMonologUsing(function(Monolog\Logger $monoLog) use ($app){ $monoLog->pushProcessor(new \Monolog\Processor\IntrospectionProcessor(Monolog\Logger::WARNING, ['Facade'])); // monolog 日誌發送到sentry $client = new Raven_Client(env('SENTRY_LARAVEL_DSN')); $handler = new Monolog\Handler\RavenHandler($client); $handler->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true, true)); $monoLog->pushHandler($handler); return $monoLog->pushHandler( (new Monolog\Handler\RotatingFileHandler( env('APP_LOG_PATH') ?: storage_path('logs/lumen.log'), 90, env('APP_LOG_LEVEL') ?: Monolog\Logger::DEBUG) )->setFormatter(new \Monolog\Formatter\LineFormatter(null, null, true, true)) ); });
use Illuminate\Support\Facades\Log; Log::info(11); // [2019-01-09 14:25:47] lumen.INFO: 11 Log::error('error info', $exception->getMessage());
基本的使用就只有三步,詳情請參考官網文檔 數據庫遷移
# 1 初始化遷移文件 php artisan make:migration create_Flights_table # 2 自定義表結構 class CreateFlightsTable extends Migration { public function up() { Schema::create('flights', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('airline'); $table->timestamps(); }); } } # 3 執行遷移,執行遷移的庫是 .env 中配置好的 php artisan migrate
很推薦使用 migrate 來記錄數據庫,它的核心優點是:容許團隊簡單輕鬆的編輯並共享應用的數據庫表結構
php artisan migrate
就完成了 (線上同步配置文件可使用分佈式文件系統,好比Apollo)首先解決一個問題,爲何要使用Event+Listener 來處理業務?
在初始化lumen後,代碼中有Example示例 相關文件,更多內容能夠查看官方文檔
過去,你可能須要在服務器上爲每個調度任務去建立 Cron 入口。可是這種方式很快就會變得不友好,由於這些任務調度不在源代碼中,而且你每次都須要經過 SSH 連接登陸到服務器中才能增長 Cron 入口。
Laravel 命令行調度器容許你在 Laravel 中對命令調度進行清晰流暢的定義。且使用這個任務調度器時,你只須要在你的服務器上建立單個 Cron 入口接口。你的任務調度在 app/Console/Kernel.php 的 schedule 方法中進行定義。
這個單一入口就是在crontab中添加一行
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
這個 Cron 爲每分鐘執行一次 Laravel 的命令行調度器。當 schedule:run 命令被執行的時候,Laravel 會根據你的調度執行預約的程序。
而後在 app/Console/Kernel.php 中定義任何你想要執行的命令,腳本,代碼。
protected function schedule(Schedule $schedule) { // 調用一個閉包函數 $schedule->call(function () { event(new GetData()); })->cron("0 */6 * * *"); // 調用 Artisan 命令 $schedule->command('emails:send --force')->daily(); // 調度 隊列任務 分發任務到 "heartbeats" 隊列... $schedule->job(new Heartbeat, 'heartbeats')->everyMinute(); // 調用 Shell 命令 $schedule->exec('sh build.sh')->hourly(); // 甚至作閉包限制測試:若是給定的 Closure 返回結果爲 true,只要沒有其餘約束條件阻止任務運行,任務就會一直執行下去 $schedule->command('emails:send')->daily()->when(function () { return true; }); // 規定任務只能在一臺機器上執行 //爲了說明任務應該在單個服務器上運行,在定義調度任務時使用 onOneServer 方法。第一個獲取到任務的服務器會生成一個原子鎖,用來防止其餘服務器在同一時刻執行相同任務 ->onOneServer(); // 任務輸出到某個文件或發送到郵箱 ->sendOutputTo($filePath); ->emailOutputTo($email); }
<?php //qq郵件 return [ 'driver' => "smtp", 'host' => "smtp.qq.com", // 根據你的郵件服務提供商來填 'port' => "465", // 同上 'encryption' => "ssl", // 同上 通常是tls或ssl 'username' => 'xxx@qq.com', 'password' => 'xxx', // 在qq郵箱中,這個密碼是生成的校驗碼 'from' => [ 'address' => 'xxx@qq.com', 'name' => 'xxx', ], ];
$app->configure('mail');
$this->app->register(\Illuminate\Mail\MailServiceProvider::class); //註冊服務提供者
// 發送文本 $text = '<b>這裏是測試</b>'; Mail::raw($text, function($message) { $message->to('xxx@qiyi.com')->subject("test subject"); }); // 發送模板郵件, testMail 是模板的名字,建立在 resources/views/testMail.blade.php Mail::send('testMail', ["data" => $data, "count" => $count], function ($message) { $message->to(["xxx@qiyi.com", "xxx@qiyi.com"]) ->cc(["liguopu@qiyi.com"]) ->subject("test subject"); });
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>simple data</title> <style type="text/css"> /* gridtable */ table.gridtable { font-family: verdana,arial,sans-serif; font-size:14px; color:#333333; border-width: 1px; border-color: #666666; border-collapse: collapse; } table.gridtable th { border-width: 1px; padding: 5px; border-style: solid; border-color: #666666; background-color: #dedede; } table.gridtable td { border-width: 1px; padding: 5px; border-style: solid; border-color: #666666; background-color: #ffffff; } </style> </head> <body> <h2>數據</h2> <table class="gridtable"> <tr> <th>數據詳情</th> <th>數量</th> @foreach ($data as $key => $item) <th>{{ $key }}</th> @endforeach </tr> <tr> <td>data</td> <td>{{ count($data) }}</td> @foreach ($diffCB as $item) <td>{{ $item }}</td> @endforeach </tr> </table> </body> </html>
不開啓opcache ab -c 100 -n 1000 localhost:8002/phpinfo Benchmarking localhost (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.10.2 Server Hostname: localhost Server Port: 8002 Document Path: /test Document Length: 92827 bytes Concurrency Level: 100 Time taken for tests: 4.171 seconds Complete requests: 1000 Failed requests: 140 (Connect: 0, Receive: 0, Length: 140, Exceptions: 0) Write errors: 0 Total transferred: 92989847 bytes HTML transferred: 92826847 bytes Requests per second: 239.74 [#/sec] (mean) Time per request: 417.113 [ms] (mean) Time per request: 4.171 [ms] (mean, across all concurrent requests) Transfer rate: 21771.20 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.8 0 4 Processing: 29 394 74.6 388 628 Waiting: 27 392 74.6 385 625 Total: 32 394 74.2 388 629 Percentage of the requests served within a certain time (ms) 50% 388 66% 407 75% 445 80% 451 90% 479 95% 517 98% 557 99% 570 100% 629 (longest request)
==開啓opcache==
yum install php7.*-opcache (根據當前php版本作選擇) php -i | grep opcache.ini 修改 opcache.ini // 大部分維持默認值,少部分值能夠根據業務作調整 opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=64 opcache.max_accelerated_files=10000 opcache.validate_timestamps=0 opcache.save_comments=1 opcache.fast_shutdown=0
ab -c 100 -n 1000 localhost:8002/phpinfo Benchmarking localhost (be patient) ; Enable Zend OPcache extension module Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: nginx/1.10.2 Server Hostname: localhost Server Port: 8002 Document Path: /test Document Length: 93858 bytes Concurrency Level: 100 Time taken for tests: 0.657 seconds Complete requests: 1000 Failed requests: 298 (Connect: 0, Receive: 0, Length: 298, Exceptions: 0) Write errors: 0 Total transferred: 94021119 bytes HTML transferred: 93858119 bytes Requests per second: 1522.02 [#/sec] (mean) Time per request: 65.702 [ms] (mean) Time per request: 0.657 [ms] (mean, across all concurrent requests) Transfer rate: 139747.77 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.4 0 6 Processing: 15 61 15.8 54 119 Waiting: 10 61 15.9 54 119 Total: 19 61 15.9 54 121 Percentage of the requests served within a certain time (ms) 50% 54 66% 56 75% 62 80% 69 90% 89 95% 100 98% 108 99% 114 100% 121 (longest request)
能夠看到併發大概提高了10倍,達到了1522qps(固然這是沒有DB交互以及接口調用的簡單輸出響應測試),平均響應時間和數據傳輸速度提高了6-7倍。
在生產環境運行 composer dump-autoload --optimize
注意事項
數據傳輸量過大可能致使的問題
未完待續.....