Skip to content

缓存的序列化

Catcher Wong edited this page Nov 8, 2019 · 2 revisions

背景

对于分布式缓存,不可避免的要对缓存的内容进行序列化操作。

要如何对缓存的内容进行序列化?正常的情况将一个对象序列化成byte[]会是第一选择。这也是EasyCaching的实现。

序列化的使用

EasyCaching目前提供4种序列化可供选择,BinaryFormatterJsonMessagePackProtobuf

其中BinaryFormatter是默认的,不用添加任何第三方序列化的包。

这里有一个 Breaking Change 还是要留意一下的。

< 0.6.0的版本中,只能使用一种序列化方式,由于EasyCaching支持创建多个不同的Provider实例,对不同Provider有不同序列化方式的支持是必不可少的,所以在 >= 0.6.0的版本中,支持命名式序列化选择。

下面先来看看不同的用法。

示例1

services.AddEasyCaching(option =>
{
    option.UseRedis(config =>
    {
        config.DBConfig.Database = 7;      
        config.DBConfig.Endpoints.Add(new ServerEndPoint("localhost", 6379));
    });
});

在这个例子中,没有指定任何和序列化相关的配置!这个时候,EasyCaching给它分配的序列化器是BinaryFormatter.

如果使用的时候有对一些复杂类型进行存取操作,并且没有给它们标识[Serializable],就会出现类似下面的错误:

Unhandled Exception: System.Runtime.Serialization.SerializationException: Type 'xxxx' is not marked as serializable.

示例2

针对< 0.6.0的版本

dotnet add package EasyCaching.Serialization.MessagePack
services.AddEasyCaching(option =>
{
    option.UseRedis(config =>
    {
        config.DBConfig.Database = 7;      
        config.DBConfig.Endpoints.Add(new ServerEndPoint("localhost", 6379));
    })
    .WithMessagePack()
    ;
});

在这个例子中,指定要使用MessagePack这种序列化!这个时候,EasyCaching给它分配的序列化器就是MessagePack,替换默认的BinaryFormatter

这里需要注意的是,如果指定了多种序列化方式,则只会以最后添加到容器中的为准。

示例3

针对>= 0.6.0的版本

services.AddEasyCaching(option =>
{
    var serializerName = "s1";

    option.UseRedis(config =>
    {
        config.DBConfig.Database = 7;      
        config.DBConfig.Endpoints.Add(new ServerEndPoint("localhost", 6379));
        // 重要配置, 默认是provider的名字
        config.SerializerName = serializerName;
    }, "myredis");

    option.WithMessagePack("s1")
          .WithJson("myjson");
});

在这个例子中,名字为myredis这个provider指定了它要用名字为s1的序列化器。同时还添加了多种序列化器。

所以最后这个provider用的序列化器就是MessagePack

这里有个要注意的地方,如果没有指定SerializerName,EasyCaching会去找有没有和Provider同名的序列化器,在上面的例子中就是myredis,可以看到也没有,这个时候它要完成正常的功能,只能使用默认的BinaryFormatter

自定义序列化器

EasyCaching对序列化的操作提供了一个通用的接口 IEasyCachingSerializer,要有相应的实现。

public interface IEasyCachingSerializer
{
    string Name { get; }
    byte[] Serialize<T>(T value);
    T Deserialize<T>(byte[] bytes);
    object Deserialize(byte[] bytes, Type type);
    ArraySegment<byte> SerializeObject(object obj);
    object DeserializeObject(ArraySegment<byte> value);
}

实现自己的序列化器后,还要实现IEasyCachingOptionsExtension这个配置相关的接口。

public interface IEasyCachingOptionsExtension
{
    void AddServices(IServiceCollection services);
}

注意事项

关于MessagePack序列化导致时间不准确的问题说明

如果我们在使用的过程中,有涉及到需要序列化时间的情况下,并且没有使用UTC时间,就会导致时区信息的丢失。

在 <= v0.8.0 的版本中,默认实现中使用了ContractlessStandardResolver这个Resolver,选择这个是因为不需要给每个类和属性加特性,便于无缝融合。

v0.8.1版本中,添加了一个EnableCustomResolver的配置,当这个配置是true的时候就表示启用自定义的Resolver,反之继续用默认的。

时间问题可以使用NativeDateTimeResolver+ContractlessStandardResolver的组合来解决。下面是具体的例子。

public void ConfigureServices(IServiceCollection services)
{
    CompositeResolver.RegisterAndSetAsDefault(
        // This can solve DateTime time zone problem
        NativeDateTimeResolver.Instance,
        ContractlessStandardResolver.Instance
    );

    services.AddControllers();

    services.AddEasyCaching(option =>
    {
        option.UseCSRedis(config =>
        {
            config.DBConfig = new EasyCaching.CSRedis.CSRedisDBOptions
            {
                ConnectionStrings = new List<string> { "127.0.0.1:6379,defaultDatabase=11,poolsize=10" }
            };
            config.SerializerName = "mymsgpack";
        }, "redis1");

        // use MessagePack
        option.WithMessagePack( x => 
        {
            x.EnableCustomResolver = true; 
        },"mymsgpack");
    });
}