当前位置: 首页 > 科技观察

基于 gRPC 和 .NET Core 的服务器流

时间:2023-03-14 21:27:59 科技观察

基于gRPC和.NETCore的服务器流式传输本文转载请联系精美码农公众号。早在2019年,我就写了《用 Mapbox 绘制位置数据》,详细说明了我如何通过一个简单的文件上传,使用Mapbox绘制出约230万个位置的地图。本文介绍了我如何使用gRPC和.NETCore的服务器流快速获取所有位置历史数据。https://chandankkrr.medium.com/mapping-location-data-with-mapbox-9b256f64d569什么是gRPCgRPC是一个可以在任何环境中运行的现代开源高性能RPC框架。它有效地连接数据中心内部和跨数据中心的服务,并为负载平衡、跟踪、健康检查和身份验证提供可插拔的支持。gRPC最初是由谷歌创建的,十多年来,谷歌一直在使用一个名为Stubby的通用RPC基础设施来连接在其数据中心内和跨数据中心运行的大量微服务。2015年3月,谷歌决定构建下一个版本的Stubby并将其开源。结果就是现在被许多企业或组织使用的gRPC。https://grpc.io/gRPCServerStreaming服务器流(ServerStreaming)RPC,客户端向服务器发送请求,获取一个流来读取一系列消息。客户端从返回的流中读取,直到没有更多消息为止。gRPC保证单个RPC调用中的信息排序。rpcGetLocationData(GetLocationRequest)返回(流GetLocationResponse);协议缓冲区(Protobuf)gRPC使用协议缓冲区(protocolbuffers)作为接口定义语言(IDL)来定义客户端和服务器之间的契约。在下面的proto文件中,定义了一个RPC方法GetLocations,接收GetLocationsRequest消息类型,返回GetLocationsResponse消息类型。响应消息类型前面的stream关??键字表示响应是流类型,而不是单个响应。syntax="proto3";optioncsharp_namespace="GPRCStreaming";packagelocation_data;serviceLocationData{rpcGetLocations(GetLocationsRequest)returns(streamGetLocationsResponse);}messageGetLocationsRequest{int32dataLimit=1;}messageGetLocationsResponse{int32latitudeE7=1;int32longitudeE7=2;}创建gRPC以我们服务dotnetnewgrpc-nthreemillion命令可轻松创建.NETgRPC服务。有关在ASP.NETCore中创建gRPC服务器和客户端的更多信息,请参阅Microsoft文档。在ASP.NETCore中创建agRPCclientandserverhttps://docs.microsoft.com/en-us/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-5.0&tabs=visual-studio-code添加proto文件和生成gRPC服务后的资源文件,接下来我添加了LocationService类。在下面的代码片段中,我有一个LocationService类,它继承自Location.proto文件生成的LocationDataBase类型。客户端可以通过Startup.cs文件中Configure方法中的endpoints.MapGrpcService()来访问LocationService。当服务端收到GetLocations请求时,首先通过GetLocationData方法调用读取Data文件夹中LocationHistory.json文件中的所有数据(不包含在源代码仓库中)。此方法返回一个RootLocation类型,该类型包含一个类型为List的Location属性。Location类包含两个内部属性Longitude和Latitude。接下来,我遍历每个位置并将它们写入responseStream返回给客户端。服务器将消息写入流,直到客户端在GetLocationRequest对象中指定的dataLimit。usingSystem.Threading.Tasks;usingGrpc.Core;usingMicrosoft.Extensions.Logging;usingSystem.IO;usingSystem;usingSystem.Linq;命名空间GPRCStreaminglogger){_fileReader=fileReader;_logger=logger;}publicoverrideasyncTaskGetLocations(GetLocationsRequestrequest,IServerStreamWriterresponseStream,ServerCallContextcontext){try{_logger.LogInformation("IncomingrequestforGetLocationData");varlocationData=awaitGetLocationData();varlocationDataCount=locationData.Locations.Count;vardataLimit=request.DataLimit>locationDataCount?locationDataCount:request.DataLimit;for(vari=0;i<=dataLimit-1;i++){varitem=locationData.Locations[i];awaitresponseStream.WriteAsync(newGetLocationsResponse{LatitudeE7=item.LatitudeE7,LongitudeE7=item.LongitudeE7});}}catch(Exceptionexception){_logger.LogError(exception,"Erroroccurred");抛出;}}privateasyncTaskGetLocationData(){varcurrentDirectory=Directory.GetCurrentDirectory();varfilePath=$"{currentDirectory}/Data/Location_History.json";varlocationData=await_fileReader.ReadAllLinesAsync(filePath);returnlocationData;}}}现在,让我们运行服务并发送请求我将使用名为grpcurl的命令行工具让您与gRPC服务器交互。它基本上是gRPC服务器的curl。https://github.com/fullstorydev/grpcurl通过grpcurl与gRPC端点交互仅在启用gRPC反射服务时可用。这允许查询服务以发现服务器上的gRPC服务。扩展方法MapGrpcReflectionService需要引入Microsoft.AspNetCore.Builder的namespace:publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv){app.UseEndpoints(endpoints=>{endpoints.MapGrpcService();if(env.IsDevelopment()){endpoints.MapGrpcReflectionService();}endpoints.MapGet("/",asynccontext=>{awaitcontext.Response.WriteAsync("CommunicationwithgRPCendpointsmustbemadethroughagRPCclient.Tolearnhowtocreateaclient,visit:https://go.microsoft.com/fwlink/?linkid=2086909");});});}grpcurl-plaintext-d'{"dataLimit":"100000"}'localhost:80location_data.LocationData/GetLocations一旦服务器收到请求,它就会读取文件并循环遍历位置列表,直到它达到dataLimit计数并向客户端返回位置数据。接下来,让我们创建一个Blazor客户端来调用gRPC服务。我们可以使用IServiceCollection接口上的AddGrpcClient扩展方法设置gRPC客户端:LocationDataClient>(client=>{client.Address=newUri("http://localhost:80");});}我使用VirtualizeBlazor组件来呈现这些位置。Virtualize组件不会一次渲染列表中的每个项目,只会渲染当前可见的项目。ASP.NETCoreBlazor组件虚拟化https://docs.microsoft.com/en-us/aspnet/core/blazor/components/virtualization?view=aspnetcore-5.0相关代码:@page"/locationdata"@usingGrpc.Core@usingGPRCStreaming@usingthreemillion.Data@usingSystem.Diagnostics@usingMicrosoft.AspNetCore.Components.Web.Virtualization@injectIJSRuntimeJSRuntime;@injectSystem.Net.Http.IHttpClientFactory_clientFactory@injectGPRCStreaming.LocationData.LocationDataClient_locationDataClientNoofrecordstofetchCallgRPC

总记录数:@_locations.Count</span>

耗时:@_stopWatch.ElapsedMilliseconds毫秒

经度纬度@locations.LongitudeE7@locations.LatitudeE7
@code{privateint_dataLimit=1000;privateList_locations=newList();privateStopwatch_stopWatch=newStopwatch();protectedoverrideasyncTaskOnInitializedAsync(){awaitFetchData();}privateasyncTaskFetchData(){ResetState();_stopWatch.Start();使用(varcall=_locationDataClient.GetLocations(newGetLocationsRequest{DataLimit=_dataLimit})){awaitforeach(varresponseincall.ResponseStream.ReadAllAsync()){_locations.Add(newLocation{LongitudeE7=response.LongitudeE7,LatitudeE7=response.LatitudeE7});StateHasChanged();}}_stopWatch();}privatevoidResetState(){_locations.Clear();_stopWatch.Reset();StateHasChanged();}}通过本地运行的流调用从gRPC服务器接收到2,876,679个单独的响应大约需要8秒让我们也在Mapbox中加载数据:@usingGrpc.Core@usingGPRCStreaming@usingSystem.Diagnostics@injectIJSRuntimeJSRuntime;@injectSystem.Net.Http.IHttpClientFactory_clientFactory@injectGPRCStreaming.LocationData.LocationDataClientLocationDataClientNoofrecordstofetch<按钮@onclick="LoadMap"class="btn-submit">加载数据
Totalrecords:@_locations.Count

耗时:@_stopWatch.ElapsedMilliseconds毫秒

/表格>
@code{privateint_dataLimit=100;privateList_locations=newList();privateStopwatch_stopWatch=newStopwatch();protectedoverrideasyncTaskOnAfterRenderAsync(boolfirstRender){if(!firstRender){return;}awaitJSRuntime.InvokeVoidAsync("mapBoxFunctions.initMapBox");}privateasyncTaskLoadMap(){ResetState();_stopWatch.Start();using(varcall=LocationDataClient.GetLocations(newGetLocationsRequest{DataLimit=_dataLimit})){awaitforeach(varresponseincall.ResponseStream.ReadAllAsync()){varpow=Math.Pow(10,7);varlongitude=response.LongitudeE7/pow;varlatitude=response.LatitudeE7/pow;_locations.Add(new{type="Feature",geometry=new{type="Point",coordinates=newdouble[]{longitude,latitude}}});StateHasChanged();}_stopWatch.Stop();awaitJSRuntime.InvokeVoidAsync("mapBoxFunctions.addClusterData",_locations);}}privatevoidResetState(){JSRuntime.InvokeVoidAsync("mapBoxFunctions.clearClusterData");_locations.Clear();_stopWatch.Reset();StateHasChanged();}}源代码在我的GitHub上:https://github.com/Chandankkrr/threemillion