1. 程式人生 > >【JMicro】微服務開發及使用

【JMicro】微服務開發及使用

JMicro是一個用Java語言實現的開源微服務全家桶,

原始碼地址:https://github.com/mynewworldyyl/jmicro,

Demo地址:http://124.70.152.7  。

摘要

假設你已經按照前面分享的文章下載JMicro原始碼並編譯成功。現在開始開發一個JMicro微服務,並通過Java客戶端及JS呼叫此微服務,Java支援同步和非同步呼叫,JS目前只支援非同步呼叫。

 

服務提供者和消費者模式

 

 

 上圖是最基本的服務提供者和消費者關係圖,服務方實現服務介面,消費方通過介面使用服務,面向介面程式設計,服務方與消費方沒有直接依賴。另一方面,JMicro為了給消費方提供非同步呼叫支援,在消費方與服務介面之間自動加入一個非同步介面,以提供非同步呼叫支援,如下圖:

 

 

 在ISimpleRpc$JMAsyncClient介面中,每個方法都與ISimpleRpc中的方法一一對應,ISimpleRpc$JMAsyncClient完全由JMicro程式碼生成工具自動生成,不需要服務實現方及訊息方做任何額外工作,非常便。非同步介面字尾$JMAsyncClient及非同步方法名字尾JMAsync是框架固定字尾,所以寫程式碼時儘量避免使用,以免混淆。

 

開發服務提供者

1)建立介面專案

因為ISimpleRpc介面同時被服務實現方及服務消費方依賴,所以需要單獨建立一個專案存放此介面

新建介面對應的Maven專案,並在POM加主如下依賴

 <dependency>
    <groupId>cn.jmicro</groupId>
    <artifactId>api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>    

新建介面類如下所示

package cn.jmicro.example.api.rpc;

import cn.jmicro.api.test.Person;
import cn.jmicro.codegenerator.AsyncClientProxy;

@AsyncClientProxy
public interface ISimpleRpc {
    
    String hello(String name);
    
    String hi(Person p);
    
    String linkRpc(String msg);
    
}

@AsyncClientProxy註解指示JMicro為此介面生成非同步介面,即ISimpleRpc$JMAsyncClient,在此專案根目錄執行mvn clean install,成功後將可在src/main/gen目錄下找到cn/jmicro/example/api/rpc/genclient/ISimpleRpc$JMAsyncClient.java介面,及此介面實現類cn/jmicro/example/api/rpc/genclient/SimpleRpc$JMAsyncClientImpl.java,此實現類只用於服務提供方,消費方用不到。

 

2)新建服務提供者專案

新建一個Maven專案,並在POM檔案加入如下依賴

<dependency>
    <groupId>cn.jmicro</groupId>
    <artifactId>all</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
        
<dependency>
       <groupId>cn.jmicro</groupId>
       <artifactId>example.api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

基中example.api即為上面建立的介面專案

新建服務介面實現類,如下:

  1 package cn.jmicro.example.rpc.impl;
  2 
  3 import java.util.Random;
  4 
  5 import cn.jmicro.api.JMicroContext;
  6 import cn.jmicro.api.annotation.Component;
  7 import cn.jmicro.api.annotation.Reference;
  8 import cn.jmicro.api.annotation.SBreakingRule;
  9 import cn.jmicro.api.annotation.SMethod;
 10 import cn.jmicro.api.annotation.Service;
 11 import cn.jmicro.api.async.IPromise;
 12 import cn.jmicro.api.monitor.MC;
 13 import cn.jmicro.api.monitor.SF;
 14 import cn.jmicro.api.service.IServiceAsyncResponse;
 15 import cn.jmicro.api.test.Person;
 16 import cn.jmicro.common.Constants;
 17 import cn.jmicro.example.api.rpc.ISimpleRpc;
 18 import cn.jmicro.example.api.rpc.genclient.IRpcA$JMAsyncClient;
 19 
 20 @Service(namespace="simpleRpc", version="0.0.1", monitorEnable=1, maxSpeed=-1,debugMode=1,
 21 baseTimeUnit=Constants.TIME_SECONDS, clientId=1000,external=true)
 22 @Component
 23 public class SimpleRpcImpl implements ISimpleRpc {
 24 
 25     @Reference(namespace="rpca", version="0.0.1")
 26     private IRpcA$JMAsyncClient rpca;
 27     
 28     private Random r = new Random(100);
 29     
 30     @Override
 31     @SMethod(
 32             //breakingRule="1S 50% 500MS",
 33             //1秒鐘內異常超50%,熔斷服務,熔斷後每80毫秒做一次測試
 34             breakingRule = @SBreakingRule(enable=true,percent=50,checkInterval=5000),
 35             logLevel=MC.LOG_DEBUG,    
 36             testingArgs="[\"test args\"]",//測試引數
 37             monitorEnable=1,
 38             timeWindow=5*60000,//統計時間視窗5分鐘
 39             slotInterval=100,
 40             checkInterval=5000,//取樣週期2S
 41             timeout=5000,
 42             retryInterval=1000,
 43             debugMode=1,
 44             maxSpeed=1000,
 45             baseTimeUnit=Constants.TIME_MILLISECONDS
 46     )
 47     public String hello(String name) {
 48         if(SF.isLoggable(MC.LOG_DEBUG)) {
 49             SF.eventLog(MC.MT_PLATFORM_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, name);
 50         }
 51         /*int rv = r.nextInt();
 52         if(rv < 50) {
 53             throw new CommonException("test breaker exception");
 54         }*/
 55         System.out.println("Server hello: " +name);
 56         return "Server say hello to: "+name;
 57     }
 58     
 59     @Override
 60     @SMethod(
 61             //breakingRule="1S 50% 500MS",
 62             //1秒鐘內異常超50%,熔斷服務,熔斷後每80毫秒做一次測試
 63             breakingRule = @SBreakingRule(enable=true,percent=50,checkInterval=5000),
 64             logLevel=MC.LOG_DEBUG,
 65             testingArgs="[{\"username\":\"Zhangsan\",\"id\":\"1\"}]",//測試引數
 66             monitorEnable=1,
 67             timeWindow=5*60000,//統計時間視窗5分鐘
 68             slotInterval=100,
 69             checkInterval=5000,//取樣週期2S
 70             timeout=5000,
 71             retryInterval=1000,
 72             debugMode=0,
 73             maxSpeed=1000,
 74             baseTimeUnit=Constants.TIME_MILLISECONDS
 75     )
 76     public String hi(Person person) {
 77         if(SF.isLoggable(MC.LOG_DEBUG)) {
 78             SF.eventLog(MC.MT_PLATFORM_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, person.getUsername());
 79         }
 80         return "Server say hello to: " + person.toString();
 81     }
 82 
 83     @Override
 84     public String linkRpc(String msg) {
 85         if(SF.isLoggable(MC.LOG_DEBUG)) {
 86             SF.eventLog(MC.MT_APP_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, "linkRpc call IRpcA with: " + msg);
 87         }
 88         
 89         System.out.println("linkRpc: " + msg);
 90         //return this.rpca.invokeRpcA(msg+" linkRpc => invokeRpcA");
 91         
 92         //IPromise<String> p = PromiseUtils.callService(this.rpca, "invokeRpcA","linkRpc call IRpcA with: " + msg);
 93         
 94         IPromise<String> p = this.rpca.invokeRpcAJMAsync("invokeRpcA");
 95         JMicroContext cxt = JMicroContext.get();
 96         if(cxt.isAsync()) {
 97             IServiceAsyncResponse cb = cxt.getParam(Constants.CONTEXT_SERVICE_RESPONSE,null);
 98             p.then((rst,fail) -> {
 99                 cb.result(rst);
100             });
101             return null;
102         } else {
103             return p.getResult();
104         }
105         
106     }   
109 }

此服務實現類包含微服務相關的很多細節,如限流,超時,重試,監聽,日誌,熔斷,非同步。 另外服務路由和負載均衡保持預設沒有額外配置。下面對每行程式碼做概要解釋

從第5到18行是jmicro框架相關類匯入,你可以看到,JMicro實現微服務核心應用沒有任何第三方依賴。

第20行Service註解提示此類是一個服務實現類,其屬性說明如下:

namespace和version分別是服務名稱空間和版本號,服務名稱不需要指定,固定是服務介面名全稱,3個值有值構成服務的唯一標識,使用服務時需要通過此3個值。

monitorable:此服務是否可監控,啟用監控後,可以在後臺管理頁面看到服務呼叫的實時細節,比如QPS,失敗數,成功數,超時數,及各指標占比等等;

maxSpeed:服務最大qps,用於服務限流;

debugMode:是否開啟除錯模式,在除錯模式下,RPC會輸出更多日誌,鋪助問題排查。

baseTimeUnit:此服務與時間相關的屬性的基本時間單位,可以是微秒,毫秒,秒,分,小時等;

external:是否可通過Api閘道器供外部呼叫,如果false則只能內部呼叫,外部不能訪問;

clientId:原本是用於服務隔離,但現在使用過程中覺得不盡如人間,所以後面大概率做調整或去除。

 

第21行Component註解告訴JMicro容器,這個類是一個元件,在JMicro容器啟動時自動做例項化,以供其他元件使用,功能和Spring的Component註解類似。

23行為類宣告,實現ISimpleRpc服務介面

25行和26行,Reference宣告26行這個成員變數是一個服務引用,引用服務名稱空間和版本分別為rpca及0.0.1,IRpcA$JMAsyncClient是另外一個服務介面的非同步介面引用,JMicro內部使用另外一個服務時,只要這兩行程式碼就能完成,相當方便。

第31到45行為服務方法註解,其很多屬性和Service註解相同,JMicro優先使用SMethod註解的屬性,如果SMethod屬性為-1,則使用Service註解的同名屬性,如果SMthod對應屬性值為0表示禁用,1表示啟用。

breakingRule : 服務方法的熔斷規則,enable=true表示啟用熔斷規則;percent=50表示服務失敗百分比超過50%時,開啟服務熔斷器,從而禁用服務;checkInterval=5000表示當服務熔斷後,間隔多長時間做一次自動呼叫服務,其單位由baseTimeUnit確定。業界大部份微服務實現中,都定義一個半熔斷狀態,在半熔斷狀態中,開啟部份RPC請求,以測試服務是否可以使用,這樣會導致部份請求失敗。在JMicro中,服務迷熔斷後,JMicro自動開啟服務呼叫,從而使熔斷器斷繼收到服務RPC成功率資料,當失敗率小於percent時,熔斷器自動關閉,服務進入正常使用狀態,這樣完全不影響正常服務使用。

logLevel:此方法日誌級別,debug,info,warn,error,nolog;

testArgs:熔斷器做自動測試時使用的引數,其值可以是JSON格式,也可以是JMIcro編碼密文,一般當需要通過Api閘道器呼叫時,需要人工檢視時使用JSON,如果只是內部服務呼叫,則使用JMicro編碼比較高率。

timeWindow:JMicro監控為每個服務方法啟動一個環形“旋轉時鐘”,這個環被分為N個Slot,每個Slot所佔時間長度為slotInterval,即時鐘的一個“滴答”,則timeWindow=slotInterval*N,在每個“滴答”時間片內產生的統計資料歸類到這個時間Slot裡,當資料所屬時間片超出timeWindow時,Slot所代表的資料超時丟棄。這樣在timeWindow內的統計資料就能夠”平滑“過度而不出現”跳躍“現像。原始碼實現核心類路徑為:https://github.com/mynewworldyyl/jmicro/blob/master/api/src/main/java/cn/jmicro/api/monitor/ServiceCounter.java

checkInterval: 當需要周其性地採集資料時,此值表示採集週期,如熔斷器多久採集一次成功或失敗率,限速器多久統計一次服務QPS等

timeout: 服務超時時間,消費者等待服務提供者返回結果等待時間,一般針對同步RPC呼叫,對非同步RPC呼叫無效,注意,JMicro中,同步和非同步是指客戶端呼叫RPC服務方法的方式,不針對服務方法,因為服務方法可以同時支援同步呼叫和非同步呼叫,沒有同步方法和非同步方法這一說。

retryInterval:發生超時時,間隔多久做一次重試;

retryCnt:發生超時間,需要連續重試多少次,但不包括第一次,如retryCnt=2,則最多重試兩次,加上第一次,最多呼叫3次;

其他和Service相同屬性不再重複

第48,49行向監控伺服器發一條日誌資訊,eventLog方法第1個引數表示事件型別,JMicro監控服務根據事件型別做統計和分發,

第76行即上一章測試使用的RPC方法,Person是一個自定義的實體類,其定義(省略getter setter等程式碼)如下所示

@SO
public final class Person {
    private String username ="Yeu";
    private int id = 222;
}

其被@SO註解,JMicro序列化工具會對全部類路徑下的@SO註解的類做序列化增強,以提高序列化類例項效能,這就是為什麼每個服務啟動時需要加-javaagent:/home/ubuntu0/jmicro/resp/jmicro-agent-0.0.1-SNAPSHOT.jar引數的原因。

具體方式是通過javassist注入encode和decode兩個方法,但是執行過程中發現javassist佔用大量記憶體,如下圖,按理說只是在JVM啟動時類載入過程中使用javassist,生成目標class後javassist應該釋放這些記憶體,但是現在即使JVM因記憶體不足而退出,這部份記憶體都沒能釋放!有知道原因的大神嗎?

 

JMIcro RPC方法支援的引數型別定義如下:

     a.Java支援的8種基本資料型別極其封裝型別;

     b.  String型別,Date型別,ByteBuffer型別

     c.  由a,b構成的陣列型別,及由a,b為元素構成的List,Set,Map型別

     d.  由a,b,c作為成員變數構成的自定義類型別,必須要SO註解;

     e.  由a,b,c,d作為元素構成的陣列,List,Set,Map型別;

 

第94到104行在一個服務方法裡面呼叫另一個RPC服務方法,並且根據上下配非同步配置情況使用同步和非同步呼叫。

到此簡單的服務提供者開發完成,對使用到的一些重點細節做簡單解釋。

 

部署服務提供者

編譯

 cd ${base_dir}/example/example.provider
 mvn clean install -Dmaven.test.skip=true

在${base_dir}\example\example.provider\target找到jmicro-example.provider-0.0.1-SNAPSHOT-jar-with-dependencies.jar檔案待用

 

上傳Jar包

以http://124.70.152.7 環境為例,在瀏覽器中開啟此頁面,右上角點LOGIN,輸入使用者名稱:jmicro,密碼:jmicro123 登陸系統,只有登陸後,才可以上傳Jar包及部署操作。

登陸後除了自己建立的資料外,請不要做刪除和停止類的操作,特別是配置資訊,以免影響系統正常執行。

選 擇選單 deployment -> resposotory  --> ADD,如下圖

 

彈出如下對話方塊,選擇要上傳的Jar檔案,一定要選對如圖路徑下的可執行Jar檔案

 

 輸入名稱,預設名稱和所選檔案同名。如果倉庫中已經存在同名檔案,上傳會失敗,所以一定要輸入一個不重名的名稱,下一步部署配置時需要用到此名稱。

按確定開始上傳,如下圖

 

 上傳成功後,在資源列表中檢視如下

配置部署描述

選擇deployment --> deploy desc -->  ADD ,如下圖所示

 

 

 

彈出對話方塊,輸入如下圖所示配置資訊,JAR檔案即為剛才上傳成功的jar包檔名,勾選Enable表示啟用此部署,最後加-Xmx32m -Xms8m限制一下記憶體,否則會因記憶體申請失敗而部署失敗。其他的選項先不用填或保持預設即可,如下圖

 

 

按確定後,等待10秒左右,如下圖開啟程序列表頁面,可以看到啟動了新的JVM程序

 

 服務呼叫

我們現在開始從Web前端呼叫這個服務方法,選擇Monitor --》 Service選單,如下圖

 

 

彈出側欄服務列表,如下圖,右上角選擇Refresh重新整理一下列表,剛剛部署的服務才顯示出來

 點選hi結點後,在編輯區開啟此方法的配置,如下圖。這些服務配置和上面原始碼中Service及SMethod註解的屬性基本上一一對應,並且可以動態修改,實時生效。

比如啟用和禁用監控,debugMode等。

 在開啟的服務方法配置頁面往下拖到最後,看到Testing選項卡,如下圖,在Testing Args框輸入 [{"username":"Zhangsan","id":"1"}],點選選項卡右上角Start按鈕,方法返回值輸出在Testing Result框中,如圖:

 

 可以按相同的操作步聚呼叫別的遠端方法,注意方法引數要輸正確,正確的引數請檢視上面介面定義,也可以通過Github檢視其他的介面原始碼。

 

通過Java API呼叫遠端服務

 以Maven為例,首先在POM檔案中引入如下依賴,example.api為服務介面所在專案

 <dependency>
         <groupId>cn.jmicro</groupId>
         <artifactId>gateway.client</artifactId>
        <version>0.0.1-SNAPSHOT</version>
  </dependency>
<dependency>
    <groupId>cn.jmicro</groupId>
    <artifactId>example.api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

 

寫Java main函式直接呼叫RPC源始同步方法

public static void main(String[] args) {
        ApiGatewayClient socketClient = new ApiGatewayClient(new ApiGatewayConfig(Constants.TYPE_HTTP,"124.70.152.7",80));
        ISimpleRpc srv = socketClient.getService(ISimpleRpc.class,"simpleRpc", "0.0.1");
        System.out.println(srv.hi(new Person()));
    }

 ApiGatewayConfig為Api閘道器客戶端配置類,支援3個引數,分別為連線型別,支援Http和Socket,另外兩個為Api閘道器IP和埠。ISimpleRpc為服務介面(介面,不是實現類),通過Api閘道器客戶端取得此遠端介面代理實現類,然後就可以像本地呼叫一樣呼叫遠端方法,完全不用關注HTTP協議細節,更不用關注Socket底層細節,完全面向Java介面及自定義引數的方法呼叫,當然,原生的Java 8種基本資料型別膠封裝型別也是無差別支援的。

 

非同步方式呼叫

 1 public static void main(String[] args) {
 2         ApiGatewayClient socketClient = new ApiGatewayClient(new ApiGatewayConfig(Constants.TYPE_HTTP,"124.70.152.7",80));
 6         ISimpleRpc$JMAsyncClient srv = socketClient.getService(ISimpleRpc$JMAsyncClient.class,"simpleRpc", "0.0.1");
 7         srv.hiJMAsync(new Person()).then((val,fail) -> {
 8             System.out.println("Hi: " +val);
 9             //System.out.println(fail);
10         });
11         
12         JMicro.waitForShutdown();
13     }

這次直接通過ISimpleRpc$JMAsyncClient.class呼叫getService方法獲取非同步代理例項。第7行呼叫非同步方法hiJMAsync並得到IPromise例項,呼叫IPromise的then方法,並傳入方法引用接收非同步結果。非同步響應式程式設計相關內容請自行網上檢視相關資源。

第12行讓執行緒等待,不然JVM在RPC結果沒返回前就退出了,正常的服務程序不需要這行程式碼。

 

JS前端呼叫

 在HTML頭部引入JS檔案

<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/ws.js"></script>
<script type="text/javascript" src="js/rpc.js"></script>

這幾個JS檔案可以在原始碼路徑下的mng.web/public/js下找到,需要在rpc.js檔案中修改API閘道器IP和埠,如下

 

JS呼叫ISimpleRpc的hi方法

function hi(person) {
    let req = {};
    req.serviceName ="cn.jmicro.example.api.rpc.ISimpleRpc"; //服務介面全名
    req.namespace = "simpleRpc";//服務名稱空間
    req.version = "0.0.1";//服務版本
    req.method = "hi"; //服務方法名稱
    req.args = [person]; //服務引數,一定要包裝在一個數組裡面
   //jm.mng.callRpc返回一個Promise例項,非同步返回結果 
  jm.mng.callRpc(req,jm.rpc.Constants.PROTOCOL_JSON, jm.rpc.Constants.PROTOCOL_JSON) .then((msg)=>{
      //非同步返回結果 console.log(msg); alert(msg); }).catch(err =>{ throw err; }); }

//呼叫hi方法
hi({"username":"Zhangsan","id":"1"});

通過上面程式碼可以看出,JS這種網格和Java非同步網格非常相像。

nodejs中可以使用同樣的程式碼實現呼叫JMicro服務的方法。

&n