我们有一个运行良好的Service Fabric StatefulService
。它使用消息,对其进行处理,并具有两个IReliableState
用于存储每条消息中的一些数据。每个副本每分钟将处理大约500条消息。
对于进入MessageProcessor
的每条消息,我们使用包裹在using块中的ITransaction
创建一个新的IReliableStateManager
,并将该交易传递给OuterMessageHandler
。邮件处理完毕后,如果失败,我们将执行ITransaction.CommitAsync
或ITransaction.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
,它运行良好。为什么用TryGetValueAsync
和SetValue
而不是GetOrAddAsync
会使线程挂得这么多?我们已经对其进行了测试,生产负载几乎增加了一倍,并且每个主副本上的CPU保持在5%以下。