程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> Redis到底該如何利用(三)?,redis利用

Redis到底該如何利用(三)?,redis利用

編輯:C#入門知識

Redis到底該如何利用(三)?,redis利用


上兩篇受益匪淺,秉著趁熱打鐵,不挖到最深不罷休的精神,我決定追加這篇。上一篇裡最後我有提到實現分級緩存管理應該是個可行的方案,因此今天特別實踐了一下。不過緩存分級之後也發現了一些問題,例如下圖:

當appServerA修改了數據,並同步到Redis/DB之後,如何讓appServerB也能更新本地緩存呢?雖然Redis的出現是為了解決這個問題的,但是分級方案裡,MemoryCache還是需要保留。那麼如何保存呢?我嘗試了下面的幾種方式,現在我們逐一來看。

 全數據增量同步

所謂全數據校驗,即所有的緩存數據首先都同步至Redis,然後根據數據的時間戳來進行同步。分解步驟如下:

上面的方案咱們落地到.NET+Redis又該怎麼處理呢?

第一步很簡單直接跳過,第二步就有點問題了,這個時間戳要便於redis的排序和獲取,考慮到這些問題,我覺得取Linux的數字型時間戳比較靠譜,再配合SortSet來建立索引,進行同步,看起來的確不錯。那麼來看下代碼:

本篇還是以User為例,可能場景不適合,大家將就理解

public void UpdateToRedis(User user)
{
            var redis = ConnectionMultiplexer.Connect("localhost");
            var db = redis.GetDatabase();
            
            TimeSpan ts = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            double time = Convert.ToInt64(ts.TotalSeconds);//計算Unix時間戳
            db.SortedSetAdd("capqueen:user:index", user.Id, time);//更新記錄
            var json = JsonConvert.SerializeObject(user);
            db.StringSet("capqueen:user" + user.Id, json);//user記錄
}

 同步:

public void Sync(double timespan)
{
            var redis = ConnectionMultiplexer.Connect("localhost");
            var db = redis.GetDatabase();

            TimeSpan ts = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            double time = Convert.ToInt64(ts.TotalSeconds);//計算Unix時間戳
            
            //同步時間范圍內的記錄,這裡增加時間范圍,以防止一些數據不准確的問題
            var members = db.SortedSetRangeByScore("capqueen:user:index", timespan, time);

            var keys = members.ToList().Select<RedisValue, RedisKey>(i => i.ToString());
            var values = db.StringGet(keys.ToArray());

            //構造List Json以加速解析
            var portsJson = new StringBuilder("[");

            values.ToList().ForEach(item =>
            {
                if (!string.IsNullOrWhiteSpace(item))
                {
                    portsJson.Append(item).Append(",");
                }
            });

            portsJson.Append("]");

            var users = JsonConvert.DeserializeObject<List<User>>(portsJson.ToString());

            //和內存的List<User>做同步
            ...
            //END
}

上面的代碼只是實例,實際運行的時候感覺還不是特別靠譜。

消息通知

增量同步是好,但是同時時機也是個問題,時機不對,就會影響用戶體驗。而且感覺我們還是無法很好的掌控數據。那麼有沒有一種方式可以及時的通知到appServer更新緩存呢?這裡我腦子裡冒出來的是.NET Event機制。

我覺得.NET因為有了優秀的Event機制,才讓我覺得它使用起來很方便

但是這裡是服務器之間的通信,我想非Scoket莫屬了,之間做過Scoket雙向通信,印象特別深刻,這樣的場景讓我自然而然的想到了這個方案。可是特別的增加一個socket,本身復雜的架構要更復雜了,還是尋求一個靠譜的中間件比較適合。看過Redis的功能列表的同學,一定沒忘記Redis還有一個訂閱發布,pub/sub功能。於是我就利用了這個功能,設計了如下的方案:

先來看下Redis的,pub/sub功能,請看文檔

redis提供指定channel的訂閱發布功能,每一個Client可以訂閱指定的channel消息,也可以向指定的channel發送消息。

利用ServiceStack.Redis的pub/sub功能實現如下:

using (var redisConsumer = new RedisClient(TestConfig.SingleHost))
using (var subscription = redisConsumer.CreateSubscription())
{
    subscription.OnSubscribe = channel =>
    {
        //訂閱事件
    };
    subscription.OnUnSubscribe = channel =>
    {
       //退訂事件
    };
    subscription.OnMessage = (channel, msg) =>
    {
        // 這裡的msg,我為了測試定義成了userid
        var user= redisConsumer.As<User>().GetById(msg);//從Redis獲取記錄
        UpdateLocalCache(user);//更新本地
    };

  
    subscription.SubscribeToChannels("capqueen:redis:events"); //blocking
}

 

發送:

using (var redisPublisher = new RedisClient("localhost"))
{
    redisPublisher.PublishMessage("capqueen:redis:events", userId);//發送消息
}

 

這裡我覺得message應該定義成一個消息體,接收處理應該根據具體的消息定義來具體處理,因為訂閱發布的通道顯然應該是合理利用,如果一個業務一個通道,有點太多了。

當然這個實現起來又要講一大篇了,這裡不多說明。

總結

本篇文章,根據上一篇提的方案做了一些實現,不足之處太多了。例如事件丟了怎麼辦?如何正確的處理消息缺失,記錄少了之類的特殊情況應該是很重要的。我暫時還沒想到方案,不知道前輩們如何看待這樣的問題。可能我還是濫用了Redis,為了技術而技術了,不過不嘗試碰下壁,怎麼能得到成長。所謂兼聽則明 偏信則暗,我感覺自己有點不見棺材不落淚了。請前輩們不吝指教!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved