Project是基于MVC WebAPI的。
我们将客户端的权限上下文作为请求声明头中的序列化JSON对象传递给API服务器。这不是一个巨大的对象:6个属性和一个基于枚举的键值对集合(此处最多6项)
对API的大量请求每分钟都会发生一次(有些更频繁),来自同一组客户端。可能有700-900个客户(而且还在增长),每个客户每分钟都在一遍又一遍地发送相同的索赔。
对于每个请求,代码的各个组件可能会对该对象进行5-6次反序列化。这种反序列化会导致服务器上的CPU大量消耗。
在内存中缓存这些反序列化的最佳方式是什么?一个带有序列化JSON字符串的键的静态Dictionary对象是否工作良好,或者搜索速度太慢,因为这些字符串的大小相当大?
编辑:每个控制器的每个操作都会通过该属性进行过滤,以确保调用具有适当的权限
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中的内存中,但这似乎对
这个问题似乎有两个方面:
- 标题在问什么
- 某些东西正在吞噬CPU周期;假设这是由于UserInformation实例的反序列化
对于1.,假设确实存在数量合理有限的UserInformation可能性(您在问题中提到了这一点),ConcurrentDictionary似乎符合要求;否则,您不仅要继续承担序列化成本,而且基本上会出现类似内存泄漏的情况。
如果你可以安全地做出假设,这里有一个例子:
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>));
}
}
你曾经提到过你试图使用ConcurrentDictionary,但它没有帮助。如果反序列化对象的性能击败了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的验证,看看周期是否仍然很高。
由于每个GET返回不同的结果,您可能需要实现自己的缓存,这并不难。您可以使用MemoryCache或HttpRuntime。缓存以存储所需的任何数据。文档底部有一个简单的示例。
每个进程都有一个缓存,因此,如果为多个工作进程配置了IIS,则每个进程都将拥有自己的缓存。
但通过这种方式,您可以在缓存中保存您想要的任何数据。然后在将数据返回到客户端之前,根据需要检索并操作它。
您只需要实现某种锁定,以确保同一个缓存项不会同时被多个线程写入。有关这方面的一些想法,请参阅此处。
旧答案:
如果每个用户都看到相同的数据,那么您可以使用Strathweb。CacheOutput。WebApi2,可在NuGet中获得。它可能适合你的需要。
它将根据发送的URL缓存结果。因此,如果为/api/getmydata
返回数据,则对/api/getmydata
的下一个调用将从缓存中获取数据。您设置了缓存过期时间。
你用CacheOutputAttribute:装饰你的动作
[CacheOutput(ServerTimeSpan = 100)]
public List<string> GetMyData() {
...
}
但是,如果一个操作可以根据用户的身份返回不同的数据,那么这就不那么容易了。