XXL-JOB 註冊流程與機制 源代碼剖析

之前的項目中需要剝離出定時任務的代碼,經過調研,我們決定使用XXL-Job來實現定時任務的調度。但是在使用過過程中,出現了一個問題——

線上測試服務器的xxl-job-admin(部署調度中心)中,配置執行器的註冊方式使用自動註冊的時候,OnLine機器地址總是獲取不正確。而當我將xxl-job-admin部署在我本地電腦上的Tomcat中的時候,使用自動註冊,OnLine機器地址能正常獲取。其他條件都是一樣的。

          光思考,是解決不了問題的,從日誌中,也沒有得到有效的信息。於是,我覺定看一看XXL-JOB註冊流程的源碼,看一看其註冊的機制。

首先,我們從Spring加載開始說起。下面,我們以Spring執行器xxl-job-executor-sample-spring爲例。

在Spring執行器的配置文件applicationcontext-xxl-job.xm中,配置了一個叫做xxlJobExecutor的bean。我們先看看這個bean。

我們可以看到,當Spring加載這個Bean的時候,會自動執行其初始化方法(init-method):start方法,在其結束生命週期的時候,會自動執行其銷燬方法(destroy-method):destroy方法。這裏,我們先看其初始化方法——start()方法的源碼:

                                       

這裏,start()方法中,順序執行了另外五個方法,從作者給的註釋中結合源代碼,我們可以知道,每個方法的作用。因爲我們主要剖析註冊機制以及其流程,所以,我們這裏着重研究initExecutorServer(port, ip, appName, accessToken)這個方法。

在initExecutorServer(port, ip, appName, accessToken)這個方法中,傳入了四個參數,這四個參數的取值是通過上面配置的bean的property屬性獲取的。其中,ip 屬性尤爲重要,簡單的項目我們一般默認爲 空 (手動設置ip會綁定Host,後文會提到)。在整個過程中,ip獲取非常重要,一定要注意。下面我們看看initExecutorServer(port, ip, appName, accessToken)這個方法的源碼:

很明顯,這裏其實就把port、ip、appName三個屬性又傳遞到serverFactory.start(port, ip, appName)方法中了。傳遞過去的參數值中,如果之前執行器組件的配置中沒有配置ip(默認也不配置),則這裏ip仍爲null;下面我們看看serverFactory.start(port, ip, appName)方法的源碼:

                

很顯然,這裏參數繼續傳遞到 JettyServer的start()方法中。如果前文沒有配置ip這個屬性,這裏傳過來的ip仍爲null。下面我們研究下 JettyServer中start()方法的源碼:

首先,我們可以看,第一個紅色方框標記。如果前面 配置中心 組件的配置中正確配置了ip這個屬性,則ip在整個傳遞過程中不爲null,則執行connector.setHost(ip);這句邏輯,這裏就是上文提到的【手動設置IP時將會綁定Host】,這段邏輯就是綁定Host。

當然,一般情況下,前面不會配置ip,則就會執行下面第二個黃色方框標記部分——把參數傳遞到ExecutorRegistryThread.getInstance().start(port, ip, appName);這個start()方法中。其中,ip仍爲null。下面看一看ExecutorRegistryThread.getInstance().start(port, ip, appName);方法的源碼:

我們首先看第一個紅色方框部分。如果執行器組件Bean中配置了ip,則這裏會組裝ip和port爲executorAddress(例如:127.0.0.1:8080 這種格式);如果執行器組件中沒有配置ip,則傳遞到這裏的ip仍爲null。就會執行IpUtil.getIpPort(port)邏輯,返回值即爲executorAddress。下面我們看看IpUtil.getIpPort(port)的邏輯:

這裏就是直接調用本類的另一個方法getIp()來獲取ip,看看getIp()源碼:

在getIp()方法中,首先調用本類的getAddress()方法,如果getAddress()方法返回的是null,則再調用InetAddress類的getHostAddress()方法。首先,我們看看getAddress()方法的源碼:

這裏,又調用了getFirstValidAddress()方法,看看源碼:

首先,調用getLocalHost()方法得到一個結果,如果其驗證通過,是一個合法的IP地址,則直接認爲其是本地的IP地址,將其返回。但是,這個方法,有坑!下面會進行分析!如果調用getLocalHost方法得到的結果驗證沒有通過,得到的結果不是一個合法的IP地址,則執行下面紅色方框中的邏輯——枚舉本地所有網卡,並返回第一個合法的IP地址作爲本地地址。下面,我們看看驗證邏輯源碼:

這裏代碼很簡單,就不詳加描述了。下面,我們剖析下getFirstValidAddress()源碼(從這裏往上第二張圖)中的InetAddress.getLocalHost()這個方法。我們先看看官方API:

剛開始測試的時候,這裏InetAddress.getLocalHost()返回了一個錯誤的IP地址。爲什麼這個方法會返回一個錯誤的地址?因爲這個方法的原理是通過 獲取本機的hostname,然後對此hostname做解析,從而獲取IP地址的。那麼問題來了,在Linux操作系統下,如果在本機的/etc/hosts文件裏對這個主機名 指向了一個錯誤的IP地址,那麼InetAddress.getLocalHost就會返回這個錯誤的IP地址。當然如果你的hostname是到DNS 去解析的,碰巧DNS上的信息也是錯的,也同樣是悲慘結局。

然而,在這次測試中,我們遇到的問題還不僅如此。我們在配置執行器時,我們選擇自動註冊,結果 調度中心 的 執行器管理 條目下,發現我們配置的執行器的 OnLine機器地址(就是貫穿整個過程的ip) 是這個地址 【120.xxx.xxx.176:90xx】,90xx是我們自己配置的端口。但是,在測試機上,我們使用 ifconfig -a 查看本機網卡,並沒有發現ip爲【120.xxx.xxx.176】的ip。我們使用 hostname 命令查看本機配置的hostname,結果顯示的是 xxxxx.cn(xxxxx是被我隱藏的地址),然後我執行 ping xxxxx.cn,果然出現了【64 bytes from 120.xxx.xxx.176: icmp_seq=1 ttl=115 time=34.4 ms】這樣一句話。我們查看 /etc/hosts文件,並沒有發現其中有 xxxxx.cn 相關的配置。同時,在/etc/sysconfig/network文件裏面,有這樣一條 HOSTNAME=ts3。也就是說,在本太機器上,定義的HOSTNAME並不是 xxxxx.cn,同時,使用hostname 命令 得到的 也不是 network 文件中定義的HOSTNAME !

這又是如何呢?通過查詢資料,我發現,修改HOSTNAME後,需要重啓網卡。具體原因,不詳細講,要從Linux內核代碼開始說起。所以,我猜測,本機之前曾經在/etc/sysconfig/network文件中配置了HOSTNAME=xxxxx.cn,並且在/etc/hosts文件中爲該hostname分配了ip 120.xxx.xxx.176;後來又修改了HOSTNAME,並且刪除了hosts文件中 xxxxx.cn 相關的IP,但是,這麼做了後,並沒有重啓網卡,所以系統默認得到的還是原來的信息。

所以,我們使用InetAddress.getLocalHost()方法得到的ip是原來的xxxx.cn對應的ip,而這個IP通過了驗證,所以就沒有再執行後面邏輯——枚舉本地所有網卡,並返回第一個合法的IP地址。

到這裏,我們就得到了我們需要的IP,也就下圖中的executorAddress得到了。然後就執行下圖中第二個紅色方框中的邏輯:

首先,我們分析一下代碼執行的流程。首先,新創建一個線程registryThread,並且該線程爲守護線程。然後該線程執行邏輯——while循環,我們一直 toStop = false,也就是該端邏輯一直執行,直至註冊成功。然後注意金黃色方框中的邏輯——我們已知RegistryConfig.BEAT_TIMEOUT = 30,也就是while循環每循環一次,就休眠30秒,然後再執行。——這就是所謂的心跳註冊機制,每30秒進行一次註冊,直至註冊成功。我們再看看具體的執行邏輯:

首先將註冊必需的信息組裝爲一個registryParam實體,然後執行具體的註冊邏輯:adminBiz.registry(registryParam),我們看看adminBiz.registry()方法的源碼:

其實就是把相關信息寫入到數據庫中。至此,註冊流程就走完了。

博主也是初入職場的小菜鳥,也在不斷的學習之中,希望和大家一起努力,有問題一起解決。能幫到大家的話,我也覺得十分榮幸。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章