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(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);
    • 加载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(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 的日志处理handlerreturn $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)));
    });
    
  • 配置文件 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

<?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;}
}
使用tip
// 私钥加密则公钥解密,反之亦然
$data = \GuzzleHttp\json_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 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);
  • guzzle支持 异步请求
    // 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();
  • 值的注意的是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('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()]);}});}
  • 因为monologConfigurator 我们在 bootstrap/app.php中已经初始化了,所以lumen实际实现的log类是 RotatingFileHandler(按日期分文件) 格式的log,里面还可以详细定义日志的格式,文件路径,日志等级等
  • 中间有一段 sentry部分的代码,含义是添加一个monolog日志handler,在发生日志信息记录时,同步将日志信息发送给sentry的服务器,sentry服务器的接收地址在 .env的 SENTRY_LARAVEL_DSN 中记录
    $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: 11Log::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/phpinfoBenchmarking 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 requestsServer Software:        nginx/1.10.2
Server Hostname:        localhost
Server Port:            8002Document Path:          /test
Document Length:        92827 bytesConcurrency 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] receivedConnection 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     629Percentage of the requests served within a certain time (ms)50%    38866%    40775%    44580%    45190%    47995%    51798%    55799%    570100%    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/phpinfoBenchmarking 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 requestsServer Software:        nginx/1.10.2
Server Hostname:        localhost
Server Port:            8002Document Path:          /test
Document Length:        93858 bytesConcurrency 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] receivedConnection 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     121Percentage of the requests served within a certain time (ms)50%     5466%     5675%     6280%     6990%     8995%    10098%    10899%    114100%    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加密失败
    • 请求超时
    • 数据库存储并发
    • 列队失败重试和堵塞
  • 数据操作日志监控和到达率监控

未完待续.....

基于 lumen 的微服务架构实践相关推荐

  1. 基于CSE的微服务架构实践-轻量级架构技术选型

    [摘要] 本文在前一篇"基于CSE的微服务架构实践-基础架构"基础上,介绍了使用CSE进行轻量级架构的技术选型参考.文末提供了基于JWT的微服务认证鉴权方案. 轻量级架构模式下,可 ...

  2. 基于CSE的微服务架构实践-Spring Boot技术栈选型

    [摘要] 本文在前一篇"基于CSE的微服务架构实践-基础架构"基础上,介绍了在Spring Boot中集成CSE的技术选型参考.本文介绍了Spring Boot集成CSE的基本原理 ...

  3. 基于CSE的微服务架构实践-Spring Cloud技术栈选型

    [摘要] 本文介绍了CSE和Spring Cloud的关系,在技术选型上的差异.介绍了Spring Cloud用户使用Spring Cloud物理多租和进行CSE开发的两种策略. 当Spring Cl ...

  4. 基于 Docker 的微服务架构实践

    http://dockone.io/article/4887 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 DevOps,也见证了 Do ...

  5. 日10亿级处理,基于云的微服务架构

    德比软件:基于云的微服务架构 作者:朱攀,德比软件架构师,同济大学研究生,2007 年 2 月加入德比软件(DerbySoft),拥有 10 年以上的软件架构和开发经验.目前主要负责公司数据对接平台的 ...

  6. 爱奇艺在 Dubbo 生态下的微服务架构实践

    作者 | 周晓军  爱奇艺中间件团队负责人 导读:本文整理自作者于 2020 年云原生微服务大会上的分享<爱奇艺在 Dubbo 生态下的微服务架构实践>,重点介绍了爱奇艺在 Dubbo.S ...

  7. 基于 Docker 的微服务架构

    基于 Docker 的微服务架构-分布式企业级实践 前言 Microservice 和 Docker 服务发现模式 客户端发现模式 Netflix-Eureka 服务端发现模式 Consul Etcd ...

  8. 基于SpringCloud的微服务架构演变史?

    系统架构演变概述 在公司业务初创时期,面对的主要问题是如何将一个想法变成实际的软件实现,在这个时候整个软件系统的架构并没有搞得那么复杂,为了快速迭代,整个软件系统就是由"App+后台服务&q ...

  9. dubbo 自定义路由_爱奇艺在 Dubbo 生态下的微服务架构实践

    作者 | 周晓军 爱奇艺中间件团队负责人 导读:本文整理自作者于 2020 年云原生微服务大会上的分享<爱奇艺在 Dubbo 生态下的微服务架构实践>,重点介绍了爱奇艺在 Dubbo.Se ...

最新文章

  1. 聊聊spring的ioc
  2. MyBatis使用in进行列表中数据的批量删除
  3. 知识图谱入门 , 知识问答
  4. [redis]redis概述
  5. windows 远程连接debian_免受版权困扰的远程控制软件,优秀!
  6. 对抗生成网络(Generative Adversarial Network, GAN)
  7. 代码的执行效率(3)--缓存与局部性 摘自赵劼老师的博客
  8. 《Android音视频开发》封面由你来投票
  9. 用户服务协议和隐私政策
  10. BP神经网络算法 原理讲解以及底层代码复现
  11. Three.js从入门到放弃
  12. centos8重启网卡服务
  13. pojo与entity
  14. 学习缓冲区溢出攻击的前提知识
  15. 继电器的过流过压保护(自恢复保险丝)
  16. JS与DOM的兼容性
  17. 汽车电子入门指南:总目录
  18. linux读苹果格式文件,mac os 如何读取 Linux ext4 格式的硬盘
  19. 智库时代杂志智库时代杂志社智库时代编辑部2022年第44期目录
  20. 周杰伦《11月的萧邦》夜曲歌词

热门文章

  1. object对象进行深拷贝
  2. 我的第一个Python随笔
  3. [BZOJ3456]城市规划
  4. Openssl verify命令
  5. NBUT 1116 Flandre's Passageway (LIS变形)
  6. pku1192 最优连通子集
  7. ghost系统优化、精简、封装,光盘制作所有工具下载!!!
  8. tempdb(转载)
  9. Java同步组件之CyclicBarrier,ReentrantLock
  10. 安装linux环境及相关包方法