【RabbitMQ】RPC模式(请求/回复)
<h2>本章目标</h2><ul>
<li>
<p class="ds-markdown-paragraph">理解RabbitMQ RPC模式的工作原理和适用场景。</p>
</li>
<li>
<p class="ds-markdown-paragraph">掌握回调队列(Callback Queue)和关联ID(Correlation Id)的使用。</p>
</li>
<li>
<p class="ds-markdown-paragraph">实现基于RabbitMQ的异步RPC调用。</p>
</li>
<li>
<p class="ds-markdown-paragraph">学习RPC模式下的错误处理和超时机制。</p>
</li>
<li>
<p class="ds-markdown-paragraph">构建完整的微服务间同步通信解决方案。</p>
</li>
</ul>
<hr>
<h2>一、理论部分</h2>
<h3>1. RPC模式简介</h3>
<p class="ds-markdown-paragraph">RPC(Remote Procedure Call)模式允许客户端应用程序调用远程服务器上的方法,就像调用本地方法一样。在RabbitMQ中,RPC是通过消息队列实现的异步RPC。</p>
<p class="ds-markdown-paragraph">与传统HTTP RPC的区别:</p>
<ul>
<li>
<p class="ds-markdown-paragraph">HTTP RPC:同步,直接连接,需要服务端在线</p>
</li>
<li>
<p class="ds-markdown-paragraph">消息队列RPC:异步,通过消息代理,支持解耦和负载均衡</p>
</li>
</ul>
<h3>2. RabbitMQ RPC核心组件</h3>
<ol start="1">
<li>
<p class="ds-markdown-paragraph">请求队列(Request Queue):客户端发送请求的队列</p>
</li>
<li>
<p class="ds-markdown-paragraph">回复队列(Reply Queue):服务器返回响应的队列</p>
</li>
<li>
<p class="ds-markdown-paragraph">关联ID(Correlation Id):匹配请求和响应的唯一标识</p>
</li>
<li>
<p class="ds-markdown-paragraph">消息属性:使用<code>IBasicProperties.ReplyTo</code>和<code>IBasicProperties.CorrelationId</code></p>
</li>
</ol>
<h3>3. RPC工作流程</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">Client端:
</span>1.<span style="color: rgba(0, 0, 0, 1)"> 生成唯一CorrelationId
</span>2.<span style="color: rgba(0, 0, 0, 1)"> 创建临时回复队列
</span>3.<span style="color: rgba(0, 0, 0, 1)"> 发送请求到请求队列,设置ReplyTo和CorrelationId
</span>4.<span style="color: rgba(0, 0, 0, 1)"> 监听回复队列,等待匹配的CorrelationId
Server端:
</span>1.<span style="color: rgba(0, 0, 0, 1)"> 监听请求队列
</span>2.<span style="color: rgba(0, 0, 0, 1)"> 处理请求
</span>3.<span style="color: rgba(0, 0, 0, 1)"> 将响应发送到请求中的ReplyTo队列
</span>4.<span style="color: rgba(0, 0, 0, 1)"> 设置相同的CorrelationId
Client端:
</span>5.<span style="color: rgba(0, 0, 0, 1)"> 收到响应,根据CorrelationId匹配请求
</span>6. 处理响应</pre>
</div>
<h3>4. 适用场景</h3>
<ul>
<li>
<p class="ds-markdown-paragraph">需要同步响应的异步操作</p>
</li>
<li>
<p class="ds-markdown-paragraph">微服务间的同步通信</p>
</li>
<li>
<p class="ds-markdown-paragraph">计算密集型任务的分布式处理</p>
</li>
<li>
<p class="ds-markdown-paragraph">需要负载均衡的同步调用</p>
</li>
</ul>
<hr>
<h2>二、实操部分:构建分布式计算服务</h2>
<p class="ds-markdown-paragraph">我们将创建一个分布式斐波那契数列计算服务,演示完整的RPC模式实现。</p>
<h3>第1步:创建项目结构</h3>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 创建解决方案
dotnet new sln </span>-<span style="color: rgba(0, 0, 0, 1)">n RpcSystem
# 创建项目
dotnet new webapi </span>-<span style="color: rgba(0, 0, 0, 1)">n RpcClient.API
dotnet new classlib </span>-<span style="color: rgba(0, 0, 0, 1)">n RpcClient.Core
dotnet new classlib </span>-<span style="color: rgba(0, 0, 0, 1)">n RpcServer.Service
dotnet new classlib </span>-<span style="color: rgba(0, 0, 0, 1)">n RpcShared
# 添加到解决方案
dotnet sln add RpcClient.API</span>/<span style="color: rgba(0, 0, 0, 1)">RpcClient.API.csproj
dotnet sln add RpcClient.Core</span>/<span style="color: rgba(0, 0, 0, 1)">RpcClient.Core.csproj
dotnet sln add RpcServer.Service</span>/<span style="color: rgba(0, 0, 0, 1)">RpcServer.Service.csproj
dotnet sln add RpcShared</span>/<span style="color: rgba(0, 0, 0, 1)">RpcShared.csproj
# 添加项目引用
dotnet add RpcClient.API reference RpcClient.Core
dotnet add RpcClient.API reference RpcShared
dotnet add RpcClient.Core reference RpcShared
dotnet add RpcServer.Service reference RpcShared
# 添加NuGet包
cd RpcClient.API
dotnet add package RabbitMQ.Client
cd ..</span>/<span style="color: rgba(0, 0, 0, 1)">RpcClient.Core
dotnet add package RabbitMQ.Client
cd ..</span>/<span style="color: rgba(0, 0, 0, 1)">RpcServer.Service
dotnet add package RabbitMQ.Client</span></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<h3>第2步:定义共享模型(RpcShared)</h3>
<p class="ds-markdown-paragraph">Models/RpcRequest.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_77709edd-5bf9-4570-9206-07a0dddd9b5c" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_77709edd-5bf9-4570-9206-07a0dddd9b5c" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_77709edd-5bf9-4570-9206-07a0dddd9b5c" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text.Json.Serialization;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> RpcRequest
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> RequestId { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } =<span style="color: rgba(0, 0, 0, 1)"> Guid.NewGuid().ToString();
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Method { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, <span style="color: rgba(0, 0, 255, 1)">object</span>> Parameters { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DateTime Timestamp { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } =<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> RpcRequest WithParameter(<span style="color: rgba(0, 0, 255, 1)">string</span> key, <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> value)
{
Parameters </span>=<span style="color: rgba(0, 0, 0, 1)"> value;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> T? GetParameter<T>(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> key)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (Parameters.TryGetValue(key, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> value))
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> (T)Convert.ChangeType(value, <span style="color: rgba(0, 0, 255, 1)">typeof</span><span style="color: rgba(0, 0, 0, 1)">(T));
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Models/RpcResponse.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_19c44d6f-3a19-4851-b55a-04994a79eb1b" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_19c44d6f-3a19-4851-b55a-04994a79eb1b" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_19c44d6f-3a19-4851-b55a-04994a79eb1b" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text.Json.Serialization;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> RpcResponse
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> RequestId { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> Success { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">object</span>? Data { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Error { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DateTime Timestamp { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } =<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> ProcessingTimeMs { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> RpcResponse SuccessResponse(<span style="color: rgba(0, 0, 255, 1)">string</span> requestId, <span style="color: rgba(0, 0, 255, 1)">object</span> data, <span style="color: rgba(0, 0, 255, 1)">long</span> processingTimeMs = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RpcResponse
{
RequestId </span>=<span style="color: rgba(0, 0, 0, 1)"> requestId,
Success </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
Data </span>=<span style="color: rgba(0, 0, 0, 1)"> data,
ProcessingTimeMs </span>=<span style="color: rgba(0, 0, 0, 1)"> processingTimeMs
};
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> RpcResponse ErrorResponse(<span style="color: rgba(0, 0, 255, 1)">string</span> requestId, <span style="color: rgba(0, 0, 255, 1)">string</span> error, <span style="color: rgba(0, 0, 255, 1)">long</span> processingTimeMs = <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RpcResponse
{
RequestId </span>=<span style="color: rgba(0, 0, 0, 1)"> requestId,
Success </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
Error </span>=<span style="color: rgba(0, 0, 0, 1)"> error,
ProcessingTimeMs </span>=<span style="color: rgba(0, 0, 0, 1)"> processingTimeMs
};
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> T? GetData<T><span style="color: rgba(0, 0, 0, 1)">()
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (Data <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> JsonElement jsonElement)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> jsonElement.Deserialize<T><span style="color: rgba(0, 0, 0, 1)">();
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Data <span style="color: rgba(0, 0, 255, 1)">is</span> T typedData ? typedData : <span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Messages/FibonacciRequest.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_71fd4a21-0c1a-4670-b7ee-7a6c52775d76" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_71fd4a21-0c1a-4670-b7ee-7a6c52775d76" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_71fd4a21-0c1a-4670-b7ee-7a6c52775d76" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciRequest
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Number { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> UseOptimizedAlgorithm { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciResponse
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> Result { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">long</span> CalculationTimeMs { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> InputNumber { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<h3>第3步:RPC客户端核心库(RpcClient.Core)</h3>
<p class="ds-markdown-paragraph">Services/IRpcClient.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_3d4563a6-460d-4125-8248-07ff2013cc7e" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_3d4563a6-460d-4125-8248-07ff2013cc7e" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_3d4563a6-460d-4125-8248-07ff2013cc7e" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IRpcClient : IDisposable
{
Task</span><RpcResponse><span style="color: rgba(0, 0, 0, 1)"> CallAsync(RpcRequest request, TimeSpan timeout);
Task</span><TResponse?> CallAsync<TResponse>(RpcRequest request, TimeSpan timeout) <span style="color: rgba(0, 0, 255, 1)">where</span> TResponse : <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Services/RpcClient.cs</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Collections.Concurrent;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text.Json;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.Logging;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client.Events;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient : IRpcClient
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IConnection _connection;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IModel _channel;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<RpcClient><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> _replyQueueName;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, TaskCompletionSource<RpcResponse>><span style="color: rgba(0, 0, 0, 1)"> _pendingRequests;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> AsyncEventingBasicConsumer _consumer;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> _disposed = <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient(
IConnectionFactory connectionFactory,
ILogger</span><RpcClient><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
_pendingRequests </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> ConcurrentDictionary<<span style="color: rgba(0, 0, 255, 1)">string</span>, TaskCompletionSource<RpcResponse>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 建立连接和通道</span>
_connection =<span style="color: rgba(0, 0, 0, 1)"> connectionFactory.CreateConnection();
_channel </span>=<span style="color: rgba(0, 0, 0, 1)"> _connection.CreateModel();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 声明临时回复队列(排他性,连接关闭时自动删除)</span>
_replyQueueName =<span style="color: rgba(0, 0, 0, 1)"> _channel.QueueDeclare(
queue: </span><span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">,
durable: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
exclusive: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
autoDelete: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
arguments: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">).QueueName;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建消费者监听回复队列</span>
_consumer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AsyncEventingBasicConsumer(_channel);
_consumer.Received </span>+=<span style="color: rgba(0, 0, 0, 1)"> OnResponseReceived;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 开始消费回复队列</span>
<span style="color: rgba(0, 0, 0, 1)"> _channel.BasicConsume(
queue: _replyQueueName,
autoAck: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
consumer: _consumer);
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC Client initialized with reply queue: {ReplyQueue}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, _replyQueueName);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<RpcResponse><span style="color: rgba(0, 0, 0, 1)"> CallAsync(RpcRequest request, TimeSpan timeout)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (_disposed)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ObjectDisposedException(nameof(RpcClient));
</span><span style="color: rgba(0, 0, 255, 1)">var</span> tcs = <span style="color: rgba(0, 0, 255, 1)">new</span> TaskCompletionSource<RpcResponse><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> cancellationTokenSource = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CancellationTokenSource(timeout);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 注册超时取消</span>
cancellationTokenSource.Token.Register(() =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (_pendingRequests.TryRemove(request.RequestId, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> removedTcs))
{
removedTcs.TrySetException(</span><span style="color: rgba(0, 0, 255, 1)">new</span> TimeoutException($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC call timed out after {timeout.TotalSeconds} seconds</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC request {RequestId} timed out</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, request.RequestId);
}
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将请求添加到待处理字典</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">_pendingRequests.TryAdd(request.RequestId, tcs))
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> InvalidOperationException($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Request with ID {request.RequestId} is already pending</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 序列化请求</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> requestJson =<span style="color: rgba(0, 0, 0, 1)"> JsonSerializer.Serialize(request);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> requestBody =<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetBytes(requestJson);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置消息属性</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> properties =<span style="color: rgba(0, 0, 0, 1)"> _channel.CreateBasicProperties();
properties.ReplyTo </span>=<span style="color: rgba(0, 0, 0, 1)"> _replyQueueName;
properties.CorrelationId </span>=<span style="color: rgba(0, 0, 0, 1)"> request.RequestId;
properties.Persistent </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Sending RPC request {RequestId} to queue: rpc_queue</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, request.RequestId);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发布请求到RPC队列</span>
<span style="color: rgba(0, 0, 0, 1)"> _channel.BasicPublish(
exchange: </span><span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">,
routingKey: </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">rpc_queue</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
basicProperties: properties,
body: requestBody);
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC request {RequestId} sent successfully</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, request.RequestId);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 等待响应</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> tcs.Task;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发生异常时移除待处理请求</span>
_pendingRequests.TryRemove(request.RequestId, <span style="color: rgba(0, 0, 255, 1)">out</span><span style="color: rgba(0, 0, 0, 1)"> _);
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error sending RPC request {RequestId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, request.RequestId);
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<TResponse?> CallAsync<TResponse>(RpcRequest request, TimeSpan timeout) <span style="color: rgba(0, 0, 255, 1)">where</span> TResponse : <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> CallAsync(request, timeout);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">response.Success)
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> InvalidOperationException($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC call failed: {response.Error}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> response.GetData<TResponse><span style="color: rgba(0, 0, 0, 1)">();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task OnResponseReceived(<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)"> sender, BasicDeliverEventArgs ea)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> responseBody =<span style="color: rgba(0, 0, 0, 1)"> ea.Body.ToArray();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> responseJson =<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetString(responseBody);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> correlationId =<span style="color: rgba(0, 0, 0, 1)"> ea.BasicProperties.CorrelationId;
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received RPC response for correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = JsonSerializer.Deserialize<RpcResponse><span style="color: rgba(0, 0, 0, 1)">(responseJson);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (response == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
_logger.LogError(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Failed to deserialize RPC response for correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 查找匹配的待处理请求</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (_pendingRequests.TryRemove(correlationId, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> tcs))
{
tcs.TrySetResult(response);
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC response for {CorrelationId} delivered to waiting task</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogWarning(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received response for unknown correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 手动确认消息</span>
_channel.BasicAck(ea.DeliveryTag, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error processing RPC response for correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理失败时拒绝消息(不重新入队)</span>
_channel.BasicNack(ea.DeliveryTag, <span style="color: rgba(0, 0, 255, 1)">false</span>, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果反序列化失败,仍然通知等待的任务</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (_pendingRequests.TryRemove(correlationId, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> tcs))
{
tcs.TrySetException(</span><span style="color: rgba(0, 0, 255, 1)">new</span> InvalidOperationException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Failed to process RPC response</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
}
}
</span><span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> Task.CompletedTask;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Dispose()
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">_disposed)
{
_disposed </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取消所有待处理的请求</span>
<span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> (requestId, tcs) <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> _pendingRequests)
{
tcs.TrySetCanceled();
}
_pendingRequests.Clear();
_channel</span>?<span style="color: rgba(0, 0, 0, 1)">.Close();
_channel</span>?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
_connection</span>?<span style="color: rgba(0, 0, 0, 1)">.Close();
_connection</span>?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC Client disposed</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}
}</span></pre>
</div>
<p class="ds-markdown-paragraph">Services/FibonacciRpcClient.cs</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciRpcClient
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IRpcClient _rpcClient;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<FibonacciRpcClient><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> FibonacciRpcClient(IRpcClient rpcClient, ILogger<FibonacciRpcClient><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_rpcClient </span>=<span style="color: rgba(0, 0, 0, 1)"> rpcClient;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">long</span>> CalculateFibonacciAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> number, <span style="color: rgba(0, 0, 255, 1)">bool</span> useOptimized = <span style="color: rgba(0, 0, 255, 1)">true</span>, TimeSpan? timeout = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RpcRequest
{
Method </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fibonacci.calculate</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
Timestamp </span>=<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow
}
.WithParameter(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">number</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number)
.WithParameter(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">useOptimized</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, useOptimized);
timeout </span>??= TimeSpan.FromSeconds(<span style="color: rgba(128, 0, 128, 1)">30</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculating Fibonacci({Number}) with timeout {Timeout}s</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
number, timeout.Value.TotalSeconds);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span> _rpcClient.CallAsync<FibonacciResponse><span style="color: rgba(0, 0, 0, 1)">(request, timeout.Value);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (response != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
_logger.LogInformation(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci({Number}) = {Result} (calculated in {Time}ms)</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
number, response.Result, response.CalculationTimeMs);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response.Result;
}
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> InvalidOperationException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received null response from RPC server</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (TimeoutException ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci calculation timed out for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error calculating Fibonacci for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<FibonacciResponse> CalculateFibonacciDetailedAsync(<span style="color: rgba(0, 0, 255, 1)">int</span> number, <span style="color: rgba(0, 0, 255, 1)">bool</span> useOptimized = <span style="color: rgba(0, 0, 255, 1)">true</span>, TimeSpan? timeout = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RpcRequest
{
Method </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fibonacci.calculate</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
Timestamp </span>=<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow
}
.WithParameter(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">number</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number)
.WithParameter(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">useOptimized</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, useOptimized);
timeout </span>??= TimeSpan.FromSeconds(<span style="color: rgba(128, 0, 128, 1)">30</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span> _rpcClient.CallAsync<FibonacciResponse><span style="color: rgba(0, 0, 0, 1)">(request, timeout.Value);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> response ?? <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> InvalidOperationException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received null response from RPC server</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<h3>第4步:RPC客户端API(RpcClient.API)</h3>
<p class="ds-markdown-paragraph">Program.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_410c1ae7-c51d-4563-ad99-06f1777d5911" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_410c1ae7-c51d-4563-ad99-06f1777d5911" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_410c1ae7-c51d-4563-ad99-06f1777d5911" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.API.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> builder =<span style="color: rgba(0, 0, 0, 1)"> WebApplication.CreateBuilder(args);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add services to the container.</span>
<span style="color: rgba(0, 0, 0, 1)">builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Configure RabbitMQ</span>
builder.Services.AddSingleton<IConnectionFactory>(sp =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> configuration = sp.GetRequiredService<IConfiguration><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConnectionFactory
{
HostName </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:HostName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
UserName </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:UserName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
Password </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:Password</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
Port </span>= <span style="color: rgba(0, 0, 255, 1)">int</span>.Parse(configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:Port</span><span style="color: rgba(128, 0, 0, 1)">"</span>] ?? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">5672</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">),
VirtualHost </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:VirtualHost</span><span style="color: rgba(128, 0, 0, 1)">"</span>] ?? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
DispatchConsumersAsync </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
};
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Register RPC services</span>
builder.Services.AddSingleton<IRpcClient, RpcClient><span style="color: rgba(0, 0, 0, 1)">();
builder.Services.AddScoped</span><FibonacciRpcClient><span style="color: rgba(0, 0, 0, 1)">();
builder.Services.AddScoped</span><IMathRpcService, MathRpcService><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add health checks</span>
<span style="color: rgba(0, 0, 0, 1)">builder.Services.AddHealthChecks()
.AddRabbitMQ(provider </span>=><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> factory = provider.GetRequiredService<IConfiguration><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> factory.CreateConnection();
});
</span><span style="color: rgba(0, 0, 255, 1)">var</span> app =<span style="color: rgba(0, 0, 0, 1)"> builder.Build();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Configure the HTTP request pipeline.</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/health</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
app.Run();</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Services/IMathRpcService.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_732cbec5-d5c4-4b36-94e8-70c72624a747" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_732cbec5-d5c4-4b36-94e8-70c72624a747" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_732cbec5-d5c4-4b36-94e8-70c72624a747" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.API.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IMathRpcService
{
Task</span><<span style="color: rgba(0, 0, 255, 1)">long</span>> CalculateFibonacciAsync(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number);
Task</span><FibonacciResponse> CalculateFibonacciDetailedAsync(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number);
Task</span><<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> HealthCheckAsync();
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Services/MathRpcService.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_8cd15916-b98b-4de2-9ca3-7f9a90540c8a" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_8cd15916-b98b-4de2-9ca3-7f9a90540c8a" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_8cd15916-b98b-4de2-9ca3-7f9a90540c8a" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.API.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MathRpcService : IMathRpcService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciRpcClient _fibonacciClient;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<MathRpcService><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> MathRpcService(FibonacciRpcClient fibonacciClient, ILogger<MathRpcService><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_fibonacciClient </span>=<span style="color: rgba(0, 0, 0, 1)"> fibonacciClient;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">long</span>> CalculateFibonacciAsync(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number < <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number must be non-negative</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, nameof(number));
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number > <span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number too large for demonstration</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, nameof(number));
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _fibonacciClient.CalculateFibonacciAsync(number);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<FibonacciResponse> CalculateFibonacciDetailedAsync(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number < <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number must be non-negative</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, nameof(number));
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number > <span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number too large for demonstration</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, nameof(number));
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _fibonacciClient.CalculateFibonacciDetailedAsync(number);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<<span style="color: rgba(0, 0, 255, 1)">bool</span>><span style="color: rgba(0, 0, 0, 1)"> HealthCheckAsync()
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 简单的健康检查:计算 Fibonacci(1)</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span> _fibonacciClient.CalculateFibonacciAsync(<span style="color: rgba(128, 0, 128, 1)">1</span>, timeout: TimeSpan.FromSeconds(<span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 0, 255, 1)">return</span> result == <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogWarning(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC health check failed</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<p class="ds-markdown-paragraph">Controllers/MathController.cs</p>
<div class="cnblogs_code"><img id="code_img_closed_6c35edc8-1d15-4d95-9ac9-10e982d35e30" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_6c35edc8-1d15-4d95-9ac9-10e982d35e30" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_6c35edc8-1d15-4d95-9ac9-10e982d35e30" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Mvc;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.API.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.API.Controllers
{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MathController : ControllerBase
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IMathRpcService _mathService;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<MathController><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> MathController(IMathRpcService mathService, ILogger<MathController><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_mathService </span>=<span style="color: rgba(0, 0, 0, 1)"> mathService;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<ActionResult<<span style="color: rgba(0, 0, 255, 1)">long</span>>> CalculateFibonacci(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number)
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculating Fibonacci({Number}) via RPC</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _mathService.CalculateFibonacciAsync(number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(result);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (ArgumentException ex)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> BadRequest(ex.Message);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (TimeoutException ex)
{
_logger.LogWarning(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci calculation timed out for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(<span style="color: rgba(128, 0, 128, 1)">408</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculation timed out</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error calculating Fibonacci for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(<span style="color: rgba(128, 0, 128, 1)">500</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Internal server error</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<ActionResult<FibonacciResponse>> CalculateFibonacciDetailed(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> number)
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculating Fibonacci({Number}) with details via RPC</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _mathService.CalculateFibonacciDetailedAsync(number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Ok(result);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (ArgumentException ex)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> BadRequest(ex.Message);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (TimeoutException ex)
{
_logger.LogWarning(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci calculation timed out for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(<span style="color: rgba(128, 0, 128, 1)">408</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculation timed out</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error calculating Fibonacci for number {Number}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> StatusCode(<span style="color: rgba(128, 0, 128, 1)">500</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Internal server error</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<ActionResult><span style="color: rgba(0, 0, 0, 1)"> HealthCheck()
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> isHealthy = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _mathService.HealthCheckAsync();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> isHealthy ? Ok(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC service is healthy</span><span style="color: rgba(128, 0, 0, 1)">"</span>) : StatusCode(<span style="color: rgba(128, 0, 128, 1)">503</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC service is unavailable</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<h3>第5步:RPC服务器(RpcServer.Service)</h3>
<p class="ds-markdown-paragraph">Program.cs</p>
<div class="cnblogs_code"><img id="code_img_closed_f1aa8e6f-e75b-447a-b842-bf9b42b83c43" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_f1aa8e6f-e75b-447a-b842-bf9b42b83c43" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_f1aa8e6f-e75b-447a-b842-bf9b42b83c43" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcServer.Service.Services;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> builder =<span style="color: rgba(0, 0, 0, 1)"> Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService</span><FibonacciRpcServer><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Configure RabbitMQ</span>
builder.Services.AddSingleton<IConnectionFactory>(sp =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> configuration = sp.GetRequiredService<IConfiguration><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ConnectionFactory
{
HostName </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:HostName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
UserName </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:UserName</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
Password </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:Password</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
Port </span>= <span style="color: rgba(0, 0, 255, 1)">int</span>.Parse(configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:Port</span><span style="color: rgba(128, 0, 0, 1)">"</span>] ?? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">5672</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">),
VirtualHost </span>= configuration[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RabbitMQ:VirtualHost</span><span style="color: rgba(128, 0, 0, 1)">"</span>] ?? <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
DispatchConsumersAsync </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
};
});
builder.Services.AddSingleton</span><FibonacciCalculator><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> host =<span style="color: rgba(0, 0, 0, 1)"> builder.Build();
host.Run();</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p class="ds-markdown-paragraph">Services/FibonacciCalculator.cs</p>
<div class="cnblogs_code"><img id="code_img_closed_1a18592c-265f-4716-9919-93082de21825" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_1a18592c-265f-4716-9919-93082de21825" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_1a18592c-265f-4716-9919-93082de21825" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcServer.Service.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciCalculator
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<FibonacciCalculator><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> Dictionary<<span style="color: rgba(0, 0, 255, 1)">int</span>, <span style="color: rgba(0, 0, 255, 1)">long</span>> _cache = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">public</span> FibonacciCalculator(ILogger<FibonacciCalculator><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> FibonacciResponse Calculate(<span style="color: rgba(0, 0, 255, 1)">int</span> number, <span style="color: rgba(0, 0, 255, 1)">bool</span> useOptimized = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow;
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Calculating Fibonacci({Number}) with optimized: {Optimized}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
number, useOptimized);
</span><span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> result;
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (useOptimized)
{
result </span>=<span style="color: rgba(0, 0, 0, 1)"> CalculateOptimized(number);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
result </span>=<span style="color: rgba(0, 0, 0, 1)"> CalculateNaive(number);
}
</span><span style="color: rgba(0, 0, 255, 1)">var</span> calculationTime = (DateTime.UtcNow -<span style="color: rgba(0, 0, 0, 1)"> startTime).TotalMilliseconds;
_logger.LogInformation(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci({Number}) = {Result} (calculated in {Time}ms)</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
number, result, calculationTime);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciResponse
{
Result </span>=<span style="color: rgba(0, 0, 0, 1)"> result,
CalculationTimeMs </span>= (<span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)">)calculationTime,
InputNumber </span>=<span style="color: rgba(0, 0, 0, 1)"> number
};
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error calculating Fibonacci({Number})</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, number);
</span><span style="color: rgba(0, 0, 255, 1)">throw</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> CalculateOptimized(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> n)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (n < <span style="color: rgba(128, 0, 128, 1)">0</span>) <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number must be non-negative</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (n <= <span style="color: rgba(128, 0, 128, 1)">1</span>) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> n;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查缓存</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (_cache.TryGetValue(n, <span style="color: rgba(0, 0, 255, 1)">out</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> cachedResult))
{
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cache hit for Fibonacci({Number})</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, n);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cachedResult;
}
</span><span style="color: rgba(0, 0, 255, 1)">long</span> a = <span style="color: rgba(128, 0, 128, 1)">0</span>, b = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = <span style="color: rgba(128, 0, 128, 1)">2</span>; i <= n; i++<span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> temp = a +<span style="color: rgba(0, 0, 0, 1)"> b;
a </span>=<span style="color: rgba(0, 0, 0, 1)"> b;
b </span>=<span style="color: rgba(0, 0, 0, 1)"> temp;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 缓存中间结果</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (i % <span style="color: rgba(128, 0, 128, 1)">10</span> == <span style="color: rgba(128, 0, 128, 1)">0</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 每10个数缓存一次以减少内存使用</span>
<span style="color: rgba(0, 0, 0, 1)"> {
_cache </span>=<span style="color: rgba(0, 0, 0, 1)"> b;
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 缓存最终结果</span>
_cache =<span style="color: rgba(0, 0, 0, 1)"> b;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> b;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">long</span> CalculateNaive(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> n)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (n < <span style="color: rgba(128, 0, 128, 1)">0</span>) <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number must be non-negative</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (n <= <span style="color: rgba(128, 0, 128, 1)">1</span>) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> n;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 模拟计算密集型任务</span>
Thread.Sleep(<span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> CalculateNaive(n - <span style="color: rgba(128, 0, 128, 1)">1</span>) + CalculateNaive(n - <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ClearCache()
{
_cache.Clear();
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci cache cleared</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p class="ds-markdown-paragraph">Services/FibonacciRpcServer.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_69ad11e1-0e48-4361-bfa7-207270c9088f" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_69ad11e1-0e48-4361-bfa7-207270c9088f" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_69ad11e1-0e48-4361-bfa7-207270c9088f" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text.Json;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.Options;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RabbitMQ.Client.Events;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Messages;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcServer.Service.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciRpcServer : BackgroundService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IConnection _connection;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IModel _channel;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciCalculator _calculator;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<FibonacciRpcServer><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">string</span> QueueName = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">rpc_queue</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciRpcServer(
IConnectionFactory connectionFactory,
FibonacciCalculator calculator,
ILogger</span><FibonacciRpcServer><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_calculator </span>=<span style="color: rgba(0, 0, 0, 1)"> calculator;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 建立连接和通道</span>
_connection =<span style="color: rgba(0, 0, 0, 1)"> connectionFactory.CreateConnection();
_channel </span>=<span style="color: rgba(0, 0, 0, 1)"> _connection.CreateModel();
InitializeQueue();
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> InitializeQueue()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 声明RPC请求队列(持久化)</span>
<span style="color: rgba(0, 0, 0, 1)"> _channel.QueueDeclare(
queue: QueueName,
durable: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
exclusive: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
autoDelete: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
arguments: </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置公平分发,每次只处理一个请求</span>
_channel.BasicQos(prefetchSize: <span style="color: rgba(128, 0, 128, 1)">0</span>, prefetchCount: <span style="color: rgba(128, 0, 128, 1)">1</span>, <span style="color: rgba(0, 0, 255, 1)">global</span>: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci RPC Server initialized and listening on queue: {QueueName}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, QueueName);
}
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> Task ExecuteAsync(CancellationToken stoppingToken)
{
stoppingToken.ThrowIfCancellationRequested();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> consumer = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AsyncEventingBasicConsumer(_channel);
consumer.Received </span>+= <span style="color: rgba(0, 0, 255, 1)">async</span> (model, ea) =><span style="color: rgba(0, 0, 0, 1)">
{
RpcResponse response </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> correlationId =<span style="color: rgba(0, 0, 0, 1)"> ea.BasicProperties.CorrelationId;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> replyTo =<span style="color: rgba(0, 0, 0, 1)"> ea.BasicProperties.ReplyTo;
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received RPC request with correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理请求</span>
response = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> ProcessRequestAsync(ea.Body.ToArray());
response.RequestId </span>=<span style="color: rgba(0, 0, 0, 1)"> correlationId;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error processing RPC request {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
response </span>=<span style="color: rgba(0, 0, 0, 1)"> RpcResponse.ErrorResponse(correlationId, ex.Message);
}
</span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送响应</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.IsNullOrEmpty(replyTo))
{
SendResponse(replyTo, correlationId, response </span>??<span style="color: rgba(0, 0, 0, 1)">
RpcResponse.ErrorResponse(correlationId, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Unknown error occurred</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确认消息处理完成</span>
_channel.BasicAck(ea.DeliveryTag, <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
}
};
_channel.BasicConsume(
queue: QueueName,
autoAck: </span><span style="color: rgba(0, 0, 255, 1)">false</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 手动确认</span>
<span style="color: rgba(0, 0, 0, 1)"> consumer: consumer);
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci RPC Server started successfully</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Task.CompletedTask;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<RpcResponse> ProcessRequestAsync(<span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] body)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> startTime =<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow;
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> requestJson =<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetString(body);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> request = JsonSerializer.Deserialize<RpcRequest><span style="color: rgba(0, 0, 0, 1)">(requestJson);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (request == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> RpcResponse.ErrorResponse(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">unknown</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Invalid request format</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Processing RPC request {RequestId}, Method: {Method}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
request.RequestId, request.Method);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据方法名路由到不同的处理逻辑</span>
<span style="color: rgba(0, 0, 255, 1)">object</span> result = request.Method.ToLowerInvariant() <span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">fibonacci.calculate</span><span style="color: rgba(128, 0, 0, 1)">"</span> =><span style="color: rgba(0, 0, 0, 1)"> ProcessFibonacciRequest(request),
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ping</span><span style="color: rgba(128, 0, 0, 1)">"</span> => <span style="color: rgba(0, 0, 255, 1)">new</span> { message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">pong</span><span style="color: rgba(128, 0, 0, 1)">"</span>, timestamp =<span style="color: rgba(0, 0, 0, 1)"> DateTime.UtcNow },
_ </span>=> <span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> NotSupportedException($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Method {request.Method} is not supported</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
};
</span><span style="color: rgba(0, 0, 255, 1)">var</span> processingTime = (DateTime.UtcNow -<span style="color: rgba(0, 0, 0, 1)"> startTime).TotalMilliseconds;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> RpcResponse.SuccessResponse(
request.RequestId,
result,
(</span><span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)">)processingTime);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> processingTime = (DateTime.UtcNow -<span style="color: rgba(0, 0, 0, 1)"> startTime).TotalMilliseconds;
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error processing RPC request</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> RpcResponse.ErrorResponse(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">unknown</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
ex.Message,
(</span><span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)">)processingTime);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> FibonacciResponse ProcessFibonacciRequest(RpcRequest request)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> number = request.GetParameter<<span style="color: rgba(0, 0, 255, 1)">int</span>>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">number</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> useOptimized = request.GetParameter<<span style="color: rgba(0, 0, 255, 1)">bool</span>>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">useOptimized</span><span style="color: rgba(128, 0, 0, 1)">"</span>) ?? <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (number < <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci number must be non-negative</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 防止过大的计算消耗资源</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (number > <span style="color: rgba(128, 0, 128, 1)">50</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> ArgumentException(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Number too large for calculation</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> _calculator.Calculate(number, useOptimized);
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span> SendResponse(<span style="color: rgba(0, 0, 255, 1)">string</span> replyTo, <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> correlationId, RpcResponse response)
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> responseJson =<span style="color: rgba(0, 0, 0, 1)"> JsonSerializer.Serialize(response);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> responseBody =<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetBytes(responseJson);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> properties =<span style="color: rgba(0, 0, 0, 1)"> _channel.CreateBasicProperties();
properties.CorrelationId </span>=<span style="color: rgba(0, 0, 0, 1)"> correlationId;
properties.Persistent </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
_channel.BasicPublish(
exchange: </span><span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">,
routingKey: replyTo,
basicProperties: properties,
body: responseBody);
_logger.LogDebug(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Sent RPC response for correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
_logger.LogError(ex, </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error sending RPC response for correlation ID: {CorrelationId}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, correlationId);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Dispose()
{
_channel</span>?<span style="color: rgba(0, 0, 0, 1)">.Close();
_channel</span>?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
_connection</span>?<span style="color: rgba(0, 0, 0, 1)">.Close();
_connection</span>?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
_logger.LogInformation(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Fibonacci RPC Server disposed</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">base</span><span style="color: rgba(0, 0, 0, 1)">.Dispose();
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<h3>第6步:高级特性 - 带重试的RPC客户端</h3>
<p class="ds-markdown-paragraph">Services/ResilientRpcClient.cs</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code"><img id="code_img_closed_eada9a74-74bb-47fe-90af-f1f75478e1e3" class="code_img_closed lazyload" data-src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif"><img id="code_img_opened_eada9a74-74bb-47fe-90af-f1f75478e1e3" class="code_img_opened lazyload" style="display: none" data-src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif">
<div id="cnblogs_code_open_eada9a74-74bb-47fe-90af-f1f75478e1e3" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.Logging;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Polly;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Polly.Retry;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> RpcShared.Models;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> RpcClient.Core.Services
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ResilientRpcClient : IRpcClient
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IRpcClient _innerClient;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<ResilientRpcClient><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> AsyncRetryPolicy _retryPolicy;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> ResilientRpcClient(IRpcClient innerClient, ILogger<ResilientRpcClient><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_innerClient </span>=<span style="color: rgba(0, 0, 0, 1)"> innerClient;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置重试策略</span>
_retryPolicy =<span style="color: rgba(0, 0, 0, 1)"> Policy
.Handle</span><TimeoutException><span style="color: rgba(0, 0, 0, 1)">()
.Or</span><InvalidOperationException><span style="color: rgba(0, 0, 0, 1)">()
.WaitAndRetryAsync(
retryCount: </span><span style="color: rgba(128, 0, 128, 1)">3</span><span style="color: rgba(0, 0, 0, 1)">,
sleepDurationProvider: retryAttempt </span>=> TimeSpan.FromSeconds(Math.Pow(<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">, retryAttempt)),
onRetry: (exception, delay, retryCount, context) </span>=><span style="color: rgba(0, 0, 0, 1)">
{
_logger.LogWarning(
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RPC call failed. Retry {RetryCount} after {Delay}ms. Error: {Error}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
retryCount, delay.TotalMilliseconds, exception.Message);
});
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<RpcResponse><span style="color: rgba(0, 0, 0, 1)"> CallAsync(RpcRequest request, TimeSpan timeout)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span> _retryPolicy.ExecuteAsync(<span style="color: rgba(0, 0, 255, 1)">async</span> () =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _innerClient.CallAsync(request, timeout);
});
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<TResponse?> CallAsync<TResponse>(RpcRequest request, TimeSpan timeout) <span style="color: rgba(0, 0, 255, 1)">where</span> TResponse : <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span> _retryPolicy.ExecuteAsync(<span style="color: rgba(0, 0, 255, 1)">async</span> () =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">await</span> _innerClient.CallAsync<TResponse><span style="color: rgba(0, 0, 0, 1)">(request, timeout);
});
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Dispose()
{
_innerClient</span>?<span style="color: rgba(0, 0, 0, 1)">.Dispose();
}
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
</div>
</div>
</div>
</div>
</div>
<h3>第7步:运行与测试</h3>
<ol start="1">
<li>
<p class="ds-markdown-paragraph">启动服务</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 终端1:启动RPC服务器
cd RpcServer.Service
dotnet run
# 终端2:启动RPC客户端API
cd RpcClient.API
dotnet run</span></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li>
<p class="ds-markdown-paragraph">测试API</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 计算斐波那契数列
curl </span>-X GET <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://localhost:7000/api/math/fibonacci/10</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
curl </span>-X GET <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://localhost:7000/api/math/fibonacci/20/detailed</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
# 健康检查
curl </span>-X GET <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://localhost:7000/api/math/health</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li>
<p class="ds-markdown-paragraph">测试错误场景</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 测试超时(设置很小的超时时间)
# 测试无效输入
curl </span>-X GET <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://localhost:7000/api/math/fibonacci/-5</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
curl </span>-X GET <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://localhost:7000/api/math/fibonacci/100</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li>
<p class="ds-markdown-paragraph">观察日志输出</p>
<ul>
<li>
<p class="ds-markdown-paragraph">客户端发送请求,生成CorrelationId</p>
</li>
<li>
<p class="ds-markdown-paragraph">服务器接收请求,处理计算</p>
</li>
<li>
<p class="ds-markdown-paragraph">服务器发送响应,使用相同的CorrelationId</p>
</li>
<li>
<p class="ds-markdown-paragraph">客户端接收响应,匹配CorrelationId</p>
</li>
</ul>
</li>
</ol>
<h3>第8步:性能测试和监控</h3>
<p class="ds-markdown-paragraph">创建性能测试控制器</p>
<div class="md-code-block md-code-block-light">
<div class="md-code-block-banner-wrap">
<div class="md-code-block-banner md-code-block-banner-lite">
<div class="_121d384">
<div class="d2a24f03">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)]
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> BenchmarkController : ControllerBase
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> IMathRpcService _mathService;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ILogger<BenchmarkController><span style="color: rgba(0, 0, 0, 1)"> _logger;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> BenchmarkController(IMathRpcService mathService, ILogger<BenchmarkController><span style="color: rgba(0, 0, 0, 1)"> logger)
{
_mathService </span>=<span style="color: rgba(0, 0, 0, 1)"> mathService;
_logger </span>=<span style="color: rgba(0, 0, 0, 1)"> logger;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task<ActionResult> CalculateFibonacciBatch( List<<span style="color: rgba(0, 0, 255, 1)">int</span>><span style="color: rgba(0, 0, 0, 1)"> numbers)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> results = <span style="color: rgba(0, 0, 255, 1)">new</span> List<<span style="color: rgba(0, 0, 255, 1)">object</span>><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> totalStopwatch =<span style="color: rgba(0, 0, 0, 1)"> System.Diagnostics.Stopwatch.StartNew();
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> number <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> numbers)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> stopwatch =<span style="color: rgba(0, 0, 0, 1)"> System.Diagnostics.Stopwatch.StartNew();
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> _mathService.CalculateFibonacciAsync(number);
results.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">
{
number,
result,
success </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
durationMs </span>=<span style="color: rgba(0, 0, 0, 1)"> stopwatch.ElapsedMilliseconds
});
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
results.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">
{
number,
success </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
error </span>=<span style="color: rgba(0, 0, 0, 1)"> ex.Message,
durationMs </span>=<span style="color: rgba(0, 0, 0, 1)"> stopwatch.ElapsedMilliseconds
});
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Ok(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)">
{
totalDurationMs </span>=<span style="color: rgba(0, 0, 0, 1)"> totalStopwatch.ElapsedMilliseconds,
requests </span>=<span style="color: rgba(0, 0, 0, 1)"> numbers.Count,
results
});
}
}</span></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<hr>
<h2>本章总结</h2>
<p class="ds-markdown-paragraph">在这一章中,我们完整实现了RabbitMQ的RPC模式:</p>
<ol start="1">
<li>
<p class="ds-markdown-paragraph">RPC核心概念:理解了回调队列、关联ID、请求-响应模式。</p>
</li>
<li>
<p class="ds-markdown-paragraph">客户端实现:创建了能够发送请求并异步等待响应的RPC客户端。</p>
</li>
<li>
<p class="ds-markdown-paragraph">服务器实现:构建了处理请求并返回响应的RPC服务器。</p>
</li>
<li>
<p class="ds-markdown-paragraph">错误处理:实现了超时控制、异常处理和重试机制。</p>
</li>
<li>
<p class="ds-markdown-paragraph">性能优化:使用缓存和优化算法提高计算效率。</p>
</li>
<li>
<p class="ds-markdown-paragraph">** resilience**:通过Polly实现了弹性重试策略。</p>
</li>
</ol>
<p class="ds-markdown-paragraph">RPC模式为微服务架构提供了强大的同步通信能力,结合消息队列的异步特性,既保持了系统的解耦性,又提供了同步调用的便利性。这种模式特别适合需要等待计算结果的分布式任务。</p><br><br>
来源:https://www.cnblogs.com/jixingsuiyuan/p/19181480
頁:
[1]