通过 ServiceProxy 访问无状态服务失败 + ASP.NET 5 Web API 项目引发健康状态错误

Posted

技术标签:

【中文标题】通过 ServiceProxy 访问无状态服务失败 + ASP.NET 5 Web API 项目引发健康状态错误【英文标题】:Accessing stateless service via ServiceProxy fails + ASP.NET 5 Web API project throws Health State error 【发布时间】:2016-07-09 12:57:56 【问题描述】:

我是 Microsoft Azure Service Fabric 的新手。对于我的硕士学位,我必须在服务结构中开发一个微服务方法原型。经过数小时的研究,我的问题仍然没有得到解决。

我想在 Web 前端(如 https://azure.microsoft.com/en-us/documentation/articles/service-fabric-add-a-web-frontend/)中访问我的(在部署的本地结构集群中)无状态服务。最简单的方法是将 ASP .NET 5 Web Api 项目添加到 Service Fabric 应用程序并在 ValuesController 中调用 ServiceProxy 方法。所以我将此代码添加到我的解决方案中:

ValuesController.cs:

[Route("api/[controller]")]
public class ValuesController : Controller

  // GET api/values/IObject
  [HttpGet("interfaceName")]
  public async Task<string> Get(string interfaceName)
  
    var serviceName = "fabric:/DataServiceFabric/MasterDataMService";
    var masterDataService = ServiceProxy.Create<IMasterDataMService>(new Uri(serviceName));
    var result = await masterDataService.GetMasterDataByName(interfaceName);
    return result.Content;
  

在 F5 部署后,我的浏览器不会自动导航到我的 Web 前端。通过查看 Service Fabric Explorer,我的 ASP .NET 5 应用程序引发了健康状态错误:

Kind        Health State  Description
=============================================================================
Partitions  Error         Unhealthy partitions: 100% (1/1), MaxPercentUnhealthyPartitionsPerService=0%.
Partition   Error         Unhealthy partition: PartitionId='413...', AggregatedHealthState='Error'.
Event       Error         Error event: SourceId='System.FM', Property='State'. Partition is below target replica or instance count.

在这个this 问题之后,“分区低于目标副本或实例计数” 表示我的服务中有未处理的异常阻止它启动。但我无法在我的 Service Fabric Explorer 中找到堆栈 strace 来调试此故障。这是我的 ASP .NET Web 服务的 ServiceManifest.xml

ServiceManifest.xml (Web1):

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Name="Web1" Version="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric">
   <ServiceTypes>
      <StatelessServiceType ServiceTypeName="Web1Type">
         <Extensions>
            <Extension Name="__GeneratedServiceType__">
               <GeneratedNames xmlns="http://schemas.microsoft.com/2015/03/fabact-no-schema">
                  <DefaultService Name="Web1Service" />
                  <ServiceEndpoint Name="Web1TypeEndpoint" />
               </GeneratedNames>
            </Extension>
         </Extensions>
      </StatelessServiceType>
   </ServiceTypes>
   <CodePackage Name="C" Version="1.0.0">
      <EntryPoint>
         <ExeHost>
            <Program>approot\runtimes\dnx-clr-win-x64.1.0.0-rc1-update1\bin\dnx.exe</Program>
            <Arguments>--appbase approot\src\Web1 Microsoft.Dnx.ApplicationHost Microsoft.ServiceFabric.AspNet.Hosting --server Microsoft.AspNet.Server.WebListener</Arguments>
            <WorkingFolder>CodePackage</WorkingFolder>
            <ConsoleRedirection FileRetentionCount="5" FileMaxSizeInKb="2048" />
         </ExeHost>
      </EntryPoint>
   </CodePackage>
   <Resources>
      <Endpoints>
         <Endpoint Name="Web1TypeEndpoint" Protocol="http" Type="Input" Port="80" />
      </Endpoints>
   </Resources>
</ServiceManifest>

这里是我的ApplicationManifest.xml 我的服务结构解决方案:

ApplicationManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ApplicationTypeName="DataServiceFabricType" ApplicationTypeVersion="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric">
   <Parameters>
      <Parameter Name="ActorTestServiceActorService_PartitionCount" DefaultValue="10" />
      <Parameter Name="MasterDataMService_InstanceCount" DefaultValue="-1" />
   </Parameters>
   <ServiceManifestImport>
     <ServiceManifestRef ServiceManifestName="Web2Pkg" ServiceManifestVersion="1.0.0" />
     <ConfigOverrides />
   </ServiceManifestImport>
   <ServiceManifestImport>
      <ServiceManifestRef ServiceManifestName="Web1" ServiceManifestVersion="1.0.0" />
   </ServiceManifestImport>
   <ServiceManifestImport>
      <ServiceManifestRef ServiceManifestName="ActorTestServicePkg" ServiceManifestVersion="1.0.0" />
   </ServiceManifestImport>
   <ServiceManifestImport>
      <ServiceManifestRef ServiceManifestName="MasterDataMServicePkg" ServiceManifestVersion="1.0.0" />
      <ConfigOverrides />
   </ServiceManifestImport>
   <DefaultServices>
      <Service Name="Web1Service">
         <StatelessService ServiceTypeName="Web1Type">
            <SingletonPartition />
         </StatelessService>
      </Service>
      <Service Name="ActorTestServiceActorService" GeneratedIdRef="761ee3cf-5a3a-49d8-9c57-aa3480d1acf1">
         <StatelessService ServiceTypeName="ActorTestServiceActorServiceType">
            <UniformInt64Partition PartitionCount="[ActorTestServiceActorService_PartitionCount]" LowKey="-9223372036854775808" HighKey="9223372036854775807" />
         </StatelessService>
      </Service>
      <Service Name="MasterDataMService">
         <StatelessService ServiceTypeName="MasterDataMServiceType" InstanceCount="[MasterDataMService_InstanceCount]">
            <SingletonPartition />
         </StatelessService>
      </Service>
   </DefaultServices>
</ApplicationManifest>

因此,我使用 ASP.NET 5 Web 应用程序和相同的 ValuesController.cs 创建了一个新解决方案。我确保我的无状态服务正在我的本地集群上运行,然后我启动了我的新 Web 应用程序。在我的控制器中调用 GET 方法后,我得到了以下异常:

Exception thrown: 'System.Fabric.FabricException' in mscorlib.dll
Microsoft.AspNet.Hosting.Internal.HostingEngine: Information: Request finished in 0,2593ms 500
Microsoft.AspNet.Server.Kestrel: Error: An unhandled exception was thrown by the application.
System.Fabric.FabricException: Invalid partition key/ID '0'  for selector 1

我的无状态服务是一个 SingletonPartition,所以我需要一个分区键吗?如果是,我如何获得钥匙? Service Fabric Explorer 不会为我的无状态服务提供此信息。这是我的无状态服务的ServiceManifest.xml

ServiceManifest.xml (MasterDataMService):

<?xml version="1.0" encoding="utf-8"?>
<ServiceManifest Name="MasterDataMServicePkg"
                 Version="1.0.0"
                 xmlns="http://schemas.microsoft.com/2011/01/fabric"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ServiceTypes>
    <!-- This is the name of your ServiceType. 
         This name must match the string used in RegisterServiceType call in Program.cs. -->
    <StatelessServiceType ServiceTypeName="MasterDataMServiceType" />
  </ServiceTypes>

  <!-- Code package is your service executable. -->
  <CodePackage Name="Code" Version="1.0.0">
    <EntryPoint>
      <ExeHost>
        <Program>MasterDataMService.exe</Program>
      </ExeHost>
    </EntryPoint>
  </CodePackage>

  <!-- Config package is the contents of the Config directoy under PackageRoot that contains an 
       independently-updateable and versioned set of custom configuration settings for your service. -->
  <ConfigPackage Name="Config" Version="1.0.0" />

  <Resources>
    <Endpoints>
      <!-- This endpoint is used by the communication listener to obtain the port on which to 
           listen. Please note that if your service is partitioned, this port is shared with 
           replicas of different partitions that are placed in your code. -->
      <Endpoint Name="ServiceEndpoint" Type="Input" Protocol="http" Port="80"/>
    </Endpoints>
  </Resources>
</ServiceManifest>

之后我决定与 OWIN 建立服务通信:

MasterDataMService.cs:

internal sealed class MasterDataMService : StatelessService, IMasterDataMService

  [...]      

  protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
  
    return new[]
    
      new ServiceInstanceListener(initParams => new OwinCommunicationListener("MasterDataMService", new StartUp(), initParams))
    ;
  

现在我可以在DefaultController 中使用HttpClient 访问我的微服务:

var client = new HttpClient();
var request = "http://localhost:80/MasterDataMService/api/values/query";
var result = string.Empty;
HttpResponseMessage response = await client.GetAsync(request);
if (response.IsSuccessStatusCode)

  result = await response.Content.ReadAsStringAsync();

但这不是我最初想要的。我不想在我的请求中指定服务端点。相反,我想通过ServiceProxy 与我的无状态服务通信。我如何在这里实现这一目标?我做错了什么?以及如何使用部署到我的服务结构集群中的 ASP .NET 5 应用程序解决此健康状态错误?

感谢您的宝贵时间。

编辑:

无效分区键异常的扩展堆栈跟踪:

Exception thrown: 'System.Fabric.FabricException' in mscorlib.dll
Microsoft.AspNet.Hosting.Internal.HostingEngine: Information: Request finished in 1,35ms 500
Microsoft.AspNet.Server.WebListener.MessagePump: Error: ProcessRequestAsync
System.Fabric.FabricException: Invalid partition key/ID '0'  for selector 1 ---> System.Runtime.InteropServices.COMException: exception of HRESULT: 0x80071BBF
   at System.Fabric.Interop.NativeClient.IFabricServiceManagementClient4.EndResolveServicePartition(IFabricAsyncOperationContext context)
   at System.Fabric.FabricClient.ServiceManagementClient.ResolveServicePartitionEndWrapper(IFabricAsyncOperationContext context)
   at System.Fabric.Interop.AsyncCallOutAdapter2`1.Finish(IFabricAsyncOperationContext context, Boolean expectedCompletedSynchronously)
   --- End of inner exception stack trace ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Client.ServicePartitionResolver.<ResolveAsyncHelper>d__2a.MoveNext()
--- End of stack trace from the previous location where the exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Communication.Client.CommunicationClientFactoryBase`1.<GetClientAsync>d__a.MoveNext()

如果您需要更多,请给我反馈。 (完整的堆栈跟踪是 82 行)

无效的方案异常堆栈跟踪:

Exception thrown: 'System.ArgumentException' in mscorlib.dll
Microsoft.AspNet.Hosting.Internal.HostingEngine: Information: Request finished in 1,45ms 500
Microsoft.AspNet.Server.WebListener.MessagePump: Error: ProcessRequestAsync
System.ArgumentException: the provided uri scheme 'http' is invalid; expected 'net.tcp'.
Parametername: via
   at System.ServiceModel.Channels.TransportChannelFactory`1.ValidateScheme(Uri via)
   at System.ServiceModel.Channels.ConnectionOrientedTransportChannelFactory`1.OnCreateChannel(EndpointAddress address, Uri via)
   at System.ServiceModel.Channels.ChannelFactoryBase`1.InternalCreateChannel(EndpointAddress address, Uri via)
   at System.ServiceModel.Channels.ServiceChannelFactory.ServiceChannelFactoryOverDuplexSession.CreateInnerChannelBinder(EndpointAddress to, Uri via)
   at System.ServiceModel.Channels.ServiceChannelFactory.CreateServiceChannel(EndpointAddress address, Uri via)
   at System.ServiceModel.Channels.ServiceChannelFactory.CreateChannel(Type channelType, EndpointAddress address, Uri via)
   at System.ServiceModel.DuplexChannelFactory`1.CreateChannel(InstanceContext callbackInstance, EndpointAddress address, Uri via)
   at System.ServiceModel.DuplexChannelFactory`1.CreateChannel(InstanceContext callbackInstance, Binding binding, EndpointAddress endpointAddress)
   at Microsoft.ServiceFabric.Services.Communication.Wcf.Client.WcfCommunicationClientFactory`1.<CreateClientAsync>d__2.MoveNext()
--- End of stack trace from the previous location where the exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Communication.Client.CommunicationClientFactoryBase`1.<CreateClientWithRetriesAsync>d__1e.MoveNext()

【问题讨论】:

您可以指定任何分区键,如果您有 1 个分区则无关紧要。 无效分区键异常的堆栈跟踪是否有更多信息?您不需要为使用单例分区方案的服务指定分区键,例如您的无状态服务,因此错误看起来很奇怪。 @Mikhail 当我在我的“ActorTestServiceActorService”中指定一个 并使用 ServiceProxy 与我的微服务对话时,我得到一个 System.ArgumentException: 提供的 uri 方案“http”无效;预期的“net.tcp” @VaclavTurecek 我将堆栈跟踪的其余部分添加到我的问题中。是啊,这真的很奇怪……这是某种配置错误吗? 【参考方案1】:

我在学习 Service Fabric 时遇到了同样的问题。事实证明,仅提供 URI 是不够的 - 我还必须将分区键指定为一个魔术值:

IHelloService service = ServiceProxy.Create<IHelloService>(new Uri("fabric:/Application1/HelloService"), new ServicePartitionKey(1));

向this thread on disq.us 致敬。微软工程师 Oana Platon 还对为什么 1 的值起作用提供了更深入的解释:

Diogo,看看这篇解释分区的文章: link 具体看远程 分区(也称为 UniformInt64Partition):“这是使用 指定整数范围(由低键和高键标识)和 多个分区 (n)。它创建 n 个分区,每个分区负责 对于整个分区键范围的非重叠子范围。为了 例如,一个低键为 0,高键为 99,计数为 4 将创建四个分区,如下所示。” 然后查看您的服务清单并弄清楚它是如何配置的 - 多少个分区和范围是多少(低键 - 高键)。如果您有一个分区,则该范围内的任何键都会转到 (one) 分区,因此您指定哪个键都没有关系。如果你有 不止一个分区,你需要弄清楚你的分区是哪一个 客户需要交谈。指定范围内的分区键 分区正在服务。

我必须承认,我自己必须更深入地研究分区才能理解这个解释。

【讨论】:

将分区键值指定为 0 实际上也会对我抛出异常。相反,我只是不指定参数(默认为 null),然后它就起作用了。 COMException:来自 HRESULT 的异常:0x80071BBF System.Fabric.Interop.NativeClient+IFabricServiceManagementClient6.EndResolveServicePartition(IFabricAsyncOperationContext 上下文) FabricException:选择器 1 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 的分区键/ID“0”无效【参考方案2】:

在入门示例中,有一个类 ServiceUriBuilder。通过在构造函数中传递服务名称来初始化此类。

var proxyLocation = new ServiceUriBuilder("MasterDataMService");
var masterDataService = ServiceProxy.Create<IMasterDataMService>(proxyLocation.ToUri());

var result = await masterDataService.GetMasterDataByName(interfaceName);

同样在您的 MasterDataMService CreateServiceInstanceListeners 方法中,确保它看起来像这样:

    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    
        return new[]  new ServiceInstanceListener(context => new FabricTransportServiceRemotingListener(context, this)) ;
    

【讨论】:

如果我使用return new[] new ServiceInstanceListener(context =&gt; new ServiceRemotingListener&lt;IMasterDataMService&gt;(context, this)) ; 而不是自己的ServiceRemotingListener 实现是否一样?我不知道我可以在这里自己实现什么。 @MegaMax,我相信您的方法会创建一个 V1 侦听器,而 alltej 使用 FabricTransportServiceRemotingListener 的方法会创建一个 V2 侦听器。您可以使用 V2 侦听器,但如果这样做会出现以下异常:“FabricInvalidAddressException: NamedEndpoint 'V2Listener' not found in the address”所以我认为这种方法需要在其他地方进行额外的装饰。 作为后续,在使用 FabricTransportServiceRemotingListener 对象时,您必须为侦听器提供名称。并且 ServiceProxy 对象必须引用该名称,然后它才能与 V2 侦听器框架很好地配合使用。根据 MSDN,出于性能原因,首选使用 v2。【参考方案3】:

我在使用 ServiceProxy 和无状态服务时遇到了完全相同的问题。我有一个运行良好的有状态服务,但无状态服务正在接收:

System.Fabric.FabricException: Invalid partition key/ID '0'  for selector 1

正如 Allan T 上面提到的,您还需要为 ServiceProxy.Create() 使用第三个构造函数重载:

代替:

ServiceProxy.Create<IMasterDataMService>(0, new Uri("fabric:/DataServiceFabric/MasterDataMService"));

使用:

ServiceProxy.Create<IMasterDataMService>(new Uri("fabric:/DataServiceFabric/MasterDataMService"));

...简单地说,不要指定分区。这让我的错误消失了。

https://msdn.microsoft.com/en-us/library/mt628402.aspx

【讨论】:

我注意到,通过我在源代码中所做的全部编辑,我最终尝试使用分区 0 调用 Create()。我将其更改为正确的重载而不指定分区。我还必须删除我的 OWIN-Listener 并将其替换为 return new[] new ServiceInstanceListener(context =&gt; new ServiceRemotingListener&lt;IMasterDataMService&gt;(context, this)) ; 奇怪的是,我的问题恰恰相反——直到我将分区号指定为 1 才起作用。有人知道为什么吗?【参考方案4】:

我自己也遇到了同样令人沮丧的问题并解决了它们。

需要检查的几件事

1) 确保您的 ASP.NET 5 web api 项目没有引用任何 X64 库。

2) 端口冲突.. 将 ASPNET 5 web api 部署到集群时,请确保没有 2 个网站使用相同的端口。您可以将 [ASP.NET 5 项目]/PackageRoot/ServiceManifest.xml.. 中的端口更改为底部

**3) “包装”文件夹疯狂!验证您的 wrap 文件夹(在根解决方案文件夹中)是否仅包含您的 .net 4.5.1 类库。当我删除 wrap 文件夹中的“Newtonsoft.Json”文件夹时,我的问题就消失了。然后,您必须在解决方案上运行 dnu restore,以便重新创建 project.lock.json 文件

4) 确保您的 startup.cs 类中没有发生任何故障。在本地运行 web api 只是为了确保它的加载;服务代理类加载失败是正常的。

【讨论】:

抱歉,只是跟进...我写了此评论,假设您的服务处于就绪状态(绿色)...您的服务是否显示此信息?还是显示 InBuild? 是的,我的无状态服务处于就绪状态。感谢您的回答。 1) 我将 web api 项目的特定运行时从 x64 更改为 x86。但我无法找出如何正确检查哪些引用需要 x64 库。 2) 我删除了特定端口 3) 这对我有用。健康状态错误消失了,我可以导航到 web api。 4) 我在 F5-deploy 上调试了我的 startup.cs,似乎一切正常。还是我错过了什么?【参考方案5】:

从您的第二个堆栈跟踪来看:

System.ArgumentException: the provided uri scheme 'http' is invalid; expected 'net.tcp'.

您似乎正在尝试使用 ServiceProxy 连接到正在侦听 HTTP 端点的服务。 ServiceProxy 需要一个使用二进制协议而不是 HTTP 的服务远程处理侦听器。

第一个例外还是有点神秘:

System.Fabric.FabricException: Invalid partition key/ID '0'  for selector 1

仅当您尝试解析统一的 int64 或命名的分区服务而不提供分区键时才会发生这种情况。在您的配置中,您似乎将目标服务设置为单例分区,在这种情况下您不应该看到此错误,因此您可以仔细检查您尝试连接的服务是否实际上是作为单例分区创建的服务?

【讨论】:

很高兴知道ServiceProxy需要二进制协议。但是为什么ServiceManifest中的端点协议指定为http? (对不起,如果这是一个愚蠢的问题)是的,该服务是单例分区服务。在我的源代码编辑过程中,我最终调用了第 0 部分。我将其更改为正确的重载。我还必须删除我的 OWIN-Listener 并返回 ServiceRemotingListener (因为协议错误)。我是否假设正确,不可能同时拥有 OWIN-Listener 并通过 ServiceProxy 访问服务?【参考方案6】:

就我而言,上述方法均无效。或者更准确地说,我的解决方案是上述解决方案的组合/变体

我需要做两件事:首先,确保我已经注册了我的服务:

  protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners()
    
        return new[]  new ServiceReplicaListener(context => this.CreateServiceRemotingListener(context)) ;

其次,参考第一个服务(“魔法”值为 0)

        ServiceProxy.Create<IMasterDataMService>(new Uri("fabric:/DataServiceFabric/MasterDataMService"), new ServicePartitionKey(0));

希望对你有帮助

【讨论】:

以上是关于通过 ServiceProxy 访问无状态服务失败 + ASP.NET 5 Web API 项目引发健康状态错误的主要内容,如果未能解决你的问题,请参考以下文章

如何访问无状态服务的特定副本

如何访问 Azure Service Fabric 有状态/无状态服务中的 settings.xml?

Cloudera安装未能检测到CentOS上的root特权我试图将新主机添加到CentOS集群中。安装失败,状态为“安装失败。无法检测root特权”。我知道Cloudera需要用户具有无密码特权(“需

token Auth 认证

如何在 TACACS+ 服务器的拨号接口上应用访问列表

27服务-安全访问状态转换