资讯专栏INFORMATION COLUMN

基于 lumen 的微服务架构实践

hatlonely / 2645人阅读

摘要:现在的提供了一种更易于使用和维护的计划任务方式。注意事项建议开启这样会极大的加速类的加载。

lumen
为速度而生的 Laravel 框架

官网的介绍很简洁,而且 lumen 确实也很简单,我在调研了 lumen 相关组件(比如缓存,队列,校验,路由,中间件和最重要的容器)之后认为已经能够满足我目前这个微服务的需求了。

任务目标

因为业务需求,需要在内网服务B中获取到公网服务A中的数据,但是B服务并不能直接对接公网,于是需要开发一个relay 中转机来完成数据转存和交互。

任务列表

环境准备 【done】

RSA数据加密 【done】

guzzle请求封装 【done】

添加monolog日志【done】

数据库migrate【done】

Event和Listener的业务应用 【done】

Scheduler计划任务(基于crontab)【done】

Jobs和Queue业务应用

使用supervisor守护queue进程和java进程

添加sentry来获取服务日志信息和实现邮件报警

jwt用户身份校验

.env 文件的配置

可能的扩展 K8S docker

性能并发测试 【done】

环境准备

机器是centos6.8, 使用work用户, 安装 php(^7),mysql,nginx,redis

yum 安装的同学可以试试 https://www.softwarecollectio...

安装composer

https://getcomposer.org/downl...

# 注意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

安装lumen

composer global require "laravel/lumen-installer"

composer create-project --prefer-dist laravel/lumen YOURPROJECT

配置 .env

配置
Lumen 框架所有的配置信息都是存在 .env 文件中。一旦 Lumen 成功安装,你同时也要 配置本地环境。

应用程序密钥
在你安装完 Lumen 后,首先需要做的事情是设置一个随机字符串到应用程序密钥。通常这个密钥会有 32 字符长。 
这个密钥可以被设置在 .env 配置文件中。如果你还没将 .env.example 文件重命名为 .env,那么你现在应该
去设置下。如果应用程序密钥没有被设置的话,你的用户 Session 和其它的加密数据都是不安全的!

配置nginx 和 php-fpm

配置nginx的server

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;
    }
}

php-fpm的监听端口

推荐一篇文章:Nginx+Php-fpm运行原理详解

lumen 基础介绍

lumen的入口文件是 public/index.php,在nginx配置文件中已有体现

初始化核心容器是 bootstrap/app.php 它做了几件非常重要的事情

加载了 composer的 autoload 自动加载

创建容器并可以选择开启 Facades 和 Eloquent (建议都开启,非常方便)

Register Container Bindings:注册容器绑定 ExceptionHandler(后面monolog和sentry日志收集用到了) 和 ConsoleKernel(执行计划任务)

Register Middleware:注册中间件,例如auth验证: $app->routeMiddleware(["auth" => AppHttpMiddlewareAuthenticate::class,]);

注册Service Providers

$app->register(AppProvidersAppServiceProvider::class);
$app->register(AppProvidersAuthServiceProvider::class);
$app->register(AppProvidersEventServiceProvider::class);

在AppServiceProvider 里还能一起注册多个provider
// JWT
$this->app->register(TymonJWTAuthProvidersLumenServiceProvider::class);
// redis
$this->app->register(IlluminateRedisRedisServiceProvider::class);
// 方便IDE追踪代码的Helper,因为laravel使用了大量的魔术方法和call方法以至于,对IDE的支持并不友好,强烈推荐开发环境安装
$this->app->register(BarryvdhLaravelIdeHelperIdeHelperServiceProvider::class);
// sentry
$this->app->register(SentrySentryLaravelSentryLumenServiceProvider::class);

加载route文件 routes/web.php

//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");
});

还可以添加其他初始化控制的handler,比如说这个 monolog日志等级和格式,以及集成sentry的config

$app->configureMonologUsing(function(MonologLogger $monoLog) use ($app){
    // 设置processor的extra日志信息等级为WARNING以上,并且不展示Facade类的相关信息
    $monoLog->pushProcessor(new MonologProcessorIntrospectionProcessor(MonologLogger::WARNING, ["Facade"]));

    // monolog 日志发送到sentry
    $client = new Raven_Client(env("SENTRY_LARAVEL_DSN"));
    $handler = new MonologHandlerRavenHandler($client);
    $handler->setFormatter(new MonologFormatterLineFormatter(null, null, true, true));
    $monoLog->pushHandler($handler);

    // 设置monolog 的日志处理handler
    return $monoLog->pushHandler(
        (new MonologHandlerRotatingFileHandler(
            env("APP_LOG_PATH") ?: storage_path("logs/lumen.log"),
            90,
            env("APP_LOG_LEVEL") ?: MonologLogger::DEBUG)
        )->setFormatter(new MonologFormatterLineFormatter(null, null, true, true))
    );
});

配置文件 config/ 和 .env 文件

其他目录文件用到时再具体说明

RSA数据加密

因为业务中包含部分敏感数据,所以,数据在传输过程中需要加密传输。选用了RSA非对称加密。

借鉴了 PHP 使用非对称加密算法(RSA)

但由于传输数据量较大,加密时会报错,所以采用了分段加密连接和分段解密
php使用openssl进行Rsa长数据加密(117)解密(128)

如果选择密钥是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

使用tip
// 私钥加密则公钥解密,反之亦然
$data = GuzzleHttpjson_encode($data);
$EncryptData = Rsa::privateEncrypt($data);
$data = Rsa::publicDecrypt($EncryptData);
guzzle使用

安装超简单 composer require guzzlehttp/guzzle:~6.0

guzzle 支持PSR-7 http://docs.guzzlephp.org/en/...

官网的示例也很简单,发个post自定义参数的例子

    use GuzzleHttpClient;
    
    $client = new Client();
    // 发送 post 请求
    $response = $client->request(
        "POST", $this->queryUrl, [
        "form_params" => [
            "req" => $EncryptData
        ]
    ]);
    
    $callback = $response->getBody()->getContents();
    $callback = json_decode($callback, true);

guzzle支持 异步请求

    // Send an asynchronous request.
    $request = new GuzzleHttpPsr7Request("GET", "http://httpbin.org");
    $promise = $client->sendAsync($request)->then(function ($response) {
        echo "I completed! " . $response->getBody();
    });
    $promise->wait();

值的注意的是github上有一个很好玩的项目 https://github.com/kitetail/zttp

它在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());
monolog日志

在LaravelLumenApplication 中会初始化执行

    /**
     * Register container bindings for the application.
     *
     * @return void
     */
    protected function registerLogBindings()
    {
        $this->singleton("PsrLogLoggerInterface", 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()]);
            }
        });
    }

因为monologConfigurator 我们在 bootstrap/app.php中已经初始化了,所以lumen实际实现的log类是 RotatingFileHandler(按日期分文件) 格式的log,里面还可以详细定义日志的格式,文件路径,日志等级等

中间有一段 sentry部分的代码,含义是添加一个monolog日志handler,在发生日志信息记录时,同步将日志信息发送给sentry的服务器,sentry服务器的接收地址在 .env的 SENTRY_LARAVEL_DSN 中记录

    $app->configureMonologUsing(function(MonologLogger $monoLog) use ($app){
        $monoLog->pushProcessor(new MonologProcessorIntrospectionProcessor(MonologLogger::WARNING, ["Facade"]));
    
        // monolog 日志发送到sentry
        $client = new Raven_Client(env("SENTRY_LARAVEL_DSN"));
        $handler = new MonologHandlerRavenHandler($client);
        $handler->setFormatter(new MonologFormatterLineFormatter(null, null, true, true));
        $monoLog->pushHandler($handler);
    
        return $monoLog->pushHandler(
            (new MonologHandlerRotatingFileHandler(
                env("APP_LOG_PATH") ?: storage_path("logs/lumen.log"),
                90,
                env("APP_LOG_LEVEL") ?: MonologLogger::DEBUG)
            )->setFormatter(new MonologFormatterLineFormatter(null, null, true, true))
        );
    });

准备动作完成后使用方法就很简单了

    use IlluminateSupportFacadesLog;
    
    Log::info(11);
    // [2019-01-09 14:25:47] lumen.INFO: 11
    Log::error("error info", $exception->getMessage());
数据库migrate

基本的使用就只有三步,详情请参考官网文档 数据库迁移

# 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 来记录数据库,它的核心优势是:允许团队简单轻松的编辑并共享应用的数据库表结构

场景1:数据库迁移时,开发原本需要先从数据库导出表结构,然后在新的数据库上执行;现在只需要修改数据库连接参数,执行 php artisan migrate 就完成了 (线上同步配置文件可以使用分布式文件系统,比如Apollo)

场景2:需要alert 字段或索引时,也只需要更新迁移文件然后执行更新,因为代码全程记录了所有数据库的修改记录,日后查看相关数据库信息时也更加方便(相当于把sql.log文件放在了php代码中管理)

如果一个迁移文件执行后,内容做了修改,需要修改一下文件名称中的时间,不然执行不成功,因为在 migrations 表中已经记录该文件已同步完成的信息了

Event和Listener的业务应用

首先解决一个问题,为什么要使用Event+Listener 来处理业务?

Event事件应当作为Hook来使用,实现的是代码结构的解耦,尤其是当一个业务模块需要同时关联多个业务模块时,Event+Listener 的工具可以通过解耦代码使代码的可维护性增加,并且可以避免重复代码的出现。

在Listener 中可以通过 implements ShouldQueue 这个接口来实现异步队列执行,从而优化接口性能

转载一篇有详细内容的文章 Laravel 中的 Event 和事件的概念

在初始化lumen后,代码中有Example示例 相关文件,更多内容可以查看官方文档

AppEventsExampleEvent.php

AppListenersExampleListener.php

Appproviders/EventServiceProvider.php 配置触发关系

Scheduler计划任务

scheduler 的使用使开发摆脱了一种不好的开发方式:在各种机器上跑茫茫多的脚本,时间一长这种模式几乎不可维护,一旦发生交接时更是特别容易遗漏机器和脚本。这种传统的“简单”方式,毫无疑问会造成相当多的麻烦。

现在 laravel 的 scheduler 提供了一种更易于使用和维护的计划任务方式。

过去,你可能需要在服务器上为每一个调度任务去创建 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);
    }

还可以做一个安全的措施,本地备份数据库 Laravel定时任务备份数据库

性能测试

开启opcache

composer dump-autoload --optimize

不开启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

composer autoload 慢的主要原因在于来自对 PSR-0 和 PSR-4 的支持,加载器得到一个类名时需要到文件系统里查找对应的类文件位置,这导致了很大的性能损耗,当然这在我们开发时还是有用的,这样我们添加的新的类文件就能即时生效。 但是在生产模式下,我们想要最快的找到这些类文件,并加载他们。

composer dump-autoload --optimize 这个命令的本质是将 PSR-4/PSR-0 的规则转化为了 classmap 的规则, 因为 classmap 中包含了所有类名与类文件路径的对应关系,所以加载器不再需要到文件系统中查找文件了。可以从 classmap 中直接找到类文件的路径。

注意事项

建议开启 opcache , 这样会极大的加速类的加载。

php5.5 以后的版本中默认自带了 opcache 。

这个命令并没有考虑到当在 classmap 中找不到目标类时的情况,当加载器找不到目标类时,仍旧会根据PSR-4/PSR-0 的规则去文件系统中查找

高可用问题思考

数据传输量过大可能导致的问题

RSA加密失败

请求超时

数据库存储并发

列队失败重试和堵塞

数据操作日志监控和到达率监控

未完待续.....

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/29920.html

相关文章

  • Lumen---为速度而生的 Laravel 框架

    摘要:什么是官网是一个由组件搭建而成的微框架是当前最快的框架之一在什么时候使用专为微服务或者设计举个例子如果你的应用里面有部分业务逻辑的请求频率比较高就可以单独把这部分业务逻辑拿出来使用来构建一个小因为是对优化了框架的加载机制所以对资源的要求少很 什么是 Lumen?官网 lumen 是一个由 Laravel 组件搭建而成的微框架,是当前最快的 PHP 框架之一! 在什么时候使用 Lume...

    104828720 评论0 收藏0
  • 如何在Lumen中使用Elasticsearch

    摘要:之前受到这篇为你的站点插上的翅膀的启发就尝试在中引入,并完成中文索引。关于中文索引谷歌上关于中文搜索的文章有很多,例如这篇。中文索引中涉及的内容比较多,下次再用一个篇幅来分析。 如何在Lumen中使用Elasticsearch 前言 Lumen是基于Laravel核心组件的微框架,随着Laravel5的发布,目前版本也已经到5了。之前受到这篇为你的站点插上ElasticSearch...

    jubincn 评论0 收藏0
  • 网易容器云平台的微服务实践(一)

    摘要:本文是网易容器云平台的微服务化实践系列文章的第一篇。网易容器云平台的前身是网易应用自动部署平台,它能够利用云提供的基础设施,实现包括构建和部署一体化在内的整个应用生命周期管理。目前网易云容器服务团队以的方式管理着微服务,每周构建部署次数。 此文已由作者冯常健授权网易云社区发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 摘要:网易云容器平台期望能给实施了微服务架构的团队提供完...

    zhjx922 评论0 收藏0
  • 个推基于Docker和Kubernetes的微服务实践

    摘要:个推针对服务场景,基于和搭建了微服务框架,提高了开发效率。三容器化在微服务落地实践时我们选择了,下面将详细介绍个推基于的实践。 2016年伊始Docker无比兴盛,如今Kubernetes万人瞩目。在这个无比需要创新与速度的时代,由容器、微服务、DevOps构成的云原生席卷整个IT界。个推针对Web服务场景,基于OpenResty和Node.js搭建了微服务框架,提高了开发效率。在微服...

    yibinnn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<