微服務實戰:從架構到部署

在這篇文章裏, 計劃涵蓋微服務架構(MSA)的核心架構概念,以及如何在實踐中使用這些架構理論。

現在,微服務「Microservices」已經成爲軟件架構領域最流行的熱詞之一。市面上也有不少與微服務的基礎知識以及優勢相關的學習資料,可是關於如何在真實的企業場景中應用微服務的資料仍是很少。

在這篇文章裏, 我計劃涵蓋微服務架構(MSA)的核心架構概念,以及你如何在實踐中使用這些架構理論。html

單體架構

企業軟件設計須要知足多種多樣的業務需求。所以,一個特定的應用軟件會包括有幾百個功能項,而全部這些功能項都打包進了一個單體的應用中。典型的例子有,ERP、CRM等其餘各類各樣的軟件。對於這種野獸級別的軟件應用、部署、排錯、擴展和升級工做都是一個個噩夢。

面向服務架構(SOA)設計是針對上述問題的一個解決方案, SOA引入了服務的概念,用來將軟件中類似的功能進行分組聚合在一塊兒。所以,有了SOA,軟件就被設計爲一組粗粒度服務的組合。 可是SOA並無解決全部的問題。在SOA裏,一個服務的範圍是很是廣的。由此帶來的弊端是服務自己龐大而複雜,數十個功能點,以及複雜的消息格式和標準(例如全部的WS規範)。
git

01.png


*圖 1:單體架構


在大多數狀況下,SOA裏面的服是互相獨立的,並且是與其餘全部的服務部署在同一個運行時上面。(能夠想象一下多個Web應用部署到同一個Tomcat實例當中)。並且與單體軟件相似,這些服務會隨時間越長越大,由於累加的功能愈來愈多。最後,這些應用自己已變成了單體軟件,與傳統的單體軟件(好比ERP)也沒啥兩樣。圖1描述了一個零售業的軟件,它包含有多個服務,全部這些服務都部署在同一個運行時上。 這是一個很好的單體架構的例子。這裏我列出這種基於單體架構軟件的一些特色:github

  • 單獨應用是做爲一個總體單元來設計、開發、部署的;
  • 單體應用很是複雜,致使的結果就是維護,升級和增長新功能都很是困難;
  • 在單體架構下,很是難實踐敏捷的開發和部署方法;
  • 若是要更新它的某個部署,則須要從新部署整個應用;
  • 擴展:必須做爲單個軟件來擴展,當有資源需求衝突時擴展就變得很是困難(好比一個服務須要更多的CPU可是其餘的服務要更多內存);
  • 可靠性:一個不穩定的服務可能會致使整個應用不可用;
  • 阻礙創新: 因爲全部的功能都基於同一套技術框架來夠構建,想加入新的技術或者框架就很是困難。



微服務架構

微服務架構的基礎是開發一個應用由一組小可是獨立的服務來組成,這些服務運行在本身的進程中,能夠被獨立開發,獨立部署。

在大多數的微服務架構的定義裏,這被解釋爲將一個單體應用裏面的服務拆分爲一組獨立的服務。可是,我以爲,這不是微服務的所有。

核心的觀點是經過查看單體服務提供的功能項目,來識別出必須的業務能力。而後這些業務能力能夠做爲一個徹底獨立的,細粒度的,自包含的服務來實現(微服務)。他們的實現能夠是基於不一樣的技術棧,並且每一個服務描述的是一個明確的特定的有限的業務範圍。

所以,咱們上文中提到的在線零售系統能夠用圖2裏面的微服務架構來實現。 在微服務架構下,零售軟件應用經過一組微服務來實現。因此你看到圖2中,咱們增長在原來單體應用裏面的一組服務的基礎上新增長了一個服務。因此很明顯,使用微服務架構不是僅僅將單體應用裏面的服務拆分那麼簡單。web

02.png


圖 2:微服務架構

接下來,讓咱們更加深刻了解微服務的核心架構原則。更重要的是,讓咱們關注如何將他們應用到實踐中。spring

設計微服務: 大小、範圍和能力

你可能在使用微服務架構從頭構建一個軟件,也多是要把已有的應用服務轉換爲微服務。不管哪一種,很是重要的一點都是你必須合理的決定微服務的大小、範圍和能力。這極有多是在實踐微服務架構初期碰到的最難的事情。

接下來咱們來討論與微服務的大小、範圍、能力相關的一些實際的考慮點和錯誤觀點。docker

  • 代碼行數和團隊大小是很糟糕的度量指標:基於代碼行數或者團隊大小來決定微服務的大小已經有多個討論了。(好比兩個pizza的團隊)。 可是,這些都是很是不切實際並且很是糟糕的度量值,由於咱們用更少的代碼或者兩個pizza的團隊開發出來的服務仍然可能徹底違背微服務的架構原則。
  • 微「micro」這個詞會致使誤解:大多數開發人員傾向於認爲們應該將服務作的越小越好。可是這徹底是錯誤的解釋。
  • 在SOA的上下文裏面,服務一般被實現爲包括不少功能的和運營支持的單體結構。因此若是僅僅是將SOA那種服務從新打上微服務的標籤不會給你帶來微服務架構的如何好處。



那麼,咱們該如何合地設計微服務架構下的服務呢?數據庫

微服務設計原則

  • 單一責任原則(Single Responsibility Principle,SRP): 對於一個微服務而言具備有限的和關注的業務範圍能夠幫助咱們知足服務開發和交付的敏捷性;
  • 在微服務的設計階段, 咱們應該找到他們的邊界,並將它們與業務能力相關聯(在領域驅動設計裏這叫有邊界的上下文);
  • 必須保證微服務設計能支持服務的敏捷/獨立地開發和部署;
  • 咱們應該關注微服務的範圍,而不是一味的把服務作小。一個服務的(正確的)大小應該等於知足某個特定業務能力所須要的大小。
  • 與SOA裏面的服務不一樣,一個給定的微服務應該有至關少的運營和功能點,以及簡單的消息格式;
  • 一般一個好的實踐是先從一個比較大的服務邊界開始,而後隨着時間推移基於業務需求來重構成更小的。



在咱們的零售系統的案例中,你能夠發現咱們將原來單體應用的功能分割到了4個不一樣的微服務中, 'invenory"、"accountng"、"shipping"、"store"。 它們描述的是一個有限但關注的業務範圍,並且服務之間互相徹底解耦,保證了開發和部署的敏捷性。apache

微服務裏的消息

在單體應用裏面,不一樣組件的業務功能經過函數調用或者語言級別的方法調用來實現。在SOA中,這轉變爲更加鬆耦合的Web Service級別的消息,主要是基於HTTP、JMS等不一樣協議的SOAP。Webservice 包含的幾十種操做以及複雜的消息機制是阻礙Web Services流行的一個重要因素。對於微服務架構而言,必需要有一個簡單且輕量級的消息機制。緩存

同步消息——REST、Thrift

對於微服務領域的同步消息機制而言(可得到須要服務給一個及時的響應不然一直等待), REST是公認的選擇。它提供了一種簡單的消息風格,具體實現是HTTP的請求-響應,基於資源的API風格。所以大多數的微服務實現是使用HTTP和基於資源API風格的。(每個功能都是經過一個資源以及在它之上執行的操做來實現)安全

03.png


圖 3:經過REST接口來暴露微服務

Thrift 是REST/HTTP同步消息以外的另外一個選項。使用它你能夠給你的微服務定義一個接口定義。

異步消息——AMQP、STOMP、MQTT

在某些微服務場景下,須要使用異步消息技術(可得到不須要當即獲得回覆,甚至徹底不要回復)。在這種場景下, 異步消息AMPQSTOMPMQTT等被普遍使用

消息格式——jSON、XML、Thrift、ProtoBuf、Avro

爲微服務來決定最適合的消息格式是另外一個關鍵要素。傳統的單體的軟件使用複雜的二進制的格式,SOA/Web services的應用使用基於複雜消息格式(SOAP)和schema(xsd)的文本消息。在大多數的微服務裏面,它們使用簡單的基於文本的消息格式,例如基於HTTP資源API風格之上的JSON/XML等。在某些狀況下它們須要二進制的格式時(文本消息在某些場景下顯得囉嗦),可使用二進制的協議例如二進制的Thrift、ProtobufArvo

服務協議-定義服務的接口——Swagger、RAML、Thrift IDL

當你已經有一個業務能力以服務的形式實現以後, 你須要定義和發佈服務協議。 在傳統單體應用中, 咱們不多找到這個功能來定義某個應用的業務能力。 在SOA/Web services的世界裏面, WSDL用來描述服務協議,可是,咱們都知道,WSDL並非描述微服務的理想方案,由於它太複雜了並且與SOAP高度耦合。

既然咱們是基於REST架構風格來構建的微服務,咱們可使用一樣的REST API定義的技術來定義服務協議。所以,微服務使用標準的REST API定義語言來定義服務協議, 好比SwaggerRAML

對於其餘一些不是基於HTTP/REST的微服務實現(例如Thrift),咱們能夠協議級別的接口定義語言(好比Thrift IDL)。

集成微服務(跨服務/進程通信)

在微服務架構裏,一個軟件應用是基於一組獨立的服務構建的。 所以爲了實現某個應用場景,須要不一樣微服務、進程之間的通信機制。這也是微服務之間跨服務、進程通信這麼重要的緣由。

在SOA的實現中,服務之間的跨服務通信是經過企業服務總線ESB來實現的,而且大部分的業務邏輯在中間層中(消息路由、傳送、編排)。可是微服務架構推崇去掉中央消息總線將業務邏輯放到服務和客戶端去(也稱之爲smart endpoints)。

由於微服務使用HTTP、 JSON等標準協議,當作跨微服務之間的通信時,須要跟一個不一樣的協議作集成的需求不多。在微服務裏面的另外一個可選方案是使用一個輕量級的消息總線或者網關,網關上帶最少的路由功能,不帶任何業務邏輯實現而僅僅是一個啞管道。基於這些方式,在微服務架構裏面就有了以下幾種通信模式。

點對點風格——直接調用服務

在點對點風格里,整個的消息路由邏輯在端點上,服務之間直接通信。每一個服務暴露一組REST API,外部的服務或者客戶端經過REST API來調用。

04.png


圖 4:服務間通信:,點對點鏈接

明顯的,這種模型對於簡單的微服務架構應用有效。可是隨着服務數量的增長,它會慢慢變得複雜。這也是爲何在SOA裏面要用ESB來避免雜亂的點對點的鏈接。讓咱們試着總結一下點對點模式的弊端。

  • 非功能需求,好比用戶認證、流控、監控等必須在每一個微服務裏實現;
  • 因爲通用功能的重複,每一個微服務的實現變得複雜;
  • 在服務和客戶端之間沒有通信控制(甚至對於監控、跟蹤、過濾等都沒有);
  • 對於大的微服務實現來講直接的通信形式一般被認爲是反模式



所以, 在複雜的微服務應用場景下,不要使用點對點直連或者中央的ESB,咱們可使用一個輕量級的中央消息總線給全部微服務提供一個抽象層,並且能夠用來實現各類非功能的能力。這種風格也叫作API Gateway風格。

API Gateway風格

API Gateway風格的核心理念是使用一個輕量級的消息網關做爲全部客戶端、消費者的主入口而且在網關層面上實現通用的非功能性需求。 一般,一個API網關容許你經過REST來消費一個受管理的API。 所以咱們可使用它來暴露微服務所實現的業務功能, 以受管理的API的形式。 實際上, 這是微服務架構與API管理的組合,給你帶來兩種技術的優勢。

 

 


圖 5:全部服務經過一個API網關來暴露

在咱們零售的例子中,如圖5所描述的, 全部的服務經過API 網關來暴露,這是全部客戶端訪問的惟一入口。 若是一個微服務要訪問另外一個微服務,也要經過這個網關。

API網關帶來如下優勢:

  • 在網關層面對存在的微服務提供必要的抽象。例如,網關能夠選擇不提供一個適用全部的API, 而選擇對不一樣的用戶暴露不一樣的API;
  • 在網關層面的輕量級消息路由和轉換;
  • 一箇中心的地方提供非功能性的能力, 好比安全、監控、限流等;
  • 經過適用API網關模式,微服務能夠變得更加輕量,由於非功能性需求都在網關上實現了。



API網關風格多是大多數微服務實現裏最被廣泛採用的形式。

消息代理風格

微服務能夠與異步消息場景集成,好比單向的請求和使用隊列或者主題的發佈訂閱消息機制。某個微服務能夠是一個消息的製造者,它能將消息異步的發送到一個隊列或者主題裏面。消費型的微服務能夠消費隊列或者主題裏來的消息。這種方式將消息的製造者和消費者解耦,並且中間的消息代理會緩存消息直到消費者處理它們。 製造消息的微服務對消費消息的微服務徹底未知。

06.png


圖 6:異步消息機制, 基於PUB-SUB集成

生產者與消費者直接的通信由消息代理來完成,基於的是異步消息標準, 好比AMQP、MQTT,等等。

去中心化的數據管理

在單體架構中,應用將數據存在一個集中化的數據庫中來實現各類的功能和業務能力。

07.png


圖 7:單體應用使用一個集中化的數據庫來實現全部特性

在微服務架構裏,功能是跨多個微服務來提供的,這樣一來,若是咱們繼續使用集中化的數據庫,那麼微服務之間就不是互相獨立了(例如數據庫的某個schema爲了某個服務要更改,那麼極有可能會破壞其餘的服務)。 所以每一個微服務必須有本身的數據庫。

08.png


圖 8:微服務有本身的私有數據庫,它們沒法直接訪問其餘微服務的數據庫

要實現微服務架構下的去中心化數據庫管理有以下幾個核心關注點:

  • 每一個微服務都有一個私有的數據庫, 存放的數據用來實現它所要提供的業務功能;
  • 一個特定的微服務本身能訪問本身的私有專用的數據庫,而不能直接訪問其餘微服務的數據庫;
  • 在某些業務場景下,爲了事務性要求你可能須要一次更新多個數據庫。在這種狀況下,其餘微服務的數據庫更新應該經過它的API調用來完成(不容許直接訪問它的數據庫)。



去中心化的數據管理讓你能夠獲得徹底解耦的數據庫, 而且也有了自由選擇各類數據庫技術的能力(好比SQL 或者NOSQL,每一個服務均可以有不一樣的數據庫管理系統)。 可是, 對於複雜的涉及多個微服務的事務型應用場景下,事務操做應該使用各個微服務提供的API實現,具體邏輯應該在客戶端或者中間層(網關)中實現。

去中心化治理

微服務架構適用微服務治理。

總的來講,「治理」的意思是創建和實施「如何讓人員和解決方案爲了組織目標而一塊兒工做」。在SOA的上下文中,SOA治理指導可重用服務的開發,指導服務該如何設計和開發,以及服務如何隨時間演進。它在服務的提供者與服務消費者之間創建協議,告訴消費者它們能夠指望獲得什麼;告訴提供者它們有義務提供什麼。在SOA治理中,有兩種普通採用的治理模型:

  • 設計時治理——定義和控制服務的生成,設計以及服務策略的實現;
  • 運行時治理——在運行時實施服務策略的能力。



那麼,微服務上下文中的治理究竟是什麼意思?在微服務架構下,服務是以徹底獨立解耦的方式構建的,用的技術棧能夠徹底不一樣。所以,定義一個通用的服務設計和開發標準沒有太大必要。 咱們能夠將微服務場景下的去中心化的治理能力總結以下:

  • 在微服務架構下, 沒有必要擁有一箇中心化的設計時治理;
  • 微服務能夠本身決策本身的設計實現;
  • 微服務架構能夠共享通用/可重用的服務;
  • 某些運行時治理, 好比SLA、限流、監控、通用的安全需求以及服務發現能夠在API網關級別實現。



服務註冊與服務發現

在微服務架構下, 你須要管理的微服務數量至關之高。並且,因爲微服務自己的快速敏捷的開發部署特性,它們的運行地點會動態變化。所以,你須要可以在運行時找到一個微服務運行的位置。這個問題的解決方案是使用一個服務註冊表。

服務註冊表

服務註冊表保持微服務實例以及它們的位置。微服務實例在服務啓動時在註冊表裏面註冊,在關閉時註銷。消費者能夠經過註冊表找到可用的微服務以及它們的位置。

服務發現

要找到可用的微服務以及它們的位置,咱們須要有一個服務發現機制。 有2種服務發現機制,客戶端發現和服務端發現。 

客戶端發現——這種方式下,客戶端或者API-GW經過查詢服務註冊表來獲得服務實例的位置。

09.png


圖 9:客戶端發現

這裏,客戶端/API-GW經過調用服務註冊組件來實現服務發現邏輯。

服務端發現——這種方式下,客戶端/API-GW向運行在某個公知位置的組件發送請求(例如負載均衡器)。 這個組件調用服務註冊表而後獲得這個微服務的絕對位置。

10.png


圖 10:服務端發現

微服務部署方案如Kubernetes提供的就是服務端解決方案。

部署

提到微服務架構時,微服務的部署扮演着一個核心角色並且有以下核心要求:

  • 有能力在不依賴其餘服務的狀況下部署/撤銷;
  • 能在每一個微服務的級別進行擴展(某個服務可能比其餘服務有更多的流量);
  • 快速構建和部署微服務;
  • 一個微服務的失效不能影響其餘服務;



Docker(一個開源引擎可讓開發者和系統管理員部署自包含的應用容器到Linux環境中)提供了一個知足上述需求的部署方案。裏面涉及的核心步驟有:

  • 將微服務打包爲Docker鏡像;
  • 將每一個服務實例部署爲容器;
  • 經過改變容器的數量來實現服務的擴展;
  • 使用Docker容器時服務的構建,部署和啓動都至關快(一般比虛擬機快的多)。



Kubernetes擴展了Docker的能力:能夠像管理一個系統那樣管理一個Linux容器的集羣,跨主機運行和管理Docker容器, 提供容器的多地部署、服務發現和複製控制。正如你看到的,這些特性中的大多數在微服務場景下也是特別核心的。所以使用Kubernetes(基於Docker)來作微服務部署已成爲一種至關強大的方法,特別對於大型的微服務部署而言。

 


圖 11:以容器方式構建和部署微服務

在圖11中,展現了容器應用中的微服務的部署概覽。每一個微服務實例部署爲一個容器,每一個主機上跑了兩個容器。 在任意一臺主機上你均可以指定跑的容器的數量。

安全

微服務安全是在實際場景中應用微服務的一個廣泛要求。在講微服務安全以前,咱們先看看在單體應用下咱們一般是如何實現安全的。

  • 在一個單體應用中,安全主要關心‘調用者是誰’, ‘調用者能幹什麼’, 以及‘咱們如何傳播這個信息’;
  • 這一般在一個公用的安全組件上實現,它部署在請求處理鏈的首部,經過一個底層的用戶數據庫來填充必要的信息。



這樣, 咱們能夠將這個模型應用到微服務架構中嗎? 能夠,可是要求在每一個微服務級別實現一個安全組件,查詢中心共享的用戶庫來得到必要的信息。這是一個很是繁瑣的方式來解決微服務場景下的安全問題。咱們能夠利用普遍使用的API-安全標準來作,例如OAuth二、OpeniD Connect, 這是解決微服務安全問題的更好方式。在咱們深刻以前,我先總結一下每種標準的目的以及咱們該如何使用。

  • OAuth2——是一個訪問受權協議。客戶端相受權服務器認證獲得一個‘訪問令牌’,訪問令牌裏面不包含關於用戶或者客戶端的任何信息。它僅僅包含一個對客戶信息的應用,並且僅僅能被受權服務器查詢。所以,也常被稱爲'引用型令牌,即便在公網、互聯網上使用也是安全的。
  • OpenID Connect與OAuth2行爲相似,可是除了訪問令牌以外,受權訪問也會發出一個ID令牌,其中包含有用戶的信息。這常經過JWT(JSON WEB TOKEN)實現,由受權服務器簽名。這樣保證了受權服務器與客戶端的互相信任。JWT令牌所以也稱爲「值型令牌」,由於它裏面包含有用戶信息,經過不適於在公共網絡使用。



如今,咱們看看如何在零售的案例中使用這些安全標準來實現微服務的安全:

 


圖 12:微服務安全,基於OAuth2和OpenID Connect

如圖12, 在實現微服務安全時有以下關雎步驟:

  • 將認證交給OAuth2和OpenID Connect服務器(受權服務器),如此一來用戶只要有權使用這些數據微服務就能夠提供訪問;
  • 使用API-GW方式,對於全部的客戶請求有單一入口;
  • 客戶鏈接到受權服務器獲得訪問令牌(引用型令牌),而後將令牌和請求一塊兒發給API-GW;
  • 網關作令牌翻譯 - API-GW提出訪問令牌,發送到受權服務器獲得JWT(值型令牌);
  • 網關將JWT和請求一塊兒發給微服務層;
  • JWT含有必要的信息來作用戶會話保存等。若是每一個服務均可以理解JSON web token,那麼你就擁有了能夠分發身份信息到整個系統中的機制;
  • 在每一個微服務層,咱們能夠有一個組件來處理JWT,這個實現一般很是簡單。



事務

如何在微服務中支持事務? 實際上, 跨多個微服務來實現分佈式事務是一個至關複雜的工做。微服務架構自己鼓勵的是服務之間非事務的協調。

這個意思是基於每一個服務徹底自包含且單一責任的原則。須要跨多個服務之間的分佈式事務一般是微服務設計上的缺陷,一般應該經過重構微服務的範圍來解決。儘管如此,若是必需要有這種跨服務的分佈式事務, 這種場景能夠經過在每一個微服務層引入‘修正操做’來實現。 核心思想是,某個特定的微服務是根據單一責任設計的,若是它沒法完成某個特定操做時,咱們能夠認爲整個微服務都失敗了。 這時上游其餘的微服務就要起到用它們各自的修正操做來回滾。

爲「失效」設計

微服務架構引入了一組離散的服務集合,與單體架構相比,這增長了在每個微服務級別失敗的可能性。一個微服務的失效可能因爲網絡問題,底層資源不可用等等因素。單個微服務的不可用或者沒響應不該該讓整個應用失敗。這樣,微服務應該是容錯的,可能的話有能力自動恢復,客戶端也要能優雅處理。

另外,由於服務可能隨時失敗,快速發現失敗(實時監控),可能的話自動恢復服務也十分重要。

在微服務場景下,有幾種處理錯誤的通用模式:

鏈路斷開器

當你對一個微服務作外部調用時,你能夠給每個調用配置一個錯誤監控組件。當失敗達到某個閾值時,組件會中止對那個服務的調用(斷開鏈路)。 在特定數目的請求是open狀態以後(能夠本身定義),將鏈路閉合回去。

這個模式對避免無謂的資源消耗特別有用, 請求由於超時被推遲,也讓咱們有機會監控系統狀態(基於活躍的open的鏈路狀態)。

隔離牆

因爲應用由至關數量的微服務組成,應用的某一部分的微服務失效不該影響應用的其餘部分。隔離牆模式就是將應用的不一樣部分隔離,這樣一來,應用的某個部分的某個服務的失敗不會影響其餘服務。

超時

超時模型是這樣一種機制,它容許在當你以爲服務的響應不會回來時中止等待。這樣你就能夠配置等待的時間間隔。

那麼,咱們應該在服務中哪裏使用及如何使用這些模式呢? 在大多數時候,大多數的模式適用於網關層。也就是說當服務不可用或者沒響應時,咱們能夠在網關級別決定使用鏈路斷開或者超時的模式來給服務發請求。一樣的, 在網關級別實現隔離牆的模式也十分重要,由於它是全部請求的惟一入口,因此某個服務的失敗不會影響其餘服務的調用。

另外,網關也能夠用做咱們監控每一個服務狀態的中心點,由於每一個服務都是經過網關來調用的。

微服務、企業級集成、 API管理以及其餘

咱們已經討論了微服務架構的各類特性,以及如何在現代的企業IT裏實現它們。儘管如此,咱們必須知道微服務不是包治百病的靈丹妙藥。盲目的吸取流行概念並不會真正解決企業it的實際問題。你通篇讀下來會以爲,微服務確實有不少優勢咱們應該利用。可是,咱們也必須意識到使用微服務來解決全部的IT問題是不切實際的。 例如,微服務架構推崇去除做爲中央總線的ESB,可是在實際的IT場景下,咱們已經有至關數量的線上應用和服務並非基於微服務的。所以,爲了集成它們,咱們必須使用某種集成總線。因此, 理想狀況是,一個融合了微服務和其餘企業架構理念(例如集成)的方法顯然更切合實際。我將在另外一篇文章中單獨加以闡述。

最後給你們推薦一個Java技術交流羣:318261748 你們能夠一塊兒交流分享學習經驗和開發技巧。羣裏會分享微服務、spring boot 和高併發 分佈式架構,jvm,源碼分析等學習資料給你們免費下載學習,目前受益良多。但願你們在學習路上互相幫助互相扶持,在技術路上走的更遠。

相關文章
相關標籤/搜索