Service Fabric StatefulService CPU使用率持续增长

时间:2018-08-25 11:43:47

标签: c# azure service-fabric-stateful azure-service-fabric

我们有一个运行良好的Service Fabric StatefulService。它使用消息,对其进行处理,并具有两个IReliableState用于存储每条消息中的一些数据。每个副本每分钟将处理大约500条消息。

对于进入MessageProcessor的每条消息,我们使用包裹在using块中的ITransaction创建一个新的IReliableStateManager,并将该交易传递给OuterMessageHandler 。邮件处理完毕后,如果失败,我们将执行ITransaction.CommitAsyncITransaction.Abort

OuterMessageHandler看起来像这样:

    public async Task Handle(ITransaction tx, params Envelope[] messages)
    {
        foreach (var msg in messages)
        {
            using (var scope = _scope.BeginLifetimeScope())
            {
                var contextProvider = scope.Resolve<MessageContextProvider>();

                contextProvider.Set(tx);

                await innerHandler.Handle(msg);
            }
        }
    }

MessageContextProvider如下所示:

internal class MessageContextProvider
{
    private ITransaction _tx;

    public void Set(ITransaction tx)
    {
        _tx = tx;
    }

    public ITransaction GetTransaction()
    {
        if (_tx == null)
            throw new Exception("ITransaction has not been set");

        return _tx;
    }
}

这是在Autofac上注册的:

         builder
            .Register(c =>
            {
                var context = c.Resolve<MessageContextProvider>();

                return context.GetTransaction();
            })
            .As<ITransaction>()
            .ExternallyOwned();

        builder
            .RegisterType<MessageContextProvider>()
            .AsSelf()
            .InstancePerLifetimeScope();

MessageContextProvider的存在仅仅是为了允许我们在所有ITransaction中使用InnerMessageHandler,就好像它只是一个普通的依赖项,而在每条消息中仍然只使用相同的事务。 ITransaction被标记为ExternallyOwned,这样OuterMessageHandler不会在我们在MessageProcessor内进行提交之前丢弃它。

InnerMessageHandler只是执行我们的业务逻辑的处理程序。

IReliableState有2种方法SomeType Find(long id)Update(long id, SomeType someType)

查找方法如下:

       var snapshotHandler = await _stateManager.GetOrAddAsync<IReliableDictionary<long, SomeType>>(SomeTypeKey);

        var snapshot = await snapshotHandler.GetOrAddAsync(
            _transaction,
            Id,
            new SomeType());

        return snapshot;

更新如下:

        var snapshotHandler = await _stateManager.GetOrAddAsync<IReliableDictionary<long, SomeType>>(SomeTypeKey);

        await snapshotHandler.SetAsync(_transaction, Id, snapshot);

当我们向该服务抛出很多请求时,所有副本的CPU使用率均保持在1%以下。大约一个小时后,其中一个副本达到大约30%-35%。当我们停止测试击中服务时(即服务现在处于闲置状态),CPU使用率仍保持在30%至35%之间。如果我们出现尖峰然后又降下来,那会很好,但是持续的高CPU使用率是问题所在。

根据我们的调查,我们仅用内存ConcurrentDictionary中的2个替换了2个IReliableState。这解决了问题。我们可以将其运行几个小时,而不会超过2%的CPU使用率。这显然不是解决方案,因为出于弹性的原因,我们需要保持内部状态。

我们已经使用PerfView和dotTrace来查看正在发生的事情,并且没有太多有效信息出现。

在这一点上,我认为这与我们使用IReliableDictionary或ITransaction的方式有关。任何人都有类似的问题吗?谁能说明我们可能在做错什么?

修改 ReliableState存储库之一(简称为state2)的Find方法略有不同。如下图所示:

       var snapshotHandler = await _stateManager.GetOrAddAsync<IReliableDictionary<long, SomeType2>>(SomeTypeKey2);

        var snapshot = await snapshotHandler.TryGetValueAsync(
            _transaction,
            Id);

        if(snapshot.HasValue)
             return snapshot.Value.Clone(); // Clone is a deep copy 

       var stateFromSomeApi = await someApi.GetStartState(id);

       await snapshotHandler.SetValue(_transaction, Id, stateFromSomeApi);

        return stateFromSomeApi ;

我们将其更改为执行以下操作:

    public async Task<SomeType2> Find(long id)
    {
        var snapshotHandler = await _stateManager.GetOrAddAsync<IReliableDictionary<long, SomeType2>>(
            SomeTypeKey2);

        var snapshot = await snapshotHandler.GetOrAddAsync(
            _transaction,
            id,
            await GetStartState(id));

       return snapshot.Clone();
    }

    private async Task<SomeType2> GetStartState(long id)
    {
        return await _someApi.GetStartState(id);
    }

我们将state2更改为GetOrAddAsync,它运行良好。为什么用TryGetValueAsyncSetValue而不是GetOrAddAsync会使线程挂得这么多?我们已经对其进行了测试,生产负载几乎增加了一倍,并且每个主副本上的CPU保持在5%以下。

0 个答案:

没有答案