为速度而生的 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
注意事项
数据传输量过大可能致使的问题
未完待续.....