找到了Dubbo源碼的BUG,同事誇了我半天

背景

某天運營反饋,點了一次保存,但是後臺出現了3條數據,我當時就想,不應該啊,這代碼我幾萬年沒動了,我當時就叫他先別操作了,保留一下現場,我去排查一下。

我看了下新增的代碼,直接右鍵查看作者

找到了Dubbo源碼的BUG,同事誇了我半天

沒想到三歪做過改動,我就去問三歪,XX模塊的新增代碼你是不是動過?

他沉默了很久沒說話,然後抓起桌子上用剩下來的紙擦了擦鬢角留下的汗水,嚥了一下口水說,是的我改過,我把之前dubbo的xml配置方式改成了註解的方式。

怎麼了?現在出BUG了?

你呀你,下次這種改動跟我說一下,我估計是dubbo源碼的bug吧,不要慌,讓我去看看什麼問題。

正文

其實dubbo配置的方式有很多種,大家用的最多的就是xml配置的方式,如果不需要重試次數,我們會加上重試次數為0,因為他默認是有多次的。

<code><reference> /<code>

或者使用註解的方式

<code>@Reference(retries =0) /<code>

其實我已經大概知道是什麼原因了,但是為了證實自己的猜想,於是開啟了接下來的debug之旅~~~

注:dubbo版本:2.6.2

首先是在採用@Reference註解條件下:

找到了Dubbo源碼的BUG,同事誇了我半天

採用@Reference註解配置重試次數

首先是都找到了dubbo重試的代碼位置(啟動dubbo項目,到調用接口時,F5進入方法,會跳轉到InvokerInvocationHandler中的invoke方法中,繼續跟蹤進入MockClusterInvoker中的invoke方法,然後進入AbstractClusterInvoker中的invoke方法中,這裡主要是拿到配置的負載均衡策略,後面會到FailoverClusterInvoker的doInvoke方法中)。

重點來了,這裡會獲取配置的retries值,可以看到上面配置的是0,但是取出來居然是null,如圖:

找到了Dubbo源碼的BUG,同事誇了我半天

value為null

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

找到了Dubbo源碼的BUG,同事誇了我半天

值為3

所以可以發現,採用@Reference註解的形式配置retries為0時,dubbo重試次數為2次(3中包含本身調用的那次)。

後面是採用 dubbo:reference 標籤的方式:

找到了Dubbo源碼的BUG,同事誇了我半天

dubbo標籤方式

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

找到了Dubbo源碼的BUG,同事誇了我半天

value為0

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

找到了Dubbo源碼的BUG,同事誇了我半天

value為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,如圖:

找到了Dubbo源碼的BUG,同事誇了我半天

retries為“0”

繼續往下走,調用build方法中的configureBean時,在第一步preConfigureBean中方法,在該方法中會創建AnnotationPropertyValuesAdapter對象,在該對象構造方法中會調用adapt方法,然後走到AnnotationUtils中的getAttributes方法中,有一個關鍵方法nullSafeEquals,該方法會傳入當前屬性值和默認值。

如果相等,則會忽略掉該屬性,然後將符合條件的屬性放入actualAttributes這個map中,而我們的retries屬性是0,和默認值一致,所以map中不會保存retries屬性的值,只有timeout屬性,因此出現了後面獲取的值為null。

註解方式debug告一段落。

找到了Dubbo源碼的BUG,同事誇了我半天

map不包含retries

後面是dubbo:reference標籤形式:

上面說到了,標籤形式走到inject時,會和註解形式有所不同,採用該標籤時,dubbo會使用自定義的名稱空間解析器去解析,很容易理解,spring也不知道它自定義標籤裡面那些玩意兒是什麼意思,所以dubbo會繼承spring的。

NamespaceHandlerSupport,採用自定義的DubboNamespaceHandler解析器來解析的標籤,如下圖:

找到了Dubbo源碼的BUG,同事誇了我半天

dubbo自定義名稱空間解析器

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

找到了Dubbo源碼的BUG,同事誇了我半天

獲取屬性名的位置

找到了Dubbo源碼的BUG,同事誇了我半天

獲取retries值為0

小結

畫個簡單圖:

找到了Dubbo源碼的BUG,同事誇了我半天

大致流程

結論

  • 採用註解形式:不配置retries或者配置為0,都會重試兩次,只有配置為 -1 或更小,才會不執行重試。
  • 採用標籤形式:不配置retries會重試兩次,配置為0或更小都不會重試。

所以建議大家不需要重試時可以設置為-1,比如增刪改操作的接口,否則需要保證冪等性。需要重試則設置為1或更大,其實這應該算dubbo的一個dug吧?(我覺得是。。)

到這裡就結束了,而上面說到的調用getObject方法就是後續服務發現以及和服務端建立長連接並返回代理對象了。

數據出現3條是因為我定義了接口超時的時間比較短,但是我們的新增涉及文件的操作,流程時間比較久,但是線程還是在的,所以dubbo重試了三次,三次也都是成功的了。

我後面把文件操作改成異步,然後主流程是同步的時間就縮短了很多。

補充:2.7.3版本已修復,就是在註解情況下,nullSafeEquals方法中的默認值和後面保持一致了,都是2,所以為0時也能保存到map中。

我是敖丙,一個在互聯網苟且偷生的工具人。

你知道的越多,你不知道的越多人才們的 【三連】 就是丙丙創作的最大動力,我們下期見!

注:如果本篇博客有任何錯誤和建議,歡迎人才們留言,你快說句話啊


分享到:


相關文章: