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

标签 c# asp.net-web-api json.net

项目是基于 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 的内存中,但它似乎没有帮助

最佳答案

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

  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>));
    }
}

您曾提到您尝试使用 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/

相关文章:

c# - 如何将具有动态根名称的 json 转换为 C# 列表

c# - 转换后如何在图形对象中的任意位置绘制

c# - 为什么使用 JWT 时 UserManager.GetUserAsync 会返回 null?

asp.net-mvc - HTTP 错误 500.19 和错误代码 : 0x80070021

c# - 无法更新 JToken 值

c# - 在不声明模型类的情况下解析 JSON

c# - 这是使用和测试使用工厂模式的类的正确方法吗?

c# - C#数据库应用程序在清理表时不断崩溃(?)

c# - 如何在 Web API 中传递可选参数

Angular2 到 REST WebApi CORS 问题