内容
概览
服务启停优化原则
服务启停优化建议
结束语
附件
1. 概览
当前服务普遍存在服务启停效率低下且易出错的问题,讨论制定一系列规则以规范相关服务启停开发,为以后进行相关设计、开发工作的同事作指导。本文主要包括服务启停优化原则、建议,以及服务启动调优的思路和参考文档。规则部分是日后开发人员在进行模块设计和编写时一定要准守的,可作为代码审查的考察点。建议的部分尽量准守,可作为服务启停优化的准则。
2. 服务启停优化原则
2.1.模块的初始化函数要有明确的出错提示
2.1.1. 规则
模块初始化的明确出错提示可以是返回错误码、 true 或 false,也可以是 void,但是内部出错了要抛出异常,外层调用还要有 try catch 来捕获异常。对初始化成功或失败都要打印日志描述具体的错误原因,以及初始化成功。
2.1.2. 原因
出错提示的存在保证了模块初始化/时产生的异常情况可被上层感知并处理,有利于快速分析服务的启动流程以及异常时的问题原因。
2.1.3. 反面示例
如下代码为 CNeEntity 模块的初始化接口中的一部分。
| void CNeEntity::Init() { ObjectVisitTreePtr pObjectTree = ObjectVisitTree::RealGet<ObjectVisitTree>(); if (!pObjectTree) { return; } . //获取网元数据 std::set<UNM_OBJECT_ID_TYPE> setNEID; bool bReturn = pObjectTree->GetAllNEID(setNEID); if (!bReturn) { return; } ...... |
该接口直接声明为了 void 类型,在初始化流程中出现任何错误均只能直接返回,且没有任何日志输出,而异常情况不能被上层感知,最终的结果就是服务启动接口实际上仅仅是调用了该模块的初始化,但无法知道初始化成功还是失败。
2.1.4. 修改示例
针对 2.1.3 中描述的情况,需要修改接口的返回类型,出错时返回精确的错误码,并添加详细的日志输出。而在上层则可通过判断该接口的返回值来确认初始化成功还是失败,以做出相应的处理。如下所示为修改之后的代码。
| long CNeEntity::Init() { long lErrorCode = 0; ObjectVisitTreePtr pObjectTree = ObjectVisitTree::RealGet<ObjectVisitTree>(); if (!pObjectTree) { SMART_ERROR(GetNeEntityLogger(), "Get ObjectVisitTreePtr Failed!"); lErrorCode = errorcode::ET_UNM_NESERVICE::EN_NESERVER_INIT_FAIL; return lErrorCode; } CNeFactoryPtr poNeFactory(new CNeFactory); RegisterNeFactory(STR_FACTORY_NE, poNeFactory); |
| //获取网元数据 std::set<UNM_OBJECT_ID_TYPE> setNEID; bool bReturn = pObjectTree->GetAllNEID(setNEID); if (!bReturn) { SMART_ERROR(GetNeEntityLogger(), "GetAllNEID Failed!"); lErrorCode = errorcode::ET_UNM_NESERVICE::EN_NESERVER_INIT_FAIL; return lErrorCode; } ...... |
2.2.模块的外部调用要处理初始化的错误提示
2.2.1. 规则
模块返回的错误码、成功或失败布尔值 false、抛出的异常外部调用都必须处理。具体如何处理,要具体情况具体分析,如该模块很重要,如果初始化失败,后面的操作都会无效,则要强制退出,或者多次尝试初始化直到该模块初始化成功;如果外部调用还有其他的模块需要初始化,可是单独开启一个线程尝试重试,当前线程继续执行其他模块的初始化。
2.2.2. 原因
若不处理各个模块返回的错误、异常,则无法第一时间感知初始化/销毁过程中出的异常情况,当模块对应的功能出现问题时也难以排查。
2.2.3. 反面示例
如下代码是网元服务启动过程中初始化的一个模块,该 Init 接口的返回值是 long,而此处并未处理。如此,即使下层初始化失败返回了错误码,上层也无法感知,从而导致服务出现一些难以排查的问题。
| INeEntity::Init(); |
2.2.4. 修改示例
修改后的代码如下,处理一下错误码,并打印日志即可。
| long lErrCode = INeEntity::Init(); if (errorcode::ET_SUCCESS::EN_SUCCESS_NEW != lErrCode) { GetLogger().error("NeServer::OnClientInitMe ObjectTree init failed, ErrCode = %d.", lErrCode); stRet.SetErrorCodeValue(ERRORCODEWRAPPER_JION(errorcode:: |
| ET_UNM_NESERVICE, EN_NESERVER_INIT_OBJECTTREE_FAIL)); return stRet; } |
2.3.模块的初始化函数要具有幂等性
2.3.1. 规则
模块的初始化函数应该保证最多执行一次,如果异常情况下执行多次,应保证第二次及以后的执行和第一次执行没有区别,也就是初始化函数要具有幂等性,多次调用和调用一次对模块的内部数据结构和外部调用是一致的。
2.3.2. 原因
模块重入可因模块而异导致各种各样的问题,如操作耗时、资源浪费甚至使服务直接崩溃,因此在服务初始化过程中必须避免模块重入。
2.3.3. 反面示例
如下代码为 CNeHolderMgr 模块初始化接口的一部分。
重点看标红的行,每次该模块初始化时都会创建并添加所有的网元对象,而网元 ID 明显是必须唯一的,这里重入的话就会导致服务直接异常崩溃,然后无限重启,无限崩溃。有如下三种情况可能导致模块重入:
1) 该模块已由其他服务初始化成功,但当前服务再次进行了其初始化操作,导致模块重入;
2) 该模块已由当前服务初始化成功,但该服务其他模块初始化失败导致再次调用服务初始化
接口,从而再次初始化该模块,导致模块重入;
3) 标红的部分已初始化成功,但之后接口进入标绿的分支,导致该模块自身初始化失败,从
而上层再次调用其初始化接口,导致模块重入。
| ErrorCode CNeHolderMgr::Init() { ...... CreateObjectAndAddWhenInit(iNeID, poNe); // 该函数重入会导致失败 ...... ErrorCode stCode = poNe->Init(stNeData); If (!stCode.IsError()) { GetLogger().error("CNeHolderMgr::Init() poNe init failed, (%d, %d).", stCode.iType, stCode.iCode); |
| return stCode; } ...... return stCode; } |
2.3.4. 修改示例
针对 2.3.3 中阐述的三种情况,可使用如下两种方案解决:
1. 在 Init(或者 Initialize)中表示初始化的函数中加入 m_bInit,多线程中要对 m_bInit 加锁,这样在服务初始化失败或多个服务公用一个模块情况下多次调用 Init 时,可避免多次初始化。
2. 在 Init(或者 Initialize)中表示初始化的函数中,若初始化失败进入错误分支,需调用接口销毁该模块已有的初始化成果,以避免重新初始化可能导致的模块重入问题。若接口中存在多个分支,则可参考规则“模块初始化函数中对于包含多个步骤或分支的函数建议采用单入口-单出口原则”的方法。
修改如下。
| ErrorCode CNeHolderMgr::Init() { // 多线程情况下需加锁,此处仅给一个象征性的示例 mutex::scoped_lock lock(mu); ...... // 若标识为 true,表示已经初始化,直接返回成功 If (m_bInit) // 多线程访问条件下,要注意加锁控制 { GetLogger().info("CNeHolderMgr have already inited."); return ErrorCode(0, 0); } ...... CreateObjectAndAddWhenInit(iNeID, poNe); ...... ErrorCode stCode = poNe->Init(stNeData); If (!stCode.IsError()) { // 接口异常退出时,清理已初始化的资源 ClearAllObject(); GetLogger().error("CNeHolderMgr::Init() poNe init failed, (%d, %d).", stCode.iType, stCode.iCode); return stCode; } ...... // 将标识置为 true |
| m_bInit = true; return stCode; } |
2.4.在线程处理函数的循环中要设置退出标识
2.4.1. 规则
所有循环处理中,都必须设置退出标识,以便可以从循环外部终止循环语句。主要是为了解决线程无法退出,服务停不下来的问题。
2.4.2. 原因
如果不设置退出标识,会导致服务停止时,线程无法退出等等情况发生。而设置了退出标识,在模块销毁时,通过退出标识,结合其他无效数据通知、信号量唤醒、条件变量唤醒等操作,可以快速跳出循环,完成模块销毁操作。因此,即使是要一直运行的线程,也不能 while(true)而不给出退出的条件。
2.4.3. 反面示例
如下代码是消费者线程,可以看到,由于 while(true)而没有退出标识,这种线程处理循环,在服务停止时,如果模块等待此线程的结束,基本上是要一直等待,导致服务停止慢,或者无法停止。
| //轮询 int TaskInfoQueueMgr::LoopThread() { while (true) { try { TaskProcessorPtr pTaskProcessorPtr; if (GetTaskFromQueue(pTaskProcessorPtr)) { if (NULL == pTaskProcessorPtr.get()) { continue; } TaskExcuteThreadPtr pThread(new TaskExcuteThread()); pThread->SetTaskProcessorPtr(pTaskProcessorPtr); TaskThreadMgr::get_mutable_instance().GetThreadPool().Start(pThread); } |
| Sleep(1000); } catch (...) { continue; } } return 0; } |
2.4.4. 修改示例
修改后的代码如下,添加退出标识,当需要停止线程时,设置停止标识 m_bStop 为 true,即可结束线程。
| // int TaskInfoQueueMgr::LoopThread() { while (!m_bStop) { ...... } } TaskInfoQueueMgr::~TaskInfoQueueMgr() 或者 UnInitialize() { m_bStop = true; m_oThread.join(); } 或者上面例子的另一种形式: // int TaskInfoQueueMgr::LoopThread() { while (true) { bCanRun = xxx; // xxx 可以在外部设置(如 bCanRun = (m_pDemo != NULL)) if (!bCanRun) { break; } ...... } |
| } |
2.5.使用可中断的 Sleep 或其他替代方案进行线程等待
2.5.1. 规则
所有需要延时处理的地方,不要使用与操作系统相关的 Sleep 进行等待。可以使用
condition.timed_wati、 time、 mutex.timed_lock、 this_thread.sleep 等替代。
2.5.2. 原因
Sleep 进入后无法被打断,此时如需停止服务的话,在停止线程时,可能由于线程处于 Sleep 中,必须等待 Sleep 的时间到了后,线程才能继续执行,导致线程停止时间过长。
2.5.3. 反面示例
如下代码,没循环一次,需要等待 1 秒。如果线程循环刚好开始进入 Sleep(1000)时,设置停止标志 m_bStop 为 true 的话,也需要等待 Sleep 结束,即 1 秒后,才能再次判断停止标志 m_bStop,从而退出循环。
| //轮询 int TaskInfoQueueMgr::LoopThread() { while (!m_bStop) { try { TaskProcessorPtr pTaskProcessorPtr; if (GetTaskFromQueue(pTaskProcessorPtr)) { if (NULL == pTaskProcessorPtr.get()) { continue; } TaskExcuteThreadPtr pThread(new TaskExcuteThread()); pThread->SetTaskProcessorPtr(pTaskProcessorPtr); TaskThreadMgr::get_mutable_instance().GetThreadPool().Start(pThread); } Sleep(1000); } catch (...) |
| { continue; } } return 0; } |
2.5.4. 使用 condition.timed_wait
修改后的代码如下,在线程处理函数的循环中,通过成员变量的 TimeWait 来实现 Sleep 的效果。如果需要停止线程时,即可设置停止标志,再使用成员变量的 NotifyAll,来快速响应,使线程处理函数退出等待,从而通过停止标志退出循环。
| // class CCustomCondition { public: CCustomCondition() { } ~CCustomCondition() { } public: bool TimeWait(long lMillsecs) { boost::scoped_lock<boost::mutex> oGuard(m_oMutex); boost::posix_time::time_duration oDuration = boost::posix_time::milliseconds(lMillsecs); return m_oCondition.timed_wait(oGuard, oDuration); } void Wait() { boost::scoped_lock<boost::mutex> oGuard(m_oMutex); m_oCondition.wait(oGuard); } void NotifyOne() { boost::scoped_lock<boost::mutex> oGuard(m_oMutex); m_oCondition.notify_one(); |
| } void NotifyAll() { boost::scoped_lock<boost::mutex> oGuard(m_oMutex); m_oCondition.notify_all(); } private: boost::mutex m_oMutex; boost::condition_variable_any m_oCondition; }; // void TaskInfoQueueMgr::LoopThread() { const int iInterval = xxx; while (!m_bStop) { TaskProcessorPtr pTaskProcessorPtr; if (GetTaskFromQueue(pTaskProcessorPtr)) { if (NULL == pTaskProcessorPtr.get()) { continue; } TaskExcuteThreadPtr pThread(new TaskExcuteThread()); pThread->SetTaskProcessorPtr(pTaskProcessorPtr); TaskThreadMgr::get_mutable_instance().GetThreadPool().Start(pThread); } // 等待超时 m_obCond.TimeWait(iInterval); } } // void TaskInfoQueueMgr::Destroy() { if (!m_bStop) { m_bStop = true; m_obCond.NotifyAll(); if (m_pThread) |
| { m_pThread->join(); } } } |
注意事项: 模拟 Sleep 时,由于是先加锁,再进行超时等待,中间有一点点间隔,因此,多线程使用时,模拟 Sleep 等待的时间可能会有很小的误差(尽管 Sleep 本身等待的时间也不是十分精确),对精确度不做特别要求的处理中,可以忽略此误差。
2.5.5. 使用 mutex.timed_lock
通过超时锁,当线程启动时就加锁,之后获取锁都会超时,达到 Sleep 的效果。如需退出循环时,进行解锁,等待的加锁动作就会返回,之后就可以退出线程循环了。
| // void TaskInfoQueueMgr::LoopThread() { while (!m_bStop) { // 处理 ...... // 定时检查一次 boost::timed_mutex::scoped_timed_lock oIntervalLock( m_oTimedMutex, xxx); } } // void TaskInfoQueueMgr::Start() { m_bStop = false; m_oTimedMutex.lock(); } // exit int TaskInfoQueueMgr::Exit() { if (!m_bStop) { m_bStop = true; m_oTimedMutex.unlock(); if (m_pThread) { |
| m_pThread.join(); } } return 0; } |
2.5.6. 使用 boost::this_thread::sleep
使用 boost 库中的可中断的 sleep,如果线程调用了 Interrupt 函数 ,则 sleep 会抛出
boost::thread_interrupted 异常,代码示例如下
| #include <iostream> #include <boost/thread.hpp> #include <boost/bind.hpp> using namespace std; using namespace boost; void Work() { cout << "thread begin..." << endl; try { cout << "thread begin sleep" << endl; boost::this_thread::sleep(boost::posix_time::hours(1)); cout << "thread sleep end" << endl; } catch (const boost::thread_interrupted &) { cout << "catch interrupted exception" << endl; } cout << "thread end.." << endl; } // 该线程主要为了实现异步中断 void Interrupt(thread *oThread) { cout << "interrupt thread or not (1-YES, 0-NO)" << endl; int iCmd = 0; cin >> iCmd; if (1 == iCmd) { oThread->interrupt(); } } |
| void test() { thread th1(&Work); thread th2(&Interrupt, &th1); th1.join(); th2.join(); } int main() { test(); ::system("pause"); return 0; } |
通过调用线程的 interrupt 函数来终止线程的 sleep,运行结果如下:中断后抛出异常,然后结束线程的执行。
2.6.如果模块有初始的接口,那么就要有销毁的接口
2.6.1. 规则
如果模块定义了初始化接口,就要对应定义销毁接口。
2.6.2. 原因
1、对于有线程等待的模块,如果销毁接口中无通知停止等待的操作,会导致该模块销毁不了,进而导致服务停不下来。
2、对于有资源管理的模块,如果文件 IO、数据库连接、内存池、线程池等,如果不显示调用
销毁接口释放资源,会导致资源浪费,后续分配资源失败等问题。
2.6.3. 反面示例
定义了统一端口的初始化接口,但没有定义销毁接口。
| // 初始化函数 UNM_RPC_RETURN_CODE CRpcDispatchServerI::OnClientInitMe(const string &strTrigerOperation) |
| { // 初始化统一端口 unmport::UniPortError stPortError = InitUnmPort(); if (!stPortError.IsSuccess()) { return UNM_RPC_RETURN_CODE(stPortError.iType, stPortError.iCode); } return UNM_RPC_RETURN_CODE(0, 0); } // 销毁函数 void CRpcDispatchServerI::OnPreStop() { //... // 无销毁统一端口操作 } |
2.6.4. 正面示例
| // 初始化函数 UNM_RPC_RETURN_CODE CRpcDispatchServerI::OnClientInitMe(const string &strTrigerOperation) { try { if (!CRetrievalFacade::Instance()->Init()) { return UNM_RPC_RETURN_CODE(ET_TOPO_SERVER::GetErrorType(), -1); } } catch (CommException &e) { return UNM_RPC_RETURN_CODE(e.Type(), e.Code()); } return UNM_RPC_RETURN_CODE(0, 0); } // 销毁函数 void CRpcDispatchServerI::OnPreStop() { //... CRetrievalFacade::Instance()->UnInit(); } |
2.7.构造函数和析构函数不要做过多的逻辑处理,析构函数不要抛异常
2.7.1. 规则
构造函数只做成员的初始化操作,不作其他逻辑操作。析构函数只做简单的资源释放操作,不应做复杂的逻辑处理。
2.7.2. 原因
对象构造和析构如果时间过长,会导致分配资源或者释放资源过慢,引起操作失败,这类问题不容易排查。
2.7.3. 解决方案
初始化的逻辑处理放到初始化函数 Initial 中,析构函数的逻辑处理放到 Destroy 中。
2.7.4. 反面示例
构造函数中有反序列化操作,并且没有异常处理。
| // CNeServiceHolder::CNeServiceHolder(const CNeServiceKey &oKey, const ItemDataType &oValue) : m_oKey(oKey) { m_poNeService = CNeServiceFactoryCluster::get_mutable_instance().Create(oKey.m_iType, oKey); if (NULL != m_poNeService.get()) { m_poNeService->PBToClass(oValue); } else { // error log } } |
2.7.5. 正面示例
构造函数和析构函数均为空函数,初始化和销毁的逻辑放到 Init 和 UnInit 函数中。
| // 端口索引 class CRetrievalFacade { public: |
| // 获取唯一实例 static CRetrievalFacade* Instance(); // 单例模式 public: // 初始化 bool Init(); // 清理 bool UnInit(); //... // 清库 bool Clear(); protected: // 构造函数 ---给成员变量赋初值 CRetrievalFacade() : m_bInited(false), ... { } // 析构函数 virtual ~CRetrievalFacade() { } private: // ...... 其他成员函数 private: // ...... 其他成员变量 }; |
3. 服务启停优化建议
3.1.模块初始化函数中对于包含多个步骤或分支的函数建议采用单入口-单出口原则
3.1.1. 规则
如果函数的处理逻辑可以分为多个步骤或分支,每个步骤执行成功方可进行下一步处理,那么应该将函数所涉及的临时变量集中在函数体头部定义,返回值逻辑集中在函数体尾部实现即单入口-单出口原则。
3.1.2. 原因
函数的处理步骤或分支较多时会造成返回值处理的位置较分散,不易于维护且容易漏掉部分步骤所涉及的资源释放。 单入口-单出口原则可以减少结果处理逻辑的代码编写,在函数退出时集中释放资源或清理脏数据。
3.1.3. 反面例子
如下代码,函数有多处资源申请,且包含多个结果处理逻辑。
| int Function() { int iFtpError = FTP_EXECUTE_FAILED; FtpClientPtr pFtpClient = new FtpClient; if (NULL == pFtpClient) { return FTP_EXECUTE_FAILED; } // 1. connect iFtpError = pFtpClient->Connect(); if (FTP_EXECUTE_SUCCESS != iFtpError) { // log error //释放 pFtpClient->Quit(); delete pFtpClient; pFtpClient = NULL; return iFtpError; } // 2. login iFtpError = pFtpClient->Login(); if (FTP_EXECUTE_SUCCESS != iFtpError) { // log error //释放 pFtpClient->Quit(); delete pFtpClient; pFtpClient = NULL; return iFtpError; } //申请资源 ResourcePtr pResource = CreateResource(); if (NULL == pResource) |
| { // log error //释放 pFtpClient->Quit(); delete pFtpClient; pFtpClient = NULL; return FTP_EXECUTE_FAILED; } // 3. upload iFtpError = pFtpClient->Upload(); if (iFtpError != FTP_EXECUTE_SUCCESS) { // log error //释放 pFtpClient->Quit(); delete pFtpClient; pFtpClient = NULL; delete pResource; pResource = NULL; return iFtpError; } // 4.quit pFtpClient->Quit(); return iFtpError; } |
3.1.4. 正面示例
| int Function() { int iFtpError = FTP_EXECUTE_FAILED; FtpClientPtr pFtpClient = new FtpClient; if (pFtpClient) { do { // 1. connect iFtpError = pFtpClient->Connect(); if (FTP_EXECUTE_SUCCESS != iFtpError) { |
| // log error break; } // 2. login iFtpError = pFtpClient->Login(); if (FTP_EXECUTE_SUCCESS != iFtpError) { // log error break; } ResourcePtr pResource = CreateResource(); if (NULL == pResource) { // log error iFtpError = FTP_EXECUTE_FAILED; break; } // 3. upload iFtpError = pFtpClient->Upload(); if (iFtpError != FTP_EXECUTE_SUCCESS) { // log error break; } } while (false); // 集中处理释放 if (Check(iFtpError)) { // 4.quit if (ptrFteClient != NULL) { ptrFteClient->Quit(); delete ptrFteClient; } pFtpClient->Quit(); } DestoryResource(); } return iFtpError; } |
3.2.根据模块与服务的强弱依赖关系选择合适的加载方式进行加载
3.2.1. 规则
依赖性质定义:
强依赖:子模块未初始化时会导致服务不可用。
弱依赖:子模块未初始化时不会导致服务不可用。
处理规则:
1、 强依赖项子模块:强依赖项子模块在服务初始化过程中需要同步加载,如果该模块比较耗时,而还有其他可以并行初始化的模块,则应该开启多线程同步加载,以提高服务启动速度。
2、 弱依赖项子模块:弱依赖项子模块在服务初始化过程中可以不立即加载,统一采用延时加载或动态加载,以加快服务启动过程。特别是所加载的数据属于大量的配置数据,优先选择动态加载,在调用时加载对应的配置。
3、 在模块职责单一功能相对集中情况下,如加载协议文件,可考虑采用多线程加载不同的脚本文件。如果模块的加载非必须,则可以放到服务启动后,使用时加载,加载完成进行缓存,下次直接使用,以提高访问速度。
3.2.2. 原因
子模块较多,初始化时间过长,会导致服务启动过慢,阻塞其他无依赖性的服务启动,因此需要分析子模块与服务的依赖关系,选择合适的加载方式来提高服务的启动速度。
3.2.3. 解决方案
多线程加载:当耗时操作可以拆分成多个关联性不大的小任务时,如加载单盘的 cfg 文件,加
载端口的 xml 文件等等,可以使用多线程来进行加载,加快耗时处理,起到快速初始化的目的。
启动后加载:当耗时操作很难拆分,或者拆分后关联性比较大,此时即使使用多线程加载,也要处理线程间的同步,加载速度可能提升的不是很理想。可以考虑使用启动后加载。
启动后加载,又可以有两种形式:
第一种,边加载边初始化。初始化时,启动一个线程进行耗时操作,初始化的动作继续。如果耗时操作未完成,初始化已结束,此时模块内部为半初始化状态。注意点:当耗时操作处理完成后,需要通知当前模块,将内部状态调整为已初始化状态。所有调用此模块的时候,都应该判断模块状态,如果是半初始化状态,返回未初始化完成的错误。如果是已初始化状态,继续本模块的处理。
第二种,使用时加载。初始化时,不加载耗时操作,如单盘 cfg 协议解析等。其他模块调用本
模块的数据时,再按需加载耗时操作,并可以在此处加入缓存操作,加快之后的访问速度。注意点:这种方式,会将耗时操作分摊到初次调用的地方,虽然加快了初始化操作,但初次访
问的速度多少会有所减慢。
3.2.4. 反面例子
如下代码是某个服务初始化过程,在初始化时服务需要加载所有单盘配置数据、从 PAS 同步采集任务、初始化业务模块、加载对象树等操作。按照依赖性的强弱,上述操作可以分为:
1、 强依赖项:初始化业务模块、加载对象树;
2、 弱依赖项:加载所有单盘配置数据、从 PAS 同步采集任务。
| UNM_RPC_RETURN_CODE CService::OnClientInitMe() { UNM_RPC_RETURN_CODE stRet; //加载单盘配置数据 std::string strPath = GetPath(); stRet = LoadBoardCfgData(strPath); if (stRet.IsError()) { //log error return stRet; } //初始化采集任务 stRet = InitCollectTaskFromPAS(); if (stRet.IsError()) { //log error return stRet; } // 初始化业务模块 stRet = InitNeEntityDll(); if (stRet.IsError()) { //log error return stRet; } |
| // 对象树初始化 stRet = INeEntity::Init(m_iServiceIndex); if (stRet.IsError()) { //log error return stRet; } return stRet; } |
3.2.5. 正面示例
| UNM_RPC_RETURN_CODE CService::OnClientInitMe() { UNM_RPC_RETURN_CODE stRet; do { //利用多线程加载对象树 int iRunThread = GetThreadPool().Capacity(); for (int i = 0; i < iRunThread; ++i) { GetThreadPool().Start(LoadObjectTreeThread); } //注入回调通知对象延时加载采集任务 stRet = RegistPASClient(); if (stRet.IsError()) { //log error break; } // 初始化业务模块 stRet = InitNeEntityDll(); if (stRet.IsError()) { //log error break; } //等待多线程初始化对象树完成 GetThreadPool().JoinAll(); } while (false); return stRet; } //加载单盘配置数据 |
| UNM_RPC_RETURN_CODE CService::ProcessBoardData() { //do …… //先查找是否有配置 bool bFind = FindBoardCfg(boardType); if (!bFind) { //动态加载 std::string strPath = GetBoardCfgPath(boardType); stRet = LoadBoardCfgData(strPath); if (stRet.IsError()) { //log error return stRet; } } //continue …… return stRet; } void PASClient::Notify() { //通知采集任务获取完毕 pService->UpdateCollectTaskFromPAS(); } //加载单盘配置数据 UNM_RPC_RETURN_CODE CService::UpdateCollectTaskFromPAS() { UNM_RPC_RETURN_CODE stRet; //初始化采集任务 stRet = InitCollectTaskFromPAS(); if (stRet.IsError()) { //log error return stRet; } …… return stRet; } |
4. 结束语
服务启停的快慢直接影响网管的使用体验,因此开发人员在进行模块设计时,要考虑模块初始化和销毁的效率问题,以免影响整个服务的启动和停止。本指导书制定的这些规范和建议,对于服务启停问题排查、以及优化方向提供指导,使单个服务的启动和停止时间受控,以使整个网管服务的启动和停止最优。
5. 附件
2473



被折叠的 条评论
为什么被折叠?



