前言

在使用分布式缓存的时候,都不可避免的要做这样一步操作,将数据序列化后再存储到缓存中去。

序列化这一操作,或许是显式的,或许是隐式的,这个取决于使用的package是否有帮我们做这样一件事。

本文会拿在.NET Core环境下使用Redis和Memcached来当例子说明,其中,Redis主要是用StackExchange.Redis,Memcached主要是用EnyimMemcachedCore

先来看看一些我们常用的序列化方法。

常见的序列化方法

或许,比较常见的做法就是将一个对象序列化成byte数组,然后用这个数组和缓存服务器进行交互。

关于序列化,业界有不少算法,这些算法在某种意义上表现的结果就是速度体积这两个问题。

其实当操作分布式缓存的时候,我们对这两个问题其实也是比较看重的!

在同等条件下,序列化和反序列化的速度,可以决定执行的速度是否能快一点。

序列化的结果,也就是我们要往内存里面塞的东西,如果能让其小一点,也是能节省不少宝贵的内存空间。

当然,本文的重点不是去比较那种序列化方法比较牛逼,而是介绍怎么结合缓存去使用,也顺带提一下在使用缓存时,序列化可以考虑的一些点。

下面来看看一些常用的序列化的库:

  1. System.Runtime.Serialization.Formatters.Binary

  2. Newtonsoft.Json

  3. protobuf-net

  4. MessagePack-CSharp

  5. ....

在这些库中

System.Runtime.Serialization.Formatters.Binary是.NET类库中本身就有的,所以想在不依赖第三方的packages时,这是个不错的选择。

Newtonsoft.Json应该不用多说了。

protobuf-net是.NET实现的Protocol Buffers。

MessagePack-CSharp是极快的MessagePack序列化工具。

这几种序列化的库也是笔者平时有所涉及的,还有一些不熟悉的就没列出来了!

在开始之前,我们先定义一个产品类,后面相关的操作都是基于这个类来说明。

public class Product{      public int Id { get; set; }    public string Name { get; set; }
}

下面先来看看Redis的使用。

Redis

在介绍序列化之前,我们需要知道在StackExchange.Redis中,我们要存储的数据都是以RedisValue的形式存在的。并且RedisValue是支持string,byte[]等多种数据类型的。

换句话说就是,在我们使用StackExchange.Redis时,存进Redis的数据需要序列化成RedisValue所支持的类型。

这就是前面说的需要显式的进行序列化的操作。

先来看看.NET类库提供的BinaryFormatter

序列化的操作

using (var ms = new MemoryStream())
{formatter.Serialize(ms, product);                db.StringSet("binaryformatter", ms.ToArray(), TimeSpan.FromMinutes(1));
}

反序列化的操作

var value = db.StringGet("binaryformatter");using (var ms = new MemoryStream(value))
{    var desValue = (Product)(new BinaryFormatter().Deserialize(ms));Console.WriteLine($"{desValue.Id}-{desValue.Name}");
}

写起来还是挺简单的,但是这个时候运行代码会提示下面的错误!

说是我们的Product类没有标记Serializable。下面就是在Product类加上[Serializable]

再次运行,已经能成功了。

再来看看Newtonsoft.Json

序列化的操作

using (var ms = new MemoryStream())
{     using (var sr = new StreamWriter(ms, Encoding.UTF8))       using (var jtr = new JsonTextWriter(sr)){jsonSerializer.Serialize(jtr, product);}                db.StringSet("json", ms.ToArray(), TimeSpan.FromMinutes(1));
}

反序列化的操作

var bytes = db.StringGet("json");using (var ms = new MemoryStream(bytes))using (var sr = new StreamReader(ms, Encoding.UTF8))using (var jtr = new JsonTextReader(sr))
{    var desValue = jsonSerializer.Deserialize<Product>(jtr);Console.WriteLine($"{desValue.Id}-{desValue.Name}");
}

由于Newtonsoft.Json对我们要进行序列化的类有没有加上Serializable并没有什么强制性的要求,所以去掉或保留都可以。

运行起来是比较顺利的。

当然,也可以用下面的方式来处理的:

var objStr = JsonConvert.SerializeObject(product);
db.StringSet("json", Encoding.UTF8.GetBytes(objStr), TimeSpan.FromMinutes(1));var resStr = Encoding.UTF8.GetString(db.StringGet("json"));var res = JsonConvert.DeserializeObject<Product>(resStr);

再来看看ProtoBuf

序列化的操作

using (var ms = new MemoryStream())
{Serializer.Serialize(ms, product);db.StringSet("protobuf", ms.ToArray(), TimeSpan.FromMinutes(1));
}

反序列化的操作

var value = db.StringGet("protobuf");using (var ms = new MemoryStream(value))
{    var desValue = Serializer.Deserialize<Product>(ms); Console.WriteLine($"{desValue.Id}-{desValue.Name}");
}

用法看起来也是中规中矩。

但是想这样就跑起来是没那么顺利的。错误提示如下:

处理方法有两个,一个是在Product类和属性上面加上对应的Attribute,另一个是用ProtoBuf.Meta在运行时来处理这个问题。可以参考AutoProtobuf的实现。

下面用第一种方式来处理,直接加上[ProtoContract][ProtoMember]这两个Attribute。

再次运行就是我们所期望的结果了。

最后来看看MessagePack,据其在Github上的说明和对比,似乎比其他序列化的库都强悍不少。

它默认也是要像Protobuf那样加上MessagePackObjectKey这两个Attribute的。

不过它也提供了一个IFormatterResolver参数,可以让我们有所选择。

下面用的是不需要加Attribute的方法来演示。

序列化的操作

var serValue = MessagePackSerializer.Serialize(product, ContractlessStandardResolver.Instance);
db.StringSet("messagepack", serValue, TimeSpan.FromMinutes(1));

反序列化的操作

var value = db.StringGet("messagepack");var desValue = MessagePackSerializer.Deserialize<Product>(value, ContractlessStandardResolver.Instance);

此时运行起来也是正常的。

其实序列化这一步,对Redis来说是十分简单的,因为它显式的让我们去处理,然后把结果进行存储。

上面演示的4种方法,从使用上看,似乎都差不多,没有太大的区别。

如果拿Redis和Memcached对比,会发现Memcached的操作可能比Redis的略微复杂了一点。

下面来看看Memcached的使用。

Memcached

EnyimMemcachedCore默认有一个 DefaultTranscoder
,对于常规的数据类型(int,string等)本文不细说,只是特别说明object类型。

在DefaultTranscoder中,对Object类型的数据进行序列化是基于Bson的。

还有一个BinaryFormatterTranscoder是属于默认的另一个实现,这个就是基于我们前面的说.NET类库自带的System.Runtime.Serialization.Formatters.Binary

先来看看这两种自带的Transcoder要怎么用。

先定义好初始化Memcached相关的方法,以及读写缓存的方法。

初始化Memcached如下:

private static void InitMemcached(string transcoder = ""){IServiceCollection services = new ServiceCollection();services.AddEnyimMemcached(options =>{options.AddServer("127.0.0.1", 11211);options.Transcoder = transcoder;});services.AddLogging();IServiceProvider serviceProvider = services.BuildServiceProvider();_client = serviceProvider.GetService<IMemcachedClient>() as MemcachedClient;
}

这里的transcoder就是我们要选择那种序列化方法(针对object类型),如果是空就用Bson,如果是BinaryFormatterTranscoder用的就是BinaryFormatter。

需要注意下面两个说明

  1. 2.1.0版本之后,Transcoder由ITranscoder类型变更为string类型。

  2. 2.1.0.5版本之后,可以通过依赖注入的形式来完成,而不用指定string类型的Transcoder。

读写缓存的操作如下:

private static void MemcachedTrancode(Product product){_client.Store(Enyim.Caching.Memcached.StoreMode.Set, "defalut", product, DateTime.Now.AddMinutes(1));Console.WriteLine("serialize succeed!");    var desValue = _client.ExecuteGet<Product>("defalut").Value;Console.WriteLine($"{desValue.Id}-{desValue.Name}");Console.WriteLine("deserialize succeed!");
}

我们在Main方法中的代码如下 :

static void Main(string[] args){Product product = new Product{Id = 999,Name = "Product999"};    //Bsonstring transcoder = "";    //BinaryFormatter//string transcoder = "BinaryFormatterTranscoder";            InitMemcached(transcoder);MemcachedTrancode(product);Console.ReadKey();
}

对于自带的两种Transcoder,跑起来还是比较顺利的,在用BinaryFormatterTranscoder时记得给Product类加上[Serializable]就好!

下面来看看如何借助MessagePack来实现Memcached的Transcoder。

这里继承DefaultTranscoder就可以了,然后重写SerializeObjectDeserializeObjectDeserialize这三个方法。

public class MessagePackTranscoder : DefaultTranscoder{    protected override ArraySegment<byte> SerializeObject(object value)    {        return MessagePackSerializer.SerializeUnsafe(value, TypelessContractlessStandardResolver.Instance);}    

 public override T Deserialize<T>(CacheItem item){         return (T)base.Deserialize(item);}    

 protected override object DeserializeObject(ArraySegment<byte> value)    {        return MessagePackSerializer.Deserialize<object>(value, TypelessContractlessStandardResolver.Instance);}
}

庆幸的是,MessagePack有方法可以让我们直接把一个object序列化成ArraySegment,也可以把ArraySegment 反序列化成一个object!!

相比Json和Protobuf,省去了不少操作!!

这个时候,我们有两种方式来使用这个新定义的MessagePackTranscoder。

方式一 :在使用的时候,我们只需要替换前面定义的transcoder变量即可(适用>=2.1.0版本)。

string transcoder = "CachingSerializer.MessagePackTranscoder,CachingSerializer";

注:如果使用方式一来处理,记得将transcoder的拼写不要错,并且要带上命名空间,不然创建的Transcoder会一直是null,从而走的就是Bson了! 本质是 Activator.CreateInstance,应该不用多解释。

方式二:通过依赖注入的方式来处理(适用>=2.1.0.5版本)

private static void InitMemcached(string transcoder = ""){IServiceCollection services = new ServiceCollection();services.AddEnyimMemcached(options =>{options.AddServer("127.0.0.1", 11211);        //这里保持空字符串或不赋值,就会走下面的AddSingleton//如果这里赋了正确的值,后面的AddSingleton就不会起作用了options.Transcoder = transcoder;});    //使用新定义的MessagePackTranscoderservices.AddSingleton<ITranscoder, MessagePackTranscoder>();    //others...}

运行之前加个断点,确保真的进了我们重写的方法中。

最后的结果:

Protobuf和Json的,在这里就不一一介绍了,这两个处理起来比MessagePack复杂了不少。可以参考MemcachedTranscoder这个开源项目,也是MessagePack作者写的,虽然是5年前的,但是一样的好用。

对于Redis来说,在调用Set方法时要显式的将我们的值先进行序列化,不那么简洁,所以都会进行一次封装在使用。

对于Memcached来说,在调用Set方法的时候虽然不需要显式的进行序列化,但是有可能要我们自己去实现一个Transcoder,这也是有点麻烦的。

下面给大家推荐一个简单的缓存库来处理这些问题。

使用EasyCaching来简化操作

EasyCaching是笔者在业余时间写的一个简单的开源项目,主要目的是想简化缓存的操作,目前也在不断的完善中。

EasyCaching提供了前面所说的4种序列化方法可供选择:

  1. BinaryFormatter

  2. MessagePack

  3. Json

  4. ProtoBuf

如果这4种都不满足需求,也可以自己写一个,只要实现IEasyCachingSerializer这个接口相应的方法即可。

Redis

在介绍怎么用序列化之前,先来简单看看是怎么用的(用ASP.NET Core Web API做演示)。

添加Redis相关的nuget包

Install-Package EasyCaching.Redis

修改Startup

public class Startup{    //...public void ConfigureServices(IServiceCollection services)    {        //other services.//Important step for Redis Caching       services.AddDefaultRedisCache(option=>{                option.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6379));option.Password = "";});}
}

然后在控制器中使用:

[Route("api/[controller]")]public class ValuesController : Controller{    private readonly IEasyCachingProvider _provider; 

  public ValuesController(IEasyCachingProvider provider)    {        this._provider = provider;}[HttpGet]         public string Get()    {        //Set_provider.Set("demo", "123", TimeSpan.FromMinutes(1));        //Get without data retrievervar res = _provider.Get<string>("demo");_provider.Set("product:1", new Product { Id = 1, Name = "name"}, TimeSpan.FromMinutes(1))        var product = _provider.Get<Product>("product:1");          return  $"{res.Value}-{product.Value.Id}-{product.Value.Name}";  }
}
  1. 使用的时候,在构造函数对IEasyCachingProvider进行依赖注入即可。

  2. Redis默认用了BinaryFormatter来进行序列化。

下面我们要如何去替换我们想要的新的序列化方法呢?

以MessagePack为例,先通过nuget安装package

Install-Package EasyCaching.Serialization.MessagePack

然后只需要在ConfigureServices方法中加上下面这句就可以了。

public void ConfigureServices(IServiceCollection services){    //others..services.AddDefaultMessagePackSerializer();
}

Memcached

同样先来简单看看是怎么用的(用ASP.NET Core Web API做演示)。

添加Memcached的nuget包

Install-Package EasyCaching.Memcached

修改Startup

public class Startup{    //...public void ConfigureServices(IServiceCollection services)    {services.AddMvc();        //Important step for Memcached Cacheservices.AddDefaultMemcached(option=>{                option.AddServer("127.0.0.1",11211);            });        }    

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)    {        //Important step for Memcache Cacheapp.UseDefaultMemcached();    }
}

在控制器中使用时和Redis是一模一样的。

这里需要注意的是,在EasyCaching中,默认使用的序列化方法并不是DefaultTranscoder中的Bson,而是BinaryFormatter

如何去替换默认的序列化操作呢?

同样以MessagePack为例,先通过nuget安装package

Install-Package EasyCaching.Serialization.MessagePack

剩下的操作和Redis是一样的!

public void ConfigureServices(IServiceCollection services){    //others..services.AddDefaultMemcached(op=>{                op.AddServer("127.0.0.1",11211);});    //specify the Transcoder use messagepack serializer.services.AddDefaultMessagePackSerializer();
}

因为在EasyCaching中,有一个自己的Transcoder,这个Transcoder对IEasyCachingSerializer进行注入,所以只需要指定对应的Serializer即可。

总结

一、 先来看看文中提到的4种序列化的库

System.Runtime.Serialization.Formatters.Binary在使用上需要加上[Serializable],效率是最慢的,优势就是类库里面就有,不需要额外引用其他package。

Newtonsoft.Json使用起来比较友善,可能是用的多的缘故,也不需要我们对已经定义好的类加一些Attribute上去。

protobuf-net使用起来可能就略微麻烦一点,可以在定义类的时候加上相应的Attribute,也可以在运行时去处理(要注意处理子类),不过它的口碑还是不错的。

MessagePack-CSharp虽然可以不添加Attribute,但是不加比加的时候也会有所损耗。

至于如何选择,可能就要视情况而定了!

有兴趣的可以用BenchmarkDotNet跑跑分,我也简单写了一个可供参考:SerializerBenchmark

二、在对缓存操作的时候,可能会更倾向于“隐式”操作,能直接将一个object扔进去,也可以直接将一个object拿出来,至少能方便使用方。

三、序列化操作时,Redis要比Memcached简单一些。

最后,如果您在使用EasyCaching,有问题或建议可以联系我!

前半部分的示例代码:CachingSerializer

后半部分的示例代码:sample

原文:https://www.cnblogs.com/catcher1994/p/8543711.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

谈谈在.NET Core中使用Redis和Memcached的序列化问题相关推荐

  1. 谈谈ASP.NET Core中的ResponseCaching

    前言 前面的博客谈的大多数都是针对数据的缓存,今天我们来换换口味.来谈谈在ASP.NET Core中的ResponseCaching,与ResponseCaching关联密切的也就是常说的HTTP缓存 ...

  2. 谈谈ASP.NET CORE 中Razor Page 的TagHelper【标签助手】常见类型及其详细使用

    一,TagHelper简介 1.概念 TagHelper(标签助手)是asp.net core mvc的新特性,把HTML和服务器内容混合在一起,准确说标签助手使服务器端代码能够参与在Razor文件中 ...

  3. 使用Redis Stream来做消息队列和在Asp.Net Core中的实现

    Redis - Wikipedia 写在前面 我一直以来使用redis的时候,很多低烈度需求(并发要求不是很高)需要用到消息队列的时候,在项目本身已经使用了Redis的情况下都想直接用Redis来做消 ...

  4. .net core 中 Redis 入门

    1.Redis基础 1.Redis是REmote DIctionary Servey(远程字典服务的缩写),他是以字典结构储存数据.Redis数据库中的键值除了可以是字符串,还可以还是:字符串,has ...

  5. 【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务

    在我的上一篇文章<在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度>,我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计 ...

  6. c#获取对象的唯一标识_在 Java 中利用 redis 实现分布式全局唯一标识服务

    作者: 杨高超 juejin.im/post/5a4984265188252b145b643e 获取全局唯一标识的方法介绍 在一个IT系统中,获取一个对象的唯一标识符是一个普遍的需求.在以前的单体应用 ...

  7. springboot中使用redis详解

    一.redis简介 redis是一款高性能key-value(键值对)内存型数据库,是非关系型数据库的一种,它采用单线程的架构方式,避免了多线程存在的锁处理造成的资源耗费,读取速度非常快,非常适合变化 ...

  8. 如何在 ASP.Net Core 中使用 NCache

    虽然 ASP.Net Core 中缺少 Cache 对象,但它引入了三种不同的cache方式. 内存缓存 分布式缓存 Response缓存 Alachisoft 公司提供了一个开源项目 NCache, ...

  9. 如何在 ASP.Net Core 中实现 健康检查

    健康检查 常用于判断一个应用程序能否对 request 请求进行响应,ASP.Net Core 2.2 中引入了 健康检查 中间件用于报告应用程序的健康状态. ASP.Net Core 中的 健康检查 ...

最新文章

  1. Oracle PL/SQL编程之基础
  2. c语言返回一个数的任意倍数,几道基础C语言题
  3. [转]Oracle执行计划的相关概念
  4. Layui / WEB UI
  5. [SoapUI] 在Test Step 下加Script Assertion,用 messageExchange 获取当前步骤的response content...
  6. 多人协作代码--公共库的引用与业务约定
  7. 【HDU - 1530】Maximum Clique(最大团问题,图论)
  8. Spark Scala当中reduceByKey的用法
  9. fdfs往服务器上传文件超时,FastDFS 实现大文件分片上传
  10. STL里面的sort()函数使用结构体
  11. Java配置文件Properties的读取、写入与更新操作
  12. 如何使用STM32F4的BootLoader和APP程序
  13. Linux开机启动一些知识点
  14. 认清有毒的矿物质元素 预防中毒
  15. 失眠就吃安眠药真的好吗?好心情送你沾枕到天亮的诀窍
  16. JQuery——相关练习
  17. 看完还不会数据库优化,你来找我!
  18. dlib检测人脸landmarks
  19. DS线性表—多项式相加
  20. 中兴的知识产权之路:从防御到开放式竞争

热门文章

  1. phalcon: 缓存片段,文件缓存,memcache缓存
  2. 解决ubuntu下eclipse 经常崩溃的问题
  3. Java IO(一)
  4. Windows下MinGW编译vim7.4
  5. 了解android应用开发的更多方面有更好的认识
  6. Oracle之PLSQL总结
  7. .NET 6 新特性 PeriodicTimer
  8. Abp Vnext Vue3 的版本实现
  9. C# WPF MVVM模式下在主窗体显示子窗体并获取结果
  10. 巧用Environment.UserInteractive 实现开发和生产环境的分开调试部署