Hystrix HystricCommand配置參數全解析

https://www.cnblogs.com/zhenbianshu/p/9630167.html

https://juejin.im/post/5c4f18cb6fb9a049ff4e8524

https://blog.csdn.net/tongtong_use/article/details/78611225

前言


不久前在部門週會上分享了 Hystrix 源碼解析之後,就無奈地背上了專家包袱,同事們都認爲我對 Hystrix 很熟,我們接觸 Hystrix 更多的還是工作中的使用和配置,所以很多人一遇到 Hystrix 的配置問題就會過來問我。爲了不讓他們失望,我把 Hystrix 的 配置文檔 仔細看了一遍,將有疑問的點通過翻源碼、查官方 issue、自己實驗的方式整理了一遍,這纔對 Hystrix 的配置有了一定的瞭解。

在瞭解這些配置項的過程中,我也發現了很多坑,平常我們使用中認爲理所應當的值並不會讓 Hystrix 如期望工作,沒有經過斟酌就複製粘貼的配置會讓 Hystrix 永遠不會起作用。於是寫下本文,希望能幫助小夥伴們掌握 Hystrix。如果想了解 Hystrix 的話,可以搭配我之前的分享 PPT:Hystrix 源碼解析

文章歡迎轉載,請尊重作者勞動成果,帶上原文鏈接:https://www.cnblogs.com/zhenbianshu/p/9630167.html

HystrixCommand


配置方式

我們的配置都是基於 HystrixCommand 的,我們通過在方法上添加 @HystrixCommand 註解並配置註解的參數來實現配置,但有的時候一個類裏面會有多個 Hystrix 方法,每個方法都是類似配置的話會冗餘很多代碼,這時候我們可以在類上使用 @DefaultProperties 註解來給整個類的 Hystrix 方法設置一個默認值。

配置項

下面是 HystrixCommand 支持的參數,除了 commandKey/observableExecutionMode/fallbackMethod 外,都可以使用 @DefaultProperties 配置默認值。

  • commandKey:用來標識一個 Hystrix 命令,默認會取被註解的方法名。需要注意:Hystrix 裏同一個鍵的唯一標識並不包括 groupKey,建議取一個獨一二無的名字,防止多個方法之間因爲鍵重複而互相影響。

  • groupKey:一組 Hystrix 命令的集合, 用來統計、報告,默認取類名,可不配置。

  • threadPoolKey:用來標識一個線程池,如果沒設置的話會取 groupKey,很多情況下都是同一個類內的方法在共用同一個線程池,如果兩個共用同一線程池的方法上配置了同樣的屬性,在第一個方法被執行後線程池的屬性就固定了,所以屬性會以第一個被執行的方法上的配置爲準。

  • commandProperties:與此命令相關的屬性。

  • threadPoolProperties:與線程池相關的屬性,

  • observableExecutionMode:當 Hystrix 命令被包裝成 RxJava 的 Observer 異步執行時,此配置指定了 Observable 被執行的模式,默認是 ObservableExecutionMode.EAGER,Observable 會在被創建後立刻執行,而 ObservableExecutionMode.EAGER模式下,則會產生一個 Observable 被 subscribe 後執行。我們常見的命令都是同步執行的,此配置項可以不配置。

  • ignoreExceptions:默認 Hystrix 在執行方法時捕獲到異常時執行回退,並統計失敗率以修改熔斷器的狀態,而被忽略的異常則會直接拋到外層,不會執行回退方法,也不會影響熔斷器的狀態。

  • raiseHystrixExceptions:當配置項包括 HystrixRuntimeException 時,所有的未被忽略的異常都會被包裝成 HystrixRuntimeException,配置其他種類的異常好像並沒有什麼影響。

  • fallbackMethod:方法執行時熔斷、錯誤、超時時會執行的回退方法,需要保持此方法與 Hystrix 方法的簽名和返回值一致。

  • defaultFallback:默認回退方法,當配置 fallbackMethod 項時此項沒有意義,另外,默認回退方法不能有參數,返回值要與 Hystrix方法的返回值相同。

commandProperties


配置方式

Hystrix 的命令屬性是由 @HystrixProperty 註解數組構成的,HystrixProperty 由 name 和 value 兩個屬性,數據類型都是字符串。

以下將所有的命令屬性分組來介紹。

線程隔離(Isolation)

  • execution.isolation.strategy: 配置請求隔離的方式,有 threadPool(線程池,默認)和 semaphore(信號量)兩種,信號量方式高效但配置不靈活,我們一般採用 Java 裏常用的線程池方式。

  • execution.timeout.enabled:是否給方法執行設置超時,默認爲 true。

  • execution.isolation.thread.timeoutInMilliseconds:方法執行超時時間,默認值是 1000,即 1秒,此值根據業務場景配置。

  • execution.isolation.thread.interruptOnTimeout: execution.isolation.thread.interruptOnCancel:是否在方法執行超時/被取消時中斷方法。需要注意在 JVM 中我們無法強制中斷一個線程,如果 Hystrix 方法裏沒有處理中斷信號的邏輯,那麼中斷會被忽略。

  • execution.isolation.semaphore.maxConcurrentRequests:默認值是 10,此配置項要在 execution.isolation.strategy 配置爲 semaphore 時纔會生效,它指定了一個 Hystrix 方法使用信號量隔離時的最大併發數,超過此併發數的請求會被拒絕。信號量隔離的配置就這麼一個,也是前文說信號量隔離配置不靈活的原因。

統計器(Metrics)

滑動窗口: Hystrix 的統計器是由滑動窗口來實現的,我們可以這麼來理解滑動窗口:一位乘客坐在正在行駛的列車的靠窗座位上,列車行駛的公路兩側種着一排挺拔的白楊樹,隨着列車的前進,路邊的白楊樹迅速從窗口滑過,我們用每棵樹來代表一個請求,用列車的行駛代表時間的流逝,那麼,列車上的這個窗口就是一個典型的滑動窗口,這個乘客能通過窗口看到的白楊樹就是 Hystrix 要統計的數據。

: bucket 是 Hystrix 統計滑動窗口數據時的最小單位。同樣類比列車窗口,在列車速度非常快時,如果每掠過一棵樹就統計一次窗口內樹的數據,顯然開銷非常大,如果乘客將窗口分成十分,列車前進行時每掠過窗口的十分之一就統計一次數據,開銷就完全可以接受了。 Hystrix 的 bucket (桶)也就是窗口 N分之一 的概念。

  • metrics.rollingStats.timeInMilliseconds:此配置項指定了窗口的大小,單位是 ms,默認值是 1000,即一個滑動窗口默認統計的是 1s 內的請求數據。

  • metrics.healthSnapshot.intervalInMilliseconds:它指定了健康數據統計器(影響 Hystrix 熔斷)中每個桶的大小,默認是 500ms,在進行統計時,Hystrix 通過 metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds 計算出桶數,在窗口滑動時,每滑過一個桶的時間間隔時就統計一次當前窗口內請求的失敗率。

  • metrics.rollingStats.numBuckets:Hystrix 會將命令執行的結果類型都統計彙總到一塊,給上層應用使用或生成統計圖表,此配置項即指定了,生成統計數據流時滑動窗口應該拆分的桶數。此配置項最易跟上面的 metrics.healthSnapshot.intervalInMilliseconds 搞混,認爲此項影響健康數據流的桶數。 此項默認是 10,並且需要保持此值能被 metrics.rollingStats.timeInMilliseconds 整除。

  • metrics.rollingPercentile.enabled:是否統計方法響應時間百分比,默認爲 true 時,Hystrix 會統計方法執行的 1%,10%,50%,90%,99% 等比例請求的平均耗時用以生成統計圖表。

  • metrics.rollingPercentile.timeInMilliseconds:統計響應時間百分比時的窗口大小,默認爲 60000,即一分鐘。

  • metrics.rollingPercentile.numBuckets:統計響應時間百分比時滑動窗口要劃分的桶用,默認爲6,需要保持能被metrics.rollingPercentile.timeInMilliseconds 整除。

  • metrics.rollingPercentile.bucketSize:統計響應時間百分比時,每個滑動窗口的桶內要保留的請求數,桶內的請求超出這個值後,會覆蓋最前面保存的數據。默認值爲 100,在統計響應百分比配置全爲默認的情況下,每個桶的時間長度爲 10s = 60000ms / 6,但這 10s 內只保留最近的 100 條請求的數據。

熔斷器(Circuit Breaker)

  • circuitBreaker.enabled:是否啓用熔斷器,默認爲 true;

  • circuitBreaker.forceOpen: circuitBreaker.forceClosed:是否強制啓用/關閉熔斷器,強制啓用關閉都想不到什麼應用的場景,保持默認值,不配置即可。

  • circuitBreaker.requestVolumeThreshold:啓用熔斷器功能窗口時間內的最小請求數。試想如果沒有這麼一個限制,我們配置了 50% 的請求失敗會打開熔斷器,窗口時間內只有 3 條請求,恰巧兩條都失敗了,那麼熔斷器就被打開了,5s 內的請求都被快速失敗。此配置項的值需要根據接口的 QPS 進行計算,值太小會有誤打開熔斷器的可能,值太大超出了時間窗口內的總請求數,則熔斷永遠也不會被觸發。建議設置爲 QPS * 窗口秒數 * 60%

  • circuitBreaker.errorThresholdPercentage:在通過滑動窗口獲取到當前時間段內 Hystrix 方法執行的失敗率後,就需要根據此配置來判斷是否要將熔斷器打開了。 此配置項默認值是 50,即窗口時間內超過 50% 的請求失敗後會打開熔斷器將後續請求快速失敗。

  • circuitBreaker.sleepWindowInMilliseconds:熔斷器打開後,所有的請求都會快速失敗,但何時服務恢復正常就是下一個要面對的問題。熔斷器打開時,Hystrix 會在經過一段時間後就放行一條請求,如果這條請求執行成功了,說明此時服務很可能已經恢復了正常,那麼會將熔斷器關閉,如果此請求執行失敗,則認爲服務依然不可用,熔斷器繼續保持打開狀態。此配置項指定了熔斷器打開後經過多長時間允許一次請求嘗試執行,默認值是 5000。

其他(Context/Fallback)

  • requestCache.enabled:是否啓用請求結果緩存。默認是 true,但它並不意味着我們的每個請求都會被緩存。緩存請求結果和從緩存中獲取結果都需要我們配置 cacheKey,並且在方法上使用 @CacheResult 註解聲明一個緩存上下文。

  • requestLog.enabled:是否啓用請求日誌,默認爲 true。

  • fallback.enabled:是否啓用方法回退,默認爲 true 即可。

  • fallback.isolation.semaphore.maxConcurrentRequests:回退方法執行時的最大併發數,默認是10,如果大量請求的回退方法被執行時,超出此併發數的請求會拋出 REJECTED_SEMAPHORE_FALLBACK 異常。

threadPoolProperties


配置方式

線程池的配置也是由 HystrixProperty 數組構成,配置方式與命令屬性一致。

配置項

  • coreSize:核心線程池的大小,默認值是 10,一般根據 QPS * 99% cost + redundancy count 計算得出。

  • allowMaximumSizeToDivergeFromCoreSize:是否允許線程池擴展到最大線程池數量,默認爲 false;

  • maximumSize:線程池中線程的最大數量,默認值是 10,此配置項單獨配置時並不會生效,需要啓用 allowMaximumSizeToDivergeFromCoreSize 項。

  • maxQueueSize:作業隊列的最大值,默認值爲 -1,設置爲此值時,隊列會使用 SynchronousQueue,此時其 size 爲0,Hystrix 不會向隊列內存放作業。如果此值設置爲一個正的 int 型,隊列會使用一個固定 size 的 LinkedBlockingQueue,此時在覈心線程池內的線程都在忙碌時,會將作業暫時存放在此隊列內,但超出此隊列的請求依然會被拒絕。

  • queueSizeRejectionThreshold:由於 maxQueueSize 值在線程池被創建後就固定了大小,如果需要動態修改隊列長度的話可以設置此值,即使隊列未滿,隊列內作業達到此值時同樣會拒絕請求。此值默認是 5,所以有時候只設置了 maxQueueSize 也不會起作用。

  • keepAliveTimeMinutes:由上面的 maximumSize,我們知道,線程池內核心線程數目都在忙碌,再有新的請求到達時,線程池容量可以被擴充爲到最大數量,等到線程池空閒後,多於核心數量的線程還會被回收,此值指定了線程被回收前的存活時間,默認爲 2,即兩分鐘。

工作方式

Hystrix 內線程池的使用是基於 Java 內置線程池的簡單包裝,通常有以下三種狀態:

  • 如果請求量少,達不到 coreSize,通常會使用核心線程來執行任務。
  • 如果設置了 maxQueueSize,當請求數超過了 coreSize, 通常會把請求放到 queue 裏,待覈心線程有空閒時消費。
  • 如果 queue 長度無法存儲請求,則會創建新線程執行直到達到 maximumSize 最大線程數,多出核心線程數的線程會在空閒時回收。

小結


排查配置問題本身也是學習的過程,瞭解 Hystrix 源碼的過程中也學到了不少 Java 線程池相關的知識,嗯,收穫不小。

關於本文有什麼問題可以在下面留言交流,如果您覺得本文對您有幫助,可以點擊下面的 推薦 支持一下我,博客一直在更新,歡迎 關注

 

一、什麼情況下會觸發fallback方法?
名字

描述

觸發fallback

EMIT

值傳遞

NO

SUCCESS

執行完成,沒有錯誤

NO

FAILURE

執行拋出異常

YES

TIMEOUT

執行開始,但沒有在允許的時間內完成

YES

BAD_REQUEST

執行拋出HystrixBadRequestException

NO

SHORT_CIRCUITED

斷路器打開,不嘗試執行

YES

THREAD_POOL_REJECTED

線程池拒絕,不嘗試執行

YES

SEMAPHORE_REJECTED

信號量拒絕,不嘗試執行

YES

 

二、fallback方法在什麼情況下會拋出異常
名字

描述

拋異常

FALLBACK_EMIT

Fallback值傳遞

NO

FALLBACK_SUCCESS

Fallback執行完成,沒有錯誤

NO

FALLBACK_FAILURE

Fallback執行拋出出錯

YES

FALLBACK_REJECTED

Fallback信號量拒絕,不嘗試執行

YES

FALLBACK_MISSING

沒有Fallback實例

YES

 

三、hystrix dashboard界面監控參數
 

四、配置信息(default或HystrixCommandKey)最常用的幾項
超時時間(默認1000ms,單位:ms) 

(1)hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

在調用方配置,被該調用方的所有方法的超時時間都是該值,優先級低於下邊的指定配置

(2)hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds

在調用方配置,被該調用方的指定方法(HystrixCommandKey方法名)的超時時間是該值

線程池核心線程數

hystrix.threadpool.default.coreSize(默認爲10)

Queue

(1)hystrix.threadpool.default.maxQueueSize(最大排隊長度。默認-1,使用SynchronousQueue。其他值則使用 LinkedBlockingQueue。如果要從-1換成其他值則需重啓,即該值不能動態調整,若要動態調整,需要使用到下邊這個配置)

(2)hystrix.threadpool.default.queueSizeRejectionThreshold(排隊線程數量閾值,默認爲5,達到時拒絕,如果配置了該選項,隊列的大小是該隊列)

注意:如果maxQueueSize=-1的話,則該選項不起作用

斷路器

(1)hystrix.command.default.circuitBreaker.requestVolumeThreshold(當在配置時間窗口內達到此數量的失敗後,進行短路。默認20個)

For example, if the value is 20, then if only 19 requests are received in the rolling window (say a window of 10 seconds) the circuit will not trip open even if all 19 failed.

簡言之,10s內請求失敗數量達到20個,斷路器開。

(2)hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds(短路多久以後開始嘗試是否恢復,默認5s)

(3)hystrix.command.default.circuitBreaker.errorThresholdPercentage(出錯百分比閾值,當達到此閾值後,開始短路。默認50%)

fallback

hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests(調用線程允許請求HystrixCommand.GetFallback()的最大數量,默認10。超出時將會有異常拋出,注意:該項配置對於THREAD隔離模式也起作用)

 

五、屬性配置參數
參數說明英文地址:https://github.com/Netflix/Hystrix/wiki/Configuration

 

HystrixProperty參考代碼地址:http://www.programcreek.com/java-api-examples/index.php?source_dir=Hystrix-master/hystrix-contrib/hystrix-javanica/src/test/java/com/netflix/hystrix/contrib/javanica/test/common/configuration/command/BasicCommandPropertiesTest.java

 

(一)Command Properties
以下屬性控制HystrixCommand行爲:

1、Execution
以下屬性控制HystrixCommand.run()如何執行。

 

參數

描述

默認值

execution.isolation.strategy

隔離策略,有THREAD和SEMAPHORE

THREAD - 它在單獨的線程上執行,併發請求受線程池中的線程數量的限制
SEMAPHORE - 它在調用線程上執行,併發請求受到信號量計數的限制

默認使用THREAD模式,以下幾種場景可以使用SEMAPHORE模式:

只想控制併發度

外部的方法已經做了線程隔離

調用的是本地方法或者可靠度非常高、耗時特別小的方法(如medis)

 

execution.isolation.thread.timeoutInMilliseconds

超時時間

默認值:1000

在THREAD模式下,達到超時時間,可以中斷

在SEMAPHORE模式下,會等待執行完成後,再去判斷是否超時

設置標準:

有retry,99meantime+avg meantime

沒有retry,99.5meantime

 

execution.timeout.enabled

HystrixCommand.run()執行是否應該有超時。

默認值:true

execution.isolation.thread.interruptOnTimeout

在發生超時時是否應中斷HystrixCommand.run()執行。

默認值:true

THREAD模式有效

execution.isolation.thread.interruptOnCancel

當發生取消時,執行是否應該中斷。

默認值爲false

THREAD模式有效

execution.isolation.semaphore.maxConcurrentRequests

設置在使用時允許到HystrixCommand.run()方法的最大請求數。

默認值:10

SEMAPHORE模式有效

 

2、Fallback
以下屬性控制HystrixCommand.getFallback()如何執行。這些屬性適用於ExecutionIsolationStrategy.THREAD和ExecutionIsolationStrategy.SEMAPHORE。

參數

描述

默認值

fallback.isolation.semaphore.maxConcurrentRequests

設置從調用線程允許HystrixCommand.getFallback()方法的最大請求數。

SEMAPHORE模式有效

默認值:10

fallback.enabled

確定在發生失敗或拒絕時是否嘗試調用HystrixCommand.getFallback()。

默認值爲true

 

3、Circuit Breaker
斷路器屬性控制HystrixCircuitBreaker的行爲。

 

參數

描述

默認值

circuitBreaker.enabled

確定斷路器是否用於跟蹤運行狀況和短路請求(如果跳閘)。

默認值爲true

circuitBreaker.requestVolumeThreshold

熔斷觸發的最小個數/10s

默認值:20

circuitBreaker.sleepWindowInMilliseconds

熔斷多少秒後去嘗試請求

默認值:5000

circuitBreaker.errorThresholdPercentage

失敗率達到多少百分比後熔斷

默認值:50

主要根據依賴重要性進行調整

 

circuitBreaker.forceOpen

 

屬性如果爲真,強制斷路器進入打開(跳閘)狀態,其中它將拒絕所有請求。

默認值爲false

此屬性優先於circuitBreaker.forceClosed

circuitBreaker.forceClosed

該屬性如果爲真,則迫使斷路器進入閉合狀態,其中它將允許請求,而不考慮誤差百分比。

默認值爲false

如果是強依賴,應該設置爲true

circuitBreaker.forceOpen屬性優先,因此如果forceOpen設置爲true,此屬性不執行任何操作。

 

4、Metrics
以下屬性與從HystrixCommand和HystrixObservableCommand執行捕獲指標有關。

參數

描述

默認值

metrics.rollingStats.timeInMilliseconds

此屬性設置統計滾動窗口的持續時間(以毫秒爲單位)。對於斷路器的使用和發佈Hystrix保持多長時間的指標。

默認值:10000

metrics.rollingStats.numBuckets

此屬性設置rollingstatistical窗口劃分的桶數。

以下必須爲true - “metrics.rollingStats.timeInMilliseconds%metrics.rollingStats.numBuckets == 0” -否則將拋出異常。

默認值:10

metrics.rollingPercentile.enabled

此屬性指示是否應以百分位數跟蹤和計算執行延遲。 如果禁用它們,則所有摘要統計信息(平均值,百分位數)都將返回-1。

默認值爲true

metrics.rollingPercentile.timeInMilliseconds

 

此屬性設置滾動窗口的持續時間,其中保留執行時間以允許百分位數計算,以毫秒爲單位。

默認值:60000

metrics.rollingPercentile.numBuckets

此屬性設置rollingPercentile窗口將劃分的桶的數量。

以下內容必須爲true - “metrics.rollingPercentile.timeInMilliseconds%metrics.rollingPercentile.numBuckets == 0” -否則將拋出異常。

默認值:6

metrics.rollingPercentile.bucketSize

此屬性設置每個存儲桶保留的最大執行次數。如果在這段時間內發生更多的執行,它們將繞回並開始在桶的開始處重寫。

默認值:100

metrics.healthSnapshot.intervalInMilliseconds

此屬性設置在允許計算成功和錯誤百分比並影響斷路器狀態的健康快照之間等待的時間(以毫秒爲單位)。

默認值:500

 

5、Request Context
這些屬性涉及HystrixCommand使用的HystrixRequestContext功能。

 

參數

描述

默認值

requestCache.enabled

HystrixCommand.getCacheKey()是否應與HystrixRequestCache一起使用,以通過請求範圍的緩存提供重複數據刪除功能。

默認值爲true

requestLog.enabled

HystrixCommand執行和事件是否應記錄到HystrixRequestLog。

默認值爲true

 

 

(二)Collapser Properties
下列屬性控制HystrixCollapser行爲。

參數

描述

默認值

maxRequestsInBatch

 

此屬性設置在觸發批處理執行之前批處理中允許的最大請求數。

Integer.MAX_VALUE

timerDelayInMilliseconds

 

此屬性設置創建批處理後觸發其執行的毫秒數。

默認值:10

requestCache.enabled

 

此屬性指示是否爲HystrixCollapser.execute()和HystrixCollapser.queue()調用啓用請求高速緩存。

默認值:true

 

(三)ThreadPool Properties
以下屬性控制Hystrix命令在其上執行的線程池的行爲。

大多數時候,默認值爲10的線程會很好(通常可以做得更小)。

 

參數

描述

默認值

coreSize

線程池coreSize

默認值:10

設置標準:qps*99meantime+breathing room

maximumSize

此屬性設置最大線程池大小。 這是在不開始拒絕HystrixCommands的情況下可以支持的最大併發數。 請注意,此設置僅在您還設置allowMaximumSizeToDivergeFromCoreSize時纔會生效。

默認值:10

maxQueueSize

請求等待隊列

默認值:-1

如果使用正數,隊列將從SynchronizeQueue改爲LinkedBlockingQueue

queueSizeRejectionThreshold

此屬性設置隊列大小拒絕閾值 - 即使未達到maxQueueSize也將發生拒絕的人爲最大隊列大小。 此屬性存在,因爲BlockingQueue的maxQueueSize不能動態更改,我們希望允許您動態更改影響拒絕的隊列大小。

默認值:5

注意:如果maxQueueSize == -1,則此屬性不適用。

keepAliveTimeMinutes

此屬性設置保持活動時間,以分鐘爲單位。

默認值:1

allowMaximumSizeToDivergeFromCoreSize

此屬性允許maximumSize的配置生效。 那麼該值可以等於或高於coreSize。 設置coreSize <maximumSize會創建一個線程池,該線程池可以支持maximumSize併發,但在相對不活動期間將向系統返回線程。 (以keepAliveTimeInMinutes爲準)

默認值:false

metrics.rollingStats.timeInMilliseconds

此屬性設置statistical rolling窗口的持續時間(以毫秒爲單位)。 這是爲線程池保留多長時間。

默認值:10000

metrics.rollingStats.numBuckets

此屬性設置滾動統計窗口劃分的桶數。
注意:以下必須爲true - “metrics.rollingStats.timeInMilliseconds%metrics.rollingStats.numBuckets == 0” -否則將引發異常。

默認值:10

 

 

(四)其他
 

參數

描述

默認值

groupKey

表示所屬的group,一個group共用線程池

默認值:getClass().getSimpleName();

commandKey

 

默認值:當前執行方法名

 
————————————————
版權聲明:本文爲CSDN博主「IT桐桐」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/tongtong_use/article/details/78611225

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