项目是基于 MVC WebAPI 的。
我们将客户端的权限上下文作为请求的声明 header 中的序列化 JSON 对象传递给我们的 API 服务器。这不是一个巨大的对象:6 个属性和一个基于枚举的键值对集合(此处最多 6 项)
绝大多数对 API 的请求每分钟(有些更频繁)来自同一组客户端。可能有 700-900 个客户(并且还在增加),每个客户每分钟都在一遍又一遍地发送相同的 claim 。
对于每个请求,代码的各个组件可能会反序列化该对象 5-6 次。这种反序列化会导致服务器上出现大量 CPU 消耗。
在内存中缓存这些反序列化的最佳方式是什么?具有序列化 JSON 字符串的键的静态字典对象是否可以很好地工作,或者搜索它会太慢,因为这些字符串的大小相当大?
编辑: 每个 Controller 的每个操作都通过此属性进行过滤,以确保调用具有适当的权限
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));
}
}
}
基础 Controller 中也有查询声明的调用,但 AccountResolve 可能会将第一次反序列化的结果缓存到 Controller 中,以便这些调用不会再次尝试反序列化。但是,声明一遍又一遍地相同,我只是想找到一种优化方法,不要一次又一次地反序列化同一个字符串。我已经尝试将序列化字符串作为键和结果对象缓存到全局静态 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>));
}
}
您曾提到您尝试使用 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 的任何验证,看看周期是否仍然很高。
关于c# - 在内存中缓存 json.net 序列化结果的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37007907/