宝哥软件园

在中使用Redis和Memcached进行序列化的详细分析 净核心

编辑:宝哥软件园 来源:互联网 时间:2021-09-01

前言

在使用分布式缓存时,不可避免地要进行这样的操作,将数据序列化,然后存储在缓存中。

序列化可以是显式的,也可以是隐式的,这取决于所使用的包是否有助于我们完成这样的事情。

在本文中,我们将在。NET Core环境为例,其中Redis主要使用StackExchange。Redis和Memcached主要使用EnyimMemcachedCore。

让我们看一下我们常用的一些序列化方法。

常见的序列化方法

也许常见的做法是将对象序列化为字节数组,然后使用该数组与缓存服务器进行交互。

至于序列化,业界有很多算法。从某种意义上说,这些算法的结果就是速度和体积。

其实在操作分布式缓存的时候,我们其实更注重这两个问题!

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

序列化的结果,也就是我们想要插入内存的东西,如果可以做得更小,可以节省很多宝贵的内存空间。

当然,本文的重点不是比较哪种序列化方法更强大,而是介绍如何结合缓存使用,顺便提一下使用缓存时序列化可以考虑的几点。

让我们看看一些常用的序列化库:

在这些库中,系统。runtime . serialization . formatters . binary newsoft . JSON proto buf-net消息包-csharp.

系统。运行时。序列化。格式化程序。二进制是。NET类库,所以不依赖第三方包的时候是个不错的选择。

牛顿英尺。Json应该不用说了。

Protobuf-NET是由实现的协议缓冲区。净。

MessagePack-CSharp是一个速度极快的消息包序列化工具。

这些连载库平时也是作者参与的,有些不熟悉的就不列举了!

在开始之前,我们定义了一个产品类,并基于这个类解释了相关的操作。

公共类Product { public int Id { get设置;}公共字符串名称{ get设置;}}我们先来看看Redis的使用。

使用心得

在引入序列化之前,我们需要知道在StackExchange中。Redis,我们想要存储的所有数据都以RedisValue的形式存在。而RedisValue支持字符串、字节[]等数据类型。

换句话说,当我们使用StackExchange时。Redis,存储在Redis中的数据需要序列化为RedisValue支持的类型。

这是需要显式序列化的操作。

让我们来看看。NET类库。

序列化操作

使用(var ms=new memory stream()){ formatter。序列化(ms,产品);db。StringSet('binaryformatter ',ms.ToArray(),TimeSpan。from MINUTES(1));}反序列化的操作

var值=db。stringset(' binary formatter ');使用(var ms=new memory stream(value)){ var desValue=(Product)(new binary formatter()。反序列化(ms));控制台。WriteLine($'{desValue。Id}-{desValue。name } ');}写起来挺简单的,但是此时运行代码会提示如下错误!

我们的产品类未标记为可序列化。下面是将[可序列化]添加到产品类。

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

让我们来看看牛顿索夫。数据

序列化操作

使用(var ms=new MemoryStream()){使用(var sr=new StreamWriter(ms,Encoding。UTF8))使用(var jtr=new JsonTextWriter(Sr)){ jsonSerializer。序列化(jtr,product);} db。StringSet('json ',ms.ToArray(),TimeSpan。from MINUTES(1));}反序列化的操作

定义变量字节=db .string set(' JSON ');使用(var ms=新内存流(字节))使用(var sr=新流阅读器(女士,编码)。UTF8))使用(var jtr=new JsonTextReader(Sr)){ var desValue=jsonSerializer .反序列化产品(jtr);控制台WriteLine($'{desValue .Id}-{desValue .name } ');}由于牛顿英尺。数据对我们要进行序列化的类有没有加上可序列化并没有什么强制性的要求,所以去掉或保留都可以。

运行起来是比较顺利的。

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

var objStr=JsonConvert .SerializeObject(产品);db .字符串集(' json ',编码. UTF8。GetBytes(objStr),TimeSpan .from MINUTES(1));编码. UTF8。GetString(数据库.string set(' JSON ');var res=JsonConvert .反序列化对象产品(Resstr);再来看看工具

序列化的操作

使用(var ms=新内存流()){序列化程序.序列化(女士,产品);db .StringSet('protobuf ',ms.ToArray(),TimeSpan .from MINUTES(1));}反序列化的操作

定义变量值=db .string set(' proto buf ');使用(var ms=新内存流(值)){ var desValue=Serializer .反序列化产品(毫秒);控制台WriteLine($'{desValue .Id}-{desValue .name } ');}用法看起来也是中规中矩。

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

处理方法有两个,一个是在产品类和属性上面加上对应的属性,另一个是用原型Buf .自指的在运行时来处理这个问题。可以参考自动协议布法罗的实现。

下面用第一种方式来处理,直接加上[协议合同]和[原型成员]这两个属性。

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

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

它默认也是要像工具那样加上MessagePackObject和钥匙这两个属性的。

不过它也提供了一个表单模式解析器参数,可以让我们有所选择。

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

序列化的操作

var serv value=MessagePackSerializer .序列化(产品,ContractlessStandardResolver .实例);db .StringSet('messagepack ',serValue,TimeSpan .from MINUTES(1));反序列化的操作

定义变量值=db .stringset('消息包');var desValue=MessagePackSerializer .反序列化产品(值,ContractlessStandardResolver .实例);此时运行起来也是正常的。

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

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

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

下面来看看Memcached的使用。

Memcached

EnyimMemcachedCore默认有一个默认代码转换器,对于常规的数据类型(int,string等)本文不细说,只是特别说明目标类型。

在默认代码转换器中,对目标类型的数据进行序列化是基于二进制的。

还有一个BinaryFormatterTranscoder是属于默认的另一个实现,这个就是基于我们前面的说。网类库自带的系统。运行时。序列化。格式化程序。二进制文件。

先来看看这两种自带的代码转换机要怎么用。

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

初始化Memcached如下:

私有静态void InitMemcached(字符串代码转换器=' '){ IServiceCollection services=new service collection();服务AddEnyimMemcached(options={ options .AddServer('127.0.0.1 ',11211);选项。转码器=转码器;});服务AddLogging();IServiceProvider服务提供者=服务buildservice provider();_client=serviceProvider .GetServiceIMemcachedClient()作为MemcachedClient}这里的代码转换机就是我们要选择那种序列化方法(针对目标类型),如果是空就用Bson,如果是BinaryFormatterTranscoder用的就是二进制格式化程序。

需要注意下面两个说明

在2.1.0版本之后,代码转换器从ITranscoder类型更改为字符串类型。在2.1.0.5版本之后,它可以以依赖注入的形式完成,而不需要指定字符串类型的代码转换器。读写缓存操作如下:

私有静态void MemcachedTrancode(产品产品){ _client。商店(Enyim。缓存。Memcached.StoreMode.Set,' defalut ',product,DateTime。现在。添加分钟(1));控制台。WriteLine('序列化成功!');var desValue=_client。execute getproduct(' default ')。价值;控制台。WriteLine($'{desValue。Id}-{desValue。name } ');控制台。WriteLine('反序列化成功!');Main方法中的代码如下:

静态void Main(字符串[] args){产品产品=新产品{ Id=999,名称=' Product 999 ' };//Bson字符串转码器=' ';//Binaryformatter//字符串转码器=' BinaryFormatterTranscoderInitMemcached(代码转换器);MemcachedTrancode(产品);控制台。ReadKey();}对于两个代码转换器来说,它们运行平稳。在使用BinaryFormatterTranscoder时,只要记住将[Serializable]添加到Product类即可!

让我们看看如何在MessagePack的帮助下实现Memcached的代码转换器。

这里,只需继承DefaultTranscoder,然后重写SerializeObject、DeserializeObject和Deserialize方法。

公共类messagepackktranskcoder : Defaultcoder {受保护的重写ArraySegmentByteSeriateObject(对象值){返回MessagePackSerializer。SerializeUnsafe(值,typellescontractlessstandardsresolver。实例);}公共重写T反序列化(CacheItem item) { return (T)base。反序列化(项);}受保护的覆盖对象反序列化对象(ArraySegmentbyte值){返回MessagePackSerializer。反序列化对象(值,typellescontractlessstandardsresolver。实例);}}幸运的是,MessagePack有一种方法可以将对象直接序列化为ArraySegment,或者将ArraySegment反序列化为对象!

相比于Json和Protobuf,节省了很多操作!

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

方法一:使用时,我们只需要替换上面定义的转码器变量(适用=2 . 1 . 0版)。

字符串代码转换器='CachingSerializer。MessagePackTranscoder,cachingSerializer ';注意:如果用第一种方法处理,记得要正确拼写转码器并带名字空间,否则创建的转码器永远为空,从而取Bson!本质就是Activator。CreateInstance,这不应该解释。

方法2:通过依赖注入进行处理(适用=2.1.0.5版本)

private static void InitMemcached(字符串代码转换器=' '){ IServiceCollection services=new service collection();服务。AddEnyimMemcached(options={ options。AddServer('127.0.0.1 ',11211);//如果您保留一个空字符串或者没有在这里赋值,您将转到下面的AddSingleton。//如果在这里分配了正确的值,下面的AddSingleton将不起作用。选项。转码器=转码器;});//使用新定义的messagepacktranscoder服务。addsingletonitranscoder,messagepackktrancoder();//其他.}在运行之前添加一个断点,以确保它确实在我们重写的方法中。

最终结果:

这里不一一介绍Protobuf和Json。这两个比MessagePack要复杂得多。你可以参考开源项目MemcachedTranscoder,它也是由MessagePack的作者编写的。虽然是五年前的,但也同样好用。

对于Redis,当我们调用Set方法时,我们需要显式序列化我们的值,这不是那么简洁,所以它将被封装并使用一次。

对于Memcached,虽然在调用Set方法的时候不需要显式序列化,但是我们可能要自己实现一个代码转换器,这有点麻烦。

让我们推荐一个简单的缓存库来处理这些问题。

使用轻松缓存简化操作

EasyCaching是作者在业余时间写的一个简单的开源项目。它的主要目的是简化缓存操作,并且在不断改进。

轻松缓存提供了前面所说的四种序列化方法可供选择:

二进制格式化程序消息包Json ProtoBuF如果这四种都不满足需求,也可以自己写一个,只要实现IEasyCachingSerializer这个接口相应的方法即可。

使用心得

在介绍怎么用序列化之前,先来简单看看是怎么用的(用ASP .核心网络应用编程接口做演示)。

添加使用心得相关的框架包

安装包轻松缓存。使用心得修改启动

公共类启动{ //.public void ConfigureServices(IServiceCollection services){//其他服务Redis .缓存服务的重要步骤. AddDefaultRedisCache(选项={选项.端点。添加(新服务器端点(' 127.0.0.1 ',6379));选项。密码=' ';});}}然后在控制器中使用:

[路由(' API/[控制器]')]公共类值控制器:控制器{私有只读IEasyCachingProvider _ providerppublic值控制器(IEasyCachingProvider提供程序){这个._ provider=provider} [HttpGet]公共字符串Get() { //Set _provider .Set('demo ',' 123 ',TimeSpan .from MINUTES(1));//在没有数据检索器var res=_provider的情况下获取get string(' demo ');_提供商。设置(' product:1 ',新产品{ Id=1,名称='name'},时间跨度来自MINUTES(1))var product=_ provider .GetProduct(' product :1 ');返回$'{res.Value}-{product .值。标识}-{产品。值。名称} ';}}使用的时候,在构造函数对IEasyCachingProvider进行依赖注入即可Redis。默认用了序列化来进行序列化。下面我们要如何去替换我们想要的新的序列化方法呢?

以MessagePack为例,先通过框架安装包裹

安装包轻松缓存。序列化。消息包然后只需要在配置服务方法中加上下面这句就可以了。

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

同样先来简单看看是怎么用的(用ASP .核心网络应用编程接口做演示)。

添加Memcached的框架包

安装包轻松缓存Memcached .修改启动

公共类启动{ //.public void ConfigureServices(IServiceCollection services){ services .AddMvc();记忆缓存服务的重要步骤. AddDefaultMemcached(选项={选项.AddServer('127.0.0.1 ',11211);});}公共无效配置(应用程序生成器应用程序,ihostingenvironmentenv){//Memcache Cache Cache应用程序的重要步骤UseDefaultMemcached();}}在控制器中使用时和使用心得是一模一样的。

这里需要注意的是,在轻松缓存中,默认使用的序列化方法并不是默认代码转换器中的Bson,而是序列化

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

同样以MessagePack为例,先通过框架安装包裹

安装包轻松缓存。序列化。消息包剩下的操作和使用心得是一样的!

public void ConfigureServices(IServiceCollection services){//others.服务. AddDefaultMemcached(op={ op . AddServer(' 127。0 .0 .1 ',11211);});//指定代码转换器使用messagepack序列化程序。服务AddDefaultMessagePackSerializer();}因为在轻松缓存中,有一个自己的转码器,这个代码转换机对IEasyCachingSerializer进行注入,所以只需要指定对应的串行器即可。

总结

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

系统。运行时。序列化。格式化程序。二进制在使用上需要加上[可序列化],效率是最慢的,优势就是类库里面就有,不需要额外引用其他包装。

牛顿英尺。数据使用起来比较友善,可能是用的多的缘故,也不需要我们对已经定义好的类加一些属性上去。

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

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

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

如果你感兴趣,可以用BenchmarkDotNet来跑分。我还写了一个简单的参考:SerializerBenchmark

第二,在操作缓存的时候,可能更倾向于“隐式”操作,可以直接把一个对象扔进去,也可以把一个对象取出来,至少是为了方便用户。

第三,序列化时,Redis比Memcached简单。

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

前半部分的示例代码:CachingSerializer

后半部分示例代码:示例

好了,这就是本文的全部内容。希望本文的内容对你的学习或工作有一定的参考价值。有问题可以留言交流。谢谢你的支持。

更多资讯
游戏推荐
更多+