NoSQL之一:Memcached

一、NoSQL簡介

       NoSQL並不是No SQL(不再需要SQL),而是指Not Only SQL(不僅僅只有SQL)。NoSQL並不是用來替代關係型資料庫的,而是在某些使用關係型資料庫不合適的場景中,可以使用NoSQL資料庫進行優化,而在系統中主要的、常規的數據仍然使用關係型資料庫。
  常用的NoSQL資料庫有Memcached、Redis、MongoDB等,其中前兩者屬於鍵值對資料庫,後者屬於文檔資料庫。它們都有各自的優缺點以及使用場景。

二、Memcached介紹與安裝

       Memcached是一個專門用來做快取的資料庫,快取的數據都是在記憶體當中,當資料庫重啟之後,數據也就都丟失了。其相當於一個Dictionary鍵值對集合,根據Key值取Value值。

  1、Memcached安裝

         從網上下載Memcached-win64-1.4.4-14.zip安裝包。解壓後,通過管理員許可權執行如下命令,可將其安裝成服務:

G:\MemcachedAfterInstall>memcached.exe -d install

   2、Memcached可視化工具

    TreeNMS是一款Redis、Memcached可視化客戶端工具,實現基於Web方式對Redis、Memcached資料庫進行管理、維護。可通過如下鏈接//www.treesoft.cn/dms.html進行下載。

    下圖即該工具的使用介面:

 三、在.Net Core中的使用

  1、組件包的安裝與使用

    在.Net Core項目中調用Memcached資料庫的資源,可以使用EnyimMemcachedCore客戶端組件包。

    通過該組件向Memcached資料庫存入數據有三種模式,分別如下:

      1)Set:存在則覆蓋,不存在則新增;

      2)Replace:如果存在則覆蓋,並且返回true;如果不存在則不處理,並且返回false;

      3)Add:如果不存在則新增,並且返回true;如果存在則不處理,並且返回false;

    故,根據以上可知,如果沒有特殊要求一般用Set即可。

    注意:Memcached資料庫中的key的長度最高為250個字元,value的值最大為1M。

  2、案例演示

    為了演示方便,創建了一個基於.Net Core3.1版本的WPF項目,介面如下:

 

 

 

 

 

 

 

 

 

     1)存儲數據、讀取數據 

public partial class MainWindow : Window
{
  private readonly MemcachedClient _memcachedClient;
  private static readonly ILoggerFactory _loggerFactory = new LoggerFactory();
  public MainWindow()
  {
    InitializeComponent();
    var options = new MemcachedClientOptions();
    options.AddServer("127.0.0.1", 11211);
    _memcachedClient = new MemcachedClient(_loggerFactory, new MemcachedClientConfiguration(_loggerFactory, options));
  }
}
private void btnStorage_Click(object sender, RoutedEventArgs e)
{
  var result = _memcachedClient.Store(StoreMode.Set, "supersnow", "yao");
  MessageBox.Show($"保存成功?{result}");
}

private void btnRead_Click(object sender, RoutedEventArgs e)
{
  var result = _memcachedClient.Get("supersnow");
  MessageBox.Show($"讀取結果:{result}");
}  

  

     2)存儲類數據、讀取類數據

private async void btnStorage_Class_Click(object sender, RoutedEventArgs e)
{
  var person = new Person
  {
    Name = "SuperSnow",
    Age = 25
  };
  var result = await _memcachedClient.StoreAsync(StoreMode.Set, "person_key", person, TimeSpan.FromSeconds(3600));
  MessageBox.Show($"保存Person成功?{result}");
}
private async void btnRead_Class_Click(object sender, RoutedEventArgs e) {   var result = await _memcachedClient.GetAsync<Person>("person_key");   if (result.Value == null)     MessageBox.Show("數據已經失效");   else     MessageBox.Show($"讀取Person結果:{result.Value.Name}:{result.Value.Age}"); } public class Person {   public string Name { get; set; }   public int Age { get; set; } }
  

  

     3)讀取數據異常

    對於讀取數據失敗時,返回結果為false。但是,此時並不知道失敗的原因是什麼,可以通過如下方法獲取失敗原因:

 private void btnRead_Exception_Click(object sender, RoutedEventArgs e)
 {
    var result = _memcachedClient.ExecuteRemove("abc");
    MessageBox.Show($"結果:{result.Success},Message:{result.Message},Exception:{result.Exception},StatusCode:{result.StatusCode},InnerResult:{result.InnerResult.Message}");
 } 

  

     4)Memcached Cas操作

    Cas操作類似於關係型資料庫中的「樂觀鎖」,查詢的時候會順帶查出一個Cas值,在寫入的時候會帶著這個Cas值,如果發現Cas值改變了,則說明已經有其他操作提前修改了相應的值。本質上來說Cas就是用來解決並發問題,通過讀取一個值,然後做一些處理或判斷,然後再寫回到資料庫中,這種操作有可能產生並發問題。

private CasResult<Person> casResult;
 private void btnA_Read_Click(object sender, RoutedEventArgs e)
 {
     casResult = _memcachedClient.GetWithCas<Person>("person_key");
     if (casResult.Result != null)
         MessageBox.Show($"讀取Person結果:{casResult.Result.Name}:{casResult.Result.Age}");
     else
         MessageBox.Show("沒有結果");
 }

 private void btnB_Save_Click(object sender, RoutedEventArgs e)
 {
     var result = _memcachedClient.GetWithCas<Person>("person_key");
     if (result.Result.Age >= 25)
         result.Result.Age++;
     var tag = _memcachedClient.Cas(StoreMode.Set, "person_key", result.Result, result.Cas);
     if (tag.Result)
         MessageBox.Show("修改成功");
     else
         MessageBox.Show("修改失敗");
 }

 private void btnA_Save_Click(object sender, RoutedEventArgs e)
 {
     if (casResult.Result.Age >= 25)
         casResult.Result.Age++;
     else
         casResult.Result.Age++;
     var tag = _memcachedClient.Cas(StoreMode.Set, "person_key", casResult.Result, casResult.Cas);
     if (tag.Result)
         MessageBox.Show("修改成功");
     else
         MessageBox.Show("修改失敗");
 }

 

     5)Memcached集群

    Memcached重啟之後,在短時間內大量的請求會湧入資料庫,給資料庫造成巨大壓力,解決此類問題的方法就是使用集群,即使用多Memcached伺服器提供服務。

    除此之外,Memcached還有可能面臨「雪崩」問題,如果所有快取設置過期時間是一樣的,或者失效時間在一個短距離的範圍內。那麼每隔一段時間就會造成一次資料庫訪問的高峰。此類問題的解決方法就是將不同快取的快取失效時間設置成不一樣,如對失效時間加上隨機數。

    Memcached集群的節點之間不用進行通訊和數據同步,只需要在多個伺服器上啟動多個Memcached資料庫即可。客戶端決定了將數據寫入不同的Memcached實例,不用做主從複製操作。

    節點定位演算法有很多種,最常用的就是Ketama演算法,該演算法根據Key算出一個hash值,然後根據hash值再得出伺服器。如下例所示:

private readonly MemcachedClient _memcachedClient;
private static readonly ILoggerFactory _loggerFactory = new LoggerFactory();
public MainWindow()
{
    InitializeComponent();

    var options = new MemcachedClientOptions();
    options.AddServer("127.0.0.1", 11211);
    options.AddServer("127.0.0.1", 11212);
    options.NodeLocatorFactory = new DefaultNodeLocatorFactory(11211);
    _memcachedClient = new MemcachedClient(_loggerFactory, new MemcachedClientConfiguration(_loggerFactory, options));
}
private void btnA_Save_Cluster_Click(object sender, RoutedEventArgs e)
{
    var person = new Person
    {
        Name = "張三",
        Age = 25
    };
    var result =  _memcachedClient.Store(StoreMode.Set, "1", person);
    MessageBox.Show($"保存Person成功?{result}");
}

private void btnA_Read_Cluster_Click(object sender, RoutedEventArgs e)
{
    var result = _memcachedClient.Get<Person>("1");
    if (result == null)
        MessageBox.Show("數據已經失效");
    else
        MessageBox.Show($"讀取Person結果:{result.Name}:{result.Age}");
}

private void btnB_Save_Cluster_Click(object sender, RoutedEventArgs e)
{
    var person = new Person
    {
        Name = "李四",
        Age = 23
    };
    var result = _memcachedClient.Store(StoreMode.Set, "2", person);
    MessageBox.Show($"保存Person成功?{result}");
}

private void btnB_Read_Cluster_Click(object sender, RoutedEventArgs e)
{
    var result = _memcachedClient.Get<Person>("2");
    if (result == null)
        MessageBox.Show("數據已經失效");
    else
        MessageBox.Show($"讀取Person結果:{result.Name}:{result.Age}");
} 

 

     通過如下命令分別啟動兩個Memcached實例:

G:\MemcachedAfterInstall>memcached.exe -p 11212
G:\MemcachedAfterInstall>memcached.exe -p 11211

    在TreeNMS系統中配置兩個資料庫連接:

    由於Key=1和Key=2的hash值不同,所以快取數據分別存在於兩個Memcached實例中,如下圖所示: