«Реальные» ссылки на объекты в распределенном кэше? - PullRequest
1 голос
/ 31 марта 2009

Я лично привержен решениям распределенного кэширования .net, но я думаю, что этот вопрос интересен для всех платформ.

Существует ли решение распределенного кэширования (или общая стратегия), которое позволяет хранить объекты в кэше, сохраняя при этом целостность ссылок между ними?

В качестве примера. Предположим, у меня есть объект Foo foo, который ссылается на объект Bar bar, а также и объект Foo foo2, который ссылается на тот же Bar bar. Если я загружаю foo в кеш, вместе с ним сохраняется копия bar. Если я также загружаю foo2 в кеш, вместе с этим сохраняется отдельная копия bar. Если я изменю foo.bar в кеше, изменение не повлияет на foo2.bar: (

Существует ли существующее решение с распределенным кешем, которое позволит мне загружать foo, foo2 и bar в кеш при сохранении ссылок foo.bar foo2.bar?

1 Ответ

3 голосов
/ 31 марта 2009

В первую очередь

Я не знаю ни одной распределенной системы и не претендую на ее создание. В этом посте объясняется, как можно смоделировать это поведение в .NET и C # с помощью интерфейса IObjectReference с сериализуемыми объектами.

Теперь давайте продолжим с шоу

Я не знаю такой распределенной системы, но вы можете легко добиться этого с помощью .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);
    }
}

Этот код вызывается с помощью SerializationProxy.PrepareSerialization (info, myKey, myKey => LoadedInstances.GetById (myKey)) из вашего метода GetObjectData, и ваш LoadedInstances.GetById должен возвращать экземпляр из словаря или загрузить его из кэша / базы данных, если он еще не загружен.

EDIT:

Я написал пример кода, чтобы показать, что я имею в виду.

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