欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET Core中使用Redis與Memcached的序列化問題詳析

 更新時(shí)間:2018年03月12日 09:44:43   作者:https://www.cnblogs.com/catcher1994/p/8543711.html  
這篇文章主要介紹了.NET Core中使用Redis與Memcached的序列化問題的相關(guān)內(nèi)容,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。

前言

在使用分布式緩存的時(shí)候,都不可避免的要做這樣一步操作,將數(shù)據(jù)序列化后再存儲到緩存中去。

序列化這一操作,或許是顯式的,或許是隱式的,這個(gè)取決于使用的package是否有幫我們做這樣一件事。

本文會拿在.NET Core環(huán)境下使用Redis和Memcached來當(dāng)例子說明,其中,Redis主要是用StackExchange.Redis,Memcached主要是用EnyimMemcachedCore。

先來看看一些我們常用的序列化方法。

常見的序列化方法

或許,比較常見的做法就是將一個(gè)對象序列化成byte數(shù)組,然后用這個(gè)數(shù)組和緩存服務(wù)器進(jìn)行交互。

關(guān)于序列化,業(yè)界有不少算法,這些算法在某種意義上表現(xiàn)的結(jié)果就是速度和體積這兩個(gè)問題。

其實(shí)當(dāng)操作分布式緩存的時(shí)候,我們對這兩個(gè)問題其實(shí)也是比較看重的!

在同等條件下,序列化和反序列化的速度,可以決定執(zhí)行的速度是否能快一點(diǎn)。

序列化的結(jié)果,也就是我們要往內(nèi)存里面塞的東西,如果能讓其小一點(diǎn),也是能節(jié)省不少寶貴的內(nèi)存空間。

當(dāng)然,本文的重點(diǎn)不是去比較那種序列化方法比較牛逼,而是介紹怎么結(jié)合緩存去使用,也順帶提一下在使用緩存時(shí),序列化可以考慮的一些點(diǎn)。

下面來看看一些常用的序列化的庫:

在這些庫中

System.Runtime.Serialization.Formatters.Binary是.NET類庫中本身就有的,所以想在不依賴第三方的packages時(shí),這是個(gè)不錯(cuò)的選擇。

Newtonsoft.Json應(yīng)該不用多說了。

protobuf-net是.NET實(shí)現(xiàn)的Protocol Buffers。

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

這幾種序列化的庫也是筆者平時(shí)有所涉及的,還有一些不熟悉的就沒列出來了!

在開始之前,我們先定義一個(gè)產(chǎn)品類,后面相關(guān)的操作都是基于這個(gè)類來說明。

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

下面先來看看Redis的使用。

Redis

在介紹序列化之前,我們需要知道在StackExchange.Redis中,我們要存儲的數(shù)據(jù)都是以RedisValue的形式存在的。并且RedisValue是支持string,byte[]等多種數(shù)據(jù)類型的。

換句話說就是,在我們使用StackExchange.Redis時(shí),存進(jìn)Redis的數(shù)據(jù)需要序列化成RedisValue所支持的類型。

這就是前面說的需要顯式的進(jìn)行序列化的操作。

先來看看.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}");
}

寫起來還是挺簡單的,但是這個(gè)時(shí)候運(yùn)行代碼會提示下面的錯(cuò)誤!

說是我們的Product類沒有標(biāo)記Serializable。下面就是在Product類加上[Serializable]。

再次運(yùn)行,已經(jīng)能成功了。

再來看看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對我們要進(jìn)行序列化的類有沒有加上Serializable并沒有什么強(qiáng)制性的要求,所以去掉或保留都可以。

運(yùn)行起來是比較順利的。

當(dāng)然,也可以用下面的方式來處理的:

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}");
}

用法看起來也是中規(guī)中矩。

但是想這樣就跑起來是沒那么順利的。錯(cuò)誤提示如下:

處理方法有兩個(gè),一個(gè)是在Product類和屬性上面加上對應(yīng)的Attribute,另一個(gè)是用ProtoBuf.Meta在運(yùn)行時(shí)來處理這個(gè)問題。可以參考AutoProtobuf的實(shí)現(xiàn)。

下面用第一種方式來處理,直接加上[ProtoContract][ProtoMember]這兩個(gè)Attribute。

再次運(yùn)行就是我們所期望的結(jié)果了。

最后來看看MessagePack,據(jù)其在Github上的說明和對比,似乎比其他序列化的庫都強(qiáng)悍不少。

它默認(rèn)也是要像Protobuf那樣加上MessagePackObjectKey這兩個(gè)Attribute的。

不過它也提供了一個(gè)IFormatterResolver參數(shù),可以讓我們有所選擇。

下面用的是不需要加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);

此時(shí)運(yùn)行起來也是正常的。

其實(shí)序列化這一步,對Redis來說是十分簡單的,因?yàn)樗@式的讓我們?nèi)ヌ幚?,然后把結(jié)果進(jìn)行存儲。

上面演示的4種方法,從使用上看,似乎都差不多,沒有太大的區(qū)別。

如果拿Redis和Memcached對比,會發(fā)現(xiàn)Memcached的操作可能比Redis的略微復(fù)雜了一點(diǎn)。

下面來看看Memcached的使用。

Memcached

EnyimMemcachedCore默認(rèn)有一個(gè) DefaultTranscoder
,對于常規(guī)的數(shù)據(jù)類型(int,string等)本文不細(xì)說,只是特別說明object類型。

在DefaultTranscoder中,對Object類型的數(shù)據(jù)進(jìn)行序列化是基于Bson的。

還有一個(gè)BinaryFormatterTranscoder是屬于默認(rèn)的另一個(gè)實(shí)現(xiàn),這個(gè)就是基于我們前面的說.NET類庫自帶的System.Runtime.Serialization.Formatters.Binary。

先來看看這兩種自帶的Transcoder要怎么用。

先定義好初始化Memcached相關(guān)的方法,以及讀寫緩存的方法。

初始化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。

需要注意下面兩個(gè)說明

  • 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時(shí)記得給Product類加上[Serializable]就好!

下面來看看如何借助MessagePack來實(shí)現(xiàn)Memcached的Transcoder。

這里繼承DefaultTranscoder就可以了,然后重寫SerializeObject,DeserializeObject和Deserialize這三個(gè)方法。

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有方法可以讓我們直接把一個(gè)object序列化成ArraySegment,也可以把ArraySegment 反序列化成一個(gè)object!!

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

這個(gè)時(shí)候,我們有兩種方式來使用這個(gè)新定義的MessagePackTranscoder。

方式一 :在使用的時(shí)候,我們只需要替換前面定義的transcoder變量即可(適用>=2.1.0版本)。

string transcoder = "CachingSerializer.MessagePackTranscoder,CachingSerializer";

注:如果使用方式一來處理,記得將transcoder的拼寫不要錯(cuò),并且要帶上命名空間,不然創(chuàng)建的Transcoder會一直是null,從而走的就是Bson了! 本質(zhì)是 Activator.CreateInstance,應(yīng)該不用多解釋。

方式二:通過依賴注入的方式來處理(適用>=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...
}

運(yùn)行之前加個(gè)斷點(diǎn),確保真的進(jìn)了我們重寫的方法中。

最后的結(jié)果:

Protobuf和Json的,在這里就不一一介紹了,這兩個(gè)處理起來比MessagePack復(fù)雜了不少??梢詤⒖?a rel="external nofollow" target="_blank" >MemcachedTranscoder這個(gè)開源項(xiàng)目,也是MessagePack作者寫的,雖然是5年前的,但是一樣的好用。

對于Redis來說,在調(diào)用Set方法時(shí)要顯式的將我們的值先進(jìn)行序列化,不那么簡潔,所以都會進(jìn)行一次封裝在使用。

對于Memcached來說,在調(diào)用Set方法的時(shí)候雖然不需要顯式的進(jìn)行序列化,但是有可能要我們自己去實(shí)現(xiàn)一個(gè)Transcoder,這也是有點(diǎn)麻煩的。

下面給大家推薦一個(gè)簡單的緩存庫來處理這些問題。

使用EasyCaching來簡化操作

EasyCaching是筆者在業(yè)余時(shí)間寫的一個(gè)簡單的開源項(xiàng)目,主要目的是想簡化緩存的操作,目前也在不斷的完善中。

EasyCaching提供了前面所說的4種序列化方法可供選擇:

  • BinaryFormatter
  • MessagePack
  • Json
  • ProtoBuf

如果這4種都不滿足需求,也可以自己寫一個(gè),只要實(shí)現(xiàn)IEasyCachingSerializer這個(gè)接口相應(yīng)的方法即可。

Redis

在介紹怎么用序列化之前,先來簡單看看是怎么用的(用ASP.NET Core Web API做演示)。

添加Redis相關(guān)的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}"; 
 }
}
  • 使用的時(shí)候,在構(gòu)造函數(shù)對IEasyCachingProvider進(jìn)行依賴注入即可。
  • Redis默認(rèn)用了BinaryFormatter來進(jìn)行序列化。

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

以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(); 
 }
}

在控制器中使用時(shí)和Redis是一模一樣的。

這里需要注意的是,在EasyCaching中,默認(rèn)使用的序列化方法并不是DefaultTranscoder中的Bson,而是BinaryFormatter

如何去替換默認(rèn)的序列化操作呢?

同樣以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();
}

因?yàn)樵贓asyCaching中,有一個(gè)自己的Transcoder,這個(gè)Transcoder對IEasyCachingSerializer進(jìn)行注入,所以只需要指定對應(yīng)的Serializer即可。

總結(jié)

一、 先來看看文中提到的4種序列化的庫

System.Runtime.Serialization.Formatters.Binary在使用上需要加上[Serializable],效率是最慢的,優(yōu)勢就是類庫里面就有,不需要額外引用其他package。

Newtonsoft.Json使用起來比較友善,可能是用的多的緣故,也不需要我們對已經(jīng)定義好的類加一些Attribute上去。

protobuf-net使用起來可能就略微麻煩一點(diǎn),可以在定義類的時(shí)候加上相應(yīng)的Attribute,也可以在運(yùn)行時(shí)去處理(要注意處理子類),不過它的口碑還是不錯(cuò)的。

MessagePack-CSharp雖然可以不添加Attribute,但是不加比加的時(shí)候也會有所損耗。

至于如何選擇,可能就要視情況而定了!

有興趣的可以用BenchmarkDotNet跑跑分,我也簡單寫了一個(gè)可供參考:SerializerBenchmark

二、在對緩存操作的時(shí)候,可能會更傾向于“隱式”操作,能直接將一個(gè)object扔進(jìn)去,也可以直接將一個(gè)object拿出來,至少能方便使用方。

三、序列化操作時(shí),Redis要比Memcached簡單一些。

最后,如果您在使用EasyCaching,有問題或建議可以聯(lián)系我!

前半部分的示例代碼:CachingSerializer

后半部分的示例代碼:sample

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • asp.net gridview代碼綁定

    asp.net gridview代碼綁定

    昨天試過了傳統(tǒng)的數(shù)據(jù)連接方式和數(shù)據(jù)顯示,但代碼看起來總有點(diǎn)ASP的痕跡,今天就試試ASP.net服務(wù)器控件gridview的數(shù)據(jù)綁定!
    2008-10-10
  • asp.net連接數(shù)據(jù)庫 增加,修改,刪除,查詢代碼

    asp.net連接數(shù)據(jù)庫 增加,修改,刪除,查詢代碼

    asp.net連接數(shù)據(jù)庫,實(shí)現(xiàn)增加,修改,刪除,查詢的四大功能完整代碼。
    2009-07-07
  • ASP.NET?MVC擴(kuò)展帶驗(yàn)證的單選按鈕

    ASP.NET?MVC擴(kuò)展帶驗(yàn)證的單選按鈕

    這篇文章實(shí)現(xiàn)了ASP.NET?MVC擴(kuò)展帶驗(yàn)證的單選按鈕功能,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • .NET微信公眾號 用戶分組管理

    .NET微信公眾號 用戶分組管理

    這篇文章主要介紹了.NET微信公眾號 用戶分組管理,web頁面設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • asp.net高效替換大容量字符實(shí)現(xiàn)代碼

    asp.net高效替換大容量字符實(shí)現(xiàn)代碼

    每次替換完后,在下次替換時(shí)先排除這次替換的內(nèi)容,累加本次替換的內(nèi)容。
    2008-08-08
  • asp.net core配合vue實(shí)現(xiàn)后端驗(yàn)證碼邏輯

    asp.net core配合vue實(shí)現(xiàn)后端驗(yàn)證碼邏輯

    網(wǎng)上的前端驗(yàn)證碼邏輯總感覺不安全,驗(yàn)證碼建議還是使用后端配合驗(yàn)證。本文主要介紹了asp.net core配合vue實(shí)現(xiàn)后端驗(yàn)證碼邏輯,感興趣的可以了解一下
    2021-06-06
  • asp.net輸出重寫壓縮頁面文件實(shí)例代碼

    asp.net輸出重寫壓縮頁面文件實(shí)例代碼

    這篇文章主要介紹了asp.net輸出重寫壓縮頁面文件實(shí)例代碼,需要的朋友可以參考下
    2014-02-02
  • MAUI項(xiàng)目中使用SnackBar與Toast通知功能

    MAUI項(xiàng)目中使用SnackBar與Toast通知功能

    這篇文章介紹了MAUI項(xiàng)目中使用SnackBar與Toast通知功能的方法,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-02-02
  • asp.net URL 顯示亂碼 解決方法

    asp.net URL 顯示亂碼 解決方法

    剛剛接觸網(wǎng)頁開發(fā),不過遇到URR參數(shù)傳遞錯(cuò)誤的問題,兩個(gè)頁面進(jìn)行信息交互時(shí)可以用url進(jìn)行傳遞,但是如果傳遞信息的編碼格式不是UTF-8(或者不是設(shè)置的默認(rèn)格式)URL傳遞時(shí)會出現(xiàn)亂碼。
    2009-06-06
  • 理解ASP.NET Core 啟動類(Startup)

    理解ASP.NET Core 啟動類(Startup)

    這篇文章主要介紹了ASP.NET Core 啟動類(Startup),文中運(yùn)用代碼講解相關(guān)知識非常詳細(xì),感興趣的小伙伴可以參考一下
    2021-09-09

最新評論