NET Core中使用Redis和Memcached
.NET Core中使用Redis和Memcached的序列化问题
前言
在使用分布式缓存的时候,都不可避免的要做这样一步操作,将数据序列化后再存储到缓存中去。
序列化这一操作,或许是显式的,或许是隐式的,这个取决于使用的package是否有帮我们做这样一件事。
本文会拿在.NET Core环境下使用Redis和Memcached来当例子说明,其中,Redis主要是用StackExchange.Redis,Memcached主要是用EnyimMemcachedCore。
先来看看一些我们常用的序列化方法。
常见的序列化方法
或许,比较常见的做法就是将一个对象序列化成byte数组,然后用这个数组和缓存服务器进行交互。
关于序列化,业界有不少算法,这些算法在某种意义上表现的结果就是速度和体积这两个问题。
其实当操作分布式缓存的时候,我们对这两个问题其实也是比较看重的!
在同等条件下,序列化和反序列化的速度,可以决定执行的速度是否能快一点。
序列化的结果,也就是我们要往内存里面塞的东西,如果能让其小一点,也是能节省不少宝贵的内存空间。
当然,本文的重点不是去比较那种序列化方法比较牛逼,而是介绍怎么结合缓存去使用,也顺带提一下在使用缓存时,序列化可以考虑的一些点。
下面来看看一些常用的序列化的库:
在这些库中
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那样加上MessagePackObject
和Key
这两个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。
需要注意下面两个说明
- 2.1.0版本之后,Transcoder由ITranscoder类型变更为string类型。
- 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"
};
//Bson
string transcoder = "";
//BinaryFormatter
//string transcoder = "BinaryFormatterTranscoder";
InitMemcached(transcoder);
MemcachedTrancode(product);
Console.ReadKey();
}
对于自带的两种Transcoder,跑起来还是比较顺利的,在用BinaryFormatterTranscoder时记得给Product类加上[Serializable]
就好!
下面来看看如何借助MessagePack来实现Memcached的Transcoder。
这里继承DefaultTranscoder就可以了,然后重写SerializeObject,DeserializeObject和Deserialize这三个方法。
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;
});
//使用新定义的MessagePackTranscoder
services.AddSingleton<ITranscoder, MessagePackTranscoder>();
//others...
}
运行之前加个断点,确保真的进了我们重写的方法中。
最后的结果:
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包
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 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
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 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
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
NET Core中使用Redis和Memcached的更多相关文章
- 谈谈在.NET Core中使用Redis和Memcached的序列化问题
前言 在使用分布式缓存的时候,都不可避免的要做这样一步操作,将数据序列化后再存储到缓存中去. 序列化这一操作,或许是显式的,或许是隐式的,这个取决于使用的package是否有帮我们做这样一件事. 本文 ...
- NET Core中使用Redis
NET Core中使用Redis 注:本文提到的代码示例下载地址> https://code.msdn.microsoft.com/How-to-use-Redis-in-ASPNET-0d82 ...
- 如何在ASP.NET Core中使用Redis
注:本文提到的代码示例下载地址> https://code.msdn.microsoft.com/How-to-use-Redis-in-ASPNET-0d826418 Redis是一个开源的内 ...
- Asp.net Core中使用Redis 来保存Session, 读取配置文件
今天 无意看到Asp.net Core中使用Session ,首先要使用Session就必须添加Microsoft.AspNetCore.Session包,默认Session是只能存去字节,所以如果你 ...
- ASP.NET Core教程:ASP.NET Core中使用Redis缓存
参考网址:https://www.cnblogs.com/dotnet261010/p/12033624.html 一.前言 我们这里以StackExchange.Redis为例,讲解如何在ASP.N ...
- mvc core 中使用 redis
redis 下载安装路径: https://github.com/MicrosoftArchive/redis/releases 右键打开cmd命令行,运行命令: .\redis-server.e ...
- 三分钟学会Redis在.NET Core中做缓存中间件
大家好,今天给大家说明如何在.NET Core中使用Redis,我们在想要辩论程序的好与坏,都想需要一个可视化工具,我经常使用的是一位国内大牛开发的免费工具,其Github地址为: https://g ...
- 在AspNetCore 中 使用Redis实现分布式缓存
AspNetCore 使用Redis实现分布式缓存 上一篇讲到了,Core的内置缓存:IMemoryCache,以及缓存的基础概念.本篇会进行一些概念上的补充. 本篇我们记录的内容是怎么在Core中使 ...
- .Net Core缓存组件(Redis)源码解析
上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...
随机推荐
- LoadRunner监控图表与配置(二)监控运行状况和交易状况
1.在左侧Available Graphs视图中展开Runtime Graphs节点,选择其中一种类型添加至控制器运行标签的界面. 2.在图中显示的空白区域点击右键,在弹出的快捷菜单中选择config ...
- JavaScript里值比较的方法
JavaScript里值比较的方法 参考资料 一张图彻底搞懂JavaScript的==运算 toString()和valueof()方法的区别 Object.is 和 == 与 === 不同 == 运 ...
- css书写规则
无规矩不成方圆,不管有多少人共同参与同一项目,一定要确保每一行代码都像是同一个人编写的 不要在自闭合(self-closing)元素的尾部添加斜线 不要省略可选的结束标签(closing tag)(例 ...
- Jexus是一款Linux平台上的高性能WEB服务器和负载均衡网关
什么是Jexus Jexus是一款Linux平台上的高性能WEB服务器和负载均衡网关,以支持ASP.NET.ASP.NET CORE.PHP为特色,同时具备反向代理.入侵检测等重要功能.可以这样说,J ...
- python 生成特定间隔数列的方法
(1)range() 和 xrange( )[python内置函数] range(开始,结束,间隔). 值得注意的是:生成数列最后一个数< 结束值. 返回结果类型:list,其中元素是integ ...
- 网络编程学习笔记-listen函数
listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程.在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接 ...
- Django:locals()小技巧
locals()返回一个包含当前作用域里面的所有变量和它们的值的字典. 所以可以把views改写为 def current_datetime(request): current_date = ...
- ACM学习历程—ZOJ3471 Most Powerful(dp && 状态压缩 && 记忆化搜索 && 位运算)
Description Recently, researchers on Mars have discovered N powerful atoms. All of them are differen ...
- python爬虫知识点总结(三)urllib库详解
一.什么是Urllib? 官方学习文档:https://docs.python.org/3/library/urllib.html 廖雪峰的网站:https://www.liaoxuefeng.com ...
- 【转】Pro Android学习笔记(三):了解Android资源(上)
在Android开发中,资源包括文件或者值,它们和执行应用捆绑,无需在源代码中写死,因此我们可以改变或替换他们,而无需对应用重新编译. 了解资源构成 参考阅读Android学习笔记(三八):资源res ...