【问题标题】:protobuf-net: how to store in the users sessionprotobuf-net:如何存储在用户会话中
【发布时间】:2011-12-14 16:27:43
【问题描述】:

我目前能够将我创建的对象存储到HttpContext.Current.Session,并且我遇到了protobuf-net。有没有办法通过使用 protobuf 序列化对象来存储我的对象?

看起来 protobuf 想要将信息存储到 Stream 中,那么我应该(可以吗?)将 Stream 对象存储到用户会话中?或者我应该先将它从Stream 转换为另一种对象类型?如果是这样,转换序列化对象是否会绕过使用protobuf的原始目的(cpu使用率,内存使用率)?以前有人做过吗?

我的目标是使用 protobuf 作为压缩层,将信息存储到用户会话中。是否有更好的方法(更小的尺寸、更快的压缩、更易于维护、更小的实现开销)来执行此操作,或者 protobuf 是否适合此任务?


更新

我正在使用这个类对象

[Serializable]
public class DynamicMenuCache
{
    public System.DateTime lastUpdated { get; set; }
    public MenuList menu { get; set; }
}

这个类是我的 MenuList 类的包装器,它(基本上)是一个包含内置类型(字符串、整数)的列表列表。我创建了包装器以将时间戳与我的对象相关联。

如果我有一个会话缓存未命中(会话键为 null 或 session.lastUpdated 大于全局存储时间),我会进行正常的数据库查找 (MSSQL),创建 MenuList 对象,并将其存储到会话,像这样

HttpContext.Current.Session.Add("DynamicMenu" + MenuType, new DynamicMenuCache()
{
    lastUpdated = System.DateTime.Now,
    menu = Menu
});

目前我们的会话存储在内存中,但将来我们可能会转移到数据库会话存储中。

我们的会话使用量非常大,因为我们在其中存储了许多大对象(尽管我希望在将来某个时候清理我们存储在会话中的内容)。

例如,我们将每个用户的权限集存储到他们的会话存储中,以避免数据库命中。目前有很多权限和权限存储结构存储到会话中。

此时我只是查看可用的选项,因为我想在未来更智能、更严格地使用会话缓存。

如果您还有其他需要,请告诉我。

【问题讨论】:

  • 我想这取决于你的状态对象的复杂性/大小,对象越大,你就越能从 protobuf 中受益。对于简单的对象,双重序列化可能是一种惩罚。编辑:您可以针对不同的会话对象对 memcached + protobuff 实现与经典的 asp.net 会话状态服务器进行基准测试,这会很有趣:)
  • 您好 JesseB - 您目前的设置是什么?您当前使用的是哪个会话提供程序?在记忆中? SQL?或者...?另外:您的会话使用范围有多广?另外,您在会话中存储了哪些类型的东西? (这样我才能给出最合适的指导)
  • 刚刚注意到更新(注意:我没有收到自动通知 - 您可能需要添加 @marc 评论以便我看到它;p)。有点晚了,很累 - 我可以保证明天阅读并回复吗?
  • @MarcGravell 哎呀我的错;p 是的,这很酷。我还在research phase 中,所以暂时没有什么是一成不变的。我只是在四处寻找一些可以很好用的东西。

标签: c# .net caching protobuf-net


【解决方案1】:

请注意,这里主要使用 protobuf-net 仅在您考虑在某个时候迁移到持久状态提供程序时才有意义。

首先,由于您目前使用的是内存中(因此类型未序列化,AFAIK),一些关于更改会话以使用任何类型的基于序列化的提供程序的注意事项:

  • 类型必须可由提供程序序列化(听起来很明显,但如果您有圆形图等,这会产生特别的影响)
  • 因为数据是序列化的,所以语义不同;您每次都会得到一个副本,这意味着您在请求期间所做的任何更改都会丢失 - 只要您确保再次明确地重新存储数据就可以了,并且可以避免一些线程问题- 双刃剑
  • 内置状态机制通常将会话检索为 single 操作 - 如果(正如您提到的)您有一些大对象,这可能是一个问题;与 protobuf-net 无关,但我曾经被要求调查一个垂死的服务器,结果证明它是一个处于杀死系统状态的多 MB 对象,因为 每个 请求(即使那些不是使用那条数据)导致这个巨大的物体在网络上(双向)传输

在许多方面,我实际上 根本不是标准会话状态模型的粉丝 - 这是在我谈到它与 protobuf-net 的关系之前!

protobuf-net 最终是一个序列化层。标准会话状态实现的另一个特点是因为它最初是用BinaryFormatter 编写的,它假定对象可以在没有任何额外上下文的情况下进行反序列化。然而,protobuf-net(就像XmlSerializerDataContractSerializerJavaScriptSerializer)不依赖于任何特定的类型系统——它采用的方法是“你告诉我你想让我填充什么类型,我会担心关于数据”。这实际上是一件非常的事情,因为我在发布新版本时看到了 BinaryFormatter 杀死的网络服务器,因为有人有 大胆 去触摸 甚至是碰巧与存储在持久会话中的对象相关的类型之一。 BinaryFormatter 不喜欢那样; 尤其是如果你(喘气)重命名一个类型,或者(shock)从一个字段+属性到一个自动实现的属性。提示:这些是 google 设计 protobuf 来避免的问题。

但是!这确实意味着与标准会话状态模型一起使用并不非常方便。我之前已经实现了将类型名称编码到流中的系统(例如,我为 protobuf-net 编写了一个 enyim/memcached 转码器),但是......它并不漂亮。 IMO,更好的方法是将知道数据是什么的负担转移给调用者。我的意思是,真的......调用者应该知道他们期望在任何给定键中的数据类型,对吗?

一种方法是存储byte[]。几乎任何状态实现都可以处理 BLOB。如果它无法处理,只需使用Convert.ToBase64String / Convert.FromBase64String 存储string - 任何不处理string 的实现都需要射击!要与流一起使用,您可以执行以下操作(此处为伪代码):

public static T GetFromState<T>(string key) {
    byte[] blob = {standard state provider get by key}
    using(var ms = new MemoryStream(blob)) {
        return Serializer.Deserialize<T>(ms);
    }
}

(添加类似)

请注意,protobuf-net 与 BinaryFormatter 不同 - 他们对合理的期望有不同的期望,例如 默认情况下 protobuf-net 希望提前知道 strong> 数据是什么样的(即public object Value {get;set;} 会很痛苦),并且不处理圆形图(尽管有支持这两种情况的规定)。作为一般经验法则:如果您可以使用 XmlSerializerDataContractSerializer 之类的东西序列化您的数据,那么它将使用 protobuf-net 轻松序列化; protobuf-net 也支持其他场景,但不保证序列化每个任意数据模型。从 DTO 的角度思考会让生活更轻松。在大多数情况下,这根本不是问题,因为大多数人都有合理的数据。有些人没有合理的数据,我只是想适当地设定期望!

不过,就我个人而言,正如我所说 - 尤其是,当涉及到大型对象时,我根本不喜欢内置会话状态模式。我可能建议改为使用单独的每个键数据存储(意思是:每个用户每个键一个记录,而不是每个用户只有一个记录) - 可能只适用于较大的对象,也可能适用于所有内容.这可能是 SQL Server,或者类似 redis/memcached 的东西。如果您使用期望使用会话状态的 3rd-party 控件(网络表单等),这显然有点痛苦,但如果您在代码中手动使用状态,则实现起来非常简单。 FWIW,BookSleeve 耦合到 redis 非常适合这样的事情,并提供对基于byte[] 的存储的良好访问。从byte[],您可以反序列化对象,如上所示。

无论如何 - 我将停在那里,以防我离题太远;如有任何问题,请随时回复,但执行摘要:

  • protobuf-net 可以阻止您在BinaryFormatter 中看到的许多版本控制 问题
  • 但它不一定是直接的 1:1 交换,因为 protobuf-net 不编码“类型”信息(这是内置会话机制所期望的)
  • 它可以工作,最常见的是byte[]
  • 但如果您要存储大型对象,您可能会遇到与会话状态工作方式有关的其他问题(与 protobuf-net 无关)
  • 特别是对于较大的对象,我建议使用您自己的机制(即不是会话状态);键值存储系统(redis、memcached、AppFabric 缓存)可以很好地解决这个问题

【讨论】:

  • 很棒的解释!阅读完这篇文章然后用我的代码进行试验后,我有几个问题。我应该注意哪些技术来改善 ser/deser 时间?我可以看到 protobuf 在看似随机的场合消耗大量(相对)时间。我已经阅读了this,但我将不得不在每次页面加载时调用它。这听起来像是你将重复调用 ser/deser 背靠背的时候。此外,protobuf 也使用了更少的内存,但你能给出任何提示来最大限度地减少内存吗?提前致谢!你摇滚!
  • @JesseB 你不需要在每次页面加载时调用它;只有一次 - 甚至是可选的(在 v1 和 v2 中,默认情况下它会按需编译)。恐怕“最大限度地减少”取决于数据 - 对原语列表使用“打包”编码会有所帮助(请参阅:[ProtoMember] 上的IsPacked),并在子对象上使用“分组”编码(或者个人或列表)可以帮助序列化时间。但是要给出一个具体的答案,我需要一个非常具体的场景。否则它类似于“我怎样才能让我的 C# 更快?”
  • 我刚刚通读了code.google.com/apis/protocolbuffers/docs/encoding.html,这有助于理解信息的编码方式。我确实注意到在第一次使用后开销似乎消失了,所以它在第一次使用时编译是有道理的。再次感谢您帮助我理解这一切!也感谢您移植这个很棒的序列化程序!你至少应该得到一个高五,或者更坏的东西;p
猜你喜欢
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-25
  • 1970-01-01
  • 1970-01-01
  • 2017-12-22
相关资源
最近更新 更多