微服務框架學習二:Http調用


1. HTTP接口的意義

二進制接口使用的是java/hessian序列化協議,不能很好的與其他語言通信,雖然hessian也是一種跨語言的通用協議,但很多語言沒有很好的實現該協議的產品。
所以為了能夠與其他語言進行服務通信,我們實現了http + json的協議實現,利用json原生的跨語言的特性。

 

2. 原理簡圖

描述:通過Netty暴露http服務端口,接收到http請求,通過HttpDecoder將其解析為HttpRequest,通過JSONDecoder提取service請求信息,生成Request請求對象,從而adapt到binary協議實現,交由request processor處理,返回Response結果,最終通過JSONEncoder編碼為JSON格式數據,再通過HttpEncoder生成HttpResponse返回給Http接口調用方。

 

3. 協議格式

假定:有服務暴露接口ExampleService,服務名稱:http://service.huifu.com/ExampleService/exampleService_1.0.0,定義了服務方法public String sayHello(String message),則HTTP訪問協議描述如下:

請求協議:

Name Value Description
URL http://{machine-ip}:{http-port}/rpc.json HTTP服務的訪問地址,注意http端口號為pegasus端口號+1000,即:pegasus配置為8888,http端口就是9888
Method POST 訪問方式,推薦使用POST方式
Encoding UTF-8 HTTP請求編碼
Params _service

http://service.huifu.com/ExampleService/exampleService_1.0.0

訪問的服務名稱
_method

sayHello

訪問的服務接口方法名稱
_param {"message" : "kitty"}

傳遞到接口方法的參數值,使用json格式,形如:{"param1" : "value1", "param2" : {"f1" : 1, "f2" : "vvv"}},其中param1,param2為方法的參數名稱,param1為string類型,param2為javabean類型,f1和f2為該javabean的屬性名。

如:調用方法為sayHello(String message),則對應json為{"message":"kitty"}。如果調用方法為:sayHello(RequestDTO userDTO),RequestDTO中有一個屬性為name,則對應的json為{"userDTO":{"name":"kitty"}}。

響應協議:

Name Value Description
格式 {"type" : "xxx", "result" : "xxx", "error" : "xxx"} HTTP服務接口的響應內容為JSON格式
type "service" | "service-exception" | "exception"

"service":業務處理正常返回
"service-exception":業務處理拋出異常
"exception":框架處理拋出異常 

result type = "service"時,服務方法的返回值,為JSON格式

若方法返回值為簡單類型:
"result" : "hello world"
若為復雜類型:
"result" : "{"f1" : "v1", "f2" : "v2"}" 

error

type = "service-exception" | "exception"時,返回的錯誤信息,為JSON格式

形如{"type" : "com.huifu.xx.BizException", "message" : "order_no is not supported"}
 
 
4. 注意事項
  1. 關於服務方法參數名
    默認情況下,pegasus通過asm讀取服務實現類的方法簽名信息,獲取方法的參數名,但因重構或其他原因,該方法的參數名有可能被修改,從而導致原有的http接口使用方無法正常調用;
    所以推薦在有可能提供http訪問的服務方法上通過@Param注解定義參數名,這樣就可以防止參數名被修改的情況,如void sayHello(@Param("message") String message),此時該方法就可以安全地重構為void sayHello(@Param("message") String msg),而不影響原有調用方;
    注意:在使用的過程中發現,通過javassist獲取參數名,如果使用javac編譯器編譯的class文件(maven默認使用),可能會出現無法讀取參數名或讀取錯誤; 我們遇到的幾個問題case改用eclipse的ecj編譯器后都可以fix,但我們無法去測試覆蓋所有的代碼case,因此若方法涉及到http調用時,請使用@Param的方式顯式提供參數名; 
    btw: maven使用ecj作為編譯器的詳細配置如下 (1.0.2中已經使用asm替換javassist,該問題已FIXED) 

  2. 關於服務傳輸對象(DTO)的定義
    由於使用json格式傳遞方法參數值,就牽扯到json ==> javabean的轉換過程,而這個過程要求能明確地知道所有java對象及其屬性的類型,所以在創建服務協議DTO對象時,如果這個對象牽扯到http方式的調用,那么就需要明確定義其類型,比如集合類型或數組,必須有明確的元素類型信息,所以Map, List, Set, Object[]都是無法轉換的,必須是Map<String, String>, List<Order>, Set<Integer>, Order[]等有明確的元素類型聲明的集合類和數組,即混合類型的集合http方式不支持;
    由於json對象無法表示對象引用,所以在定義DTO時,應避免引用的方式,比如parent下定義了list<child>,child中又定義一個parent引用剛才的parent對象,這種方式不允許。
    由於json對象的key一定是string類型,所以對於DTO中的Map,只能是Map<String, xx>, Map<Integer, xx>, Map<BigDecimal, xx>, Map<Boolean, xx>,Map<Date, xx>這樣的Map類型,而Map<JavaBean, xx>這種類型則無法通過http接口調用,因為json無法表示這樣的key。json轉map:{"k1", "v1"} => map {"k1", "v1"}。
    對於有Map<JavaBean, JavaBean>這種類型的dto,如果又需要通過http方式訪問,那么請重構該map類型為List方式,比如List<MapBean>, MapBean中有JavaBean的key和JavaBean類型的value屬性,即通過List<Entry>的方式實現Map,此時可以通過json表示,即[{"key1" : {xxx}, "value1" : {xxx}}, {"key2" : {xxx}, "value2" : {xxx}}]。
  3. 關於客戶端調用
    可以按照正常的http方式訪問服務接口,由於目前各語言還沒有自己封裝的客戶端,所以集群方面只能通過前端架設軟負載的方式實現,后期考慮各語言封裝自己的客戶端,內部維護到各服務提供者的http長連接,並定期地通過服務注冊中心的http接口更新服務提供者列表信息,搭配定期心跳探測移除不可用的連接,以便最終摒棄軟負載。
    通過http方式訪問服務接口時,需要在客戶端設置http請求的超時時間,以便在超過設定的超時時間后,客戶端會立即拋出超時異常,並中斷這次請求,否則會一直等待服務端返回,一旦服務端業務邏輯處理僵死,將無法返回http響應,並一直維持該http連接,造成資源無法釋放;
 


免責聲明!

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



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