Project是基于MVC WebAPI的。
我们将客户端的权限上下文作为请求的声明标头中的序列化JSON对象传递给我们的API服务器。这不是一个大对象:6个属性和一个基于枚举的键值对的集合(这里最多6个项目)
绝大多数API请求都是从同一组客户端每分钟(更频繁地)发生的。可能是700-900个客户(并且还在增长),每个客户每分钟都会反复发送相同的声明。
对于每个请求,代码的各个组件可能会反序列化此对象5-6次。这种反序列化会导致服务器上的CPU耗尽。
在内存中缓存这些反序列化的最佳方法是什么?具有键的静态Dictionary对象是序列化的JSON字符串,运行良好还是通过它搜索太慢,因为这些字符串的大小会相当大?
编辑: 每个控制器的每个Action都通过此属性进行过滤,以确保调用具有适当的权限
public class AccountResolveAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext context)
{
var controller = (ControllerBase) context.ControllerContext.Controller;
var identity = (ClaimsIdentity) controller.User.Identity;
var users = identity.Claims
.Where(c => c.Type == ClaimTypes.UserData.ToString())
.Select(c => JsonConvert.DeserializeObject<UserInformation>(c.Value))
.ToList();
var accountId = controller.ReadAccountIdFromHeader();
if (users.All(u => u.AccountId != accountId))
{
throw new ApplicationException(string.Format("You have no rights for viewing of information on an account Id={0}", accountId));
}
}
}
在基本控制器中也有调用询问声明的调用,但AccountResolve可能会将第一次反序列化的结果缓存到控制器中,以便这些调用不会再次尝试反序列化。但是,声明一遍又一遍,我只是想找到一种优化方法,不要反复反序列化相同的字符串。我已经尝试将序列化字符串作为键和结果对象缓存到全局静态ConcurrentDictionary的内存中,但它似乎没有帮助
答案 0 :(得分:1)
这个问题似乎有两个方面:
对于1.,似乎ConcurrentDictionary符合该法案,假设确实存在合理有限数量的UserInformation可能性(你在问题中提到这一点);否则,你不仅会继续采用序列化成本,而且基本上会有一些看起来像内存泄漏的东西。
如果您可以安全地做出假设,这是一个例子:
public static class ClaimsIdentityExtensions
{
private static readonly ConcurrentDictionary<string, UserInformation> CachedUserInformations = new ConcurrentDictionary<string, UserInformation>();
public static IEnumerable<UserInformation> GetUserInformationClaims(this ClaimsIdentity identity)
{
return identity
.Claims
.Where(c => c.Type == ClaimTypes.UserData)
.Select(c => CachedUserInformations.GetOrAdd(
c.Value,
JsonConvert.DeserializeObject<UserInformation>));
}
}
您曾提到您尝试使用ConcerrentDictionary,但它没有帮助。如果反序列化对象的性能击败ConcurrentDictionary中的查找(再次,做出上述假设),即使键是“长”字符串,我也会感到震惊。如果没有UserInformation类的示例,很难从我们的结果中100%确定...但是,这里有一个示例,表明给定具有AccountId属性的UserInformation,ConcurrentDictionary方法通过以下方式击败了强制 - 反序列化方法一个数量级:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using Newtonsoft.Json;
namespace ConsoleApplication2
{
public class UserInformation
{
public int AccountId { get; set; }
}
public static class ClaimsIdentityExtensions
{
private static readonly ConcurrentDictionary<string, UserInformation> CachedUserInformations = new ConcurrentDictionary<string, UserInformation>();
public static IEnumerable<UserInformation> GetUserInformationClaims(this ClaimsIdentity identity, bool withConcurrentDictionary)
{
if (withConcurrentDictionary)
{
return identity
.Claims
.Where(c => c.Type == ClaimTypes.UserData)
.Select(c => CachedUserInformations.GetOrAdd(
c.Value,
JsonConvert.DeserializeObject<UserInformation>));
}
return identity
.Claims
.Where(c => c.Type == ClaimTypes.UserData)
.Select(c => JsonConvert.DeserializeObject<UserInformation>(c.Value));
}
}
class Program
{
static void Main()
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.UserData, "{AccountId: 1}"),
new Claim(ClaimTypes.UserData, "{AccountId: 2}"),
new Claim(ClaimTypes.UserData, "{AccountId: 3}"),
new Claim(ClaimTypes.UserData, "{AccountId: 4}"),
new Claim(ClaimTypes.UserData, "{AccountId: 5}"),
});
const int iterations = 1000000;
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; ++i)
{
identity.GetUserInformationClaims(withConcurrentDictionary: true).ToList();
}
Console.WriteLine($"With ConcurrentDictionary: {stopwatch.Elapsed}");
stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; ++i)
{
identity.GetUserInformationClaims(withConcurrentDictionary: false).ToList();
}
Console.WriteLine($"Without ConcurrentDictionary: {stopwatch.Elapsed}");
}
}
}
输出:
With ConcurrentDictionary: 00:00:00.8731377
Without ConcurrentDictionary: 00:00:05.5883120
一种快速的方法可以知道UserInformation实例的反序列化是否是造成可疑的高CPU周期的原因,请尝试注释掉并根据UserInformation删除任何验证并查看周期是否仍然很高。
答案 1 :(得分:0)
由于每个GET都会返回不同的结果,因此您可能需要实现自己的缓存,这并不是非常困难。您可以使用MemoryCache或HttpRuntime.Cache来存储您想要的任何数据。文档底部有一个简单的例子。
每个进程都有一个缓存,因此如果为多个工作进程配置了IIS,则每个进程都将拥有自己的缓存。
但是这样,你可以在缓存中保存你想要的任何数据。然后在将数据返回给客户端之前检索它并操作它。
您只需要实现某种锁定,以确保多个线程不会同时写入相同的缓存项。有关这方面的一些想法,请参阅here。
旧答案:
如果每个用户都看到相同的数据,那么您可以使用NuGet中提供的Strathweb.CacheOutput.WebApi2。它可能符合您的需求。
它将根据发送的URL缓存结果。因此,如果为/api/getmydata
返回数据,则对/api/getmydata
的下一次调用将从缓存中获取数据。您设置缓存过期。
使用CacheOutputAttribute:
装饰您的操作[CacheOutput(ServerTimeSpan = 100)]
public List<string> GetMyData() {
...
}
但是,如果一个动作可以根据用户的身份返回不同的数据,那么这将不会那么容易。