gpt4 book ai didi

.NET Core中使用Redis与Memcached的序列化问题详析

转载 作者:qq735679552 更新时间:2022-09-29 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章.NET Core中使用Redis与Memcached的序列化问题详析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

前言 。

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

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

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

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

常见的序列化方法 。

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

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

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

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

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

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

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

  • System.Runtime.Serialization.Formatters.Binary
  • Newtonsoft.Json
  • protobuf-net
  • MessagePack-CSharp
  • ....

在这些库中 。

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

Newtonsoft.Json应该不用多说了.

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

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

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

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

?
1
2
3
4
5
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.

序列化的操作 。

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

反序列化的操作 。

?
1
2
3
4
5
6
var value = db.StringGet( "binaryformatter" );
using (var ms = new MemoryStream(value))
{
  var desValue = (Product)( new BinaryFormatter().Deserialize(ms));
  Console.WriteLine($ "{desValue.Id}-{desValue.Name}" );
}

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

.NET Core中使用Redis与Memcached的序列化问题详析

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

.NET Core中使用Redis与Memcached的序列化问题详析

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

.NET Core中使用Redis与Memcached的序列化问题详析

再来看看Newtonsoft.Json 。

序列化的操作 。

?
1
2
3
4
5
6
7
8
9
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));
}

反序列化的操作 。

?
1
2
3
4
5
6
7
8
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并没有什么强制性的要求,所以去掉或保留都可以.

运行起来是比较顺利的.

.NET Core中使用Redis与Memcached的序列化问题详析

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

?
1
2
3
4
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 。

序列化的操作 。

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

反序列化的操作 。

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

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

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

.NET Core中使用Redis与Memcached的序列化问题详析

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

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

.NET Core中使用Redis与Memcached的序列化问题详析

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

.NET Core中使用Redis与Memcached的序列化问题详析

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

它默认也是要像Protobuf那样加上MessagePackObject和Key这两个Attribute的.

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

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

序列化的操作 。

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

反序列化的操作 。

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

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

.NET Core中使用Redis与Memcached的序列化问题详析

其实序列化这一步,对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如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
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.

需要注意下面两个说明 。

  • 2.1.0版本之后,Transcoder由ITranscoder类型变更为string类型。
  • 2.1.0.5版本之后,可以通过依赖注入的形式来完成,而不用指定string类型的Transcoder。

读写缓存的操作如下:

?
1
2
3
4
5
6
7
8
9
10
11
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方法中的代码如下 :

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main( string [] args)
{
  Product product = new Product
  {
   Id = 999,
   Name = "Product999"
  };
  //Bson
  string transcoder = "" ;
  //BinaryFormatter
  //string transcoder = "BinaryFormatterTranscoder";  
  InitMemcached(transcoder);
  MemcachedTrancode(product);
  Console.ReadKey();
}

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

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

这里继承DefaultTranscoder就可以了,然后重写SerializeObject,DeserializeObject和Deserialize 这三个方法.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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版本).

?
1
string transcoder = "CachingSerializer.MessagePackTranscoder,CachingSerializer" ;

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
  });
  //使用新定义的MessagePackTranscoder
  services.AddSingleton<ITranscoder, MessagePackTranscoder>();
  //others...
}

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

.NET Core中使用Redis与Memcached的序列化问题详析

最后的结果:

.NET Core中使用Redis与Memcached的序列化问题详析

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

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

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

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

使用EasyCaching来简化操作 。

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

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

  • BinaryFormatter
  • MessagePack
  • Json
  • ProtoBuf

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

Redis 。

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

添加Redis相关的nuget包 。

?
1
Install-Package EasyCaching.Redis

修改Startup 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 = "" ;
   });
  }
}

然后在控制器中使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[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 retriever
   var 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}" ;
  }
}
  • 使用的时候,在构造函数对IEasyCachingProvider进行依赖注入即可。
  • Redis默认用了BinaryFormatter来进行序列化。

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

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

?
1
Install-Package EasyCaching.Serialization.MessagePack

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

?
1
2
3
4
5
public void ConfigureServices(IServiceCollection services)
{
  //others..
  services.AddDefaultMessagePackSerializer();
}

Memcached 。

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

添加Memcached的nuget包 。

?
1
Install-Package EasyCaching.Memcached

修改Startup 。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Startup
{
  //...
  public void ConfigureServices(IServiceCollection services)
  {
   services.AddMvc();
   //Important step for Memcached Cache
   services.AddDefaultMemcached(option=>
   {   
    option.AddServer( "127.0.0.1" ,11211);  
   }); 
  }
 
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
   //Important step for Memcache Cache
   app.UseDefaultMemcached();
  }
}

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

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

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

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

?
1
Install-Package EasyCaching.Serialization.MessagePack

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

?
1
2
3
4
5
6
7
8
9
10
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即可.

总结 。

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

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

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

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

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

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

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

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

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

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

前半部分的示例代码:CachingSerializer 。

后半部分的示例代码:sample 。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持.

最后此篇关于.NET Core中使用Redis与Memcached的序列化问题详析的文章就讲到这里了,如果你想了解更多关于.NET Core中使用Redis与Memcached的序列化问题详析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com