【问题标题】:"Real" Object References in Distributed Cache?分布式缓存中的“真实”对象引用?
【发布时间】:2009-04-01 02:40:31
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: distributed-cache


    【解决方案1】:

    首先

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

    现在,让我们继续表演吧

    我不知道这样的分布式系统,但您可以使用 .NET 使用 IObjectReference 接口轻松实现这一目标。您对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 => LoadedInstances.GetById(myKey)) 调用,并且您的 LoadedInstances.GetById 应该从 Dictionary 或如果尚未加载,则从缓存/数据库中加载它。

    编辑:

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

    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;
        }
    }
    

    【讨论】:

    • Simon,感谢您的精心回复。恐怕我有点过头了。您能否解释一下序列化代理与我打算存储对象的分布式缓存有何关系?
    猜你喜欢
    • 2013-01-12
    • 2014-10-15
    • 1970-01-01
    • 2011-04-10
    • 2014-02-28
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多