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

Performance issue while deserializing large collection of user defined object from Redis via protobufnet

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

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

为了从我的应用程序中与 Redis 对话,我使用 StackExchange.Redis

为了 serialize/deserialize 收到的字节 to/from 服务器 from/to 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.
    }
}

Users 是在我的上下文包装器 class 中公开的 属性。

这是我为用户准备的 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; }

    .
    .
    .
    .
}

这是我在 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();
    }
}

在获取时 - 将字节转换为 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);
}

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

如您所见,将字节反序列化为用户字典(Dictionary Users)所需的平均时间约为 1.5 到 2 秒,这太多了,因为我在很多地方都使用 属性从该字典中获取用户信息。

你能告诉我我做错了什么吗?

每次从Redis中反序列化100,000个用户列表到应用程序中然后使用它好吗? (每个请求都必须进一步反序列化用户 属性 用于处理请求的地方)。

将用户的 dictionary/collection/list 或任何其他大型集合以字节为单位存储到 Redis 中,然后在每次我们必须使用它时通过反序列化取回它是否正确?

根据以下post Does Stack Exchange use caching and if so, how? 我了解到 StackExchange 大量使用 Redis。我相信我的 100,000 个用户要少得多,而且它的大小(大约 60-80 MB)也远小于 StackExchange 和其他网站(FB 等)所拥有的。为什么 Whosebug 如此快速地反序列化如此大的 users/top 问题列表和许多其他项目(在缓存中)?

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

当我使用 HttpRuntime.Cache 作为缓存提供程序时,list/dictionary 没有问题,但是当我切换到 Redis 时,反序列化部分导致了阻碍,因为它仍然很慢。

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

是的,如果您尝试传输包含许多对象的大型集合,您将始终需要为整个图形支付带宽 + 反序列化成本。这里的关键是:不要那样做。每个请求多次获取 100,000 个用户的列表 似乎完全没有必要,而且非常有性能瓶颈。

有两种常见的方法:

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

这两种方法都可以,您更喜欢哪种方法可能取决于您的请求率与数据更改率以及您要求数据的最新程度等因素。例如,对于第二种方法,您可以考虑使用 redis hash,其中的密钥与您现在使用的非常相似,散列槽密钥是 int(或那里的一些字符串/二进制表示形式) -of),哈希槽值是 single DyveUser 实例的序列化形式。在这里使用 hash 的优点(与每个用户的字符串相反)是您仍然可以通过 redis 哈希命令(hgetall, 例如)。 SE.Redis 中带有 Hash* 前缀的所有必需的哈希操作都可用。