NodeServer的Node.tar部分功能流程
isValid(sIP) 判斷指定的sIP是否有效
一個很重要的函數,1分鍾內新增一次有效ip列表(為啥設計成新增而不是更新模式?)。這里可認為是有效的ip有3種:
NodeServer當前節點的ip;
配置在/tars/node<cmd_white_list_ip>中的默認白名單ip,沒配默認有兩個ip(話說直接用這兩個ip會不會有存在安全隱患);
從/tars/node<cmd_white_list>配置的(沒配默認是tars.tarsregistry.AdminRegObj:tars.tarsAdminRegistry.AdminRegObj)服務中拉取的Ip,其實這個配置就是配的主控和分控,調用 QueryFPrx->tars_endpointsAll(RegObj).[完全看不懂系列,tars_endpointsAll並不是定義在QueryF.tars中,而是定義在ServantProxy::tars_endpointsAll(),這個函數的最終實現是會調用到客戶端流程的QueryEpBase::refreshReg(),最終調用QueryFPrx->findObjectById4Any()直連配置的RegistryServer拉取 全部的Registry和AdminRegistry的ip。這個暫還不確認..但是我理解不了的是為啥是直接寫成QueryFPrx->tars_endpointsAll()。],拉取到的activityEndPoint和inActivityEndPoint.的這些ip。
如果查到與這些ip匹配上了,isValid()就返回true.否則返回false。
所以這里的isValid ip全部都是Registry或者是AdminReg或者是本Node的ip.
下面所有NodeImp的函數執行時候,都在流程前會檢測下g_app.isValid(current->getIp())..
destroyServer的流程:
1、參數檢查等等。並根據參數ServerFactory::getInstance()->getServer( application, serverName ),返回一個ServerObjectPtr對象serverPtr.如果不存在,返回錯誤碼及result參數。
2、如果存在serverPtr。執行CommandDestroy。這個里面會先判斷下服務內部狀態是否Inactive.如果Inactive返回對應錯誤碼。
3、如果服務內部狀態非Inactive.刪除服務日志目錄(RemoveLogThread異步線程刪除),刪除data下的服務文件對應及子目錄(RemoveLogThread異步線程刪除)。
完事。出錯返回異常。。
patchPro的流程:
1、根據參數 loadServer.返回一個ServerObjectPtr對象server
2、各種 參數檢測,md5檢測,服務重啟中檢測(重啟中不讓發版),os檢測...
3、server的state改成BatchPatching,進度設置為0,將server對象塞入BatchPatch.如果服務之前在發版中,則忽略此次發版,並拋出異常給registry
4、BatchPatch里有一個任務隊列和線程池.線程循環輪詢隊列,有發版信息則發版,沒有則wait 2s.
下面是隊列中執行具體發布內容的流程:
5、從PatchServer中下載對應包,並且下載類中,傳輸進度按百分比有變化時,更新ServerObjectPtr對象中的進度值,另外此值在此時最大只能設置成99。在此函數中,下載前對包文件檢查,下載完后對包是否存在進行檢查,並檢查md5等.包下載超時默認為1分鍾
6、從解壓目錄中,刪之前版本的文件;解壓tgz包(tars_java的解壓格式特殊處理 jar包格式是直接改成對應后綴,tgz格式是unzip -oq解壓);java的解壓目錄是在當前下載目錄sLocalTgzFile_bak,,其它語言是在sLocalExtractPach(下載目錄/BatchPatching/appname/servername),
7、exepath目錄, 對於tars_java 備份之前的一些文件;對於tars_nodejs要刪除之前exepath下的文件,從解壓目錄,copy內容到exepath.向registry發送updatePatchResult 更新 t_server_conf 中節點的patch信息。如果向registry發送信息失敗,則reportServer 向上報告此次異常
8、設置進度為100%,設置發布結果,成功與否都會設置。core頻率限制重新計算。reportServer此次發布結果
9、其中有一步失敗 則置錯誤碼,不進行下一個環節。並走到最后的上報錯誤碼流程
addFile下載指定文件的流程
1、根據參數 loadServer.返回一個ServerObjectPtr對象server。不存在,返回錯誤碼。
2、存在,執行CommandAddFile.
先設置服務當前狀態為AddFilesing狀態;
如果要add的是腳本文件,執行拉取腳本.使用ConfigPrx->loadConfig()或者是ConfigPrx->loadConfigByInfo()。(其中ConfigPrx實際上是/tars/application/server<config> 配置的tars.tarsconfig.ConfigObj此服務)
如果不是腳本文件,使用TarsRemoteConfig.addConfig(sFineName)將將ConfigServer上指定的配置文件讀取到本地(保存到應用程序執行目錄).
流程完畢。為啥要分腳本文件和非腳本文件呢?在拉取腳本文件時候,會對讀取到的腳步文件內容格式進行替換換行符等轉碼處理(windows到linux格式兼容就是蛋疼)
getName 獲取node名稱
返回本nodeName。實際上是返回 ServerConfig::LocalIp。配置在/tars/application/server<localip>中
getLoad 獲取node上負載
調用linux系統api,getloadavg – 獲取系統平均負載。 此函數可能被廢棄。在tarscpp代碼中未看到調用處。
shutdown 關閉本node進程
函數如題。。調用Application::terminate()關本進程
stopAllServers 關閉node上所有服務
遍歷Node上全部的ServerObject。挨個執行CommandStop。CommandStop的內容,參考下面的stopServer流程。
loadServer 載入指定服務
調用ServerFactory::getInstance()->loadServe()。加載指定的服務。。
這里是通過執行CommandLoad實現的。實現的內容挺多,用一句話總結來說就是:生成好配置文件,日志文件等目錄和環境;加載配置,生成好對應ServerObject並緩存。
此函數在startServer時候也必然會調用.
synState同步服務狀態到db的流程
將ServerObject中服務的當前狀態,通知registry同步到db中的對應字段
這里有個對異常的處理要注意下,連續3次同步失敗后(同步異常),由同步通知改成異步通知,這是防止主控超時導致阻塞服務上報心跳
startServer的流程
1、參數錯誤檢測.
2、根據參數 loadServer.返回一個ServerObjectPtr對象server。執行CommandStart
3、判斷server的服務狀態,如果狀態不為Inactive 則返回執行失敗
4、若exe文件不存在,並且StartScript為空,並且服務不為tars_nodejs或tars_php,設置已patch為false,返回失敗並記log
5、設置已patch為true,設置服務狀態為Activating (此時不通知regisrty。checkpid成功后再通知).設置重定向目錄。
6、啟動分類處理
1]若服務為tars_php類,執行php腳本目錄中的啟動腳本。
2]若有啟動選擇腳本或非taf服務,調用startByScript,啟動腳本,腳本分2種,啟動腳本和監控腳本。服務啟動等待時間可配,默認為3s,最長不超過1分鍾。啟動過程中,默認循環定時100ms去檢測服務啟動狀態(通過shell的ps -ef命令檢測pid,並向此pid值發送kill -0信號成功),若服務啟動失敗,記下log,拋出對應失敗的異常。
3]若服務類型為普通服務,
根據服務類型(tars_java, tars_nodejs,tars_php,tars_c++)生成對應的手動啟動腳本tars_start.sh,並設置文件屬性成可執行模式(60秒內服務最多啟動10次,達到10次啟動仍失敗后,每隔600秒再重試一次,通過Activator這個類來實現這個控制);
檢查參數,參數錯誤則拋相應異常返回。參數正確,執行fork() (注意這里fork的是NodeServer自己),fork失敗 拋出對應異常返回
fork成功.父進程刪掉操作時生成的內存數據,checkpid,子進程調用sysconf(_SC_OPEN_MAX),並close掉大於2的fd(完全沒看懂這是啥意思).;重定向日志文件及滾動日志,失敗exit(0);轉換全部環境變量字符串參數;調用execvp 執行進程及其傳入的參數,失敗打印log;exit(0)掉子進程。(startNormal這里 完全沒看懂,為啥要fork個子進程來搞這事??另外原進程fork返回后,就對返回值子進程的pid發信號成功就表示目標進程創建成功了??)
7、成功:調用registryProxy的updateServer,向registry同步服務當前狀態到t_server_conf表中;設置些ServerObject中的參數,core計數,keepAliveTime,LimitUpdate,startTime等
8、失敗:設置服務狀態為Inactive並向registry同步到t_server_conf表,設置啟動失敗標記。記錄log,返回出對應的錯誤值或者是異常
stopServer流程
基本流程跟startServer類似。
1、參數判斷那里的差異是,當服務Inactive,Destroying,Patching,BatchPatching這些狀態時,記錄log,返回對應錯誤碼.成功,將服務狀態設置成Deactivating,並同步寫到db中
2、stop流程分類處理:
1]如果是服務是tars_php. 生成手動tars_stop.sh.但還是通過配置的stopScript來執行stop操作
2]有腳本或者壓根不是tars服務,執行stopScript。。執行腳本最終還是調用shell用sh來執行
3]其它情況,如果使用管理接口,則異步調用服務管理代理AdminFPrx->async_shutdown.最終調用Application::terminate()。這個就完全看不懂了,咋感覺是關NodeServer自己的操作呢?所停服務的名都沒傳進去。
3、服務關閉等待時間是可配的默認是2s,最大不超過60s。有個循環定時器,每100ms檢測一次服務是否關閉,關閉成功執行下一步收尾操作.如果超時時間后還是關閉不成功,則執行kill -9 ,再等待2s 成功執行收尾操作。如果還是失敗打錯誤log.返回錯誤碼
4、關閉成功收尾操作:ServerObject設置pid=0;設置服務狀態為Inactive,並同步到registry更改db的present_state為Inactive;設置為非啟動狀態.返回成功
notifyServer流程
1、參數正確性檢查。
2、如果是非tars.tarsnode服務,進行服務在線狀態檢查
3、看起來是調用AdminFPrx,通知給指定的端口?
如果通知對象是tars.tarsnode服務,則調用
pAdminPrx = Application::getCommunicator()->stringToProxy<AdminFPrx>("AdminObj@"+ServerConfig::Local) ->notify() ;(本機的AdminFPrx)
如果通知對象是其它類型的服務,則調用
pAdminPrx = Application::getCommunicator()->stringToProxy<AdminFPrx>("AdminObj@"+_serverObjectPtr->getLocalEndpoint().toString())->notify();(指定的ip端口,應該對應是要通知的指定服務)
AdminServant::notify()調用到這個去了..完全沒看懂這是啥??貌似是node自己的NotifyObServer把這個通知處理掉了?
到處,通知流程走入servant部分指定流程處理..參考servant部分的《通知部分的實現BaseNotify及NotifyObserver》章節
說實話 這種設計我覺得比較奇怪..
getServerPid 獲取指定服務pid進程號
這個pid是ServerImp::keepAlive()中帶過來的,此接口是 Node下的服務上報給Node心跳時候帶過來的.
getSettingState 獲取指定服務registry設置的狀態
如果Node的ServerObject列表中有此服務緩存,根據對應的服務有效性isEnabled().返回.Active或者Inactive。否則返回錯誤碼及Inactive。
getState 獲取指定服務狀態
如果Node的ServerObject列表中有此服務緩存,返回對應的服務當前狀態通過內部的getState().此狀態包括很多(Activity,Inactive,Activating,Deactivating等等)否則返回錯誤碼及Inactive
getStateInfo 獲取指定服務在node信息
這個函數獲取的結果相當於上面的getServerPid(),getSettingState(),getState()這幾個函數的集合
synState 同步服務狀態
此函數有點奇怪,也沒看到框架中使用此函數之處。
此函數的實現是,調用內部的synState(),調用registry的updateServer。更改服務狀態.內部的synState()函數在同步更新 重啟服務 需要准確通知主控狀態,心跳檢測定時更新服務狀態這些場合時會被調用。這個函數上報時候默認是阻塞模式,有個失敗保護,如果超過3次上報異常后,改成異步上報。上報成功后,清除失敗次數。
getPatchPercent 獲取發布服務進度
沒啥好說的。讀取對應服務ServerObject中的進度位置
delCache 備份和刪除cache的共享內存
刪除 "/usr/local/app/tars/tarsnode/data/"+sFullCacheName+"/bin/" 目錄下的shm文件。。這個功能有必要嗎?並沒有看到框架中有使用此函數之處
getUnusedShmKeys 獲取機器沒有使用的共享內存的key列表,每台機器最多分配256個key, -1分配失敗
使用shmget和ftok。讀取未使用的共享內存key. 並沒有看到框架中有使用此函數之處
NodeServer的NodeF.tar文件部分功能流程
此部分是 NodeServer節點服務器中的服務向NodeServer上報數據所用的 函數
keepAlive 向node定時上報serverInfo
函數實現是,調用對應app+servername的服務ServerObjectPtr->keepAlive(pid, adapterName);
此處會更新進程的pid;更新對應adapterName最后的心跳時間為tNow;將發布狀態或者是load狀態改成load完成,發布完成狀態;服務狀態除Activating外,若處於其它的ing態時,改成Active態。
這個函數的調用之處,定義在Application.h的
#define TARS_KEEPALIVE(adapter) {TarsNodeFHelper::getInstance()->keepAlive(adapter);}
調用之處在兩個地方,一個在Application::main()啟動途中;一個在服務端的線程ServantHandle::heartbeat(),異步調用NodeFPtr->keepAlive()挨個上報adapters的心跳.( 完全看不懂系列,話說此處是否合理呢?每個ServantHandle線程都自己上報,會不會太浪費了?)
另外內部函數, ServerObjectPtr->keepAlive()有兩個地方在調用.一個在此處,應該在心跳定時檢測線程中,心跳定時檢測線程中會對Activating的進程或者心跳超時的進程執行doMonScript()函數,成功獲得pid后,執行keepAlive(),這里會對本ServerObject下全部的adapterName更新最后心跳時間為tNow。
完全看不懂系列。這里感覺有些不科學呀,進程僵死情況下,doMonScript()應該是可以成功,但肯定不會報心跳上來,這不是放過了僵死進程嗎??
reportVersion 向node上報TARS版本信息
更新服務的tars版本。最終會上報給registry。。
這個函數的調用之處,定義在Application.h的:
#define TARS_REPORTVERSION(x) {TarsNodeFHelper::getInstance()->reportVersion(TARS_VERSION);}
唯一調用之處在Application::main()啟動途中
接口功能實現部分已介紹完畢。其它模塊部分介紹如下:
RollLoggerManager 日志類。。
看起來沒啥特殊的地方。
KeepAliveThread 心跳檢測線程.
有些關鍵的變量:
_heartTimeout 業務心跳超時時間s 配置在/tars/node/keepalive<heartTimeout> 默認配置60s。代碼默認是10s。
_monitorInterval 監控server狀態的間隔時間(s) 配置在 /tars/node/keepalive<monitorInterval> 。默認配置和代碼默認都是2s。另外,代碼保護此值在1->10之間。。這個值 沒用到
_monitorIntervalMs 新的監控狀態間隔,改成毫秒。跟上面類似功能。 配置在/tars/node/keepalive<monitorIntervalMs>。代碼默認是10.配置默認沒填 這個值 也沒用到
_synInterval 同步與regisrty server狀態的間隔時間(s) 配置在/tars/node/keepalive<synStatInterval> ,默認配置為300s.代碼默認60s
_synStatBatch 批量同步 默認是Y。配置沒配。
實際上,此心跳線程的定時頻率 是來自於,ServerFactory::getInstance()->getMinMonitorIntervalMs()。這個值默認是60s。但這個值是可變的。
每個Node下的服務的ServerObject,對應都有個ServerObject::ServerLimitInfo變量, 服務進程的limit資源狀態。這個ServerLimitInfo有些內容是通過讀取配置時候加載的。而每次給ServerObject執行對應的loadLimitInfo()時,會檢測下 若默認的_iMinMonitorIntervalMs<本ServerLimitInfo.iMonitorIntervalMs。則將_iMinMonitorIntervalMs改成ServerLimitInfo.iMonitorIntervalMs。
更具體的ServerLimitInfo方面的內容,看ServerLimitInfo部分的介紹。
看代碼和配置,好像官方給的配置文件里 壓根就沒有ServerLimitInfo方面的配置,配置位置在/tars/app_conf/??完全沒看懂
還有個完全沒看懂,心跳檢測線程 看配置是1分鍾一次??這是否太久了。
心跳線程的執行流程如下:
1、如果未獲得主控RegistryProxy,先獲取RegistryProxy。
2、如果未往registry注冊node信息,注冊node信息(操作失敗,下個定時器過來后重試),這是啥概念呢?其實就是調用RegistryServer的registerNode,把當前NodeServer往Regisitry注冊下,並且registry與此Node建立好交流的ServantProxy通道。。.
3、調用ServerFactory::getInstance()->loadServer(),加載本Node下全部服務。注意,此處有個容災保護細節。
由於加載失敗或者node下沒有部署服務,這里就會一直去訪問registry,增加這個限制,如果超過5次失敗,則不去加載,10分鍾內后再重試,把失敗次數清0,重復這段加載流程。此種加載,只要成功,Node沒重啟就不會再執行此流程。如果之后有新服務部署,會自動添加到node緩存中,並不會出現遺漏有服務未被load上.
4、如果加載成功..檢查服務的limit配置是否需要更新..這里其實是,配置更新之后,定時器會跑到這里,給每個ServerObject執行對應的loadLimitInfo().再setServerLimitInfo().更新limit資源限制條件.這里做了個條件檢測_bReousceLimitConfChanged,當有配置更改時候,_bReousceLimitConfChanged=true。本步驟才會被執行。執行完成后,_bReousceLimitConfChanged=false重置。
5、檢查服務
1]遍歷本Node下的所有服務的ServerObject。並調用他們的pServerObjectPtr->doMonScript();
2]檢測coredump limit。實際上是開關coredump的設置。在成功更改后,會調用其中sCmd="tars.closecore yes"或者"tars.closecore no"; CommandNotify(sCmd);這個就參考上面的Notify流程了。其實就是通過直連的方式,調用目標服務的AdminFPrx的notify()。把sCmd傳遞過去。在 Application.h中,定義有宏,#define TARS_CMD_CLOSE_CORE "tars.closecore";
在 Application.h中,TARS_ADD_ADMIN_CMD_PREFIX(TARS_CMD_CLOSE_CORE, Application::cmdCloseCoreDump),宏與cmdCloseCoreDump()函數綁定.實際上就是調用到ServerObject所對應服務的cmdCloseCoreDump()。最終調用linux的api..setrlimit(RLIMIT_CORE,&tlimit)。達到更改coredump設置的效果。
3]上報Server所占用的內存給registry..注釋是這么寫的。但是看代碼,內容全是進程狀態檢測。。
這里首先會檢測服務所對應進程是否有效。
如果當前進程狀態 !Inactive&&!Deactivating&& 給進程pid發信號出異常的話。並且此時若進程在Activating中並且超時,記錄失敗log
[alarm] activating,pid not exist。或者非Activating態,記錄失敗log [alarm] down,pid not exist。執行CommandStop。停止此服務進程
如果當前進程狀態!Inactive 並且其心跳超時(這里超時時間分情況討論,如果此服務的模板配置文件中,單獨配置過超時時間,則以此配置時間為准,如果沒配 則用上面的變量_heartTimeout。基本上這兩個心跳時間值在官方給的模板文件中都是60s),記錄log [alarm] zombie process,no keep alive msg for.執行CommandStop。停止此服務進程。這里判斷服務超時的標記是Node收到ServerObject對應業務服務keepAlive()上報心跳時,會置Now()為ServerObject的心跳及其Adapter所對應的心跳時間。在心跳進程檢測時,若記錄的心跳時間。(ServerObject的心跳時間或其任一Adapter心跳時間)與當前時間的時間差 > 配置的超時時間時,判斷此業務服務進程超時。。
如果服務為Inactive。並且 服務可自動啟動。記錄log.[alarm] down, server is inactive.執行CommandStart。。
完畢。
4]根據上次同步狀態時間與當前時間差 若 > _synInterval 則更新緩存中服務狀態。並且異步調用registry->async_updateServer。將業務進程服務狀態報告給registry;另,這里同步還有另一種方式,看配置中的_synStatBatch若為 Y 則將緩存中的服務狀態批量同步給registry.看配置部分,代碼默認是開啟批量的。
注意在執行每個pServerObjectPtr->doMonScript()之后,若是Node進程啟動的ServantHandle::HEART_BEAT_INTERVAL * 5這段時間內(默認50s),則continue,輪下個ServerObject.這個設計的有點奇怪,完全看不懂,看注釋寫的是 等待心跳包。為啥?
6、上報node狀態
調用registry->keepAlive()。。上報Node自己的狀態。上報頻率是 _heartTimeout。若上報失敗,說明registry重啟。步驟2得重走。
注意。本線程即使是出異常,也只是記錄log。並不會中斷線程。只有當被terminate時,線程才會退出.
ReportMemThread 上報內存線程
_monitorInterval 跟上面心跳檢測中用的配置一樣。默認是2s
此線程。執行頻率為_monitorInterval。
挨個輪詢ServerObject列表。檢測對應進程pid的 shm.物理內存。。調用REPORT_MAX 上報,實際上用的PropertyReportPtr。。
BatchPatchThread 批量發布線程
參考NodeF的接口 patchPro 部分。
RemoveLogThread 刪除日志線程.
這個東東其實是在destoryServer的時候,怕會卡住響應接口。搞了個異步刪除。實現功能是把傳入的文件及文件夾 TC_File::removeFile.
ServerLimitInfo 部分的實現