Thrift之TProcess类体系原理及源码详细解析

简介: 我的新浪微博:http://weibo.com/freshairbrucewoo。欢迎大家相互交流,共同提高技术。  之前对Thrift自动生成代码的实现细节做了详细的分析,下面进行处理层的实现做详细分析了!会利用到自动代码生成的知识。

我的新浪微博:http://weibo.com/freshairbrucewoo

欢迎大家相互交流,共同提高技术。

  之前对Thrift自动生成代码的实现细节做了详细的分析,下面进行处理层的实现做详细分析了!会利用到自动代码生成的知识。

 

  这部分是协议层和用户提供的服务实现之间的纽带,定义了调用服务实现的接口框架,真正实现某种服务接口是通过上一章介绍的代码生成工具生成的代码。本章将介绍这个框架的基本原理,然后通过生成的一个实例来具体介绍怎样完成一次完整的服务,这个可能涉及到下面章节的一些知识,对于这些知识不详细分析其功能,只是介绍它在其中起什么作用。选择的实例是Facebook内部用这个框架实现的一个分布式日志收集系统scribe。下面是这部分相关类的类关系图:

 

  从上图中可以看出TProcessor是这个部分的顶层基类,其他之类基本上都是通过Thrift代码生成工具生成的,只有少数是为了扩展一些功能而直接写代码实现,如PeekProcessor类就增加了一些对原始数据处理的功能。scribeProcessor和FacebookServiceProcessor类就是用代码生成器根据IDL文件生成的,也是我们后面需要分析的一个实例。

第一节 服务接口调用框架分析

  这个基本的框架包括三个类,一个就是抽象类TProcessor,负责调用用户定义的服务接口,从一个接口读入数据,写入一个输出接口。一个最主要的函数定义如下:

1 virtual bool process(boost::shared_ptr<protocol::TProtocol> in,
2 
3                        boost::shared_ptr<protocol::TProtocol> out, void* connectionContext) = 0;

 

这个函数是一个纯虚函数,所以继承这个类的子类都必须实现这个函数,这个函数就是最主要的数据传输功能。

第二个类就是负责处理TProcessor类产生的事件的类TProcessorEventHandler,主要定义了一些当某事件发生时的处理函数,例如当读取参数之前可以做一些处理功能。下面是这个类定义的各个成员函数,每一个函数都处理一种事件发送时的情况:

函数名称

函数功能

getContext

调用其他回调函数之前调用,期望返回一些有序的上下文对象以便传递给其他回调函数使用

freeContext

期望释放一个上下文有关的资源

preRead

在读参数以前调用

postRead

在读参数和处理函数之间调用

preWrite

在处理和写响应之间调用

postWrite

在写响应之后调用

asyncComplete

当一个异步函数成功完成调用时调用

handlerError

如果处理函数抛出没有定义的异常就会调用此函数

最后一个类就是TProcessorContextFreer类,这个类是一个帮助类,帮助生成的代码来释放上下文资源。

第二节 基于框架生成的服务实例分析

本节将对scribe服务器采用的服务实现进行详细分析。

1 接口定义语言文件(IDL)

(1)Facebook内部共用服务协议

主要有两个文件,一个是在Thrift中定义,是用于Facebook内部的一些接口服务定义,这个不仅仅用于scribe服务器,可能还用于Facebook内部其他系统,这个文件内容如下:

复制代码
 1 namespace java com.facebook.fb303
 2 
 3 namespace cpp facebook.fb303
 4 
 5 namespace perl Facebook.FB303
 6 
 7 enum fb_status {
 8 
 9   DEAD = 0,
10 
11   STARTING = 1,
12 
13   ALIVE = 2,
14 
15   STOPPING = 3,
16 
17   STOPPED = 4,
18 
19   WARNING = 5,
20 
21 }
22 
23 service FacebookService {
24 
25   string getName(),
26 
27   string getVersion(),
28 
29   fb_status getStatus(),
30 
31   string getStatusDetails(),
32 
33   map<string, i64> getCounters(),
34 
35   i64 getCounter(1: string key),
36 
37   void setOption(1: string key, 2: string value),
38 
39   string getOption(1: string key),
40 
41   map<string, string> getOptions(),
42 
43   string getCpuProfile(1: i32 profileDurationInSec),
44 
45   i64 aliveSince(),
46 
47   oneway void reinitialize(),
48 
49   oneway void shutdown(),
50 
51 }
复制代码

 

上面这个IDL文件定义了一个枚举类型用于表示服务的状态,还定义了一个名位FacebookService的服务,里面定义了各种操作,如获取服务状态的操作、得到计数的操作等等。

下面我们来看看根据这个IDL文件生成的C++代码是什么样的一个架构。首先生成了一个基于上面服务定义的抽象类如下:

复制代码
class FacebookServiceIf {

 public:

  virtual ~FacebookServiceIf() {}

  virtual void getName(std::string& _return) = 0;

  virtual void getVersion(std::string& _return) = 0;

  virtual fb_status getStatus() = 0;

  virtual void getStatusDetails(std::string& _return) = 0;

  virtual void getCounters(std::map<std::string, int64_t> & _return) = 0;

  virtual int64_t getCounter(const std::string& key) = 0;

  virtual void setOption(const std::string& key, const std::string& value) = 0;

  virtual void getOption(std::string& _return, const std::string& key) = 0;

  virtual void getOptions(std::map<std::string, std::string> & _return) = 0;

  virtual void getCpuProfile(std::string& _return, const int32_t profileDurationInSec) = 0;

  virtual int64_t aliveSince() = 0;

  virtual void reinitialize() = 0;

  virtual void shutdown() = 0;

};
复制代码

 

注意观察,除了这个类多了一个虚析构函数,其他函数就是IDL中定义的。接着定义了类FacebookServiceNull,这个是上面那个抽象类的空实现(就是所有方法都没有做具体的事情),这样做的好处就是我们需要重写一些函数的时候只需要关注我们需要写的函数,而不是重写所有函数。接着又定义了封装每一个函数参数的相应类,就是一个函数的参数都用一个类来封装定义,函数的返回值也是这样处理。这样做的目的是统一远程调用的实现接口,因为传递参数都只需要这个封装类的对象就可以了。所以你会看到每一个服务里面定义的函数都有下面一组类的定义:

复制代码
 1 (1)class FacebookService_getName_args {…}
 2 
 3 (2)class FacebookService_getName_pargs {…}
 4 
 5 (3)typedef struct _FacebookService_getName_result__isset {…} _FacebookService_getName_result__isset;
 6 
 7 (4)class FacebookService_getName_result{…}
 8 
 9 (5)typedef struct _FacebookService_getName_presult__isset {…} _FacebookService_getName_presult__isset;
10 
11 (6)class FacebookService_getName_presult{…}
复制代码

 

上面这六个类定义就是为服务中的getName函数服务的,相应的每一个函数都会有这种类似的定义和实现。接下来就会定义三个具体实现IDL定义的功能的类,一个客户端的类,它继承定义的服务抽象类,每一个具体的函数实现都是同样的方式和思路,同样我结合getName函数的实现来看看这个过程,其他函数都是这样实现的,代码如下:

1 send_getName();
2 
3 recv_getName(_return);

 

由上面代码可以看出首先调用函数发送函数名称及相关信息到远程,然后接受函数调用的返回值,发送函数send_getName()的代码如下:

复制代码
 1 int32_t cseqid = 0;
 2 
 3 oprot_->writeMessageBegin("getName", ::apache::thrift::protocol::T_CALL, cseqid);//写一个函数调用消息RPC
 4 
 5 FacebookService_getName_pargs args;
 6 
 7 args.write(oprot_);//写入参数
 8 
 9 oprot_->writeMessageEnd();
10 
11 oprot_->getTransport()->writeEnd();
12 
13 oprot_->getTransport()->flush();//保证这次写入过程立即生效
复制代码

 

上面代码就完成了函数名称以及参数的传输,调用的是TProtocol相关的类的函数实现,具体的实现内容和方式会在TProtocol部分介绍。下面接着看一下接收返回值的函数recv_getName的代码:

复制代码
 1   int32_t rseqid = 0;//接收的消息序列号
 2 
 3   std::string fname;//函数名称
 4 
 5   ::apache::thrift::protocol::TMessageType mtype;//消息的类型(调用(T_CALL)、异常(T_EXCEPTION)等)
 6 
 7   iprot_->readMessageBegin(fname, mtype, rseqid);//从返回消息读取函数名称、消息类型
 8 
 9   if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {//处理异常消息
10 
11     ::apache::thrift::TApplicationException x;
12 
13     x.read(iprot_);
14 
15     iprot_->readMessageEnd();
16 
17     iprot_->getTransport()->readEnd();
18 
19     throw x;
20 
21   }
22 
23   if (mtype != ::apache::thrift::protocol::T_REPLY) {//处理返回消息
24 
25     iprot_->skip(::apache::thrift::protocol::T_STRUCT);
26 
27     iprot_->readMessageEnd();
28 
29     iprot_->getTransport()->readEnd();
30 
31   }
32 
33   if (fname.compare("getName") != 0) {//看是否是我们需要的函数名,不是就跳过消息读取
34 
35     iprot_->skip(::apache::thrift::protocol::T_STRUCT);
36 
37     iprot_->readMessageEnd();
38 
39     iprot_->getTransport()->readEnd();
40 
41   }
42 
43   FacebookService_getName_presult result;
44 
45   result.success = &_return;
46 
47   result.read(iprot_);//读取函数返回值
48 
49   iprot_->readMessageEnd();
50 
51   iprot_->getTransport()->readEnd();
52 
53   if (result.__isset.success) {//成功就返回结果(已经在_return里面),否则抛出异常
54 
55     return;
56 
57   }
58 
59   throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "getName failed: unknown result");
复制代码

 

上面代码就是处理远程调用的返回结果,代码里面有注释。一个服务函数的实现大概流程已经展现在我们面前了,处理的过程也已经清晰。这个只是用于客户端的处理流程,必须通过有效的机制来通知服务器端调用相应的函数(这就是RPC)在服务器端完成相应功能并将结果返回。这种机制就是通过我们这部分介绍的TProcessor类实现,这就是上面提到三个类中的第二个类,在这个实例中是FacebookServiceProcessor类,它从TProcessor类继承,重点实现两个函数process和process_fn,其中process会调用process_fn函数来处理客户端具体调用的那个服务函数,process函数定义如下:

复制代码
 1 bool FacebookServiceProcessor::process(boost::shared_ptr< ::apache::thrift::protocol::TProtocol> piprot, 
 2 
 3 boost::shared_ptr< ::apache::thrift::protocol::TProtocol> poprot, void* callContext) {
 4 
 5 ::apache::thrift::protocol::TProtocol* iprot = piprot.get();
 6 
 7 ::apache::thrift::protocol::TProtocol* oprot = poprot.get();
 8 
 9 std::string fname;
10 
11 ::apache::thrift::protocol::TMessageType mtype;
12 
13 int32_t seqid;
14 
15   iprot->readMessageBegin(fname, mtype, seqid);//读取得到函数名称、消息类型和函数序列号
16 
17 //处理不是函数调用消息的情况
18 
19 if (mtype != ::apache::thrift::protocol::T_CALL && mtype != ::apache::thrift::protocol::T_ONEWAY) {
20 
21     iprot->skip(::apache::thrift::protocol::T_STRUCT);
22 
23     iprot->readMessageEnd();
24 
25     iprot->getTransport()->readEnd();
26 
27     ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::INVALID_MESSAGE_TYPE);
28 
29 //写入(返回)一个异常信息给调用客户端,客户端会根据返回结果处理异常
30 
31     oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);
32 
33     x.write(oprot);
34 
35     oprot->writeMessageEnd();
36 
37     oprot->getTransport()->writeEnd();
38 
39     oprot->getTransport()->flush();
40 
41     return true;
42 
43 }
44 
45 return process_fn(iprot, oprot, fname, seqid, callContext);//调用实际的函数处理
46 
47 }
复制代码

 

上面代码有比较详细的注释,还需要说明一点的就是如果传递的不是函数调用的消息类型就会返回给客户端一个异常的消息,客户端的接收返回值的函数就会根据收到的异常消息做相应处理,上面getName函数的接收返回值函数就是抛出一个服务器端给的异常信息。下面继续看最终服务器端调用相应映射函数的处理,这个是通过process_fn函数实现:具体定义如下:

复制代码
 1 bool FacebookServiceProcessor::process_fn(::apache::thrift::protocol::TProtocol* iprot,
 2 
 3 ::apache::thrift::protocol::TProtocol* oprot, std::string& fname, int32_t seqid, void* callContext) {
 4 
 5 //定义个map的迭代器,用于接收在函数映射查找到的映射函数
 6 
 7 std::map<std::string, void (FacebookServiceProcessor::*)(int32_t, ::apache::thrift::protocol::TProtocol*, 
 8 
 9 ::apache::thrift::protocol::TProtocol*, void*)>::iterator pfn;
10 
11   pfn = processMap_.find(fname);//根据函数名称查找对应的映射处理函数
12 
13   if (pfn == processMap_.end()) {//如果没有找到,做下面的处理
14 
15     iprot->skip(::apache::thrift::protocol::T_STRUCT);
16 
17     iprot->readMessageEnd();
18 
19     iprot->getTransport()->readEnd();
20 
21 //抛出一个不知道的方法的异常
22 
23     ::apache::thrift::TApplicationException x(::apache::thrift::TApplicationException::UNKNOWN_METHOD, 
24 
25 "Invalid method name: '"+fname+"'");
26 
27 //写入到调用客户端
28 
29     oprot->writeMessageBegin(fname, ::apache::thrift::protocol::T_EXCEPTION, seqid);
30 
31     x.write(oprot);
32 
33     oprot->writeMessageEnd();
34 
35     oprot->getTransport()->writeEnd();
36 
37     oprot->getTransport()->flush();
38 
39     return true;
40 
41   }
42 
43   (this->*(pfn->second))(seqid, iprot, oprot, callContext);//调用具体的函数(RPC过程完成)
44 
45   return true;
46 
47 }
复制代码

 

上面这个函数最终完成了RPC的过程,那个函数与映射函数的对应关系的map结构是在构造函数中初始化的,所以可以找到,例如我们举例的getName函数是下面这样初始化的:

1 processMap_["getName"] = &FacebookServiceProcessor::process_getName;

 

和getName函数一样,对于IDL定义的每一个函数在FacebookServiceProcessor类中都有一个映射的处理函数,为了展示一个完整的处理过程我们在看看getName函数的映射处理函数process_getName,它的定义如下:

复制代码
 1 void FacebookServiceProcessor::process_getName(int32_t seqid,
 2 
 3 ::apache::thrift::protocol::TProtocol* iprot, ::apache::thrift::protocol::TProtocol* oprot, void* callContext)
 4 
 5 {
 6 
 7 void* ctx = NULL;
 8 
 9 if (eventHandler_.get() != NULL) {
10 
11 //得到上下文调用环境
12 
13     ctx = eventHandler_->getContext("FacebookService.getName", callContext);
14 
15   }
16 
17 //定义并初始化一个用于释放资源的帮助类对象
18 
19   ::apache::thrift::TProcessorContextFreer freer(eventHandler_.get(), ctx, "FacebookService.getName");
20 
21   if (eventHandler_.get() != NULL) {
22 
23     eventHandler_->preRead(ctx, "FacebookService.getName");//读之前事件处理
24 
25   }
26 
27   FacebookService_getName_args args;
28 
29   args.read(iprot);
30 
31   iprot->readMessageEnd();
32 
33   uint32_t bytes = iprot->getTransport()->readEnd();
34 
35   if (eventHandler_.get() != NULL) {
36 
37     eventHandler_->postRead(ctx, "FacebookService.getName", bytes);//读取和读完之间的事件处理
38 
39   }
40 
41   FacebookService_getName_result result;
42 
43   try {
44 
45     iface_->getName(result.success);//这是重点:调用服务器端的getName函数
46 
47     result.__isset.success = true;
48 
49   } catch (const std::exception& e) {
50 
51     if (eventHandler_.get() != NULL) {
52 
53       eventHandler_->handlerError(ctx, "FacebookService.getName");//错误处理
54 
55     }
56 
57 //写入具体的异常到客户端
58 
59     ::apache::thrift::TApplicationException x(e.what());
60 
61     oprot->writeMessageBegin("getName", ::apache::thrift::protocol::T_EXCEPTION, seqid);
62 
63     x.write(oprot);
64 
65     oprot->writeMessageEnd();
66 
67     oprot->getTransport()->writeEnd();
68 
69     oprot->getTransport()->flush();
70 
71     return;
72 
73   }
74 
75   if (eventHandler_.get() != NULL) {
76 
77     eventHandler_->preWrite(ctx, "FacebookService.getName");//写入之前事件处理
78 
79   }
80 
81 //写入调用返回值(T_REPLY)消息到调用客户端
82 
83   oprot->writeMessageBegin("getName", ::apache::thrift::protocol::T_REPLY, seqid);
84 
85   result.write(oprot);
86 
87   oprot->writeMessageEnd();
88 
89   bytes = oprot->getTransport()->writeEnd();
90 
91   oprot->getTransport()->flush();
92 
93   if (eventHandler_.get() != NULL) {
94 
95     eventHandler_->postWrite(ctx, "FacebookService.getName", bytes);//写相应之后处理
96 
97   }
98 
99 }
复制代码

 

上面这个函数就是真正完成服务器端调用客户端传递过来的函数的处理过程,有事件处理类处理相应的事件(不过,目前都还是空实现,以后可以继承这个处理类重写需要处理事件的函数,例如:在调用服务器真正的处理函数之前可以先处理一下参数,验证参数是否正确之类的),也有帮助释放资源的帮助类。

(2)scribe服务IDL文件

复制代码
 1 include "/home/brucewoo/thrift-0.6.1/contrib/fb303/if/fb303.thrift"
 2 
 3 namespace cpp scribe.thrift
 4 
 5 namespace java scribe.thrift
 6 
 7 namespace perl Scribe.Thrift
 8 
 9 enum ResultCode
10 
11 {
12 
13   OK,
14 
15   TRY_LATER
16 
17 }
18 
19 struct LogEntry
20 
21 {
22 
23   1:  string category,
24 
25   2:  string message
26 
27 }
28 
29 service scribe extends fb303.FacebookService
30 
31 {
32 
33   ResultCode Log(1: list<LogEntry> messages);
34 
35 }
复制代码

 

这个IDL文件只定义了一个服务接口,就是用完成日志文件传输的几个Log,不过这个服务继承FacebookService服务,所以上面介绍FacebookService服务的功能它也具备,传输日志的结构就是分类和具体的消息。这个服务的具体实现和上面介绍的FacebookService流程都是一样的,不在详细介绍,只要知道一点就是:客户端在调用Log写日志到scribe服务器的时候就会传递到服务器端来调用同名的函数处理日志。

第三节 总结

TProcessor类体系主要定义一个服务生产的框架,通过这个框架生产的各种语言的代码可以实现RPC调用,具体的传输细节、协议和方式是通过后面讲解的内容实现的。

第二节对一个具体服务的实现内容做详细分析,不过都是基于文字描述和代码分析,下面根据scribe服务提供的Log函数怎样完成一次具体的处理过程用下面的图形展示:

 

这个图形并没有展示内部数据通信的细节,只是简单的说明了一个客户端的调用是怎样完成的,服务器处理还涉及到很多相关细节,将在后面章节中详细分析。

目录
相关文章
|
12月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1125 29
|
12月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
470 4
|
12月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
12月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
3362 1
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
1281 1
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
12月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
428 0

推荐镜像

更多
  • DNS