對于這個話題,我相信大家很容易想到,HTTP 接口的方式可以解決。
大概的做法類似這樣:
根據(jù) HTTP 接口需要的參數(shù),將參數(shù)序列化成 json 字符串;
根據(jù)當(dāng)前項目開發(fā)語言,封裝一個處理 Post||Get 請求的方法,發(fā)送請求;
接口返回結(jié)果,然后反序列化成實體在邏輯代碼中使用;
可能在單個項目內(nèi)使用這樣的方式痛點并不是很明顯。但如果是在多個項目內(nèi),每個項目都依賴于這個接口,那為了獲取到數(shù)據(jù)可能會寫很多重復(fù)代碼。而且可能因為沒有統(tǒng)一的反序列實體來映射,會出現(xiàn)A項目對這個屬性名反序列化后叫西門慶,到了B項目同樣的屬性名就叫成了武大郎,這樣開發(fā)的同學(xué)也會覺得很不開心。
我們目前的情況是多個項目,而且項目也不是統(tǒng)一的語言。為了解決上面的問題,我們決定使用 gRPC 的方式來處理,基本需要滿足以下條件:
- 微服務(wù)化
- 跨語言
- 性能
- 調(diào)用者方便
因為我目前參與的項目基于 .NET 開發(fā),所以下面的一些例子可能會不自覺偏向 C#。
在使用 gRPC 之前,需要先了解一下 RPC、HTTP 接口
RPC
- RPC(Remote Procedure Call Protocol)就是從一臺機(jī)器(客戶端)上通過參數(shù)傳遞的方式調(diào)用另一臺機(jī)器(服務(wù)器)上的一個函數(shù)或方法并得到返回的結(jié)果,流程如下:

HTTP 接口
論復(fù)雜度,RPC 框架肯定是高于簡單的 HTTP 接口的;
HTTP 接口由于受限于 HTTP 協(xié)議,需要帶 HTTP 請求頭,導(dǎo)致傳輸起來效率不如 RPC;
RPC 是長鏈接,不必每次通信都要像 HTTP 一樣去握手。
gRPC
gRPC 是一個高性能 RPC 框架,和 HTTP 一樣都是一種對 RPC 的實現(xiàn),但性能相比之下更好;
使用 Protocol Buffers 來作為序列化和反序列化,以及接口定義語言,Protocol Buffers 已經(jīng)被證明是非常高效序列化框架;
跨語言,跨平臺,gRPC 支持多種平臺和多種語言;
基于 HTTP/2。
gRPC 服務(wù)創(chuàng)建大概流程如下:
1、創(chuàng)建 .proto 文件,定義通信的數(shù)據(jù)結(jié)構(gòu)和服務(wù)接口。文件內(nèi)包含方法、請求參數(shù)、返回結(jié)果等;
syntax = "proto3";
package MD.CacheService;
service MDCache {
// 獲取單賬號信息
rpc GetAccountInfo (AccountInfoRequest) returns (AccountInfoResponse) {}
}
// 獲取單賬號數(shù)據(jù)請求參數(shù)
message AccountInfoRequest {
// 賬號id
string accountId = 1;
}
// 獲取單個賬號數(shù)據(jù)返回值
message AccountInfoResponse {
// 賬號實體
AccountInfo accountInfo = 1;
}
// 賬號實體
message AccountInfo{
// 賬號id
string accountId = 1;
// 姓名
string fullname = 2;
// 頭像
string avatar = 3;
// 賬號創(chuàng)建時間,時間戳
int64 createTime = 4;
}
2、根據(jù) .proto 文件通過 Protocol Buffer 編譯器分別生成服務(wù)隊和客戶端的代碼(看官方的支持,可以選擇一種你最擅長的語言,服務(wù)端和客戶端完全不需要規(guī)定一樣的語言);
3、基于生成的代碼創(chuàng)建服務(wù)端和客戶端應(yīng)用;
4、在服務(wù)端創(chuàng)建一個類對服務(wù)接口進(jìn)行 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、啟動服務(wù)(服務(wù)安裝基于 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"];//服務(wù)IP
private readonly string port = ConfigurationManager.AppSettings["Port"];//服務(wù)端口
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 ,照著做也可以實現(xiàn)效果
部署:
- 基于.NET Core 的可以將服務(wù)部署在Linux上;
- 基于.NET Framework 的可以部署成 Windows 服務(wù),關(guān)于 Windows 服務(wù)方式,這里推薦使用Topshelf,非常簡單。
服務(wù)部署后涉及到負(fù)載和高可用的問題,單點故障是不能接收的。關(guān)于這部分的實現(xiàn)方式,相信熟悉 Nginx 的同學(xué)可以很快解決,當(dāng)然也可以使用其他方案。