限流 熔斷:Hystrix、 Sentinel

目錄

一、Hystrix    SpringCloud

1、Hystrix名字由來?

2、Hystrix是什麼?

3、爲什麼需要Hystrix ?

4、Hystrix的設計原則是什麼?

5、Hystrix如何解決依賴隔離      

6、Hystrix是如何實現它的目標的?

7、如何使用Hystrix?

8、Hystrix-dashboard監控平臺搭建

9、Hystrix配置與分析

10、參數配置

11、實戰

12、根據儀表盤調參

13、Hystrix停止開發,我們該何去何從?

二、Sentinel 分佈式系統的流量防衛兵     SpringCloud alibaba

1、簡介

2、Sentinel 功能和設計理念

3、Sentinel 的開源生態:

4、Sentinel 是如何工作的

5、Sentinel+Nacos使用


 

一、Hystrix    SpringCloud

1、Hystrix名字由來?

        Hystrix對應的中文名字是“豪豬”,豪豬周身長滿了刺,能保護自己不受天敵的傷害,代表了一種防禦機制,這與hystrix本身的功能不謀而合,因此Netflix團隊將該框架命名爲Hystrix,並使用了對應的卡通形象做作爲logo。

                                                

 

2、Hystrix是什麼?

       在一個分佈式系統裏,許多依賴不可避免的會調用失敗,比如超時、異常等,如何能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,這個就是Hystrix需要做的事情。

       Hystrix提供了熔斷、隔離、Fallback、cache、監控等功能,能夠在一個、或多個依賴同時出現問題時保證系統依然可用。

    

       下圖是一個Hystrix在微服務系統中的應用及角色:

                 

           Eureka/Consul/Zookeeper:服務發現 (根據情況選擇一個,eureka已經宣佈閉源)

           Hystrix:斷路器

           Zuul:智能路由

           Ribbon/Feign:客戶端負載均衡 (Feign用的更多)

           Turbine&hystrix-dashboard:集羣監控

           Springcloud-config:遠程獲取配置文件

 

 

3、爲什麼需要Hystrix ?

       在微服務架構中,根據業務來拆分成一個個的服務,服務與服務之間可以相互調用(RPC) 。爲了保證其高可用,單個服務通常會集羣部署。由於網絡原因或者自身的原因,服務並不能保證100%可用,如果單個服務出現問題,調用這個服務就會出現線程阻塞,此時若有大量的請求涌入,Servlet容器的線程資源會被消耗完畢,導致服務癱瘓。服務與服務之間的依賴性,故障會傳播,會對整個微服務系統造成災難性的嚴重後果,這就是服務故障的“雪崩”效應。

       如下圖所示:A作爲服務提供者,B爲A的服務消費者,C和D是B的服務消費者。A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。

                                       

        Hystrix語義爲“豪豬",具有自我保護的能力。   Hystrix的出現即爲解決雪崩效應。

 

 

進一步解釋:

       在大中型分佈式系統中,通常系統很多依賴(HTTP,hession,Netty,Dubbo等),如下圖:

                                      

         在高併發訪問下,這些依賴的穩定性與否對系統的影響非常大,但是依賴有很多不可控問題:如網絡連接緩慢,資源繁忙,暫時不可用,服務脫機等.

         如下圖:QPS爲50的依賴 I 出現不可用,但是其他依賴仍然可用.

                                     

        當依賴I 阻塞時,大多數服務器的線程池就出現阻塞(BLOCK),影響整個線上服務的穩定性.如下圖:

                                     

          在複雜的分佈式架構的應用程序有很多的依賴,都會不可避免地在某些時候失敗。高併發的依賴失敗時如果沒有隔離措施,當前應用服務就有被拖垮的風險。

 

例如:一個依賴30個SOA服務的系統,每個服務99.99%可用。

           99.99%的30次方 ≈ 99.7%

           0.3% 意味着一億次請求 會有 3,000,00次失敗

           換算成時間大約每月有2個小時服務不穩定.

           隨着服務依賴數量的變多,服務不穩定的概率會成指數性提高.

 

解決問題方案對依賴做隔離Hystrix就是處理依賴隔離的框架,同時也是可以幫我們做依賴服務的治理和監控.

 

     Netflix 公司開發併成功使用Hystrix,使用規模如下:

                The Netflix API processes 10+ billion HystrixCommand executions per day using thread isolation.

                Each API instance has 40+ thread-pools with 5-20 threads in each (most are set to 10).

     (大致意思是:Netflix 內部API 每天100億的HystrixCommand依賴請求使用線程隔,每個應用大約40多個線程池,每個線程池大約5-20個線程。(但一般設置爲10個線程池)。

 

4、Hystrix的設計原則是什麼?

    資源隔離(線程池隔離和信號量隔離)機制:限制調用分佈式服務的資源使用,某一個調用的服務出現問題不會影響其它服務調用。

    限流機制:限流機制主要是提前對各個類型的請求設置最高的QPS閾值,若高於設置的閾值則對該請求直接返回,不再調用後續資源。

    熔斷機制:當失敗率達到閥值自動觸發降級(如因網絡故障、超時造成的失敗率真高),熔斷器觸發的快速失敗會進行快速恢復。

    降級機制:超時降級、資源不足時(線程或信號量)降級 、運行異常降級等,降級後可以配合降級接口返回託底數據。

    緩存支持:提供了請求緩存、請求合併實現

    通過近實時的統計/監控/報警功能,來提高故障發現的速度

通過近實時的屬性和配置熱修改功能,來提高故障處理和恢復的速度

 

5、Hystrix如何解決依賴隔離      

    1. Hystrix使用命令模式HystrixCommand(Command)包裝依賴調用邏輯,每個命令在單獨線程中/信號授權下執行。

    2. 可配置依賴調用超時時間,超時時間一般設爲比99.5%平均時間略高即可. 當調用超時時,直接返回或執行fallback邏輯。

    3. 爲每個依賴提供一個小的線程池(或信號),如果線程池已滿調用將被立即拒絕,默認不採用排隊.加速失敗判定時間。

    4. 依賴調用結果分:成功,失敗(拋出異常),超時,線程拒絕,短路。 請求失敗(異常,拒絕,超時,短路)時執行fallback(降級)邏輯。

    5. 提供熔斷器組件,可以自動運行或手動調用,停止當前依賴一段時間(10秒),熔斷器默認錯誤率閾值爲50%,超過將自動運行。

    6. 提供近實時依賴的統計和監控

   Hystrix依賴的隔離架構,如下圖:

                                      

 

6、Hystrix是如何實現它的目標的?

    1. 通過HystrixCommand或者HystrixObservableCommand來封裝對外部依賴的訪問請求,這個訪問請求一般會運行在獨立的線程中,資源隔離

    2. 對於超出我們設定閾值的服務調用,直接進行超時,不允許其耗費過長時間阻塞住。這個超時時間默認是99.5%的訪問時間,但是一般我們可以自己設置一下

    3. 爲每一個依賴服務維護一個獨立的線程池,或者是semaphore,當線程池已滿時,直接拒絕對這個服務的調用

    4. 對依賴服務的調用的成功次數,失敗次數,拒絕次數,超時次數,進行統計

    5. 如果對一個依賴服務的調用失敗次數超過了一定的閾值,自動進行熔斷,在一定時間內對該服務的調用直接降級,一段時間後再自動嘗試恢復

    6. 當一個服務調用出現失敗,被拒絕,超時,短路等異常情況時,自動調用fallback降級機制

    7. 對屬性和配置的修改提供近實時的支持
 

7、如何使用Hystrix?

 

 

8、Hystrix-dashboard監控平臺搭建

 

 

9、Hystrix配置與分析

 

 

10、參數配置

最常用的幾項(default或HystrixCommandKey)

超時時間(默認1000ms,單位:ms)

      ——hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds   

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

     ——hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds 

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

 

線程池核心線程數

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

 

Queue

      ——hystrix.threadpool.default.maxQueueSize

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

      ——hystrix.threadpool.default.queueSizeRejectionThreshold

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

注意:如果maxQueueSize=-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個,斷路器開。

   ——hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds

                             (短路多久以後開始嘗試是否恢復,默認5s)

   ——hystrix.command.default.circuitBreaker.errorThresholdPercentage

                            (出錯百分比閾值,當達到此閾值後,開始短路。默認50%)

Fallback

     ——hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests

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

 

常用:

/**
 * coreSize 設置核心線程池大小,默認值:10
 * maximumSize 設置最大線程池大小,這是在不拒絕請求下的最大併發數,僅allowMaximumSizeToDivergeFromCoreSize爲true時生效,默認值:10
 * maxQueueSize BlockingQueue的最大隊列大小,設爲-1使用SynchronousQueue,否則使用LinkedBlockingQueue,默認值:-1
 * queueSizeRejectionThreshold 隊列大小拒絕閾值(未達到maxQueueSize也會拒絕),若maxQueueSize=-1時屬性無效,默認值:5
 * allowMaximumSizeToDivergeFromCoreSize 設置允許maxQueueSize和queueSizeRejectionThreshold生效,默認:false
 * execution.isolation.strategy 隔離策略,取值如下:
 * THREAD:默認值,線程池隔離,在一個單獨的線程上執行,併發請求受到線程池中線程數的限制
 * SEMAPHORE:信號量策略,在調用線程上執行,計算當前線程的總數,併發請求受信號量計數的限制
 * execution.isolation.thread.timeoutInMilliseconds 執行超時時間,默認爲1000,即1秒
 * execution.isolation.semaphore.maxConcurrentRequests 信號量大小,僅信號量隔離有效,超過拒絕後續請求,默認10
 * fallback.isolation.semaphore.maxConcurrentRequests
 * 回調最大併發量,若超過則拒絕請求拋出異常,因爲無法fallback,對線程池和信號量兩種策略都生效,主要用於併發過高需要快速失敗的情況
 * circuitBreaker.requestVolumeThreshold
 * 請求量閾值,若10秒內請求量超過閾值,且錯誤量百分比超過errorThresholdPercentage,則跳閘,默認值:20
 * circuitBreaker.errorThresholdPercentage
 * 錯誤閾值百分比,若10秒內請求量超過circuitBreaker.requestVolumeThreshold,且錯誤量百分比超過此值,則跳閘,默認值:50
 * circuitBreaker.sleepWindowInMilliseconds 半開試探休眠時間,當熔斷器開啓時間超過此值後,會嘗試放流試探服務是否恢復,默認值:5000
 */

 

 

11、實戰

     搭建的監控平臺:

                                                            

    監控儀表盤:

           

 

12、根據儀表盤調參

      發佈生產後可以根據Thread Pools調整線程池的大小、隊列等參數,或根據Circuit中的Host:156.9/s調整信號量大小:

數據釋義如下,折線圖和圓圈的大小代表流量趨勢,圓圈的顏色代表最近10s的錯誤百分比,隨錯誤百分比增大從綠轉黃再轉紅:

             

                                                    

根據儀表盤優化配置(僅參考)

        剛開始配置足夠多的資源啓動,首先保證隔離資源的安全性,線上灰一臺後高峯期查看dashboard

      1.超時時間:設爲比99.5th高兩三倍就可以了(若99.5th已經太長則考慮接口性能是否有問題),若超時設置太長線程池來不及處理,會拒絕請求直接進fallback

      2.線程池:假設每秒40個請求,每個請求99.5th爲0.02秒,可以設置40*0.02=0.8,再加1個作buffer,設置2個線程池

      3.信號量:直接看每秒請求,多個1/3,如最高峯每秒40個請求,則設置爲40+13=53個最高併發

 

13、Hystrix停止開發,我們該何去何從?

是的,Hystrix停止開發了。官方的新聞如下:

       考慮到之前Netflix宣佈Eureka 2.0孵化失敗時,被業界過度消費(關於Eureka 2.x,別再人云亦云了),爲了防止再度出現類似現象,筆者編寫了這篇文章。我相信看到這篇文章,大家無非會思考幾個問題:

    1、如果Hystrix還能不能繼續用於生產?

    就筆者經驗來看,Hystrix是比較穩定的,並且Hystrix只是停止開發新的版本,並不是完全停止維護,Bug什麼的依然會維護的。因此短期內,Hystrix依然是繼續使用的。

    2、Spring Cloud生態中是否有替代實現?

    但從長遠來看,Hystrix總會達到它的生命週期,那麼Spring Cloud生態中是否有替代產品呢?

    答案顯然是有。

(1)Resilience4J

    Resilicence4J 在今年的7月進入筆者視野,小玩了一下,覺得非常輕量、簡單,並且文檔非常清晰、豐富。個人比較看好,這也是Hystrix官方推薦的替代產品

    不僅如此,Resilicence4j還原生支持Spring Boot 1.x/2.x,而且監控也不像Hystrix一樣弄Dashboard/Hystrix等一堆輪子,而是支持和Micrometer(Pivotal開源的監控門面,Spring Boot 2.x中的Actuator就是基於Micrometer的)、prometheus(開源監控系統,來自谷歌的論文)、以及Dropwizard metrics(Spring Boot曾經的模仿對象,類似於Spring Boot)進行整合。

    筆者特別看重Resilience4J和micrometer整合的能力,這意味着:如果你用Spring Boot 2.x並在項目中引入Resilience4J,那麼監控數據和Actuator天生就是打通的!你不再需要一個專屬的、類似於Hystrix Dashboard的東西去監控斷路器。

(2)Alibaba Sentinel

    Sentinel 是阿里巴巴開源的一款斷路器實現,目前在Spring Cloud的孵化器項目Spring Cloud Alibaba中,預計Spring Cloud H系列中可以孵化完成。

    Sentinel本身在阿里內部已經被大規模採用,非常穩定。因此可以作爲一個較好的替代品。

 

 

 

 

 

二、Sentinel  分佈式系統的流量防衛兵     SpringCloud alibaba

1、簡介

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D 

https://github.com/alibaba/Sentinel

 

Sentinel 是什麼?

     隨着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 是面向分佈式服務架構的輕量級流量控制組件,主要以流量爲切入點,從限流、流量整形、熔斷降級、系統負載保護等多個維度來幫助您保障微服務的穩定性。

 

Sentinel 的歷史:

  • 2012 年,Sentinel 誕生,主要功能爲入口流量控制。
  • 2013-2017 年,Sentinel 在阿里巴巴集團內部迅速發展,成爲基礎技術模塊,覆蓋了所有的核心場景。Sentinel 也因此積累了大量的流量歸整場景以及生產實踐。
  • 2018 年,Sentinel 開源,並持續演進。

 

Sentinel 分爲兩個部分:

  • 核心庫(Java 客戶端)不依賴任何框架/庫,能夠運行於所有 Java 運行時環境,同時對 Dubbo / Spring Cloud 等框架也有較好的支持。
  • 控制檯(Dashboard)基於 Spring Boot 開發,打包後可以直接運行,不需要額外的 Tomcat 等應用容器。

 

Sentinel 基本概念:

      資源:資源是 Sentinel 的關鍵概念。它可以是 Java 應用程序中的任何內容,例如,由應用程序提供的服務,或由應用程序調用的其它應用提供的服務,甚至可以是一段代碼。在接下來的文檔中,我們都會用資源來描述代碼塊。只要通過 Sentinel API 定義的代碼,就是資源,能夠被 Sentinel 保護起來。大部分情況下,可以使用方法簽名,URL,甚至服務名稱作爲資源名來標示資源。

      規則:圍繞資源的實時狀態設定的規則,可以包括流量控制規則、熔斷降級規則以及系統保護規則。所有規則可以動態實時調整。

 

Sentinel 具有以下特徵:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、消息削峯填谷、集羣流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制檯中看到接入應用的單臺機器秒級數據,甚至 500 臺以下規模的集羣的彙總運行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
  • 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定製邏輯。例如定製規則管理、適配動態數據源等。

Sentinel 的主要特性:

 

 

 

 

2、Sentinel 功能和設計理念

(1)流量控制

什麼是流量控制?

     流量控制在網絡傳輸中是一個常用的概念,它用於調整網絡包的發送數據。然而,從系統穩定性角度考慮,在處理請求的速度上,也有非常多的講究。任意時間到來的請求往往是隨機不可控的,而系統的處理能力是有限的。我們需要根據系統的處理能力對流量進行控制。Sentinel 作爲一個調配器,可以根據需要把隨機的請求調整成合適的形狀,如下圖所示:

流量控制設計理念:

    流量控制有以下幾個角度:

  • 資源的調用關係,例如資源的調用鏈路,資源和資源之間的關係;
  • 運行指標,例如 QPS、線程池、系統負載等;
  • 控制的效果,例如直接限流、冷啓動、排隊等。

Sentinel 的設計理念是讓您自由選擇控制的角度,並進行靈活組合,從而達到想要的效果。

 

 

(2)熔斷降級

什麼是熔斷降級?

     除了流量控制以外,降低調用鏈路中的不穩定資源也是 Sentinel 的使命之一。由於調用關係的複雜性,如果調用鏈路中的某個資源出現了不穩定,最終會導致請求發生堆積。

     Sentinel 和 Hystrix 的原則是一致的: 當檢測到調用鏈路中某個資源出現不穩定的表現,例如請求響應時間長或異常比例升高的時候,則對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯故障。

熔斷降級設計理念:

    在限制的手段上,Sentinel 和 Hystrix 採取了完全不一樣的方法。

    Hystrix 通過 線程池隔離 的方式,來對依賴(在 Sentinel 的概念中對應 資源)進行了隔離。這樣做的好處是資源和資源之間做到了最徹底的隔離。缺點是除了增加了線程切換的成本(過多的線程池導致線程數目過多),還需要預先給各個資源做線程池大小的分配。

Sentinel 對這個問題採取了兩種手段:

  • 通過併發線程數進行限制

      和資源池隔離的方法不同,Sentinel 通過限制資源併發線程的數量,來減少不穩定資源對其它資源的影響。這樣不但沒有線程切換的損耗,也不需要您預先分配線程池的大小。當某個資源出現不穩定的情況下,例如響應時間變長,對資源的直接影響就是會造成線程數的逐步堆積。當線程數在特定資源上堆積到一定的數量之後,對該資源的新請求就會被拒絕。堆積的線程完成任務後纔開始繼續接收請求。

  • 通過響應時間對資源進行降級

     除了對併發線程數進行控制以外,Sentinel 還可以通過響應時間來快速降級不穩定的資源。當依賴的資源出現響應時間過長後,所有對該資源的訪問都會被直接拒絕,直到過了指定的時間窗口之後才重新恢復。

 

(3)系統負載保護

    Sentinel 同時提供系統維度的自適應保護能力。防止雪崩,是系統防護中重要的一環。當系統負載較高的時候,如果還持續讓請求進入,可能會導致系統崩潰,無法響應。在集羣環境下,網絡負載均衡會把本應這臺機器承載的流量轉發到其它的機器上去。如果這個時候其它的機器也處在一個邊緣狀態的時候,這個增加的流量就會導致這臺機器也崩潰,最後導致整個集羣不可用。

    針對這個情況,Sentinel 提供了對應的保護機制,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍之內處理最多的請求。

 

 

3、Sentinel 的開源生態:

 

 

 

4、Sentinel 是如何工作的

Sentinel 的主要工作機制如下:

  • 對主流框架提供適配或者顯示的 API,來定義需要保護的資源,並提供設施對資源進行實時統計和調用鏈路分析。

  • 根據預設的規則,結合對資源的實時統計信息,對流量進行控制。同時,Sentinel 提供開放的接口,方便您定義及改變規則。

  • Sentinel 提供實時的監控系統,方便您快速瞭解目前系統的狀態。

 

5、Sentinel+Nacos使用

(1)引入依賴,版本1.4.2

<!-- sentinel核心 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-core</artifactId>
   <version>${sentinel.version}</version>
</dependency>
<!-- sentinel控制檯 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-transport-simple-http</artifactId>
   <version>${sentinel.version}</version>
</dependency>
<!-- sentinel註解 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-annotation-aspectj</artifactId>
   <version>${sentinel.version}</version>
</dependency>
<!-- sentinel使用nacos數據源 -->
<dependency>
   <groupId>com.alibaba.csp</groupId>
   <artifactId>sentinel-datasource-nacos</artifactId>
   <version>${sentinel.version}</version>
</dependency>

(2)添加切面註解

<aop:aspectj-autoproxy/>
   <bean id="sentinelAspect" class="com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect"/>

(3)初始化規則

使用nacos數據源初始化規則,nacos定位配置文件是namespace+groupId+dataId,namespace可以用於區分環境(默認Public,其它環境需要在nacos上配置並使用其UUID),groupId建議爲系統名:模塊名,如Q9Ebill:Sentinel,dataId建議爲包名(約定大於設置)


(4)規則配置

——限流規則

    resource:資源名稱

    controlBehavior:控制行爲,0-直接拒絕(默認),1-冷啓動,2-勻速器,3-勻速冷啓動

    grade:限流模式,類別  0-線程數,1-QPS(默認)

    count:限流閾值

    limitApp:調用方限流,生效順序爲{some_origin_name}>other>default,default-不根據調用方限流(默認),             {some_origin_name}-規定調用方逗號分隔,other-針對除{some_origin_name}外的限流(需定義多個規則)

    strategy:策略,0-直接流控(默認),1-關聯流控(資源爭搶時),2-鏈路流控(限入口)

    clusterMode:是否採用集羣模式    true集羣  false非集羣

    clusterConfig:{

           flowId:  全局唯一的一個Id

           thresholdType: 閾值模式   1-全局

           fallbackToLocalWhenFail:  true   遠程失敗後,使用本地的   

    }

 

——熔斷規則

   resource:資源名稱

   count:閾值,rt單位ms,異常率[0.0, 1.0],異常數整數

   grade:類別,0-響應時間(默認),  1-1秒內異常率,  2-1分鐘內異常數

   timeWindow:時間窗口,這個時間內會熔斷,單位秒

 

 

 

       未完待續.................................

 

 

 

 

 

        說明:本文是參照了其它幾篇博客,加上自己的實際應用心得而成。  如有錯誤和侵權之處,敬請諒解和指出,大家共同學習......???

 

 

 

 

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