經驗整理-1-SpringCloud---100-@


爲什麼用springcloud,和傳統rpc相比有什麼好處?和dubbo相比呢?

.答:在傳統rpc(如httpclient)遠程調用中,服務與服務依賴關係,管理比較複雜,所以需要使用
服務治理,管理服務與服務之間依賴關係,可以實現
1.服務調用、負載均衡、容錯等;(注意是本地負載均衡,即:調用者拿到服務方調用信息之後,就像本地調用一樣的使用調用方法。和傳統nginx負載均衡是有區別的)
2.實現服務註冊與發現
。(
服務提供者會把當前自己服務器的信息 比如服務地址通訊地址等以別名方式註冊到註冊中心上
調用者以別名的方式去註冊中心上獲取到實際的服務器的信息,然後在實現本地rpc調用遠程
還有一個很好的優點,動態註冊與發現,即服務註冊中心不用重啓,提供者可以開啓一個新的服務,註冊中心會檢測到它。

.答:Dubbo僅僅是一個實現了遠程調用的RPC框架,服務化的中間件則需要自己開發;而SpringCloud則是實施微服務的一系列套件,包括:遠程調用負載均衡、服務註冊與發現、斷路器服務保護、服務狀態監控、分佈式配置中心、智能路由網關、一次性令牌、全局鎖、分佈式會話管理、集羣狀態管理等

springcloud和dubbo組件運行流程對比

下圖中的每個組件都是需要部署在單獨的服務器上,gateway用來接受前端請求、聚合服務,並批量調用後臺原子服務。每個service層和單獨的DB交互。

▲Dubbo組件運行流程

  • gateWay:前置網關,具體業務操作,gateWay通過dubbo提供的負載均衡機制自動完成

  • Service:原子服務,只提供該業務相關的原子服務

  • Zookeeper:原子服務註冊到zk上

▲Spring Cloud 組件運行

Spring Cloud

  • 所有請求都統一通過 API 網關(Zuul)來訪問內部服務。

  • 網關接收到請求後,從註冊中心(Eureka)獲取可用服務。

  • 由 Ribbon 進行均衡負載後,分發到後端的具體實例。

  • 微服務之間通過 Feign 進行通信處理業務。

點評:業務部署方式相同,都需要前置一個網關來隔絕外部直接調用原子服務的風險。Dubbo需要自己開發一套API 網關,而Spring Cloud則可以通過Zuul配置即可完成網關定製。使用方式上Spring Cloud略勝一籌。


什麼是分佈式,什麼集羣,什麼RPC

不同模塊部署在不同服務器上,作用:分佈式解決網站高併發帶來問題
多臺服務器部署相同應用模塊構成一個集羣,作用:通過負載均衡設備共同對外提供服務

RPC 是調用另一個地址空間,由於不在一個內存空間,不能直接調用就可以應用RPC框架的實現來解決

rpc遠程調用框架

幾種比較典型的RPC的實現和調用框架。 
(1)RMI實現,利用java.rmi包實現,基於Java遠程方法協議(Java Remote Method Protocol) 
和java的原生序列化。 
(2)Hessian,是一個輕量級的remoting onhttp工具,使用簡單的方法提供了RMI的功能。 基於HTTP協議,採用二進制編解碼。 
(3)thrift是一種可伸縮的跨語言服務的軟件框架。thrift允許你定義一個描述文件,描述數據類型和服務接口。依據該文件,編譯器方便地生成RPC客戶端和服務器通信代碼。(4)SpringCloud 提供了快速構建分佈式系統的一些工具,包括配置管理、服務發現、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分佈式會話等等。
5)DUBBO
 

-------------------Eureka(自搭Eureka服務端)-------------

 

?工作原理(Eureka集羣下)?

Applecation-server :服務提供者
Application-cliene:服務消費者
服務提供者啓動後向Eureka註冊Eureka Server會將註冊信息向其他Eureka Server進行同步,當服務消費者要調用服務提供者,則向服務註冊中心獲取服務提供者地址,然後會將服務提供者地址緩存在消費者本地,下次再調用時,則直接從本地緩存中取,完成一次調用

?的作用或優點?

?我搭建過,如何搭建?

?搭建和使用過程中遇到哪些問題?

?Eureka 自身集羣原理?

假設有三臺 Eureka Server 組成的集羣,第一臺 Eureka Server 在北京機房,另外兩臺 Eureka Server 在深圳和西安機房。這樣三臺 Eureka Server 就組建成了一個跨區域的高可用集羣。
集羣相互之間通過 Replicate 來同步數據,相互之間不區分主節點和從節點,所有的節點都是平等的。
在這種架構中,節點通過彼此互相註冊來提高可用性,每個節點需要添加一個或多個有效的 serviceUrl 指向其他節點。如果某臺 Eureka Server 宕機,Eureka Client 的請求會自動切換到新的 Eureka Server 節點。當宕機的服務器重新恢復後,Eureka 會再次將其納入到服務器集羣管理之中。當節點開始接受客戶端請求時,所有的操作都會進行節點間複製,將請求複製到其它 Eureka Server 當前所知的所有節點中。完美地解決了註冊中心的穩定性和高可用性。

?Eurka集羣 保證 AP?
Eureka Server 各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩餘的節點依然可以提供註冊和查詢服務。而 Eureka Client 在向某個 Eureka 註冊時,如果發現連接失敗,則會自動切換至其它節點。只要有一臺 Eureka Server 還在,就能保證註冊服務可用(保證可用性),只不過查到的信息可能不是最新的(不保證強一致性)。

Eureka自我保護機制是什麼?目前的最好解決辦法是什麼?

.答:默認情況下,是不會走自我保護機制的,會正常清除Eureka客戶端(比如提供者)90秒內未發送心跳至Eureka服務端的Eureka客戶端。
Eureka自我保護的產生原因:
Eureka在運行期間會統計心跳失敗的比例,短時間內大量出現(在15分鐘內是否低於85%)客戶端和服務端之間中斷無法通訊,走自我保護機制,不再刪除服務註冊表中的數據。也就是不會註銷任何微服務。
.答:
開發過程中關閉,生產打開
Eureka Server端:配置關閉自我保護,並按需配置Eureka Server清理無效節點的時間間隔。
eureka.server.enable-self-preservation# 設爲false,關閉自我保護
eureka.server.eviction-interval-timer-in-ms # 清理間隔(單位毫秒,默認是60*1000)
 Eureka Client端:配置開啓健康檢查,並按需配置續約更新時間和到期時間 
eureka.client.healthcheck.enabled# 開啓健康檢查(需要spring-boot-starter-actuator依賴)
eureka.instance.lease-renewal-interval-in-seconds# 續約更新時間間隔(默認30秒)
eureka.instance.lease-expiration-duration-in-seconds # 續約到期時間(默認90秒)

Eureka與zookeeper/consul的特點區別?分別怎麼使用?

https://blog.csdn.net/Phone_1070333541/article/details/87345810
一個分佈式系統不可能同時滿足C(一致性)、A(可用性)和P(分區容錯性)。

一.答:
Zookeeper和Consul :CP設計,保證了一致性,集羣中,某個節點失效,則會選舉出新的主節點leader;但半數以上節點不可用,則無法提供服務,因此可用性沒法滿足
Eureka:AP原則,無主從節點,一個節點掛了,自動切換其他節點可以使用,去中心化;eureka大量因網絡通訊中斷(可用低於85%)是不會刪宕機節點,就是它的自我保護機制(還是會返回錯誤頁)。
如果要求一致性,則選擇zookeeper、Consul,如金融行業----集羣同步來保證一致性
如果要求可用性,則Eureka,如電商系統----節點通過彼此互相註冊來提高可用性
二.答:用法區別:
註冊中心的客戶端,把配置裏的eureka:相關屬性,換成spring:cloud:zookeeper:或spring:cloud:consul的屬性;把啓動類的註解@EnableEurekaClient換成@EnableDiscoveryClient
註冊中心的服務端,(eureka就是個servlet程序,跑在servlet容器中,)eureka需要單獨運行java系統運行服務端;(Consul則是go編寫而成) ,Consul和zk都是下載第三方工具運行服務端

 

-------------------Ribbon和feign(只存在於客戶端)-------------

?Ribbon與Nginx區別?

服務器端負載均衡Nginx
 nginx是客戶端所有請求統一交給nginx,由nginx進行實現負載均衡請求轉發,屬於服務器端負載均衡(軟負載均衡,即Nginx軟件)。
 請求由nginx服務器端進行轉發。適合於Tomcat 
客戶端負載均衡Ribbon
 Ribbon是從eureka註冊中心服務器端上獲取服務註冊信息列表,緩存到本地,讓後在本地實現輪訓負載均衡策略
 既在客戶端實現負載均衡。適合於微服務中RPC遠程調用實現本地服務負載均衡,如Dubbo、SpringCloud 

 ?Ribbon與feign區別?

選擇feign來進行負載均衡:

原因有以下幾點:

1. feign本身裏面就包含有了ribbon

2. feign不需要借用restTemplate調用提供者,自身底層默認包含httpurlconnection去調用提供者,寫起來更加思路清晰和方便

3. fegin是一個採用基於接口和註解的編程方式,更加簡便


?Ribbon如何配置超時時間?(要主動設置,不要去依賴服務器的超時)

 ribbon:ConnectTimeout 建立連接所用的時間
 ribbon:ReadTimeout  服務器讀取到可用資源 


? Ribbon組件工作原理?

1、調用者首先通過@LoadBalanced註解放在RestTemplate實例方法上,註解裏面的intercept方法,攔截RestTemplate的每一次HTTP請求,在裏面來實現負載均衡、別名切換成服務地址。
2、用RestTemplate的調用方式,以別名方式調用提供者接口,實際上@LoadBalanced裏有攔截處理負載、切換別名等。

? feign組件工作原理?

1、調用者首先通過@EnableFeignCleints註解開啓掃描FeignCleint(第3步啓動會掃描)
2、調用者根據Feign的規則在本地寫個接口類去繼承但不實現提供者的接口(這樣以少重寫裏面的方法;你若不繼承重寫一個提供者相同的方法也行)(提供者肯定是pom工程可以被引用),並加註解@FeignCleint(value="提供者服務別名",fallback=本地某個降級類或當前類指定任意方法),這裏提一下本地某個降級類,必須需要重寫一個和提供者方法名相同的方法,一般建議用它,因爲服務隔離新線程池範圍僅限執行調用者方法那一步。
3、調用者程序啓動後,會進行掃描所有的@FeignCleint的註解的類,並將其注入到ioc容器中。(因爲第1步加註解了)
4、當調用者本地繼承的接口方法被調用,通過jdk的動態代理(註解@FeignCleint),來生成具體的請求對象RequesTemplate
5、RequestTemplate再生成Request
6、Request交給Client去處理,其中Client默認是HttpUrlConnection去處理(也可以是HttpClient或Okhttp,需要配置)
7、最後Client被封裝到LoadBalanceClient類,這個類結合類Ribbon做到了負載均衡拿到遠程調用的結果返回。(這裏再提一下,是通過別名從Eureka拿到的具體的提供者端口列表,才能遠程調用的)

? Feign的作用或功能?

 1、將服務提供者服務接口暴露出來,方便調用者調用。@FeignCleint註解暴露提供服務別名
 2、配合服務降級熔斷。
3、負載均衡,因爲集成了Ribbon,最後要調的Client被封裝到LoadBalanceClient類。(用ribbon的話,還得單獨加這個負載均衡註解) 4、省卻了feign中轉碼編碼的過程,同時將接口和實現都整合成一個對象,更加符合面向對象的編程方法

 服務提供者和調用者都在啓動類加上@EnableFeignClients,提供者只需要暴露接口,由調用者來實現該接口比如實現類d2,但要加 @FeignClient("app-itmayiedu-member")來指明要用httpclient調提供者的哪個服務,如果沒有這個就沒有遠程調用,調用方想調提供者接口時就調到自已寫的實現類d2了。 @FeignClient("app-itmayiedu-member")這個註解下的接口服務提供者的實現類要寫在調用者的系統裏的原因是:方便做服務降級熔斷(比如網絡中斷,提供者是無法返回降級頁面,只能由調用者提供)
 

?Ribbon與feign你搭建過嗎,如何搭建?(就是加幾個註解看一下就行,重點背原理)

 

?feign搭建和使用過程中遇到哪些問題?

1)有一些註解並不支持,如@GetMapping,@PutMapping 等,僅支持使用@RequestMapping 等。後來看源碼才發現,Spring Cloud 沒有基於Spring MVC 全部註解來做Feign 客戶端註解協議解析,
個人認爲這個是一個不小的坑。
2)默認會使用Java HttpURLConnection 進行Http請求,每次訪問時都會創建一個 Connection對象,這種代價是很大。這也解釋了Feign默認情況下性能不高的問題。
可以使用OkHttp3進行Http請求,這樣消耗時間基本花費在處理Http請求上,像對象處理、數據轉換消耗的時間基本上可以做到忽略不計

 

-------------------Hystrix斷路器(Hystrix只支持放在調用端做降級等)-------------

 

“服務熔斷”和“服務降級”的類似性和區別?

類似性:
目的很一致,都是從可用性可靠性着想,爲防止系統的整體緩慢甚至崩潰,採用的技術手段;
最終表現類似,對於兩者來說,最終讓用戶體驗到的是某些功能暫時不可達或不可用;
粒度一般都是服務級別,當然,業界也有不少更細粒度的做法,比如做到數據持久層(允許查詢,不允許增刪改);
自治性要求很高,熔斷模式一般都是服務基於策略的自動觸發,降級雖說可人工干預,但在微服務架構下,完全靠人顯然不可能,開關預置、配置中心都是必要手段;
區別:
看下圖裏有說明區別,Hystrix工作原理的圖

?Hystrix工作原理?(或者問斷路器實現服務保護的原理)

把三個都答出來,一般三個一起用的:服務降級、服務熔斷、服務隔離

?Hystrix的作用或功能?

通過註解@HystrixCommand(fallbackMethod = "降級方法名")// 默認開啓線程池隔離方式,服務降級(默認1秒就超時走降級方法),服務熔斷
隔離(線程池隔離和信號量隔離):限制調用分佈式服務的資源使用,某一個調用的服務出現問題不會影響其他服務調用。
優雅的降級機制:超時降級、資源不足時(線程或信號量)降級,降級後可以配合降級接口返回託底數據。
融斷:當失敗率達到閥值自動觸發降級(如因網絡故障/超時造成的失敗率高),熔斷器觸發的快速失敗會進行快速恢復。
緩存:提供了請求緩存、請求合併實現。支持實時監控、報警、控制(修改配置)

?你搭建過嗎,如何搭建?

Hystrix只支持放在調用端做降級等,
Hystrix的兩種方式:fallback=本地某個降級類當前類指定任意名稱的方法,如下圖的4.1,4.2.
這裏提一下本地某個降級類,必須需要重寫一個和提供者方法名相同的方法,一般建議用它,因爲服務隔離新線程池範圍僅限執行調用者方法那一步。

服務雪崩的應對策略?

1.流量控制:nginx+Lua的網關進行流量控制
2.改進緩存模式:緩存預加載、同步改爲異步刷新
3.服務調用者降級服務:

-------------------分佈式配置中心(自搭配置服務端)-------------

?Cloud分佈式配置中心-工作原理?

簡單看過,配置服務器pull git上最新變化值的源碼:@EnableConfigServer註解跟進去之後,裏面有個方法refresh(String label) 會去執行git的pull請求。(spring-cloud-config-server這個Pom引用包裏面自已內嵌了jgit的pom引用)。


如果改動配置,默認是需要重啓服務才能刷新。但是有兩種方式不用重啓服務就可加載新值:
手動---actuator開啓監控斷點pom及配置,引用refreshscope註解
自動---BUS開啓消息隊列總線

這裏提一下bus和手動的對比,手動需要每個服務都調一下,幾百個服務太麻煩了,所以最後還是引入bus消息總線,調一個服務,其他服務會通過發佈-訂閱方式全部刷新配置。

順便講一下消息總線BUS實時刷新配置原理:

配置服務端和所有配置客戶端都需要引入spring-cloud-starter-bus-amqp和隊列集羣,
配置客戶端需要引入監控斷點spring-boot-starter-actuator和隊列集羣,開啓監控斷點爲bus刷新方式(手動的是*方式)。include: bus-refresh
配置好之後,啓動客戶端時會默認綁定要同一個bus關聯名稱的交換機上面,構成發佈到一個交換機,多個訂閱模式。

?分佈式配置中心-的作用?


cloud.config你搭建過嗎,如何搭建?

沒有單獨的註解,cloud.config只要配置了,配置客戶端啓動就會通過配置服務器把值從git加載進配置客戶端JVM,配置客戶端這裏就能用了。看第二張圖有追加說明

如果要開啓手動刷新,需要注意引用監控中心Pom包,開啓所有監控中心接口才能用手動刷新配置接口;
還有,zuul配置加上refreshscope註解能實現動態實時刷新。

?搭建和使用過程中遇到哪些問題?


1、默認不實時刷新----解決:手動去刷新是最好的。還有一種方法是:標有@RefreshScope的Bean將得到特殊處理來實時生效配置


 

2、生產和測試環境沒有分開,配置混亂問題----解決:不同環境分離、不同項目分離

 

-------------------bus消息總線-------------
?工作原理?

順便講一下消息總線BUS實時刷新配置原理:
 一般bus是與cloud config整合,以bus集成的RabbitMq作爲消息代理,實現了應用配置的動態更新。
集成了隊列,比如mq,採用發佈訂訂閱模式。

手動去調一個服務A拉取到配置改動(訪問/bus/refresh接口),A會發布消息總線的隊列,其他訂閱監聽事件的服務會接收變更事件消息,也會去消費拉取一次最新的配置。


概念及作用

bus整合java的事件處理機制消息中間件的發送和接收,主要是由發送端、接收端和事件組成
避免了機器太多,人工參與麻煩


?我搭建過,如何搭建?

配置服務端和所有配置客戶端都需要引入spring-cloud-starter-bus-amqp和隊列集羣,
配置客戶端需要引入監控斷點spring-boot-starter-actuator和隊列集羣,開啓監控斷點爲bus刷新方式(手動的是*方式)。include: bus-refresh

配置好之後,啓動客戶端時會默認綁定要同一個bus關聯名稱的交換機上面,構成發佈到一個交換機,多個訂閱模式

 

------------------stream 消息驅動(整合mq/kafka)(生產者和消費者各自注解整合就行)-------------


?stream工作原理?

提供一箇中間層,實現了應用程序與消息中間件細節之間的隔離。使應用程序不需要再考慮各種不同的消息中間件的實現。當需要升級消息中間件,或者是更換其他消息中間件產品時,我們需要做的就是更換對應的Binder綁定器而不需要修改任何應用邏輯 。

 

?我搭建過,如何搭建?

?搭建和使用過程中遇到哪些問題?

消費組
集羣部署啓動了兩個應用的實例,消息被重複消費了兩次。爲解決這個問題,Spring Cloud Stream 中提供了消費組,通過配置 spring.cloud.stream.bindings.myInput.group 屬性爲應用指定一個組名,同一組的只能有一個消費成功
 

-------------------Zuul(某個微服務做爲zuul,自已整合zuul路由filter等)-------------

?工作原理?


zuul的所有功能都是基於過濾器Filter去實現的。
啓動類加上@EnbaleZuulProxy,裏面有ZuulFilter(裏面默認開啓了Ribbon本地負載均衡功能)
寫一個自定義類來繼承ZuulFilter抽象類,來做一些過濾功能:
登錄驗證和安全性--自定義寫
動態智能路由----application.yml或配置中心配置路由轉發,將請求routes到不同的提供者服務集羣,實現反向代理。
Zuul限流----漏桶算法/令牌算法
負載均衡----裏面默認開啓了Ribbon本地負載均衡功能
飢餓加載----開啓zuul:ribbon的飢餓加載配置(可以啓動加載完,後面每次調用體驗快)
重試機制----引入spring-retry包,打開重試機制配置(網關調其他提供者時,請求失敗就會重試)
洞察和監控——--在邊緣跟蹤有意義的數據和統計數據
聚合API接口,統一接口轉發

?的作用?

同上

?我搭建過,如何搭建?

在網關那個系統,加上註解@EnableZuulProxy// 開啓網關代理,配上各服務提供者的路由信息。

?搭建和使用過程中遇到哪些問題?

 

爲什麼不用nginx來做轉發,Nginx與Zuul的區別?

(nginx來做的話,日誌有點難管理;網關可以統一管理日誌,aop)
Nginx是採用服務器負載均衡進行轉發
Zuul依賴Ribbon和eureka實現本地負載均衡轉發
相對來說Nginx功能比Zuul功能更加強大,能夠整合其他語言比如lua腳本實現強大的功能,同時Nginx可以更好的抗高併發,Zuul網關適用於請求過濾和攔截等。
註解api,網關統一收集

-------------------Swagger2(每個服務註解api,網關統一收集)-------------

?工作原理?

?的作用?

如果人工單獨去更新API文檔,久而久之,很容易出現和代碼不一致的問題。
1.功能豐富:支持多種註解,自動生成接口文檔界面,支持在界面測試API接口功能;
2.及時更新:開發過程中花一點寫註釋的時間,就可以及時的更新API文檔,省心省力;
3.整合簡單:通過添加pom依賴和簡單配置,內嵌於應用中就可同時發佈API接口文檔界面,不需要部署獨立服務。

?我搭建過,如何搭建?

 

-------------------監控中心Actuator+可視化AdminUI(自搭AdminUI服務端)-------------

?工作原理?

運行一個獨立的監控AdminUI服務器,在所有微服務裏打開監控配置並註冊到AdminUI服務器的地址
實現將所有服務的監控中心管理存放在admin ui平臺上.

?的作用或優點?

 針對微服務器監控、服務器內存變化(堆內存,線程,日誌管理等)檢測服務配置連接池是否可用(模擬訪問、懶加載)、統計現有Bean(通過Spring容器)Http接口(@RequestMapping)的一系列數據管理。Actuator監控應用只通過JSON形式返回數據統計結果,沒有UI界面處理;AdminUI則內置Actuator服務監控,並對返回JSON數據進行圖形化處理展示。

?我搭建過,如何搭建?

?搭建和使用過程中遇到哪些問題?

 問題1:一開始只用了SpringBoot的一個附加功能Actuator,完成在生產環境運行時的監控和管理,但是接口調用,返回JSON格式的信息。
  解決:可以用Admin-UI,進行圖形化處理展示

 

--------------Sleuth+Zipkin(底層集成了RabbitMQ)(Zipkin UI服務端是個JAR,運行即可;zipkin客戶端配置一下zipkin和Sleuth)-------------

?鏈路監控服務跟蹤工作原理?(Sleuth+MQ+Zipkin+ES)

服務跟蹤原理
  一、Sleuth作用:爲了實現請求跟蹤,當請求發送到分佈式系統的入口端點時,  服務跟蹤框架爲該請求創建一個唯的跟蹤標識Trace ID放在請求頭裏, 保持傳遞該唯一標識, 直到返回給請求方爲止通過Trace ID的記錄,我們就能將所有請求過程的日誌關聯起來。 爲了統計各處理單元的時間延遲,當請求到達各個服務組件時,或是處理邏輯到達某個狀態時,創建唯一 標識Span ID來標記各個服務組件的開始、 具體過程以及結束。對於每個Span來說,它必須有開始和結束兩個節點,通過記錄開始Span和結束Span的時間戳,就能統計出該Span的時間延遲,除了時間戳記錄之外,它還可以包含一些其他元數據, 比如事件名稱、請求信息
  SpanId記錄每一次請求, TraceID記錄整個調用鏈全局ID  
二、Zipkin作用:Sleuth收集事件、請求信息等,通過MQ通配符方式,多往一方式(zipkin消費),發送給Zipkin服務器,Zipkin服務器有兩個作用:
把Sleuth收集的信息,從MQ讀出來,然後存在數據庫ES裏
在UI界面訪問Zipkin服務器去讀取ES的數據。


  

?的作用或優點?

背景原因:微服務系統變得越來越大,調用關係越來越複雜,某個服務出現網絡延遲過高或發送錯誤導致請求失敗,這個時候,查詢日誌分析問題就變得很複雜。
Sleuth 可以對請求調用進行鏈路監控,提供了分佈式服務鏈路監控的解決方案

?我搭建過,如何搭建?

搭建Zipkin集成RabbitMQ異步傳輸:
1、運行Zipkin鏈路監控服務端的JAR,就可以訪問Zipkin服務器UI了,
2、然後配置鏈路監控客戶端。(客戶端收集信息傳給zipkin)

服務啓動時,加載流程:
 1、啓動RabbitMQ
 2、啓動Zipkin(自動會創建一個Zipkin 隊列)
 3、啓動ZipkinClient以隊列形式異步傳輸

 

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