.NET Core 使用 grpc 實現微服務

GRPC 是Google發布的一個開源、高性能、通用RPC(Remote Procedure Call)框架。提供跨語言、跨平臺支持。以下以一個.NET Core Console項目演示如何使用GRPC框架。

一、定義服務

通過proto定義一個數學計算服務,其中包括兩個服務方法(Add, Multipy)以及4個請求響應對象(AddRequest, AddReply, MultiplyRequest, MultiplyReply)。

// 文件名:mathservice.proto

syntax = "proto3";
option java_multiple_files = false;
option java_package = "MathServices";
option java_outer_classname = "MathServicesProto";
option objc_class_prefix = "MathServices";
package MathServices;

// 數學運算服務
service MathService 
{
  rpc Add (AddRequest) returns (AddReply) {}
  rpc Multiply (MultiplyRequest) returns (MultiplyReply) {}
}
message AddRequest {
  double First = 1;
  double Second = 2;
}
message AddReply {
  double Sum = 1;
}
message MultiplyRequest {
  double First = 1;
  double Second = 2;
}
message MultiplyReply {
  double Result = 1;
}

二、將服務編譯成存根(stub)

通過以下批處理命令generate_protos.bat將服務定義生成多種語言和平臺版本的客戶端和服務端存根。

@rem 生成客戶端和服務器端存根

setlocal

@rem 進入當前目錄
cd /d %~dp0

set TOOLS_PATH=C:\Users\Freeman\.nuget\packages\Grpc.Tools\1.0.0\tools\windows_x86

%TOOLS_PATH%\protoc.exe ^
--proto_path protos ^
--cpp_out=Interfaces/cpp ^
--csharp_out=Interfaces/csharp ^
--java_out=Interfaces/java ^
--js_out=Interfaces/javascript ^
--grpc_out=Interfaces/csharp ^
--plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe ^
protos/mathservice.proto

endlocal
timeout 5

針對CSHARP語言,protoc.exe編譯器生成了如下圖幾個類,其中左邊4個類用于構造請求和響應對象,MathService類用于下一步構造服務和消費服務。


CSHARP STUBS

三、實現并運行服務

通過上一步的編譯,自動生成了MathService類,下面通過該類構造并啟動grpc服務。

通過繼承基類實現服務接口

    /// <summary>
    /// 實現RPC服務端接口。
    /// </summary>
    public class MathServiceImpl : MathService.MathServiceBase
    {
        public override Task<AddReply> Add(AddRequest request, ServerCallContext context)
        {
            return Task.FromResult(new AddReply { Sum = request.First + request.Second });
        }

        public override Task<MultiplyReply> Multiply(MultiplyRequest request, ServerCallContext context)
        {
            return Task.FromResult(new MultiplyReply { Result = request.First * request.Second });
        }
    }

啟動服務

const string ip = "0.0.0.0";
const int port = 50051;
Server server = new Server();
server.Ports.Add(new ServerPort(ip, port, ServerCredentials.Insecure));
server.Services.Add(MathService.BindService(new MathServiceImpl()));
server.Start();
server.Ports.ToList().ForEach(a => Console.WriteLine($"Server listening on port {a.Port}..."));
Console.ReadLine();

四、客戶端調用服務

客戶端通過創建一個Channel和一個服務客戶端來使用服務。

var channel = new Channel($"{"127.0.0.1"}:{port}", SslCredentials.Insecure);
var client = new MathService.MathServiceClient(channel);
var random = new Random();

while (true)
{
    var first = random.NextDouble();
    var second = random.NextDouble();
    var reply = client.Add(new AddRequest { First = first, Second = second });
    Console.WriteLine($"RPC call Add service: {first:F4} + {second:F4} = {reply.Sum:F4}");
    Thread.Sleep(500);
} 
RPC調用

五、使用SSL實現加密通訊

grpc默認實現了基于證書的SSL加密通訊,使用中需要注意以下事項。

  • 在Windows上開發請安裝 OpenSSL對應版本并將openssl.exe所在路徑添加到環境變量中。

  • 通過以下樣例腳本生成通訊中所需要的服務端和客戶端證書,其中需要特別注意的是,Generate server signing request:中的CN=KEKYK字段如果是本機測試,請一定使用本機名稱,如果是真實環境請使用域名,因為客戶端必須通過機器名(本地測試)或域名訪問該服務。如果此處CN字段不使用機器名或域名,將導致以下錯誤:


    CN字段不使用主機名或域名時產生的錯誤
  • 生成服務端和客戶端證書腳本generate_ssl.bat

@echo off
set OPENSSL_CONF=c:\OpenSSL-Win64\bin\openssl.cfg   

echo Generate CA key:
openssl genrsa -passout pass:1111 -des3 -out ca.key 4096

echo Generate CA certificate:
openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=MyRootCA"

echo Generate server key:
openssl genrsa -passout pass:1111 -des3 -out server.key 4096

echo Generate server signing request:
openssl req -passin pass:1111 -new -key server.key -out server.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=kekyk"

echo Self-sign server certificate:
openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

echo Remove passphrase from server key:
openssl rsa -passin pass:1111 -in server.key -out server.key

echo Generate client key
openssl genrsa -passout pass:1111 -des3 -out client.key 4096

echo Generate client signing request:
openssl req -passin pass:1111 -new -key client.key -out client.csr -subj  "/C=US/ST=CA/L=Cupertino/O=YourCompany/OU=YourApp/CN=client"

echo Self-sign client certificate:
openssl x509 -passin pass:1111 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

echo Remove passphrase from client key:
openssl rsa -passin pass:1111 -in client.key -out client.key
pause
  • 基于SSL的服務端啟動如下,創建服務的時候請使用主機名(開發環境)或域名(生產環境),不要使用IP地址。
 public static void RpcServerSsl()
{
    var cacert = File.ReadAllText(CombinePath("ca.crt"));
    var servercert = File.ReadAllText(CombinePath("server.crt"));
    var serverkey = File.ReadAllText(CombinePath("server.key"));
    var keypair = new KeyCertificatePair(servercert, serverkey);
    var sslCredentials = new SslServerCredentials(new List<KeyCertificatePair>() { keypair }, cacert, false);

    var server = new Server
    {
        Services = { MathService.BindService(new MathServiceImpl()) },
        Ports = { new ServerPort("KEKYK", sslPort, sslCredentials) }
    };
    server.Start();
    server.Ports.ToList().ForEach(a => Console.WriteLine($"Server (SSL) listening on port {a.Port}..."));
    Console.ReadLine();
}
  • 基于SSL的客戶端使用如下,注意測試環境中使用主機名,生產環境中使用域名來,不要使用任何形式的IP地址。
public static void RpcClientSsl()
{
    var cacert = File.ReadAllText(CombinePath("ca.crt"));
    var clientcert = File.ReadAllText(CombinePath("client.crt"));
    var clientkey = File.ReadAllText(CombinePath("client.key"));
    var ssl = new SslCredentials(cacert, new KeyCertificatePair(clientcert, clientkey));
    var channel = new Channel("KEKYK", sslPort, ssl);
    var client = new MathService.MathServiceClient(channel);

    var random = new Random();
    while (true)
    {
        var first = random.NextDouble();
        var second = random.NextDouble();

        var reply = client.AddAsync(new AddRequest { First = first, Second = second }, new CallOptions()).ResponseAsync.Result;
        Console.WriteLine($"RPC call Add service: {first:F4} + {second:F4} = {reply.Sum:F4}");
        Thread.Sleep(1000);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容