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



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中的内存中,但这似乎对

没有帮助

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

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

你曾经提到过你试图使用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() {
    ...
}

但是,如果一个操作可以根据用户的身份返回不同的数据,那么这就不那么容易了。

相关内容

  • 没有找到相关文章

最新更新