protobuf-net:如何在用户会话中存储

时间:2011-12-13 17:20:43

标签: c# .net caching protobuf-net

我目前能够将我创建的对象存储到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类的包装器,它基本上是一个包含内置类型(字符串,整数)的列表。我已经创建了一个包装器来将时间戳与我的对象相关联。

如果我有会话缓存未命中(会话密钥为空或session.lastUpdated大于全局存储时间),我执行正常的数据库查找(MSSQL),创建MenuList对象并存储它进入会议,就像这样

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

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

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

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

此时我只是查看可用的选项,因为我希望将来能够更加智能和严格地使用会话缓存。

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

1 个答案:

答案 0 :(得分:4)

请注意,在这里使用protobuf-net 主要是只有在你想要在某个时候转移到持久状态提供者时才有意义。

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

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

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

protobuf-net最终是一个序列化层。标准会话状态实现的另一个特性是因为它最初是用BinaryFormatter编写的,它假定对象可以在没有任何额外上下文的情况下反序列化。然而,protobuf-net(就像XmlSerializerDataContractSerializerJavaScriptSerializer)没有绑定任何特定的类型系统 - 它采取的方法"你告诉我你是什么类型的希望我填充,我会担心数据"。这实际上是一个非常的事情,因为我发现在发布新版本时被BinaryFormatter杀死的网络服务器,因为有人拥有 audacity 触摸甚至轻微与持久化会话中存储的对象相关的其中一种类型。 BinaryFormatter不喜欢这样; 尤其是如果你(喘气)重命名一个类型,或者( shock )从一个字段+属性到一个自动实现的属性。提示:这些是谷歌设计的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希望提前知道 数据的样子(即public object Value {get;set;}会很痛苦),并且不会处理循环图(尽管有适当的条款来支持这两种情况)。一般来说,如果您可以使用XmlSerializerDataContractSerializer等序列化数据,则可以使用protobuf-net轻松序列化; protobuf-net也支持其他方案,但没有公开保证序列化每个任意数据模型。根据DTO进行思考将使生活更轻松。在大多数情况下,这并不是所有的问题,因为大多数人都有合理的数据。有些人没有有合理的数据,我只想适当地设定期望!

就个人而言,正如我所说 - 尤其是当大型对象可以参与其中时,我根本不是内置会话状态模式的粉丝。我可能建议的是使用单独的每个密钥数据存储(意思是:每个用户每个密钥一个记录,而不是每个用户只有一个记录) - 可能仅适用于较大的对象,也许适用于所有内容。这可能是SQL Server,或类似redis / memcached。如果您使用期望使用会话状态的第三方控件(webforms等),这显然有点痛苦,但如果您在代码中手动使用状态,则实现起来非常简单。 FWIW,BookSleeve与redis相结合,可以很好地处理这类事情,并提供对基于byte[]的存储的良好访问。从byte[]开始,您可以反序列化对象,如上所示。

无论如何 - 我会在那里停下来,以防我离主题太远;随时回答任何问题,但执行摘要:

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