ServiceComb開放性設計


【摘要】 本文從連接組織和開發人員、連接異構系統兩方面闡述了ServiceComb的開放性設計,並從內部系統結構、與三方系統集成等角度介紹了ServiceComb的擴展性。通過理解這些基本的設計概念,可以幫助開發者能夠更好的理解ServiceComb,從而更好的使用ServiceComb和參與其項目開發。

和很多微服務開發框架類似,ServiceComb的早期版本,為了追求高性能,做過非常多的嘗試,比如改善編碼效率,改進通信協議等。隨着業務規模的遞增,問題隨之而來。首先面對的是和遺留系統的通信,接着是各種不同的接入終端,然后就涉及到一些更加復雜的問題,協議健壯性、防攻擊等。ServiceComb准備開源的前期,用戶對於與業界其他開源組件和開發框架的集成的述求又源源的涌來。在這些問題的驅動下,ServiceComb設計團隊慢慢的達成了共識,系統應該全開放,使用標准協議,容易拆分和擴展,對開發人員友好,並和業界其他系統良好集成。本文將分享這些方面的設計考慮。    

開放和標准

開放和標准應用到設計的不同的層面。一方面是連接組織和開發人員,一方面是連接異構系統。組織和開發人員的復雜性來源於技能的多樣性,大家使用不同的開發語言,同一種開發語言存在多樣的開發習慣;系統的多樣性來源於系統之間的通信協議,為了實現與異構系統的通信,必須具備良好的適配不同通信協議的能力。

連接組織和開發人員

l  代碼風格

每個開發人員都有自己熟練的技術和寫代碼的方式,使用熟悉的方式寫代碼往往更加高效。ServiceComb的早期版本實現了gRPC協議,推廣的過程中發現大量開發人員不能熟練的書寫IDL,對IDL支持哪些特性不清楚,碰到一個場景就需要翻開協議規范看一遍。加上接口語言一般缺少配套的編輯和語法檢查工具,開發效率非常低。如果開發人員能夠以熟悉的開發方式馬上開始工作,將是一件美妙的事情,新進來的人沒門檻,老系統也能快速切過來,一舉兩得。在JAVA世界里開發框架不勝枚舉,我們從這些方式中找到了很多的共性,能夠涵蓋到90%以上的習慣:1. 使用RPC的方式描述對外接口。這個在gRPC、Corba、WebService等開發人員面前非常熟悉, 是最直接和有效的寫代碼方式; 2. 使用JAX-RS或者Spring MVC風格開發REST接口。REST風格開發隨着微服務架構興起,JAX-RS是JAVA REST開發標准,Spring MVC是JAVA開發的事實標准,Spring擁護者很熟悉。  ServiceComb選擇這幾種缺省的開發風格,擁抱90%的開發者,讓大家能夠快速開始工作。   

在下面的例子中,呈現了Provider和Consumer代碼的各種實現,在同一個微服務中,這些呈現方式可以同時出現;同一段Consumer代碼,可以訪問各種不同的Provider實現,非常靈活。

代碼:RPC形式的Provider.

@RpcSchema(schemaId   = "hello")
public class   HelloImpl implements Hello {
  @Override
  public String sayHi(String name) {
    return "Hello " + name;
  }
 
  @Override
  public String sayHello(Person person) {
    return "Hello person " +   person.getName();
  }
}

代碼:JAX-RS形式的Provider.

 

@RestSchema(schemaId   = "jaxrsHello")
@Path("/jaxrshello")
@Produces(MediaType.APPLICATION_JSON)
public class   JaxrsHelloImpl implements Hello {
 
  @Path("/sayhi")
  @POST
  @Override
  public String sayHi(String name) {
    return "Hello " + name;
  }
 
  @Path("/sayhello")
  @POST
  @Override
  public String sayHello(Person person) {
    return "Hello person " +   person.getName();
  }
}

代碼:Spring MVC形式的Provider.

 

@RestSchema(schemaId   = "springmvcHello")
@RequestMapping(path   = "/springmvchello", produces = MediaType.APPLICATION_JSON)
public class   SpringmvcHelloImpl implements Hello {
 
  @Override
  @RequestMapping(path = "/sayhi",   method = RequestMethod.POST)
  public String sayHi(@RequestParam(name =   "name") String name) {
    return "Hello " + name;
  }
 
  @Override
  @RequestMapping(path =   "/sayhello", method = RequestMethod.POST)
  public String sayHello(@RequestBody Person   person) {
    return "Hello person " +   person.getName();
  }
}

代碼:RPC方式訪問上述三種服務的Consumer.

 

@RpcReference(microserviceName   = "hello", schemaId = "hello")
private Hello   hello;
System.out.println(hello.sayHi("Java   Chassis"));

看到這里,開發者或者有個疑問,Consumer既然可以通過一致的API方式訪問不同的Provider,為什么還需要額外的JAX-RS和SpringMVC標簽了?這里的主要考量並不僅僅是SDK的Consumer,還有瀏覽器等非SDK的Consumer,他們識別的是HTTP形式的消息,通過這些標簽,可以更加精細的指定瀏覽器如何訪問后台接口。這一點非常類似於Web Service的WSDL描述語言,ServiceComb稱之為契約。服務契約會在運行時通過代碼定義生成,並注冊到服務中心。契約在運行時可以用於獨立的服務治理邏輯開發,生成Consumer代碼。也可以作為文檔對外發布,供非SDK的Consumer參考。

l  契約

微服務強調服務自治,對外體現的功能,全部以接口提供,而且只能以通信的方式相互訪問。這個原則給團隊協作帶來了根本的變革,一個團隊通常由5~6個人的全功能團隊組成,端到端的完成功能設計、開發和運維,系統的結構和組織的結構是匹配的。小團隊以后的核心問題就是團隊之間如何進行高效的溝通。為了更好的連接開發人員,我們讓不同的開發人員能夠使用自己熟悉的語言和編程習慣寫代碼,這樣就需要一種中立的機制讓每個功能團隊進行有效協作。在RPC的世界里,有Corda IDL,WSDL,ProtoBuffer等可以參考的優秀實踐。REST風格的接口讓團隊成員之間可以通過HTTP的語義進行溝通,但是不能像IDL一樣描述跨語言時的數據格式。Open API的出現很好的解決了這些問題。Open API首先是一個開放的標准,並且在不斷的發展壯大。Open API對於RPC、REST等不同的開發方式都完整的兼顧到了,並且吸收了大量的跨語言經驗,能夠在不同的語言之間解析。 對於JAVA開發者,下面的類型定義已經是毫無疑問的:

 

User:
  type: object
  properties:
    age:
      type: integer

如果開發人員有豐富的跨語言開發經驗,可以看出Swagger在解決跨語言編程方面的努力,swagger通過format來定義數據類型的存儲格式,以解決不同的語言在數據類型表示上的差異:

 

User:
  type: object
  properties:
    age:
      type: integer
      format: int32

SerivceComb兼顧開發效率和開發規范。開發者可以先寫接口定義,再寫代碼的方式來完成自己的開發過程,也可以直接通過自己熟悉的方式寫代碼。兩種方式都會生成服務的契約(Open API描述文件),並且將內容注冊到服務中心。使用者可以從服務中心下載相關的契約進行開發。ServiceComb的各種治理結構也是基於契約的,可以讓開發者獨立於業務實現對系統進行統一的管控。

連接異構系統

ServiceComb早期版本提供了gRPC、REST、SOAP等多種協議。gRPC相對於REST的最大好處就是性能,采用長連接,高效的二進制序列化方式,並提供多種語言支持。在接口定義方面,提供了IDL語言約束開發者按照標准的方式工作。一切看起來是那么的完美,實際上ServiceComb的第一輪重構,首選的也是gRPC的方式。系統上線以后,首要的問題來源於網關的壓力。網關作為所有業務的接入端,必須高效的管理連接和保證公平,長連接非常容易導致拒絕服務。gRPC程序開發完成后,開發聯調也變得不方便起來,特別是生產環境。開發人員無法利用系統提供的各種工具進行測試,網絡包分析也變得困難。隨着系統規模的擴大,gRPC再次面對更加嚴峻的問題,其他系統如何與它直接通信,如何跨網關與它間接通信,解決這些問題,意味着我們需要擴展和改善老的協議和程序,提供gRPC客戶端支持,開發者需要提供一個額外的表示層用於業務接口的邏輯轉換,造成大量的重復代碼。同時由於gRPC依賴於接口定義,並根據定義生成代碼,一套代碼只能跑在gRPC協議上,希望業務代碼使用其他更加靈活的方式,比如REST訪問的時候,就得重新寫一套代碼。面對以上問題,gRPC在選擇上,最終被定義為只能在中小型系統內部之間使用,並通過協議網關與外部系統進行溝通。   

接着就是REST了。業界可用的REST實現,和gRPC比較起來,最大的痛點就是性能。有一個觀點在很多設計人員和開發人員腦海里根深蒂固:”二進制編碼效率遠高於文本協議,采用二進制編碼的系統的性能遠高於采用文本的HTTP”。這個觀點甚至會讓多數決策止步於理論,大家甚至不願意嘗試去優化REST。可喜的是ServiceComb走出了重構REST底層通信實現的第一步,基於Netty的異步框架來替換Tomcat實現,效果大大超出我們的預期。一些基准測試數據的結果顯示比gRPC還要好,gRPC最終輸在了HTTP2協議上的額外報文。同時優化后的REST和業界開源的其他基於二進制的RPC實現的性能也基本持平,只有微小的差異。在一個簡單的提供數據庫查詢的代碼邏輯中,REST通信框架處理時間,占用總的處理時間遠小於千分之一。這意味着在系統框架層面的大量優化,抵不上業務系統最簡單的一筆操作,為了優化性能放棄的其他好處就不值得了。最終,ServiceComb選擇了REST作為首選和缺省協議(http + json)。

使用REST也存在和其他系統對接,通過網關對接的場景,但是問題已經好了很多。

但是我們遠沒有止步於此。隨着需要遷移到系統平台的服務越來越多,早期的一些遺留系統也需要進行對接。我們提供的每一種通信協議,都對應着不同的開發者接口,增加通信協議,意味着需要對業務代碼進行大量的重復構建。為了進一步滿足更多的場景,通信協議層被剝離了出來,做到和業務代碼分離,系統運行基於契約,實現通信協議的擴展。利用協議擴展機制,用戶解決了與老的gRPC框架、自定義二進制框架等很多遺留系統的通信問題。      

在ServiceComb框架中,切換協議非常簡單,不需要修改一行業務代碼。多個協議共存也是允許的。

 

cse:
  rest:
    address: 0.0.0.0:8084
  highway:
    address: 0.0.0.0:8094

關於協議擴展的更多內容,將在下面的章節進行介紹。

擴展性

擴展性是系統進一步發展的基石。ServiceComb創造性的將擴展性拓展到Provider和Consumer,讓它們擁有一致的開發體驗。這個在現有的各種開發框架里面是獨有的。

內部系統結構

連接開發者和通信協議層面已經讓系統具備了很大的擴展性。微服務化給系統解耦、團隊自治帶來了很大的靈活性,加快了生產效率;同時也帶來了服務管控的復雜性。需要用通用的管控機制來解決雪崩效應、調用跟蹤、性能監控與分析等問題。基於服務契約,ServiceComb提供了動態插拔擴展的處理鏈機制,並且為應對這些管控,提供了默認實現,開發者可以靈活插拔這些處理模塊,或者調整他們的順序以應對不同的處理場景,增加新的處理模塊等。不管Provider,還是Consumer,都會經過該處理鏈,這給客戶端治理功能開發帶來了非常大的便利,這是其他現有的微服務框架不具備的特性。ServiceComb的運行結構如下圖:

 

ServiceComb在用戶編程接口上,支持同步和異步兩種方式。在通信實現上,采用了純異步的方式,對於運行模型的擴展,也是基於異步回調接口的。這種方式提供了比同步模式(比如Filter)更加優雅和靈活的擴展方式。

在這個系統結構中,有幾個核心的接口,這些接口均在core模塊進行定義:

ProducerProvider:Provider編程模型的擴展,通過實現這個接口,可以適配不同的Provider編程風格;目前實現了RPC、SpringMVC、JAX-RS三種風格。

ConsumerProvider:Consumer編程模型的擴展,通過實現這個接口,可以適配不同的Consumer編程風格;目前實現了RPC、RestTemplate兩種風格。RestTemplate是Spring MVC提供了REST編程接口,可以在服務層方面解除接口依賴,只需要依賴數據模型。

Handler:處理鏈的接口。通過擴展該接口,可以在處理過程中插入任意的邏輯。目前實現了負載均衡、服務治理、流量控制等多個處理鏈。開發者可以針對Consumer和Provider定義不同的處理鏈,並且為訪問不同的微服務定制不同的處理鏈。

Transport:通信協議擴展。目前實現了REST over Vertx,Rest over Servlet,Highway等多個協議。

Invocation:中立的對象。所有的運行模型都面對這個中立的對象進行編程,當定義好服務接口后,對服務的治理和服務業務邏輯的開發可以並行。在編程模型和通信模型里面,也面對這個模型進行編解碼。

對接外部系統

運行框架涉及到的外部系統包括服務注冊發現的服務中心、配置管控和治理的配置中心、運行監控和運維的治理中心等。這些功能也預留了接口,能夠讓開發者靈活切換使用第三方提供的服務。 下圖是不同的開發框架支持和運行的第三方系統情況,這些基礎服務都給開發者預留了可以進行支持接入的接口。

 

下面是幾個重要的擴展:

ServiceRegistryClient: 實現這個接口以對接不同的注冊服務。

ConfigCenterConfigurationSource: 實現這個接口以對接不同的配置服務。

此外,ServiceComb還提供了對接Zipkin、Servo等開源系統的功能,這些可以從github官網代碼中查找到對應的例子。

運行環境集成

一個完整的業務系統不是使用RPC框架就算完成了,它們還需要其他的計算資源。對於一般的業務系統都需要訪問數據庫,或者基於J2EE的設施進行工作。ServiceComb可以以非常輕量級的方式運行,也可以集成到其他系統框架里面工作。下面的示意圖說明了ServiceComb的一些工作環境。

 

  1. 如果業務只需要提供REST接口,可以以輕量級的方式運行ServiceComb。所有的REST接口運行於ServiceComb提供的Netty HTTP之上。

  2. 如果業務是基於J2EE來構建,那么ServiceComb可以作為一個Servlet,運行於WEB容器里面(比如Tomcat、Jetty等)。

  3. 如果業務要基於Spring Boot生態構建,ServiceComb可以作為一個starter對外提供REST服務,開發者可以自由使用其他基於Spring Boot的功能。

由於ServiceComb使用了Spring,因此也能夠和很多通用的組件很好的集成,比如mybatis、JPA等。各種集成方式,都可以從ServiceComb官網或者CSE示例庫找到對應的例子。

總結

本文簡單的介紹了一下ServiceComb開發框架開發性設計的一些考慮。除了這些特性之外,ServicComb還提供了Edge Service網關服務、Saga分布式事務等特性。

參考文獻:

1. ServiceComb官網包括源碼:https://github.com/apache/incubator-servicecomb-java-chassis和文檔資料:http://servicecomb.incubator.apache.org/

2. 華為雲使用ServiceComb作為首選微服務開發框架,商用支持參考幫助網站:http://support.huaweicloud.com/cse_dld/index.html 和資料的Preview版本:https://java.huaweicse.com/

【版權聲明】本文為華為雲社區用戶原創內容,轉載時必須標注文章的來源(華為雲社區),文章鏈接,文章作者等基本信息,否則作者和本社區有權追究責任。如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: huaweicloud.bbs@huawei.com進行舉報,並提供相關證據,一經查實,本社區將立刻刪除涉嫌侵權內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM