在内存中缓存json.net序列化结果的最佳方法是什么?

Igo*_*rek 9 c# json.net asp.net-web-api

项目是基于MVC WebAPI的.

我们将客户端的权限上下文作为请求的声明头中的序列化JSON对象传递给我们的API服务器.这不是一个大对象:6个属性和一个基于枚举的键值对的集合(这里最多6个项目)

绝大多数API请求都是从同一组客户端每分钟(更频繁地)发生的.可能是700-900个客户(并且正在增长),每个客户每分钟都会反复发送相同的声明.

对于每个请求,代码的各个组件可能会反序列化此对象5-6次.这种反序列化会导致服务器上的CPU流量大量增加.

将这些反序列化缓存在内存中的最佳方法是什么?具有键的静态Dictionary对象是序列化JSON字符串,运行良好还是通过它搜索太慢,因为这些字符串的大小会相当大?

编辑:每个控制器的每个操作都通过此属性进行过滤,以确保调用具有适当的权限

    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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在基本控制器中也有询问索赔的调用,但AccountResolve可能会将第一个反序列化的结果缓存到控制器中,以便这些调用不会再次尝试反序列化.但是,声明一遍又一遍,我只是想找到一种优化方法,不要反复反序列化相同的字符串.我已经尝试将序列化字符串作为键和结果对象缓存到全局静态ConcurrentDictionary的内存中,但它似乎没有帮助

Raf*_*man 1

这个问题似乎有两个方面:

  1. 标题想问什么
  2. 有什么东西正在消耗 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>));
    }
}
Run Code Online (Sandbox Code Playgroud)

您曾提到您尝试使用 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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

With ConcurrentDictionary: 00:00:00.8731377
Without ConcurrentDictionary: 00:00:05.5883120
Run Code Online (Sandbox Code Playgroud)

了解 UserInformation 实例的反序列化是否是可疑的高 CPU 周期的原因的一种快速方法是,尝试注释掉并删除针对 UserInformation 的任何验证,并查看周期是否仍然很高。