分布式缓存中的“真实”对象引用?

时间:2009-03-31 15:45:27

标签: distributed-cache

我个人致力于.net分布式缓存解决方案,但我认为这个问题在所有平台上都很有趣。

是否存在分布式缓存解决方案(或通用策略),它允许在缓存中存储对象,同时保持它们之间引用的完整性?

举例说明 - 假设我有一个引用对象Foo foo的对象Bar bar以及引用相同Foo foo2的对象Bar bar。如果我将foo加载到缓存中,则会同时存储bar的副本。如果我还将foo2加载到缓存中,则会同时存储bar的单独副本。如果我在缓存中更改foo.bar,则更改不会影响foo2.bar :(

是否存在现有的分布式缓存解决方案,可让我将foofoo2bar加载到缓存中,同时保留foo.bar foo2.bar引用?

1 个答案:

答案 0 :(得分:3)

首先

我不知道任何分布式系统,我不假装构建一个。这篇文章解释了如何使用带有可序列化对象的IObjectReference接口模拟.NET和C#的这种行为。

现在,继续播放

我不知道这样的分布式系统,但您可以使用IObjectReference接口轻松地使用.NET。您对ISerializable.GetObjectData的实现需要调用SerializationInfo.SetType来指出实现IObjectReference的代理类,并且能够(在GetObjectData方法提供的数据的帮助下)获取对真实对象的引用应该使用。

示例代码:

[Serializable]
internal sealed class SerializationProxy<TOwner, TKey> : ISerializable, IObjectReference {
    private const string KeyName = "Key";
    private const string InstantiatorName = "Instantiator";
    private static readonly Type thisType = typeof(SerializationProxy<TOwner, TKey>);
    private static readonly Type keyType = typeof(TKey);

    private static readonly Type instantiatorType = typeof(Func<TKey, TOwner>);
    private readonly Func<TKey, TOwner> _instantiator;
    private readonly TKey _key;

    private SerializationProxy() {
    }

    private SerializationProxy(SerializationInfo info, StreamingContext context) {
        if (info == null) throw new ArgumentNullException("info");

        _key = (TKey)info.GetValue(KeyName, keyType);
        _instantiator = (Func<TKey, TOwner>)info.GetValue(InstantiatorName, instantiatorType);
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
        throw new NotSupportedException("This type should never be serialized.");
    }

    object IObjectReference.GetRealObject(StreamingContext context) {
        return _instantiator(_key);
    }

    internal static void PrepareSerialization(SerializationInfo info, TKey key, Func<TKey, TOwner> instantiator) {
        if (info == null) throw new ArgumentNullException("info");
        if (instantiator == null) throw new ArgumentNullException("instantiator");

        info.SetType(thisType);
        info.AddValue(KeyName, key, keyType);
        info.AddValue(InstantiatorName, instantiator, instantiatorType);
    }
}

将使用GetObjectData方法使用SerializationProxy.PrepareSerialization(info,myKey,myKey =&gt; LoadedInstances.GetById(myKey))调用此代码,并且您的LoadedInstances.GetById应从Dictionary&lt; TKey,WeakReference&gt;返回实例。或者如果尚未加载,则从缓存/数据库加载它。

编辑:

我写了一些示例代码来说明我的意思。

public static class Program {
    public static void Main() {
        // Create an item and serialize it.
        // Pretend that the bytes are stored in some magical
        // domain where everyone lives happily ever after.
        var item = new Item { Name = "Bleh" };
        var bytes = Serialize(item);

        {
            // Deserialize those bytes back into the cruel world.
            var loadedItem1 = Deserialize<Item>(bytes);
            var loadedItem2 = Deserialize<Item>(bytes);

            // This should work since we've deserialized identical
            // data twice.
            Debug.Assert(loadedItem1.Id == loadedItem2.Id);
            Debug.Assert(loadedItem1.Name == loadedItem2.Name);

            // Notice that both variables refer to the same object.
            Debug.Assert(ReferenceEquals(loadedItem1, loadedItem2));

            loadedItem1.Name = "Bluh";
            Debug.Assert(loadedItem1.Name == loadedItem2.Name);
        }

        {
            // Deserialize those bytes back into the cruel world. (Once again.)
            var loadedItem1 = Deserialize<Item>(bytes);

            // Notice that we got the same item that we messed
            // around with earlier.
            Debug.Assert(loadedItem1.Name == "Bluh");

            // Once again, force the peaceful object to hide its
            // identity, and take on a fake name.
            loadedItem1.Name = "Blargh";

            var loadedItem2 = Deserialize<Item>(bytes);
            Debug.Assert(loadedItem1.Name == loadedItem2.Name);
        }
    }

    #region Serialization helpers
    private static readonly IFormatter _formatter
        = new BinaryFormatter();

    public static byte[] Serialize(ISerializable item) {
        using (var stream = new MemoryStream()) {
            _formatter.Serialize(stream, item);
            return stream.ToArray();
        }
    }

    public static T Deserialize<T>(Byte[] bytes) {
        using (var stream = new MemoryStream(bytes)) {
            return (T)_formatter.Deserialize(stream);
        }
    }
    #endregion
}

// Supercalifragilisticexpialidocious interface.
public interface IDomainObject {
    Guid Id { get; }
}

// Holds all loaded instances using weak references, allowing
// the almighty garbage collector to grab our stuff at any time.
// I have no real data to lend on here, but I _presume_ that this
// wont be to overly evil since we use weak references.
public static class LoadedInstances<T>
    where T : class, IDomainObject {

    private static readonly Dictionary<Guid, WeakReference> _items
        = new Dictionary<Guid, WeakReference>();

    public static void Set(T item) {
        var itemId = item.Id;
        if (_items.ContainsKey(itemId))
            _items.Remove(itemId);

        _items.Add(itemId, new WeakReference(item));
    }

    public static T Get(Guid id) {
        if (_items.ContainsKey(id)) {
            var itemRef = _items[id];
            return (T)itemRef.Target;
        }

        return null;
    }
}

[DebuggerDisplay("{Id} {Name}")]
[Serializable]
public class Item : IDomainObject, ISerializable {
    public Guid Id { get; private set; }
    public String Name { get; set; }

    // This constructor can be avoided if you have a 
    // static Create method that creates and saves new items.
    public Item() {
        Id = Guid.NewGuid();
        LoadedInstances<Item>.Set(this);
    }

    #region ISerializable Members
    public void GetObjectData(SerializationInfo info, StreamingContext context) {
        // We're calling SerializationProxy to call GetById(this.Id)
        // when we should be deserialized. Notice that we have no
        // deserialization constructor. Fxcop will hate us for that.
        SerializationProxy<Item, Guid>.PrepareSerialization(info, Id, GetById);
    }
    #endregion

    public static Item GetById(Guid id) {
        var alreadyLoaded = LoadedInstances<Item>.Get(id);
        if (alreadyLoaded != null)
            return alreadyLoaded;

        // TODO: Load from storage container (database, cache).
        // TODO: The item we load should be passed to LoadedInstances<Item>.Set
        return null;
    }
}