当前位置:网站首页>Implementation of message queue system and underlying source code based on redis
Implementation of message queue system and underlying source code based on redis
2022-06-09 00:12:00 【weixin_ two billion forty-seven million six hundred and seventy】
Introduction to message queuing
A complete queue system consists of three components :
- queue (Queue)
- news (Message)
- Process (Worker)
The corresponding basic workflow is the producer ( Business code ) First push the message data to the queue , And then consume the message data in the queue through other processing processes , So as to realize the decoupling between producers and consumers . therefore , Message queuing is ideal for time-consuming operations that require asynchronous execution ( E.g. email sending 、 Upload files ), Or temporary high concurrency operations ( For example, seckill 、 Message push ), It is very effective for improving system performance and load , In especial PHP This language does not support concurrent programming by itself , It is the best choice for asynchronous programming .
Before demonstrating how to implement message queuing , Let's briefly introduce the above three components .
queue
Queues are actually linear data structures , This is what you are Data structure Has been described in detail in , This data structure has first in first out (FIFO) Characteristics , Therefore, it is very suitable for decoupling between producers and consumers , At the same time, it does not affect the execution sequence of business logic .
stay PHP in , You can use native array functions or SplQueue Class can easily implement data structures like queues , But here we introduce Redis, So you can also use Redis Self contained List the type To achieve .
We can improve the system performance through queue asynchronous implementation of the browse number update operation in the previous tutorial . To simplify the process , We go through post-views-increment To identify the queue name , The message data pushed to the queue passes through the article ID Are identified :
/ Article views +1
public function addViews(Post $post)
{
// Push message data to the queue , Handle database updates through asynchronous processes
Redis::rpush('post-views-increment', $post->id);
return ++$post->views;
}news
The so-called news , That is, the data pushed to the queue , It's usually a string , If it is a non string type , It can be converted to a string by serialization , After the processing process at the consumer gets the message data from the queue , It can be parsed , Complete the closed loop of business logic .
The producer or the message itself does not have to care about how the consumer processing process handles the message data , The processing process on the consumer side does not have to care who sent the message , The three are completely decoupled , But it also builds a bridge between producers and consumers through message data .
Message data can be passed inside the application , It can also be passed across applications , Cross application delivery usually requires the help of third-party Message Queuing Middleware , For example, based on Redis Implementation of the queue system 、RabbitMQ、Kafka、RocketMQ etc. .
In the example code above , We will be writing ID As message data .
Process
The consumer process is usually one or more memory resident processes , They either subscribe to or poll message queues , If the message queue is not empty , Then take out the message data and process it .
In order to simplify the process , We create a Artisan Command to simulate a memory resident polling process as a message processor :
php artisan make:command MockQueueWorkerAnd write its implementation code as follows :
<?php
namespace App\Console\Commands;
use App\Models\Post;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
class MockQueueWorker extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mock:queue-worker';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Mock Queue Worker';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info(' Listen message queue post-views-increment...');
while (true) {
// Get the message data from the queue
$postId = Redis::lpop('post-views-increment');
// Number of views of the current article +1, And stored in the corresponding Sorted Set Of score Field
if ($postId && Post::newQuery()->where('id', $postId)->increment('views')) {
Redis::zincrby('popular_posts', 1, $postId);
$this->info(" Update article #{$postId} Browse number ");
}
}
}
} Focus on handle Method , We go through while (true) Analog resident memory , Then keep polling post-views-increment queue , If there is an article ID data , Then take out and update the number of article views .
thus , We have implemented a simple message queue , Start the message handler :

Then visit any article http://redis.test/posts/1, You can see the task processing record of the queue in the queue processor window :

At the same time, you can see the updated number of views in the database , Prove that the queue message is processed successfully .
The above process is also Laravel The basic principle of the underlying implementation of the queue system , With this knowledge reserve , Let's look at Laravel The underlying implementation of message queuing will be much easier .
Laravel Implementation and use of queue system
Basic configuration
however ,Laravel Provides a more elegant Queue system Realization , We don't need to write queues manually 、 Implementation code of message and processing process , And support different queue system drivers , Include database 、Beanstalkd、Amazon SQS、Redis etc. , Here, of course, we take Redis For example .
To be in Laravel Project use Redis Implement the queue system , Just configure it Redis After connecting the information, the environment configuration file .env Medium QUEUE_CONNECTION The configuration value is adjusted to redis that will do :
QUEUE_CONNECTION=redis thus ,Laravel It can be based on config/queue.php Medium redis Configure and initialize the queue system :
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],Queue system service provider
stay Laravel When app starts , Will pass QueueServiceProvider To register the queue system related services to the service container :
public function register()
{
$this->registerManager();
$this->registerConnection();
$this->registerWorker();
$this->registerListener();
$this->registerFailedJobServices();
}
...
// Queue manager
protected function registerManager()
{
$this->app->singleton('queue', function ($app) {
return tap(new QueueManager($app), function ($manager) {
$this->registerConnectors($manager);
});
});
}
// Default queue connection , Here, it will be initialized to... According to the configuration value redis Connect
protected function registerConnection()
{
$this->app->singleton('queue.connection', function ($app) {
return $app['queue']->connection();
});
}
// Queue processor
protected function registerWorker()
{
$this->app->singleton('queue.worker', function ($app) {
$isDownForMaintenance = function () {
return $this->app->isDownForMaintenance();
};
return new Worker(
$app['queue'],
$app['events'],
$app[ExceptionHandler::class],
$isDownForMaintenance
);
});
}
// Queue listeners , Listen for queue events
protected function registerListener()
{
$this->app->singleton('queue.listener', function ($app) {
return new Listener($app->basePath());
});
}
// Failed task handling ( The default is based on the database )
protected function registerFailedJobServices()
{
$this->app->singleton('queue.failer', function ($app) {
$config = $app['config']['queue.failed'];
if (isset($config['driver']) && $config['driver'] === 'dynamodb') {
return $this->dynamoFailedJobProvider($config);
} elseif (isset($config['driver']) && $config['driver'] === 'database-uuids') {
return $this->databaseUuidFailedJobProvider($config);
} elseif (isset($config['table'])) {
return $this->databaseFailedJobProvider($config);
} else {
return new NullFailedJobProvider;
}
});
}RedisQueue Queue implementation
The underlying code design is similar to caching —— be based on QueueManager Manage queue system connections for different drivers , The final message push and receive are distributed to the corresponding queue system for processing according to the currently used queue driver , The configuration here uses Redis As a message system driver , So it will eventually pass RedisConnector Connect to RedisQueue To deal with :
/**
* Establish a queue connection
*
* @param array $config
* @return \Illuminate\Contracts\Queue\Queue
*/
public function connect(array $config)
{
return new RedisQueue(
$this->redis, $config['queue'],
$config['connection'] ?? $this->connection,
$config['retry_after'] ?? 60,
$config['block_for'] ?? null
);
} You can RedisQueue See the implementation method of pushing message data to the queue in push:
public function push($job, $data = '', $queue = null)
{
return $this->enqueueUsing(
$job,
$this->createPayload($job, $this->getQueue($queue), $data),
$queue,
null,
function ($payload, $queue) {
return $this->pushRaw($payload, $queue);
}
);
}
public function pushRaw($payload, $queue = null, array $options = [])
{
$this->getConnection()->eval(
LuaScripts::push(), 2, $this->getQueue($queue),
$this->getQueue($queue).':notify', $payload
);
return json_decode($payload, true)['id'] ?? null;
}Laravel Use The task class As the default format for message data , Because it is an object type , Therefore, serialization processing will be performed , The final push operation uses Lua The script by Reis RPUSH Command complete :
public static function push()
{
return <<<'LUA'
-- Push the job onto the queue...
redis.call('rpush', KEYS[1], ARGV[1])
-- Push a notification onto the "notify" queue...
redis.call('rpush', KEYS[2], 1)
LUA;
} The queue connection here is Redis, The default queue is default. Reading data from the message queue uses pop Method realization :
public function pop($queue = null)
{
$this->migrate($prefixed = $this->getQueue($queue));
if (empty($nextJob = $this->retrieveNextJob($prefixed))) {
return;
}
[$job, $reserved] = $nextJob;
if ($reserved) {
return new RedisJob(
$this->container, $this, $job,
$reserved, $this->connectionName, $queue ?: $this->default
);
}
}
...
protected function retrieveNextJob($queue, $block = true)
{
$nextJob = $this->getConnection()->eval(
LuaScripts::pop(), 3, $queue, $queue.':reserved', $queue.':notify',
$this->availableAt($this->retryAfter)
);
if (empty($nextJob)) {
return [null, null];
}
[$job, $reserved] = $nextJob;
if (! $job && ! is_null($this->blockFor) && $block &&
$this->getConnection()->blpop([$queue.':notify'], $this->blockFor)) {
return $this->retrieveNextJob($queue, false);
}
return [$job, $reserved];
} In the process of obtaining data Lua The script uses Redis LPOP Instructions , The specific code will not be posted , The queue connection is Redis, The default queue is default.
Although it seems that the underlying implementation is very complex , But the basic principle is the same as that we passed above Redis The native code implementation is consistent . Yes, of course ,Laravel It also supports some more complex operations , Such as delayed push 、 Batch processing, etc , You can study it yourself RedisQueue Understand the underlying details of the corresponding implementation source code in .
The message data
Laravel The message data in the queue system will be provided in the form of task classes , And make another layer of packaging for different drivers , So as to facilitate the unified processing of the bottom layer , about Redis Driven queue system , Finally, the obtained data will pass through RedisJob Return after encapsulation ,RedisJob The constructor for is shown below :
public function __construct(Container $container, RedisQueue $redis, $job, $reserved, $connectionName, $queue)
{
$this->job = $job;
$this->redis = $redis;
$this->queue = $queue;
$this->reserved = $reserved;
$this->container = $container;
$this->connectionName = $connectionName;
$this->decoded = $this->payload();
} among $job The corresponding is the task class instance that the business code pushes to the queue ,$this->payload() The task class load data after deserialization is returned in , The other fields are automatically obtained by the bottom layer according to the message queue configuration .
Asynchronous processing process
Laravel Multiple Artisan Command to process message queues , these Artisan The source code of the command is located in vendor/laravel/framework/src/Illuminate/Queue/Console Under the table of contents :

You can go through queue:work perhaps queue:listen Command to listen and process the data in the message queue , With queue:work For example , The corresponding source code is located in WorkCommand in , We focus on handle Method implementation
public function handle()
{
if ($this->downForMaintenance() && $this->option('once')) {
return $this->worker->sleep($this->option('sleep'));
}
$this->listenForEvents();
$connection = $this->argument('connection')
?: $this->laravel['config']['queue.default'];
$queue = $this->getQueue($connection);
return $this->runWorker(
$connection, $queue
);
} If the system is in maintenance mode , No queues are consumed , Otherwise call listenForEvents Method listens for queue events and outputs logs to the command line :
protected function listenForEvents()
{
$this->laravel['events']->listen(JobProcessing::class, function ($event) {
$this->writeOutput($event->job, 'starting');
});
$this->laravel['events']->listen(JobProcessed::class, function ($event) {
$this->writeOutput($event->job, 'success');
});
$this->laravel['events']->listen(JobFailed::class, function ($event) {
$this->writeOutput($event->job, 'failed');
$this->logFailedJob($event);
});
} Then get the current queue connection and the default queue from the queue configuration , The configuration here is Redis Queue connection , The default queue is default, After obtaining the queue system information , You can call runWorker Method to run the consumer processing process :
protected function runWorker($connection, $queue)
{
return $this->worker->setName($this->option('name'))
->setCache($this->cache)
->{$this->option('once') ? 'runNextJob' : 'daemon'}(
$connection, $queue, $this->gatherWorkerOptions()
);
} there $this->worker The corresponding is Laravel stay QueueServiceProvider Registered in queue.worker, namely Worker Class instance , If it is a one-time implementation ( adopt --once Option assignment ), Call Worker Class runNextJob Method :
public function runNextJob($connectionName, $queue, WorkerOptions $options)
{
$job = $this->getNextJob(
$this->manager->connection($connectionName), $queue
);
if ($job) {
return $this->runJob($job, $connectionName, $options);
}
$this->sleep($options->sleep);
} Here you can get the task data in the message queue getNextJob Method just calls the previous RedisQueue( The configuration here is Redis queue , Other drivers, and so on ) Of pop Method returns through RedisJob Encapsulated message data , And then call runJob Method to process the task class that represents the message data :
protected function runJob($job, $connectionName, WorkerOptions $options)
{
try {
return $this->process($connectionName, $job, $options);
} catch (Throwable $e) {
$this->exceptions->report($e);
$this->stopWorkerIfLostConnection($e);
}
} there process Method will call RedisJob Defined above fire Method executes the corresponding task logic ( The lower level calls Redis Encapsulate the processing methods on the task class ):
public function process($connectionName, $job, WorkerOptions $options)
{
try {
$this->raiseBeforeJobEvent($connectionName, $job);
$this->markJobAsFailedIfAlreadyExceedsMaxAttempts(
$connectionName, $job, (int) $options->maxTries
);
if ($job->isDeleted()) {
return $this->raiseAfterJobEvent($connectionName, $job);
}
$job->fire();
$this->raiseAfterJobEvent($connectionName, $job);
} catch (Throwable $e) {
$this->handleJobException($connectionName, $job, $options, $e);
}
} If it is not executed at one time , It calls Worker Class daemon Method :
public function daemon($connectionName, $queue, WorkerOptions $options)
{
if ($this->supportsAsyncSignals()) {
$this->listenForSignals();
}
$lastRestart = $this->getTimestampOfLastQueueRestart();
[$startTime, $jobsProcessed] = [hrtime(true) / 1e9, 0];
while (true) {
if (! $this->daemonShouldRun($options, $connectionName, $queue)) {
$status = $this->pauseWorker($options, $lastRestart);
if (! is_null($status)) {
return $this->stop($status);
}
continue;
}
$job = $this->getNextJob(
$this->manager->connection($connectionName), $queue
);
if ($this->supportsAsyncSignals()) {
$this->registerTimeoutHandler($job, $options);
}
if ($job) {
$jobsProcessed++;
$this->runJob($job, $connectionName, $options);
} else {
$this->sleep($options->sleep);
}
if ($this->supportsAsyncSignals()) {
$this->resetTimeoutHandler();
}
$status = $this->stopIfNecessary(
$options, $lastRestart, $startTime, $jobsProcessed, $job
);
if (! is_null($status)) {
return $this->stop($status);
}
}
} and runNextJob similar , Just wrapped in the outer layer while(true) Implement resident processes , And other code to ensure the robustness of the program .
A complete link for tasks to be sent and processed by analogy
I understand Laravel After the underlying implementation principle of the queue system , Let's look at how to use it in business code . Or take the update of article views as an example , According to the queue -> news -> The three components of the process are implemented in sequence , Easy to compare and understand .
For queue systems , adopt QUEUE_CONNECTION Configure the queue driver you want to use , It's configured to redis,Laravel The bottom layer will use RedisQueue This queue implements , There is no need to write extra code for the task .
Yes, of course , except Laravel In addition to the built-in queue driver , You can also refer to these built-in implementations to customize the queue system driver .
Then define a task class as the message data pushed to the queue system ,Laravel Provides make:job Artisan Command to quickly generate task classes :
php artisan make:job PostViewsIncrementIn fact, you can also use
Queue::pushRaw(string)Push message data in native string format to Redis queue , however Laravel The provided process does not know how to handle this message , So it is not generally done , If you define the processing logic for string format messages , It's not impossible .
Write the implementation code of this task class as follows , Migrate the article views update business code to handle Method :
<?php
namespace App\Jobs;
use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Redis;
class PostViewsIncrement implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public Post $post;
/**
* Create a new job instance.
*
* @param Post $post
*/
public function __construct(Post $post)
{
$this->post = $post;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if ($this->post->increment('views')) {
Redis::zincrby('popular_posts', 1, $this->post->id);
}
}
} After defining the task class , It can be accessed through the controller dispatch The helper function distributes the task class and pushes it to the queue system :
// Browse the article
public function show($id)
{
$post = $this->postRepo->getById($id);
// Distribute queue tasks
$this->dispatch(new PostViewsIncrement($post));
return "Show Post #{$post->id}, Views: {$post->views}";
} This function will eventually pass Illuminate\Bus\Dispatcher Of dispatch Method to distribute task classes :
public function dispatch($command)
{
return $this->queueResolver && $this->commandShouldBeQueued($command)
? $this->dispatchToQueue($command)
: $this->dispatchNow($command);
} If the incoming $command Parameter is an implementation of ShouldQueue An instance of an interface , Call dispatchToQueue Method to push it to the specified queue :
public function dispatchToQueue($command)
{
$connection = $command->connection ?? null;
$queue = call_user_func($this->queueResolver, $connection);
if (! $queue instanceof Queue) {
throw new RuntimeException('Queue resolver did not return a Queue implementation.');
}
if (method_exists($command, 'queue')) {
return $command->queue($queue, $command);
}
return $this->pushCommandToQueue($queue, $command);
} Here we parse to queue A variable is RedisQueue example , If the task class defines queue Method , Then use the code defined by this method to push the task class queue , Otherwise, call pushCommandToQueue Method push :
protected function pushCommandToQueue($queue, $command)
{
if (isset($command->queue, $command->delay)) {
return $queue->laterOn($command->queue, $command->delay, $command);
}
if (isset($command->queue)) {
return $queue->pushOn($command->queue, $command);
}
if (isset($command->delay)) {
return $queue->later($command->delay, $command);
}
return $queue->push($command);
} If there is no delay push setting , The task class is not set queue attribute , Call $queue->push($command) Method to push a task class to a queue , That's what we described above RedisQueue Upper push Method .
actually , Directly through
Queue::push(new PostViewsIncrement($post))You can also push task classes to Redis queue , But the use ofdispatchThe way is more elegant 、 steady , There is no need for us to handle task class verification 、 How to handle delayed push 、 How to push to a custom queue 、 Application queue message processing middleware, etc , So we use... In our daily developmentdispatchMethod push .
The task class ( The message data ) After a successful push , You can go through Laravel Provided Artisan command queue:work As a processing process, it listens to and consumes the task classes in the queue :
php artisan queue:workAccess articles in the browser , You can see the corresponding message queue processing results in the terminal window .

If you look at the data structure of a queued message before it is processed ( The default location is laravel_database_queues:default in ):

You can see that this is a process JSON Serialized message data :

job The corresponding is how to process the message data , The final implementation is data.command in unserialize Coming out PostViewsIncrement On the object handle Method .
Review the above Asynchronous processing process The final execution in is the task class RedisJob Example of fire Method , The source code is as follows :
public function fire()
{
$payload = $this->payload();
[$class, $method] = JobName::parse($payload['job']);
($this->instance = $this->resolve($class))->{$method}($this, $payload['data']);
} there payload Namely Redis Queue JSON Format message data , We go through job The field value is parsed out of the message data processor , And then data field value ( Which contains PostViewsIncrement Data of task class instances ) Pass it in as a parameter ,CallQueuedHandler Of call It will eventually be based on Dispatcher The distribution task class executes , Before that, the middleware defined in the task class will be executed :
public function call(Job $job, array $data)
{
try {
$command = $this->setJobInstanceIfNecessary(
$job, unserialize($data['command'])
);
} catch (ModelNotFoundException $e) {
return $this->handleModelNotFound($job, $e);
}
...
$this->dispatchThroughMiddleware($job, $command);
...
}
protected function dispatchThroughMiddleware(Job $job, $command)
{
return (new Pipeline($this->container))->send($command)
->through(array_merge(method_exists($command, 'middleware') ? $command->middleware() : [], $command->middleware ?? []))
->then(function ($command) use ($job) {
return $this->dispatcher->dispatchNow(
$command, $this->resolveHandler($job, $command)
);
});
} If the task class has a corresponding processor class , Run through the processor class , Otherwise, call the task class itself handle perhaps __invoke Method execution , Here we are PostViewsIncrement Defined above handle Method :
public function dispatchNow($command, $handler = null)
{
$uses = class_uses_recursive($command);
if (in_array(InteractsWithQueue::class, $uses) &&
in_array(Queueable::class, $uses) &&
! $command->job) {
$command->setJob(new SyncJob($this->container, json_encode([]), 'sync', 'sync'));
}
if ($handler || $handler = $this->getCommandHandler($command)) {
$callback = function ($command) use ($handler) {
$method = method_exists($handler, 'handle') ? 'handle' : '__invoke';
return $handler->{$method}($command);
};
} else {
$callback = function ($command) {
$method = method_exists($command, 'handle') ? 'handle' : '__invoke';
return $this->container->call([$command, $method]);
};
}
return $this->pipeline->send($command)->through($this->pipes)->then($callback);
} Come here , We will take Laravel be based on Redis In the implementation of the queue system , The task class representing the message data is defined from , To distribute , To be pushed to the queue , Last pass Artisan The complete link of command asynchronous consumption processing is shown to you , I believe you should have a clear understanding of the underlying implementation and upper use of the queue system : Queue system and asynchronous processing Laravel Frameworks have been provided , In daily development , We just need to write according to the structure of the message task class handle processing method , And then pass in the right place dispatch Method to distribute , The rest to Laravel Just deal with it , It's that simple .
You can refer to Laravel Queue documents Learn more about Laravel Queue usage details , besides ,Laravel It also provides an application for Redis An integrated solution for queue systems —— Horizon, It is recommended to use it in production environments as Redis Message queuing system solutions , You will open a special message queue system to introduce the use and deployment of all these features .
The benefits of using a queue system
At the beginning of this tutorial , Xuejun has introduced the advantages of using message queue , Let's make a summary based on it :
- Separating producers from consumers , Realize code decoupling , Improve system fault tolerance ( After the consumer fails to process , Message data can be processed repeatedly , Until the execution is successful );
- The consumer processing process can process message data asynchronously , So as to effectively improve the response speed of the system , Enhance the user experience , This is very effective for some time-consuming tasks ( E.g. email sending 、 Database operation 、 File store 、 Reptiles or something IO Intensive operation );
- except IO Intensive operation , You can also be right about CPU Optimized for intensive operations , For example, start multiple processing processes to split a large time-consuming task into multiple subtasks for execution , Message queues can be thought of as PHP A complementary implementation of asynchronous and concurrent programming ;
- Due to the first in, first out characteristics of queues , Therefore, it can ensure that the tasks in the same queue can be executed according to the specified sequence , Unlike general concurrent programming, it cannot ensure the execution order of subtasks ;
- Because Message Queuing Middleware ( Here is Redis) Can be independent of the application ( Here is Laravel project ) Deployment , And theoretically, any number of processing processes can be started to consume the tasks in the message queue , Therefore, it is very convenient to increase the system concurrency through horizontal expansion , Besides ,Laravel It also provides Message Queuing Middleware and frequency limiting functions , The abnormal flow peak can be effectively controlled , Improve the availability of message queuing .
We can optimize the database 、 cache ( With dynamic and static cache )、 Message queuing as Laravel Three board axe with performance optimization , Reasonably combining this set of three board axe moves can effectively deal with the bottleneck of application performance , Improve system throughput .
边栏推荐
- 实体、协议、服务和服务访问点
- 编程简单科普系列-什么是编程(1)
- 工具变量与两阶段最小二乘stata
- 外包学生管理系统架构文档
- NLP-D44-nlp比赛D13-zetero&marginnote3&读论文【被惊艳到】&刷题
- User 10 must be unlocked for widgets to be available
- Instrumental variables and two-stage least squares Stata
- 外包学生管理系统架构文档
- Regular replacement of efficient brick moving editor
- Understanding of key source code of connection pool
猜你喜欢

运算符

进程间通信方式(管道、消息队列、内存映射区、共享内存等)

【arXiv2018】You Only Look Twice: Rapid Multi-Scale Object Detection In Satellite Imagery【遥感/航空目标检测】

外包学生管理系统架构文档

Operation and interface definition of basic primitives such as lines, blocks and circles for source code development of cloud drawing CAD editor
![Nlp-d44-nlp competition D13 zetero & marginnote3 & reading papers [amazing] and brushing questions](/img/dd/afba754eb48432d0a6c8b1bfed22ec.png)
Nlp-d44-nlp competition D13 zetero & marginnote3 & reading papers [amazing] and brushing questions

外包学生管理系统架构文档(架构实战营 模块三作业)

枚举问题之匹配对手

Outsourced student management system architecture document (architecture practice camp module 3 homework)

Outsourcing student management system architecture document
随机推荐
Online Morse code online translation and conversion tool
直播预告|FeatureStore Meetup V3 重磅来袭!
Sequence alignment with BWA
Chicken problem extension -n chicken problem
自行编译内核,启动内核卡在“loading initial ramdisk”
JS as home page
Structured analysis
国内vscode高速下载
Deployment (10): intranet cluster NTP clock synchronization
外包学生管理系统架构文档
js 登录验证
穷举问题-搬砖
第2章 变量、数据类型和运算符
学生管理系统架构设计文档
JZ73:翻转单词序列
[问题已处理]-harbor可以login成功pull成功但是push报错
【arXiv2018】You Only Look Twice: Rapid Multi-Scale Object Detection In Satellite Imagery【遥感/航空目标检测】
云原生技术基石---容器化技术知识分享(一)
Outsourcing student management system architecture document
Flutter flow layout components