場景:java和c#寫的服務、站點,互相任意調用。實現一切即服務。
解決方案:使用這種輕量級的esb架構,通過tcp通信解決通信傳輸問題,總線服務解決服務地址問題,契約解決數據交互問題。由於組件封裝了底層細節,比較方便好用,所以開發效率還是挺高的。
主組件:
Java: Ljc.JFramework.jar(jre1.8)
C#:Ljc.Framework.dll(.net4.5)
LJC.FrameWork.SOA.dll
依賴組件:無
使用方法:
前提:
需要一個總線服務。總線服務的作用就是接收服務注冊,並轉發請求,客戶端口不需要關心服務的地址。總線服務可以發布在任何地方,只需要ip端口能訪問。
ESBServer _esb = new ESBServer(20000); //總線使用的tcp端口
_esb.StartServer(); //啟動
開發跨語言服務
1、添加配置文件
2、設計服務契約
這個是服務請求和響應的需要的類,見后面契約部分。
3、編寫服務
增加一個服務,繼承類ESBService,並且增加一個構造函數,然后重寫方法DoResponse,獲取請求參數,響應服務調用。
Java的demo:
public final class SparkService extends ESBService {
public SparkService(int sNo, boolean supportTcpServiceRidrect) throws Exception {
super(sNo, supportTcpServiceRidrect);
// TODO Auto-generated constructor stub
}
@Override
public Object DoResponse(Tuple<Integer,byte[]> tup) throws java.lang.Exception{
int funid=tup.GetItem1();
byte[] buffer=tup.GetItem2();
switch(funid) {
case 1:
{
//解析出請求參數
SubmitSparkRequest request=EntityBufCore.DeSerialize(SubmitSparkRequest.class, buffer, true);
//使用請求處理業務
...
SubmitSparkResponse resp= new SubmitSparkResponse();
resp.setSuccess(true);
//返回結果
return resp;
}
}
return super.DoResponse(tup);
}
}
C#的demo:
class SparkTaskCallBackService:ESBService
{
public SparkTaskCallBackService()
: base(101)
{
}
public override object DoResponse(int funcId, byte[] Param)
{
switch(funcId){
case 1:
{
//這里獲取請求參數
var request = EntityBufCore.DeSerialize<CallBackRequest>(Param);
//響應
return new CallBackResponse
{
Success=true
};
}
}
return base.DoResponse(funcId, Param);
}
}
4、注冊服務到總線
//100是要注冊到總線的服務號
//由於只通過總線轉發,不接受客戶端直調,所以第二個參數為false,如果要接收客戶端直接調用,第二個參數為true,但需要把服務加到防火牆白名單,
//這種模式下,服務會隨機起一個端口,注冊時會告知總線該服務端口,但客戶端要通過DoSOARequest2調用,DoSOARequest2是DoSOARequest的增強版本,支持不通過總線自動直連服務
SparkService _sparkservice = new SparkService(100, false);
_sparkservice.StartService();
5、服務關閉后注銷服務
Java
try {
_sparkservice.UnRegisterService();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
_sparkservice.CloseClient();
C#
service.UnRegisterService();
service.Dispose();
客戶端調用服務
1、添加配置文件
2、添加或者引用目標服務提供的契約,具體看下面契約部分
3、調用方法
//僅通過總線調
響應契約=ESBClient.DoSOARequest(服務號,功能號,請求契約)
//如果能直調目標服務就直調,不支持就通過總線調
響應契約=ESBClient.DoSOARequest2(服務號,功能號,請求契約)
Java:
Date end=new Date();
CallBackRequest request=new CallBackRequest();
request.setSparkTaskId(sparktaskid);
request.setStartTime(start);
request.setEndTime(end);
request.setErrorOutPut(errinfo);
request.setStdOutPut(stdinfo);
request.setSuccess(errinfo=="");
CallBackResponse resp= Ljc.JFramework.SOA.ESBClient.DoSOARequest(CallBackResponse.class, Statics.CallBackServiceNo, Statics.Fun_CallBack, request);
C#:
var resp = ESBClient.DoSOARequest<SubmitSparkResponse>(SubmitContract.Consts.SparkServiceNo,
Consts.Fun_SubmitSpark, new SubmitSparkRequest()
{
Context = new SparkLauncherContext
{
AppResource = item.AppResource,
MainClass = item.MainClass,
Master = item.Master,
SparkHome = item.SparkHome,
},
TaskId = item._id.ToString()
});
配置文件
配置文件名稱:ESBConfig.xml
內容:
<?xml version="1.0" encoding="utf-16"?>
<ESBConfig>
<ESBServer>127.0.0.1</ESBServer>
<ESBPort>20000</ESBPort>
<AutoStart>true</AutoStart>
</ESBConfig>
字段 |
含義 |
說明 |
ESBServer |
總線地址 |
支持ipv4,和計算機名 |
ESBPort |
總線端口 |
|
AutoStart |
是否自動啟動 |
固定為true |
說明:java服務放到程序執行目錄下,net服務放到程序目錄下,net web放到網站根目錄下。
關於契約
Esb組件僅使用內置的序列化和反序列化機制,不依賴外部其它的如json/pb的序列化器工作。
契約包括請求契約和響應契約,調用方發送請求契約,服務方返回響應契約。
契約可以是簡單的數據類型如int,string等,也可以是復雜的結構化類型,建議不要用簡單的類型來作契約,后期擴展性差,且建議使用一對xxxRequest/xxxResponse規范命名。由於契約是要被序列化和反序列化的,類型有限制。簡單類型目前只支持以下類型:
Java類型 |
Net類型 |
|
java.lang.Short/short |
Short |
|
java.lang.Integer/int |
Int16/int |
|
不支持 |
UInt16 |
|
不支持 |
Ushort |
|
java.lang.Long/long |
Int32/long/Int64 |
|
java.lang.Byte/byte |
Byte |
|
char/java.lang.Character |
Char |
|
java.lang.Double/double |
Double |
|
java.lang.Float/float |
Float |
|
java.lang.String |
String |
|
java.util.Date |
DateTime |
|
Boolean/boolean |
bool |
|
java.util.HashMap |
Dictionary |
|
java.util.List |
List |
|
Array |
Array |
|
不支持 |
enum |
|
不支持 |
Decimal |
|
Complex(復雜對象) |
Complex(復雜對象) |
成員類型必須是上面支持的類型,或者也是Complex及Complext的數組,Complext的列表或字典 |
說明:跨越java和c#的契約字段類型按上面對照,list和array是兩種數據類型,不能對應。有些字段java不支持,如果c#服務要求的契約包含這種數據,java無法調成功。字段名稱不一樣,但字段類型要一致,順序也要對應,字段要寫成屬性,java用getxxx和setxxx,net寫成public 類型 xxx{get;set;}。
Java:
public class SubmitSparkRequest {
private SparkLauncherContext _context;
private String _taskId;
public SparkLauncherContext getContext()
{
return this._context;
}
public void setContext(SparkLauncherContext value) {
this._context=value;
}
public String getTaskId()
{
return this._taskId;
}
public void setTaskId(String value) {
this._taskId=value;
}
}
C#:
public class SubmitSparkRequest
{
public SparkLauncherContext Context
{
get;
set;
}
public String TaskId
{
get;
set;
}
}
跨語言情況下,服務端契約和客戶端契約應該單獨寫成文件最好不要混合其它無關的字段和方法,防止干擾出錯,下面列出服務端契約變動的影響:
情況 |
影響 |
必須同步契約 |
服務端改契約字段名或者類名 |
無影響 |
不需要 |
服務端改契約字段類型 |
調方失敗 |
需要 |
服務端在響應契約末尾增加字段 |
調方獲取不了新字段,舊的無影響 |
不一定需要 |
服務端刪除字段 |
調方失敗 |
需要 |
關於性能問題:
1、c#服務使用的是iocp技術,java使用的是iocp/epoll保證了服務端性能,服務端內部全異步操作,沒有等待線程,服務端與客戶端保持長連接並且自動斷線重連,定時心跳。簡單起見,一般情況下使用總線轉發就行了,粗略測試情況下吞吐量10000左右,java作總線性能高30%左右,如果要求較高,可以使用服務端直接調用方式,不通過總線轉發,且多個相同的服務可以實現簡單負載均衡(須部署在不同機器上),c#之間實現通過udp調用,吞吐量可達30000左右(傳輸速度1.5GB/s)。序列化為了使用方便采用的是內置自己實現的,盡量通明化和傻瓜化,性能上c#單線程序列化反序化十個字段小對象可達百萬每秒。目前網站http://106.14.193.150/blog/ 和http://106.14.193.150/cjzf 及其管理后台都是通過這種技術實現的,僅花錢購買一台低配置的阿里雲作前端服務器,調用后端十幾個輕重服務搭建而成的(部署在自有機器上)。
附件下載:
本文章轉自 http://106.14.193.150/blog/Detail.cshtml?id=59fab9622e0ee312904afcf8#_配置文件