从Redis通过protobufnet反序列化大量用户定义对象时的性能问题

Rag*_*hav 1 c# protobuf-net redis stackexchange.redis

问题:在反序列化从Redis接收的字节时面临性能下降.

我正在使用REDIS在我的ASP.NET Web应用程序中分发缓存.

为了与我的应用程序中的Redis交谈,我正在使用StackExchange.Redis.

为了序列化/反序列化从/向DTO接收/从服务器接收的字节,我使用的是protobuf-net

我的目标是将100,000个用户的字典(Dictionary(int,User))存储到Redis中,并在单个请求中多次检索它.

该字典将驻留在MyContext.Current.Users属性下.该字典的键是用户ID,值是完整的dto.我现在面临的问题是,从字节反序列化100,000个用户需要1.5-2秒(Redis给我字节).我必须在我的请求中多次使用该属性.

public Dictionary<int, User> Users
{
    get
    {
        // Get users from Redis cache.
        // Save it in Redis cache if it is not there before and then get it.
    }
}
Run Code Online (Sandbox Code Playgroud)

用户是在我的上下文包装器类中公开的属性.

这是我为用户提供的DTO(此DTO拥有超过100个属性):

[ProtoContract]
public class User
{
    [ProtoMember(1)]
    public string UserName { get; set; }

    [ProtoMember(2)]
    public string UserID { get; set; }

    [ProtoMember(3)]
    public string FirstName { get; set; }

    .
    .
    .
    .
}
Run Code Online (Sandbox Code Playgroud)

以下是我在StackExchange.Redis的帮助下与Redis交谈时使用的代码片段:

在存储时 - 将我的DTO转换为字节,以便将其存储到Redis中:

db.StringSet(cacheKey,bytes,slidingExpiration)

命令:

private byte[] ObjectToByteArrayFromProtoBuff(Object obj)
{
    if (obj == null)
    {
        return null;
    }

    using (MemoryStream ms = new MemoryStream())
    {
        Serializer.Serialize(ms, obj);
        return ms.ToArray();
    }
}
Run Code Online (Sandbox Code Playgroud)

在获取时 - 将字节转换为DTO,从中接收字节

db.StringGet(cacheKey);

命令:

private T ByteArrayToObjectFromProtoBuff<T>(byte[] arrBytes)
{
    if (arrBytes != null)
    {
        using (MemoryStream ms = new MemoryStream(arrBytes))
        {
            var obj = Serializer.Deserialize<T>(ms);
            return obj;
        }
    }
    return default(T);
}
Run Code Online (Sandbox Code Playgroud)

下面是ANTS Performance Profiler的屏幕截图,显示了protobuf-net从Redis提供的字节中反序列化100,000个用户所花费的时间.

在此输入图像描述

正如您所看到的,将字节反序列化为用户词典(字典用户)的平均时间大约是1.5到2秒,这太多了,因为我在很多地方使用该属性来从该字典中获取用户信息.

你能让我知道我在这里做错了什么吗?

每次将Redis的100,000个用户列表反序列化到应用程序然后使用它是否那么好?(每个请求还必须反序列化用户属性用于处理请求的位置).

以字节为单位将字典/集合/用户列表或任何其他大型集合存储到Redis中是否正确,然后每次必须使用它时通过反序列化将其恢复?

根据以下帖子Stack Exchange使用缓存,如果是,如何? 我知道StackExchange大量使用Redis.我相信我的100,000个用户远远少于它的大小(大约60-80 MB)远远小于StackExchange和其他站点(FB等)的用户.为什么StackOverflow会如此快速地反序列化如此庞大的用户/热门问题和许多其他项目(在缓存中)?

我不能在缓存下使用DTO的100,000个用户的字典(该列表中的每个项目具有超过100个属性)并在单个请求或每个请求中多次反序列化它?

当我使用HttpRuntime.Cache作为缓存提供程序时,我对该列表/字典没有任何问题,但是当我切换到Redis时,反序列化部分导致阻塞,因为它仍然很慢.

我想在这篇文章中再添加一个细节.以前我使用BinaryFormatter来反序列化该列表,它比我现在使用的protobufnet慢了近10倍.但是,即使使用protobufnet,平均需要1.5到2秒来从字节中反序列化这些用户仍然很慢,因为该属性必须在代码中多次使用.

Mar*_*ell 5

是的,如果您尝试传输大量对象的大量集合,则始终必须为整个图形支付带宽+反序列化成本.关键在于:不要这样做.每个请求多次获取100,000个用户的列表似乎完全没有必要,而且是一个性能瓶颈.

有两种常见的方法:

  • 使用大对象(the Dictionary<,>),但只是偶尔获取它 - 如:在后台,每5分钟,或者如果你知道它已经通过pub/sub更改
  • 只处理每个请求所需的谨慎对象,并将其余部分留在redis服务器上; 每个请求最多只能获取一次

两种方法都没问题,您更喜欢哪种方法可能取决于您的请求率与数据更改率以及您需要数据的最新情况.例如,对于第二种方法,您可以考虑使用redis 哈希,其中密钥与您现在使用的非常相似,哈希槽键是int(或某些字符串/二进制表示),以及哈希 - slot值是单个 DyveUser实例的序列化形式.在这里使用哈希(而不是每用户字符串)的优点是你仍然可以通过redis哈希命令(hgetall例如)一次获取/清除/ etc所有用户.所有必需的哈希操作都在带有Hash*前缀的SE.Redis中可用.