對于這個話題柠逞,我相信大家很容易想到昧狮,HTTP 接口的方式可以解決。
大概的做法類似這樣:
根據 HTTP 接口需要的參數板壮,將參數序列化成 json 字符串逗鸣;
根據當前項目開發(fā)語言,封裝一個處理 Post||Get 請求的方法绰精,發(fā)送請求撒璧;
接口返回結果,然后反序列化成實體在邏輯代碼中使用笨使;
可能在單個項目內使用這樣的方式痛點并不是很明顯。但如果是在多個項目內贡珊,每個項目都依賴于這個接口烤送,那為了獲取到數據可能會寫很多重復代碼妻往。而且可能因為沒有統(tǒng)一的反序列實體來映射,會出現A項目對這個屬性名反序列化后叫西門慶,到了B項目同樣的屬性名就叫成了武大郎拳锚,這樣開發(fā)的同學也會覺得很不開心拌蜘。
我們目前的情況是多個項目,而且項目也不是統(tǒng)一的語言。為了解決上面的問題,我們決定使用 gRPC 的方式來處理,基本需要滿足以下條件:
- 微服務化
- 跨語言
- 性能
- 調用者方便
因為我目前參與的項目基于 .NET 開發(fā),所以下面的一些例子可能會不自覺偏向 C#。
在使用 gRPC 之前,需要先了解一下 RPC、HTTP 接口
RPC
- RPC(Remote Procedure Call Protocol)就是從一臺機器(客戶端)上通過參數傳遞的方式調用另一臺機器(服務器)上的一個函數或方法并得到返回的結果随闽,流程如下:
HTTP 接口
論復雜度父丰,RPC 框架肯定是高于簡單的 HTTP 接口的;
HTTP 接口由于受限于 HTTP 協議橱脸,需要帶 HTTP 請求頭础米,導致傳輸起來效率不如 RPC;
RPC 是長鏈接添诉,不必每次通信都要像 HTTP 一樣去握手。
gRPC
gRPC 是一個高性能 RPC 框架医寿,和 HTTP 一樣都是一種對 RPC 的實現栏赴,但性能相比之下更好;
使用 Protocol Buffers 來作為序列化和反序列化靖秩,以及接口定義語言须眷,Protocol Buffers 已經被證明是非常高效序列化框架;
跨語言沟突,跨平臺花颗,gRPC 支持多種平臺和多種語言;
基于 HTTP/2惠拭。
gRPC 服務創(chuàng)建大概流程如下:
1娃循、創(chuàng)建 .proto 文件沃呢,定義通信的數據結構和服務接口。文件內包含方法、請求參數敲霍、返回結果等;
syntax = "proto3";
package MD.CacheService;
service MDCache {
// 獲取單賬號信息
rpc GetAccountInfo (AccountInfoRequest) returns (AccountInfoResponse) {}
}
// 獲取單賬號數據請求參數
message AccountInfoRequest {
// 賬號id
string accountId = 1;
}
// 獲取單個賬號數據返回值
message AccountInfoResponse {
// 賬號實體
AccountInfo accountInfo = 1;
}
// 賬號實體
message AccountInfo{
// 賬號id
string accountId = 1;
// 姓名
string fullname = 2;
// 頭像
string avatar = 3;
// 賬號創(chuàng)建時間巴帮,時間戳
int64 createTime = 4;
}
2晶密、根據 .proto 文件通過 Protocol Buffer 編譯器分別生成服務隊和客戶端的代碼(看官方的支持,可以選擇一種你最擅長的語言秀鞭,服務端和客戶端完全不需要規(guī)定一樣的語言)趋观;
3扛禽、基于生成的代碼創(chuàng)建服務端和客戶端應用;
4皱坛、在服務端創(chuàng)建一個類對服務接口進行 override旋圆;
public class CacheServiceImpl : MDCache.MDCacheBase
{
/// <summary>
/// 獲取單個賬號accountInfo
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<AccountInfoResponse> GetAccountInfo(AccountInfoRequest request, ServerCallContext context)
{
var response = new AccountInfoResponse();
if (!string.IsNullOrEmpty(request.AccountId))
{
response.AccountInfo = GetServiceAccountInfo(request.AccountId);
}
return Task.FromResult(response);
}
}
5、啟動服務(服務安裝基于 Topshelf)麸恍;
class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.Service<CacheService>(s =>
{
s.ConstructUsing(name => new CacheService());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDisplayName("MD.CacheService");
x.SetServiceName("MD.CacheService");
});
}
}
public class CacheService
{
private readonly string host = ConfigurationManager.AppSettings["Host"];//服務IP
private readonly string port = ConfigurationManager.AppSettings["Port"];//服務端口
readonly Server server;
public CacheService()
{
server = new Server
{
Services = { MDCache.BindService(new CacheServiceImpl()) },
Ports = {
new ServerPort(host, Convert.ToInt32(port), ServerCredentials.Insecure)
}
};
}
public void Start() { server.Start(); }
public void Stop() { server.ShutdownAsync(); }
}
6灵巧、客戶端連接測試
var channel = new Channel("host:port", ChannelCredentials.Insecure);
var client = new MDCacheClient(channel);
C#官方 example ,照著做也可以實現效果
部署:
- 基于.NET Core 的可以將服務部署在Linux上抹沪;
- 基于.NET Framework 的可以部署成 Windows 服務刻肄,關于 Windows 服務方式,這里推薦使用Topshelf融欧,非常簡單敏弃。
服務部署后涉及到負載和高可用的問題,單點故障是不能接收的噪馏。關于這部分的實現方式麦到,相信熟悉 Nginx 的同學可以很快解決,當然也可以使用其他方案欠肾。