天天看點

你想了解的「SpringCloud」都在這裡

前言: 之前我們已經了解了「什麼是微服務?」,現在我們開始了解「微服務」關鍵字下比較熱門的「Spring Cloud」...

一、傳統架構發展史

部分引用自:從架構演進的角度聊聊Spring Cloud都做了些什麼? - 純潔的微笑

單體架構

單體架構在小微企業比較常見,典型代表就是一個應用、一個資料庫、一個web容器就可以跑起來。

在兩種情況下可能會選擇單體架構:一是在企業發展的初期,為了保證快速上線,采用此種方案較為簡單靈活;二是傳統企業中垂直度較高,通路壓力較小的業務。在這種模式下對技術要求較低,友善各層次開發人員接手,也能滿足客戶需求。

下面是單體架構的架構圖:

在單體架構中,技術選型非常靈活,優先滿足快速上線的要求,也便于快速跟進市場。

垂直架構

在單體架構發展一段時間後,公司的業務模式得到了認可,交易量也慢慢的大起來,這時候有些企業為了應對更大的流量,就會對原有的業務進行拆分,比如說:背景系統、前端系統、交易系統等。

在這一階段往往會将系統分為不同的層級,每個層級有對應的職責,UI層負責和使用者進行互動、業務邏輯層負責具體的業務功能、資料庫層負責和上層進行資料交換和存儲。

下面是垂直架構的架構圖:

服務化架構

如果公司進一步的做大,垂直子系統會變的越來越多,系統和系統之間的調用關系呈指數上升的趨勢。在這樣的背景下,很多公司都會考慮服務的 SOA 化。SOA 代表面向服務的架構,将應用程式根據不同的職責劃分為不同的子產品,不同的子產品直接通過特定的協定和接口進行互動。這樣使整個系統切分成很多單個元件服務來完成請求,當流量過大時通過水準擴充相應的元件來支撐,所有的元件通過互動來滿足整體的業務需求。

SOA服務化的優點是,它可以根據需求通過網絡對松散耦合的粗粒度應用元件進行分布式部署、組合和使用。服務層是SOA的基礎,可以直接被應用調用,進而有效控制系統中與軟體代理互動的人為依賴性。

服務化架構是一套松耦合的架構,服務的拆分原則是服務内部高内聚,服務之間低耦合。

下面是服務化架構圖:

在這個階段可以使用 WebService 或者 Dubbo 來服務治理。

我們發現從單體架構到服務化架構,應用數量都在不斷的增加,慢慢的下沉的就成了基礎組建,上浮的就成為業務系統。從上述也可以看出架構的本質就是不斷的拆分重構:分的過程是把系統拆分為各個子系統/子產品/元件,拆的時候,首先要解決每個元件的定位問題,然後才能劃分彼此的邊界,實作合理的拆分。合就是根據最終要求,把各個分離的元件有機整合在一起。拆分的結果使開發人員能夠做到業務聚焦、技能聚焦,實作開發靈活,合的結果是系統變得柔性,可以因需而變,實作業務靈活。

微服務架構

微服務是一種軟體架構風格,它是以專注于單一責任與功能的小型功能區塊為基礎,利用模組化的方式組合出複雜的大型應用程式,各功能區塊使用與語言無關的 API(例如 REST)集互相通訊,且每個服務可以被單獨部署,它具備以下三個核心特點:

  • 微服務為大型系統而生。随着業務的快速增長,會帶來系統流量壓力和複雜度的上升,系統的可維護性和可擴充性成為架構設計的主要考慮因素,微服務架構設計理念通過小而美的業務拆分,通過分而自治來實作複雜系統的優雅設計實作。
  • 微服務架構是面向結果的。微服務架構設計風格的産生并非是出于學術或為标準而标準的設計,而是在軟體架構設計領域不斷演進過程中,面對實際工業界所遇到問題,而出現的面向解決實際問題的架構設計風格。
  • 專注于服務的可替代性來設計。微服務架構設計風格核心要解決的問題之一便是如何便利地在大型系統中進行系統元件的維護和替換,且不影響整體系統穩定性。

SOA 與 微服務 的不同在于:

  • 服務拆分粒度更細。微服務可以說是更細次元的服務化,小到一個子子子產品,隻要該子產品依賴的資源與其他子產品都沒有關系,那麼就可以拆分成一個微服務。
  • 服務獨立部署。每個服務都嚴格遵循獨立打包部署的準則,互不影響。比如一台實體機上可以部署多個 Docker 執行個體,每個 Docker 執行個體可以部署一個微服務的代碼。
  • 服務獨立維護。每個微服務都可以交由一個小團隊甚至個人來開發、測試、釋出和運維,并對整個生命周期負責。
  • 服務治理能力要求高。因為拆分為微服務之後,服務的數量變多,是以需要有統一的服務治理平台,來對各個服務進行管理。

二、引入 Spring Cloud

什麼是 Spring Cloud?

Spring 全家桶在 Java 開發中擁有舉足輕重的地位,其中的一系列産品不僅僅大大簡化和友善了 Java 的開發,其中的 AOP 和 IoC 等一系列的理念也深刻地影響着 Java 程式員們。

Spring 全家桶産品衆多,總結起來大概就是:

  • Spring 通常指 Spring IOC。
  • Spring Framework 包含了 Spring IOC,同時包含了 Spring AOP,并實作與其它 J2EE 架構的整合。
  • Spring Boot 是對 Spring Framework 的補充,讓架構的內建變得更簡單,緻力于快速開發 獨立的 Spring 應用。
  • Spring Cloud 是基于 Spring Boot 設計的一套微服務規範,并增強了應用上下文。

我們也不妨來看看官網的介紹:

總結起來就是: Spring Cloud 是一系列架構的有序集合。我們能夠使用基于 Spring Boot 設計的 Spring Cloud 友善快速的搭建起自己的可靠、協調一緻的分布式系統。

為什麼是 Spring Cloud?

微服務的架構那麼多比如:Dubbo、Kubernetes,為什麼就要使用 Spring Cloud 的呢?

  • 産出于 Spring 大家族,Spring 在企業級開發架構中無人能敵,來頭很大,可以保證後續的更新、完善。比如 Dubbo 現在就差不多死了
  • 有 Spring Boot 這個獨立幹将可以省很多事,大大小小的活 Spring Boot 都搞的挺不錯。
  • 作為一個微服務治理的大家夥,考慮的很全面,幾乎服務治理的方方面面都考慮到了,友善開發開箱即用。
  • Spring Cloud 活躍度很高,教程很豐富,遇到問題很容易找到解決方案。
  • 輕輕松松幾行代碼就完成了熔斷、均衡負載、服務中心的各種平台功能。

三、Spring Cloud 能夠幫我們做什麼?

前面我們說到了,「Spring Cloud」是一系列架構的集合,可以幫助我們解決分布式/微服務的各種問題,那麼「Spring Cloud」究竟能幫助我們做什麼呢?

SpringCloud的基礎功能包括:

  • 服務治理: Spring Cloud Eureka
  • 用戶端負載均衡: Spring Cloud Ribbon
  • 服務容錯保護: Spring Cloud Hystrix
  • 聲明式服務調用: Spring Cloud Feign
  • API網關服務: Spring Cloud Zuul
  • 分布式配置中心: Spring Cloud Config

當然 Spring Cloud 還包括一些進階的功能:

  • 消息總線: Spring Cloud Bus
  • 消息驅動的微服務: Spring Cloud Stream
  • 分布式服務跟蹤: Spring Cloud Sleuth

服務治理:Eureka

微服務很重要的一點就是「無狀态」,也就是說每一個服務之間應該是獨立的,是以當微服務架構搭起來之後各個獨立的「微服務」之間應該如何通訊成了首要的問題。

假設我們的 A服務 需要通路 B服務,那麼我們首先需要知道對方的 ip位址,是以我們調用起來可能就像:

似乎并沒有什麼問題,但是如果 B服務 的 ip位址 變更了,那麼我們就隻能手動的去更改 A服務 的配置,如果我們的服務有很多,并且不止 A服務 調用了 B服務,那麼手動更改這些配置将會是一場噩夢。

Eureka 是 Netflix 開源的一款提供服務注冊和發現的産品,它提供了完整的 Service Registry 和 Service Discovery 實作。也是 Spring Cloud 體系中最重要最核心的元件之一。

用大白話講,Eureka 就是一個服務中心,将所有的可以提供的服務都注冊到它這裡來管理,其它各調用者需要的時候去注冊中心擷取,然後再進行調用,避免了服務之間的直接調用,友善後續的水準擴充、故障轉移等。如下圖:

當然服務中心這麼重要的元件一但挂掉将會影響全部服務,是以需要搭建 Eureka 叢集來保持高可用性,生産中建議最少兩台。随着系統的流量不斷增加,需要根據情況來擴充某個服務,Eureka 内部已經提供均衡負載的功能,隻需要增加相應的服務端執行個體既可。那麼在系統的運作期間某個執行個體挂了怎麼辦?Eureka 内容有一個心跳檢測機制, 如果某個執行個體在規定的時間内沒有進行通訊則會自動被剔除掉,避免了某個執行個體挂掉而影響服務。

是以使用了Eureka就自動具有了注冊中心、負載均衡、故障轉移的功能。如果想對Eureka進一步了解可以參考這篇文章:注冊中心Eureka

用戶端負載均衡: Ribbon

Ribbon 是一個基于 HTTP 和 TCP 用戶端的負載均衡器。Ribbon 可以在通過用戶端中配置的 ribbonServerList 服務端清單去輪詢通路以達到均衡負載的作用。

當 Ribbon 與 Eureka 聯合使用時,ribbonServerList 會被 DiscoveryEnabledNIWSServerList 重寫,擴充成從 Eureka 注冊中心中擷取服務端清單。同時它也會用 NIWSDiscoveryPing 來取代 IPing,它将職責委托給 Eureka 來确定服務端是否已經啟動。

  • 實戰:

Spring Cloud建構微服務架構(二)服務消費者 - http://blog.didispace.com/springcloud2/

服務容錯保護: Hystrix

在微服務架構中通常會有多個服務層調用,基礎服務的故障可能會導緻級聯故障,進而造成整個系統不可用的情況,這種現象被稱為服務雪崩效應。服務雪崩效應是一種因“服務提供者”的不可用導緻“服務消費者”的不可用,并将不可用逐漸放大的過程。

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

在這種情況下就需要整個服務機構具有故障隔離的功能,避免某一個服務挂掉影響全局。在 Spring Cloud 中 Hystrix 元件就扮演這個角色。

Hystrix 會在某個服務連續調用 N 次不響應的情況下,立即通知調用端調用失敗,避免調用端持續等待而影響了整體服務。Hystrix 間隔時間會再次檢查此服務,如果服務恢複将繼續提供服務。

繼續了解Hystrix可以參考:熔斷器Hystrix

Hystrix Dashboard 和 Turbine

當熔斷發生的時候需要迅速的響應來解決問題,避免故障進一步擴散,那麼對熔斷的監控就變得非常重要。熔斷的監控現在有兩款工具:Hystrix-dashboard 和 Turbine

Hystrix-dashboard 是一款針對Hystrix進行實時監控的工具,通過 Hystrix Dashboard 我們可以直覺地看到各 Hystrix Command 的請求響應時間, 請求成功率等資料。但是隻使用 Hystrix Dashboard 的話, 你隻能看到單個應用内的服務資訊, 這明顯不夠. 我們需要一個工具能讓我們彙總系統内多個服務的資料并顯示到 Hystrix Dashboard 上, 這個工具就是 Turbine. 監控的效果圖如下:

想了解具體都監控了哪些名額,以及如何監控可以參考這篇文章:熔斷監控Hystrix Dashboard和Turbine

聲明式服務調用:Feign

上面我們介紹了 Ribbon 和 Hystrix 了,可以發現:這兩個可以作為基礎工具類廣泛的嵌入到各個微服務中。為了簡化我們的開發,Spring Cloud Feign 出現了!它基于 Netflix Feign 實作,整合了 Spring Cloud Ribbon 與 Spring Cloud Hystrix, 除了整合這兩者的強大功能之外,它還提供了聲明式的服務調用(不再通過RestTemplate)。

Feign 是一種聲明式、模闆化的HTTP用戶端。在 Spring Cloud 中使用 Feign, 我們可以做到使用HTTP請求遠端服務時能與調用本地方法一樣的編碼體驗,開發者完全感覺不到這是遠端方法,更感覺不到這是個 HTTP 請求。

下面就簡單看看Feign是怎麼優雅地實作遠端調用的:

服務綁定:

// value --->指定調用哪個服務
// fallbackFactory--->熔斷器的降級提示
@FeignClient(value = "MICROSERVICECLOUD-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

    // 采用Feign我們可以使用SpringMVC的注解來對服務進行綁定!
    @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
    public Dept get(@PathVariable("id") long id);

    @RequestMapping(value = "/dept/list", method = RequestMethod.GET)
    public List<Dept> list();

    @RequestMapping(value = "/dept/add", method = RequestMethod.POST)
    public boolean add(Dept dept);
}
           

Feign 中使用熔斷器:

/**
 * Feign中使用斷路器
 * 這裡主要是處理異常出錯的情況(降級/熔斷時服務不可用,fallback就會找到這裡來)
 */
@Component // 不要忘記添加,不要忘記添加
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept get(long id) {
                return new Dept().setDeptno(id).setDname("該ID:" + id + "沒有沒有對應的資訊,Consumer用戶端提供的降級資訊,此刻服務Provider已經關閉")
                        .setDb_source("no this database in MySQL");
            }

            @Override
            public List<Dept> list() {
                return null;
            }

            @Override
            public boolean add(Dept dept) {
                return false;
            }
        };
    }
}
           

調用:

  • 引用自:

    外行人都能看懂的 Spring Cloud - https://juejin.im/post/5b83466b6fb9a019b421cecc#heading-12

API 網關服務:Zuul

在微服務架構模式下,後端服務的執行個體數一般是動态的,對于用戶端而言很難發現動态改變的服務執行個體的通路位址資訊。是以在基于微服務的項目中為了簡化前端的調用邏輯,通常會引入 API Gateway 作為輕量級網關,同時 API Gateway 中也會實作相關的認證邏輯進而簡化内部服務之間互相調用的複雜度。

Spring Cloud 體系中支援 API Gateway 落地的技術就是 Zuul。Spring Cloud Zuul 路由是微服務架構中不可或缺的一部分,提供動态路由,監控,彈性,安全等的邊緣服務。Zuul 是 Netflix 出品的一個基于 JVM 路由和服務端的負載均衡器。

它的具體作用就是服務轉發,接收并轉發所有内外部的用戶端調用。使用 Zuul 可以作為資源的統一通路入口,同時也可以在網關做一些權限校驗等類似的功能。

具體使用參考這篇文章:服務網關zuul

分布式配置中心:Config

随着業務的不斷發展,我們的「微服務」可能會越來越多,而每一個微服務都會有自己的配置檔案,在研發過程中有測試環境、UAT環境、生産環境,是以每個微服務又對應至少三個不同環境的配置檔案。這麼多的配置檔案,如果需要修改某個公共服務的配置資訊,如:緩存、資料庫等,難免會産生混亂,這個時候就需要引入 Spring Cloud 另外一個元件:Spring Cloud Config。

Spring Cloud Config 是一個解決分布式系統的配置管理方案。它包含了 Client 和 Server 兩個部分,Server 提供配置檔案的存儲、以接口的形式将配置檔案的内容提供出去,Client 通過接口擷取資料、并依據此資料初始化自己的應用。

其實就是 Server 端将所有的配置檔案服務化,需要配置檔案的服務執行個體去 Config Server 擷取對應的資料。将所有的配置檔案統一整理,避免了配置檔案碎片化。配置中心git執行個體參考:配置中心git示例;

如果服務運作期間改變配置檔案,服務是不會得到最新的配置資訊,需要解決這個問題就需要引入 Refresh。可以在服務的運作期間重新加載配置檔案,具體可以參考這篇文章:配置中心svn示例和refresh

當所有的配置檔案都存儲在配置中心的時候,配置中心就成為了一個非常重要的元件。如果配置中心出現問題将會導緻災難性的後果,是以在生産中建議對配置中心做叢集,來支援配置中心高可用性。具體參考:配置中心服務化和高可用

消息總線:Bus

上面的 Refresh 方案雖然可以解決單個微服務運作期間重載配置資訊的問題,但是在真正的實踐生産中,可能會有 N 多的服務需要更新配置,如果每次依靠手動 Refresh 将是一個巨大的工作量,這時候 Spring Cloud 提出了另外一個解決方案:Spring Cloud Bus

Spring Cloud Bus 通過輕量消息代理連接配接各個分布的節點。這會用在廣播狀态的變化(例如配置變化)或者其它的消息指令中。Spring Cloud Bus 的一個核心思想是通過分布式的啟動器對Spring Boot應用進行擴充,也可以用來建立一個或多個應用之間的通信頻道。目前唯一實作的方式是用 AMQP 消息代理作為通道。

Spring Cloud Bus 是輕量級的通訊元件,也可以用在其它類似的場景中。有了 Spring Cloud Bus 之後,當我們改變配置檔案送出到版本庫中時,會自動的觸發對應執行個體的 Refresh,具體的工作流程如下:

也可以參考這篇文章來了解:配置中心和消息總線

消息驅動的微服務:Stream

Spring Cloud Stream 是一個用來為微服務應用建構消息驅動能力的架構。它可以基于 Spring Boot 來建立獨立的、可用于生産的 Spring 應用程式。它通過使用 Spring Integration 來連接配接消息代理中間件以實作消息事件驅動的微服務應用。

下圖是官方文檔中對于 Spring Cloud Stream 應用模型的結構圖。從中我們可以看到,Spring Cloud Stream 建構的應用程式與消息中間件之間是通過綁定器 Binder 相關聯的,綁定器對于應用程式而言起到了隔離作用,它使得不同消息中間件的實作細節對應用程式來說是透明的。是以對于每一個 Spring Cloud Stream 的應用程式來說,它不需要知曉消息中間件的通信細節,它隻需要知道 Binder 對應用程式提供的概念去實作即可。如下圖案例,在應用程式和 Binder 之間定義了兩條輸入通道和三條輸出通道來傳遞消息,而綁定器則是作為這些通道和消息中間件之間的橋梁進行通信。

Spring Cloud Stream 為一些供應商的消息中間件産品提供了個性化的自動化配置實作,并且引入了釋出-訂閱、消費組以及消息分區這三個核心概念。簡單的說,Spring Cloud Stream 本質上就是整合了 Spring Boot 和 Spring Integration,實作了一套輕量級的消息驅動的微服務架構。通過使用 Spring Cloud Stream,可以有效地簡化開發人員對消息中間件的使用複雜度,讓系統開發人員可以有更多的精力關注于核心業務邏輯的處理。由于 Spring Cloud Stream 基于 Spring Boot 實作,是以它秉承了 Spring Boot 的優點,實作了自動化配置的功能幫忙我們可以快速的上手使用,但是目前為止 Spring Cloud Stream 隻支援 RabbitMQ 和 Kafka 兩個著名的消息中間件的自動化配置:

  • Spring Cloud建構微服務架構:消息驅動的微服務(入門)【Dalston版】 - http://blog.didispace.com/spring-cloud-starter-dalston-7-1/

分布式服務跟蹤:Sleuth

随着服務的越來越多,對調用鍊的分析會越來越複雜,如服務之間的調用關系、某個請求對應的調用鍊、調用之間消費的時間等,對這些資訊進行監控就成為一個問題。在實際的使用中我們需要監控服務和服務之間通訊的各項名額,這些資料将是我們改進系統架構的主要依據。是以分布式的鍊路跟蹤就變的非常重要,Spring Cloud 也給出了具體的解決方案:Spring Cloud Sleuth 和 Zipkin

Spring Cloud Sleuth 為服務之間調用提供鍊路追蹤。通過 Sleuth 可以很清楚的了解到一個服務請求經過了哪些服務,每個服務處理花費了多長時間。進而讓我們可以很友善的理清各微服務間的調用關系。

Zipkin 是 Twitter 的一個開源項目,允許開發者收集 Twitter 各個服務上的監控資料,并提供查詢接口

分布式鍊路跟蹤需要 Sleuth + Zipkin 結合來實作,具體操作參考這篇文章:分布式鍊路跟蹤(Sleuth)

總結

我們從整體上來看一下Spring Cloud各個元件如何來配套使用:

從上圖可以看出 Spring Cloud 各個元件互相配合,合作支援了一套完整的微服務架構。

  • 其中 Eureka 負責服務的注冊與發現,很好将各服務連接配接起來
  • Hystrix 負責監控服務之間的調用情況,連續多次失敗進行熔斷保護。
  • Hystrix dashboard,Turbine 負責監控 Hystrix 的熔斷情況,并給予圖形化的展示
  • Spring Cloud Config 提供了統一的配置中心服務
  • 當配置檔案發生變化的時候,Spring Cloud Bus 負責通知各服務去擷取最新的配置資訊
  • 所有對外的請求和服務,我們都通過 Zuul 來進行轉發,起到 API 網關的作用
  • 最後我們使用 Sleuth + Zipkin 将所有的請求資料記錄下來,友善我們進行後續分析

Spring Cloud 從設計之初就考慮了絕大多數網際網路公司架構演化所需的功能,如服務發現注冊、配置中心、消息總線、負載均衡、斷路器、資料監控等。這些功能都是以插拔的形式提供出來,友善我們系統架構演進的過程中,可以合理的選擇需要的元件進行內建,進而在架構演進的過程中會更加平滑、順利。

微服務架構是一種趨勢,Spring Cloud 提供了标準化的、全站式的技術方案,意義可能會堪比目前 Servlet 規範的誕生,有效推進服務端軟體系統技術水準的進步。

引用自:從架構演進的角度聊聊Spring Cloud都做了些什麼? - http://www.ityouknow.com/springcloud/2017/11/02/framework-and-springcloud.html

四、Spring Cloud 版本

剛接觸的「Spring Cloud」的童鞋可能會對它的版本感到奇怪,什麼

Angle

Brixton

Finchley

,這些都是啥啊?「為什麼會有這麼多種看起來不同的 Spring Cloud?」

從上面我們可以知道:Spring Cloud 是一個擁有諸多子項目的大型綜合項目(功能不止上面的介紹),原則上其子項目也都維護着自己的釋出版本号。那麼每一個Spring Cloud的版本都會包含不同的子項目版本,為了要管理每個版本的子項目清單,避免版本名與子項目的釋出号混淆,是以沒有采用版本号的方式,而是通過命名的方式。

這些版本名字采用了倫敦地鐵站的名字,根據字母表的順序來對應版本時間順序,比如:最早的Release版本:Angel,第二個Release版本:Brixton,以此類推……

當一個項目到達釋出臨界點或者解決了一個嚴重的 BUG 後就會釋出一個 "service Release" 版本, 簡稱 SR(X)版本,x 代表一個遞增數字。

  • 聊聊Spring Cloud版本的那些事兒 - http://blog.didispace.com/springcloud-version/

Spring Cloud & Spring Boot 版本對照表

通過查閱官網:https://spring.io/projects/spring-cloud,我們可以看到一個「Release train Spring Boot compatibility」表:

Release Train Boot Version
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston

上表可以看出,最新的「Spring Cloud」版本已經出到了 Greenwich... 每個版本都能查閱到目前版本所包含的子項目,以及子項目的版本号,我們可以通過此來決定需要選擇怎麼樣的版本。

參考資料

1. 外行人都能看懂的SpringCloud,錯過了血虧! - https://juejin.im/post/5b83466b6fb9a019b421cecc#heading-19

2. 從架構演進的角度聊聊Spring Cloud都做了些什麼? - http://www.ityouknow.com/springcloud/2017/11/02/framework-and-springcloud.html

3. 聊聊Spring Cloud版本的那些事兒 - http://blog.didispace.com/springcloud-version/

4. Spring Cloud 從入門到精通 - http://blog.didispace.com/spring-cloud-learning/

5. Spring Cloud 中文網 - https://springcloud.cc/

按照慣例黏一個尾巴:

歡迎轉載,轉載請注明出處!

簡書ID:@我沒有三顆心髒

github:wmyskxz

歡迎關注公衆微信号:wmyskxz

分享自己的學習 & 學習資料 & 生活

想要交流的朋友也可以加qq群:3382693

繼續閱讀