點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
背景
某天運營反饋,點了一次保存,但是后台出現了3條數據,我當時就想,不應該啊,這代碼我幾萬年沒動了,我當時就叫他先別操作了,保留一下現場,我去排查一下。
我看了下新增的代碼,直接右鍵查看作者

沒想到三歪做過改動,我就去問三歪,XX模塊的新增代碼你是不是動過?
他沉默了很久沒說話,然后抓起桌子上用剩下來的紙擦了擦鬢角留下的汗水,咽了一下口水說,是的我改過,我把之前dubbo的xml配置方式改成了注解的方式。
怎么了?現在出BUG了?
你呀你,下次這種改動跟我說一下,我估計是dubbo源碼的bug吧,不要慌,讓我去看看什么問題。
正文
其實dubbo配置的方式有很多種,大家用的最多的就是xml配置的方式,如果不需要重試次數,我們會加上重試次數為0,因為他默認是有多次的。
<dubbo:reference id="testService" interface="heiidea.trade.service.sdk.interfice.TestService" retries="0"/>
或者使用注解的方式
@Reference(retries =0)
其實我已經大概知道是什么原因了,但是為了證實自己的猜想,於是開啟了接下來的debug之旅~~~
注:dubbo版本:2.6.2
首先是在采用@Reference注解條件下:

采用@Reference注解配置重試次數
首先是都找到了dubbo重試的代碼位置(啟動dubbo項目,到調用接口時,F5進入方法,會跳轉到InvokerInvocationHandler中的invoke方法中,繼續跟蹤進入MockClusterInvoker中的invoke方法,然后進入AbstractClusterInvoker中的invoke方法中,這里主要是拿到配置的負載均衡策略,后面會到FailoverClusterInvoker的doInvoke方法中)。
重點來了,這里會獲取配置的retries值,可以看到上面配置的是0,但是取出來居然是null,如圖:

所以會返回defaultValue,加上本身調用的那一次,計算之后就會為3,如圖:

所以可以發現,采用@Reference注解的形式配置retries為0時,dubbo重試次數為2次(3中包含本身調用的那次)。
后面是采用 dubbo:reference 標簽的方式:

方式如上,在獲取屬性的時候,可以看到獲得的值為0,和注解形式配置的一致,如圖:

加上本身調用的那一次,計算之后就會為1,如圖:

所以可以發現,采用 dubbo:reference 標簽形式配置retries為0時,dubbo重試次數為0(1為本身調用的那次)。
原因分析
首先是@Reference注解形式:
dubbo會把每個接口先解析為ReferenceBean,加上ReferenceBean實現了FactoryBean接口,所以在注入的時候,會調用getObject方法,生成代理對象。
但是這不是關鍵,因為到這一步時,所有的屬性都已經加載完成,所以需要找到dubbo解析注解中屬性的代碼位置。
dubbo會使用自定義驅動器ReferenceAnnotationBeanPostProcessor來注入屬性,而具體執行注入的代碼位置是在ReferenceAnnotationBeanPostProcessor類的postProcessPropertyValues方法中調用inject方法執行的。
重點來了,因為采用標簽時,是采用@Autowired注解注入,所以是采用spring原生方式注入,而在采用@Reference注解時,注入時會走到dubbo自己的ReferenceAnnotationBeanPostProcessor中私有內部類ReferenceFieldElement的inject方法中,然后調用buildReferenceBean創建ReferenceBean。
離原因越來越近了,在該方法中可以看到beanBuilder中的retries值還是0,說明到這一步還沒有被解析為null,如圖:

繼續往下走,調用build方法中的configureBean時,在第一步preConfigureBean中方法,在該方法中會創建AnnotationPropertyValuesAdapter對象,在該對象構造方法中會調用adapt方法,然后走到AnnotationUtils中的getAttributes方法中,有一個關鍵方法nullSafeEquals,該方法會傳入當前屬性值和默認值。
如果相等,則會忽略掉該屬性,然后將符合條件的屬性放入actualAttributes這個map中,而我們的retries屬性是0,和默認值一致,所以map中不會保存retries屬性的值,只有timeout屬性,因此出現了后面獲取的值為null。
注解方式debug告一段落。

后面是dubbo:reference標簽形式:
上面說到了,標簽形式走到inject時,會和注解形式有所不同,采用該標簽時,dubbo會使用自定義的名稱空間解析器去解析,很容易理解,spring也不知道它自定義標簽里面那些玩意兒是什么意思,所以dubbo會繼承spring的。
NamespaceHandlerSupport,采用自定義的DubboNamespaceHandler解析器來解析的標簽,如下圖:

然后調用該類中的parse方法進行解析,而解析retries的地方就是獲取class(此時的class就是上圖綠色標明的ReferenceBean的class,其父類中有好多好多set方法,其中就包含setRetries方法)中所有的方法,過濾出set開頭的方法,然后切割出屬性名,放入屬性池中,可以看到此處解析出的值為0,並不為null,如下圖:


小結
畫個簡單圖:

結論
-
采用注解形式:不配置retries或者配置為0,都會重試兩次,只有配置為 -1 或更小,才會不執行重試。
-
采用標簽形式:不配置retries會重試兩次,配置為0或更小都不會重試。
所以建議大家不需要重試時可以設置為-1,比如增刪改操作的接口,否則需要保證冪等性。需要重試則設置為1或更大,其實這應該算dubbo的一個dug吧?(我覺得是。。)
到這里就結束了,而上面說到的調用getObject方法就是后續服務發現以及和服務端建立長連接並返回代理對象了。
數據出現3條是因為我定義了接口超時的時間比較短,但是我們的新增涉及文件的操作,流程時間比較久,但是線程還是在的,所以dubbo重試了三次,三次也都是成功的了。
我后面把文件操作改成異步,然后主流程是同步的時間就縮短了很多。
補充:2.7.3版本已修復,就是在注解情況下,nullSafeEquals方法中的默認值和后面保持一致了,都是2,所以為0時也能保存到map中。
我是敖丙,一個在互聯網苟且偷生的工具人。
你知道的越多,你不知道的越多,人才們的 【三連】 就是丙丙創作的最大動力,我們下期見!
注:如果本篇博客有任何錯誤和建議,歡迎人才們留言,你快說句話啊!
文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回復【資料】【面試】【簡歷】有我准備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。
