【Redis】Redis由淺入深代碼示例

一、下載

https://github.com/MicrosoftArchive/redis/releases

Redis支持32位和64位。這個需要根據你係統平臺的實際情況選擇,這裏下載 Redis-x64-xxx.zip壓縮包到 D 盤redis文件夾下。

 

解壓:

二、Redis臨時服務

1.打開cmd,進入到剛纔解壓到的目錄,啓動臨時服務:redis-server.exe redis.windows.conf 

(備註:通過這個命令,會創建Redis臨時服務,不會在window服務列表出現Redis服務名稱和狀態,此窗口關閉,服務會自動關閉。)

2.打開另一個cmd窗口,客戶端調用:redis-cli.exe -h 127.0.0.1 -p 6379

(默認6379端口:6379在是手機按鍵上MERZ對應的號碼,而MERZ取自意大利歌女Alessia Merz的名字。MERZ長期以來被antirez及其朋友當作愚蠢的代名詞。Redis作者antirez同學在twitter上說將在下一篇博文中向大家解釋爲什麼他選擇6379作爲默認端口號......😯) 

三、Redis自定義Windows服務安裝

1.進入Redis安裝包目錄,安裝服務

 redis-server.exe --service-install redis.windows.conf --service-name redisserver1 --loglevel verbose

 win+r -> services.msc,可以看到服務安裝成功

安裝服務:redis-server.exe --service-install redis.windows.conf --service-name redisserver1 --loglevel verbose

啓動服務:redis-server.exe  --service-start --service-name redisserver1

停止服務:redis-server.exe  --service-stop --service-name redisserver1

卸載服務:redis-server.exe  --service-uninstall--service-name redisserver1

四、主從服務

1.將d盤下新建一個文件夾叫redis2,把redis文件夾的東西拷貝到redis2文件夾下,將redis-windows.conf配置文件中的ip 和端口號改一下,然後按照上面的步驟安裝一個服務叫redisserver2

 

2.使用redis桌面管理器(下載地址:https://redisdesktop.com/),鏈接兩個redis庫

 

3.設置密碼把redis.windows.conf文件中 #requirepass foobared 的#號去掉改爲自己的密碼即可

4.設置好保存後,若要使設置起作用,需要重啓redis服務

5.端口號和ip同理

 

6.重啓後需要輸入密碼 

7.slaveof 127.0.0.1 6379 設置主從,6379是主庫,6380是從庫。(設置同步時,會將主庫所有數據一起同步過來。

五、哨兵模式 

1.在主服務器redis文件夾下新建文件:sentinel.conf

輸入:sentinel monitor host6379 127.0.0.1 6379 1

2.執行 redis-server.exe sentinel.conf --sentinel

六、StackExchange客戶端

using StackExchange.Redis;
using System;

namespace ConsoleApp1
{
    class Program
    {

        #region ConnectionMultiplexer是StackExchange.Redis的核心,它被整個應用程序共享和重用,應該設置爲單例

        // redis config
        // 哨兵26379 主從服務器6379,6380
        private static readonly ConfigurationOptions ConfigurationOptions = 
            ConfigurationOptions.Parse("127.0.0.1:26379,127.0.0.1:6379,127.0.0.1:6380,password='',connectTimeout=60");

        //the lock for singleton
        private static readonly object Locker = new object();

        //singleton
        private static ConnectionMultiplexer _redisConn;

        //singleton
        public static ConnectionMultiplexer GetRedisConn()
        {

            if (_redisConn == null)
            {
                lock (Locker)
                {
                    if (_redisConn == null || !_redisConn.IsConnected)
                    {
                        _redisConn = ConnectionMultiplexer.Connect(ConfigurationOptions);
                    }
                }
            }
            return _redisConn;
        }

        #endregion

        static void Main(string[] args)
        {
            _redisConn = GetRedisConn();
            IDatabase db = _redisConn.GetDatabase();

            #region string

            var strKey = "hello";
            var strValue = "world";
            db.StringSet(strKey, strValue);

            #endregion

            #region hash  使用場景:微博個人信息

            string hashKey = "myhash";

            //hset
            db.HashSet(hashKey, "f1", "v1");
            db.HashSet(hashKey, "f2", "v2");
            HashEntry[] values1 = db.HashGetAll(hashKey);

            //hgetall
            Console.Write("hgetall " + hashKey + ", result is");
            foreach (HashEntry hashEntry in values1)
            {
                Console.Write(" " + hashEntry.Name + " " + hashEntry.Value);
            }

            #endregion

            #region list 使用場景:微博粉絲

            //list key
            string listKey = "myList";

            //rpush
            db.ListRightPush(listKey, "a");
            db.ListRightPush(listKey, "b");
            db.ListRightPush(listKey, "c");

            //lrange
            RedisValue[] values = db.ListRange(listKey, 0, -1);

            Console.Write("lrange " + listKey + " 0 -1, result is ");
            for (int i = 0; i < values.Length; i++)
            {
                Console.Write(values[i] + " ");
            }
            Console.WriteLine();

            #endregion

            #region set 使用場景:隊列

            //set key
            string setKey = "mySet";

            //sadd
            db.SetAdd(setKey, "a");
            db.SetAdd(setKey, "b");
            db.SetAdd(setKey, "c");

            //sismember
            bool isContains = db.SetContains(setKey, "a");
            Console.WriteLine("set " + setKey + " contains a is " + isContains);

            #endregion

            #region sortedset 使用場景:隊列

            string sortedSetKey = "myZset";

            //sadd
            db.SortedSetAdd(sortedSetKey, "xiaoming", 85);
            db.SortedSetAdd(sortedSetKey, "xiaohong", 100);
            db.SortedSetAdd(sortedSetKey, "xiaofei", 62);
            db.SortedSetAdd(sortedSetKey, "xiaotang", 73);

            //zrevrangebyscore
            RedisValue[] names = db.SortedSetRangeByRank(sortedSetKey, 0, 2, Order.Ascending);
            Console.Write("zrevrangebyscore " + sortedSetKey + " 0 2, result is ");
            for (int i = 0; i < names.Length; i++)
            {
                Console.Write(names[i] + " ");
            }
            Console.WriteLine();

            #endregion

            Console.ReadLine();
        }
    }
}
View Code

 

如果主服務器掛了,從服務器不能立刻變爲主庫,寫數據會失敗,因爲哨兵切換從庫爲主庫時默認30秒(sentinel.conf配置文件中配置)

哨兵臨時服務窗口不能關閉(上一步的操作)

如果關閉需要安裝爲windows服務守護進程:redis-server --service-install sentinel.conf --sentinel --service-name RedisSentinel --port 26379

 

 

sentinel配置說明:

# Example sentinel.conf  
  
# 哨兵sentinel實例運行的端口 默認26379  
port 26379  
  
# 哨兵sentinel的工作目錄  
dir /tmp  
  
# 哨兵sentinel監控的redis主節點的 ip port   
# master-name  可以自己命名的主節點名字 只能由字母A-z、數字0-9 、這三個字符".-_"組成。  
# quorum 當這些quorum個數sentinel哨兵認爲master主節點失聯 那麼這時 客觀上認爲主節點失聯了  
# sentinel monitor <master-name> <ip> <redis-port> <quorum>  
  sentinel monitor mymaster 127.0.0.1 6379 2  
  
# 當在Redis實例中開啓了requirepass foobared 授權密碼 這樣所有連接Redis實例的客戶端都要提供密碼  
# 設置哨兵sentinel 連接主從的密碼 注意必須爲主從設置一樣的驗證密碼  
# sentinel auth-pass <master-name> <password>  
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd  
  
  
# 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認爲主節點下線 默認30秒  
# sentinel down-after-milliseconds <master-name> <milliseconds>  
sentinel down-after-milliseconds mymaster 30000  
  
# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,  
這個數字越小,完成failover所需的時間就越長,  
但是如果這個數字越大,就意味着越 多的slave因爲replication而不可用。  
可以通過將這個值設爲 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。  
# sentinel parallel-syncs <master-name> <numslaves>  
sentinel parallel-syncs mymaster 1  
  
  
  
# 故障轉移的超時時間 failover-timeout 可以用在以下這些方面:   
#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。  
#2. 當一個slave從一個錯誤的master那裏同步數據開始計算時間。直到slave被糾正爲向正確的master那裏同步數據時。  
#3.當想要取消一個正在進行的failover所需要的時間。    
#4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置爲指向master,但是就不按parallel-syncs所配置的規則來了  
# 默認三分鐘  
# sentinel failover-timeout <master-name> <milliseconds>  
sentinel failover-timeout mymaster 180000  
  
# SCRIPTS EXECUTION  
  
#配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理員,例如當系統運行不正常時發郵件通知相關人員。  
#對於腳本的運行結果有以下規則:  
#若腳本執行後返回1,那麼該腳本稍後將會被再次執行,重複次數目前默認爲10  
#若腳本執行後返回2,或者比2更高的一個返回值,腳本將不會重複執行。  
#如果腳本在執行過程中由於收到系統中斷信號被終止了,則同返回值爲1時的行爲相同。  
#一個腳本的最大執行時間爲60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之後重新執行。  
  
#通知型腳本:當sentinel有任何警告級別的事件發生時(比如說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本,  
這時這個腳本應該通過郵件,SMS等方式去通知系統管理員關於系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數,  
一個是事件的類型,  
一個是事件的描述。  
如果sentinel.conf配置文件中配置了這個腳本路徑,那麼必須保證這個腳本存在於這個路徑,並且是可執行的,否則sentinel無法正常啓動成功。  
#通知腳本  
# sentinel notification-script <master-name> <script-path>  
  sentinel notification-script mymaster /var/redis/notify.sh  
  
# 客戶端重新配置主節點參數腳本  
# 當一個master由於failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已經發生改變的信息。  
# 以下參數將會在調用腳本時傳給腳本:  
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>  
# 目前<state>總是“failover”,  
# <role>是“leader”或者“observer”中的一個。   
# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的  
# 這個腳本應該是通用的,能被多次調用,不是針對性的。  
# sentinel client-reconfig-script <master-name> <script-path>  
 sentinel client-reconfig-script mymaster /var/redis/reconfig.sh  
View Code

redis.windows.conf配置文件說明:

daemonize yes #是否以後臺進程運行
 
pidfile /var/run/redis/redis-server.pid  #pid文件位置
 
port 6379#監聽端口
 
bind 127.0.0.1  #綁定地址,如外網需要連接,設置0.0.0.0
 
timeout 300   #連接超時時間,單位秒
 
loglevel notice #日誌級別,分別有:
 
# debug :適用於開發和測試
 
# verbose :更詳細信息
 
# notice :適用於生產環境
 
# warning :只記錄警告或錯誤信息
 
logfile /var/log/redis/redis-server.log  #日誌文件位置
 
syslog-enabled no  #是否將日誌輸出到系統日誌
 
databases 16#設置數據庫數量,默認數據庫爲0
 
 
 
############### 快照方式 ###############
 
 
 
save 900 1  #在900s(15m)之後,至少有1個key發生變化,則快照
 
save 300 10  #在300s(5m)之後,至少有10個key發生變化,則快照
 
save 60 10000 #在60s(1m)之後,至少有1000個key發生變化,則快照
 
rdbcompression yes  #dump時是否壓縮數據
 
dir /var/lib/redis  #數據庫(dump.rdb)文件存放目錄
 
 
 
############### 主從複製 ###############
 
 
 
slaveof <masterip> <masterport> #主從複製使用,用於本機redis作爲slave去連接主redis
 
masterauth <master-password>  #當master設置密碼認證,slave用此選項指定master認證密碼
 
slave-serve-stale-data yes   #當slave與master之間的連接斷開或slave正在與master進行數據同步時,如果有slave請求,當設置爲yes時,slave仍然響應請求,此時可能有問題,如果設置no時,slave會返回"SYNC with master in progress"錯誤信息。但INFO和SLAVEOF命令除外。
 
 
 
############### 安全 ###############
 
 
 
requirepass foobared  #配置redis連接認證密碼
 
 
 
############### 限制 ###############
 
 
 
maxclients 128#設置最大連接數,0爲不限制
 
maxmemory <bytes>#內存清理策略,如果達到此值,將採取以下動作:
 
# volatile-lru :默認策略,只對設置過期時間的key進行LRU算法刪除
 
# allkeys-lru :刪除不經常使用的key
 
# volatile-random :隨機刪除即將過期的key
 
# allkeys-random :隨機刪除一個key
 
# volatile-ttl :刪除即將過期的key
 
# noeviction :不過期,寫操作返回報錯
 
maxmemory-policy volatile-lru#如果達到maxmemory值,採用此策略
 
maxmemory-samples 3  #默認隨機選擇3個key,從中淘汰最不經常用的
 
 
 
############### 附加模式 ###############
 
 
 
appendonly no  #AOF持久化,是否記錄更新操作日誌,默認redis是異步(快照)把數據寫入本地磁盤
 
appendfilename appendonly.aof #指定更新日誌文件名
 
# AOF持久化三種同步策略:
 
# appendfsync always  #每次有數據發生變化時都會寫入appendonly.aof
 
# appendfsync everysec #默認方式,每秒同步一次到appendonly.aof
 
# appendfsync no    #不同步,數據不會持久化
 
no-appendfsync-on-rewrite no  #當AOF日誌文件即將增長到指定百分比時,redis通過調用BGREWRITEAOF是否自動重寫AOF日誌文件。
 
 
 
############### 虛擬內存 ###############
 
 
 
vm-enabled no   #是否啓用虛擬內存機制,虛擬內存機將數據分頁存放,把很少訪問的頁放到swap上,內存佔用多,最好關閉虛擬內存
 
vm-swap-file /var/lib/redis/redis.swap  #虛擬內存文件位置
 
vm-max-memory 0  #redis使用的最大內存上限,保護redis不會因過多使用物理內存影響性能
 
vm-page-size 32  #每個頁面的大小爲32字節
 
vm-pages 134217728 #設置swap文件中頁面數量
 
vm-max-threads 4  #訪問swap文件的線程數
 
 
 
############### 高級配置 ###############
 
 
 
hash-max-zipmap-entries 512  #哈希表中元素(條目)總個數不超過設定數量時,採用線性緊湊格式存儲來節省空間
 
hash-max-zipmap-value 64   #哈希表中每個value的長度不超過多少字節時,採用線性緊湊格式存儲來節省空間
 
list-max-ziplist-entries 512 #list數據類型多少節點以下會採用去指針的緊湊存儲格式
 
list-max-ziplist-value 64  #list數據類型節點值大小小於多少字節會採用緊湊存儲格式
 
set-max-intset-entries 512  #set數據類型內部數據如果全部是數值型,且包含多少節點以下會採用緊湊格式存儲
 
activerehashing yes    #是否激活重置哈希
View Code

 

一主二從三哨兵

redis主從:是備份關係, 我們操作主庫,數據也會同步到從庫。 如果主庫機器壞了,從庫可以上。就好比你 D盤的片丟了,但是你移動硬盤裏邊備份有。
redis哨兵:哨兵保證的是HA,保證特殊情況故障自動切換,哨兵盯着你的“redis主從集羣”,如果主庫死了,它會告訴你新的老大是誰。
redis集羣:集羣保證的是高併發,因爲多了一些兄弟幫忙一起扛。同時集羣會導致數據的分散,整個redis集羣會分成一堆數據槽,即不同的key會放到不不同的槽中。

主從保證了數據備份,哨兵保證了HA 即故障時切換,集羣保證了高併發性。

七、集羣搭建(轉)

原文地址:https://blog.csdn.net/hao495430759/article/details/80540407

1.首先我們構建集羣節點目錄:

(集羣正常運作至少需要三個主節點,不過在剛開始試用集羣功能時, 強烈建議使用六個節點: 其中三個爲主節點, 而其餘三個則是各個主節點的從節點。主節點崩潰,從節點的Redis就會提升爲主節點,代替原來的主節點工作,崩潰的主Redis回覆工作後,會成爲從節點)

拷貝開始下載的redis解壓後的目錄,並修改文件名(比如按集羣下redis端口命名)如下:

6380,6381,6382,6383,6384,6385對應的就是後面個節點下啓動redis的端口。

在節點目錄下新建文件,輸入(舉例在6380文件夾下新建文件)

title redis-6380;
redis-server.exe redis.windows.conf
然後保存爲start.bat 下次啓動時直接執行該腳本即可;
接着分別打開各個文件下的 redis.windows.conf,分別修改如下配置(舉例修改6380文件下的redis.window.conf文件):

port 6380 //修改爲與當前文件夾名字一樣的端口號
appendonly yes //指定是否在每次更新操作後進行日誌記錄,Redis在默認情況下是異步的把數據寫入磁盤,如果不開啓,可能會在斷電時導致一段時間內的數據丟失。 yes表示:存儲方式,aof,將寫操作記錄保存到日誌中
cluster-enabled yes //開啓集羣模式
cluster-config-file nodes-6380.conf //保存節點配置,自動創建,自動更新(建議命名時加上端口號)
cluster-node-timeout 15000 //集羣超時時間,節點超過這個時間沒反應就斷定是宕機
注意:在修改配置文件這幾項配置時,配置項前面不能有空格,否則啓動時會報錯(參考下面)

其他文件節點 6381~6385也修改相應的節點配置信息和建立啓動腳本(略)。

2.下載Ruby並安裝:

    下載地址:http://railsinstaller.org/en  這裏下載的是2.3.3版本:

 

 

    下載完成後安裝,一步步點next知道安裝完成(安裝時勾選3個選項)

然後對ruby進行配置:

 

 

3.構建集羣腳本redis-trib.rb

可以打開 https://raw.githubusercontent.com/antirez/redis/unstable/src/redis-trib.rb 然後複製裏面的內容到本地並保存爲redis-trib.rb;

如下圖,與redis集羣節點保存在同一個文件夾下(比如我所有節點都存放在redis-cluster文件夾下)。

然後依次啓動所有集羣節點start.bat

然後cmd進入redis集羣節點目錄後,執行: (–replicas 1 表示爲集羣中的每個主節點創建一個從節點)

redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385
將會出現下圖的輸出信息

上圖可看出 主節點爲6380,6381,6382 端口的三個地址,6384,6385,6383爲三個從節點

中途會詢問是否打印更多詳細信息,輸入yes即可,然後redis-trib 就會將這份配置應用到集羣當中,讓各個節點開始互相通訊

Redis集羣數據分配策略:

採用一種叫做哈希槽 (hash slot)的方式來分配數據,redis cluster 默認分配了 16384 個slot,三個節點分別承擔的slot 區間是:(上圖3個M:節點的slots描述)

節點6380覆蓋0-5460;
節點6381覆蓋5461-10922;
節點6382覆蓋10923-16383.
最後查看所有集羣節點,會看到:

集羣搭建並啓動成功。。。

4.測試集羣

進入任意一個集羣節點,cmd執行  redis-cli.exe  -c -p 6381

寫入任意一個value,查詢

寫一個hash:

hset redis:test:hash Hash1 12345

可以看到集羣會用CRC16算法來取模得到所屬的slot,然後將這個key分到哈希槽區間的節點上CRC16(key) % 16384

所以,可以看到我們set的key計算之後被分配到了slot-162 上, 而slot-162處在節點6380上,因此redis自動redirect到了6380節點上。

八、訂閱與發佈

using StackExchange.Redis;
using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var redisconn = ConnectionMultiplexer.Connect(ConfigurationOptions.Parse("127.0.0.1:6379,password='',connectTimeout=60"));

            #region 訂閱

            Console.WriteLine("請輸入您要訂閱哪個通道的信息?");
            var channelKey = Console.ReadLine();
            redisconn.GetSubscriber().Subscribe(channelKey, (chanel, msg) =>
            {
                Console.WriteLine($"通道:{channelKey}接受到發佈的內容爲:{msg}");
            });
            Console.WriteLine("您訂閱的通道爲:<< " + channelKey + " >> ! 一切就緒,等待發布消息!勿動,一動就沒啦!!");

            #endregion

            #region 發佈

            Console.WriteLine("請輸入要發佈向哪個通道?");
            var channel = Console.ReadLine();
            Console.WriteLine("請輸入要發佈的消息內容.");
            var message = Console.ReadLine();
            redisconn.GetSubscriber().Publish(channel, message);

            #endregion

            Console.ReadKey();
        }
    }
}

封裝:

using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            #region 訂閱

            var redis = new Redis();
            redis.RedisSubMessageEvent += RedisSubMessageEvent;
            redis.Use(1).RedisSub("redis_20200105_pay");

            #endregion

            #region 發佈

            for (var i = 1; i < 20; i++)
            {
                Redis.Using(rd => {
                    rd.Use(1).RedisPub<string>("redis_20200105_pay", "pay amt=" + i);
                });

                Thread.Sleep(1000);
            }

            #endregion

            Console.ReadKey();
        }

        private static void RedisSubMessageEvent(string msg)
        {

            Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} RedisSubMessageEvent: {msg}");
        }
    }

    public class Redis : IDisposable
    {

        private static ConnectionMultiplexer redis = null;
        private static bool connected = false;
        private IDatabase db = null;
        private int current = 0;
        public static bool IsConnected { get { Open(); return redis.IsConnected; } }
        public static bool Test()
        {
            bool r = true;
            try
            {
                Redis.Using(rs => { rs.Use(0); });
            }
            catch (Exception e)
            {
                r = false;
            }
            return r;
        }
        private static int Open()
        {
            if (connected) return 1;
            redis = ConnectionMultiplexer.Connect("127.0.0.1:6379,password='',abortConnect = false");
            connected = true;
            return 1;
        }
        public static void Using(Action<Redis> a)
        {
            using (var red = new Redis())
            {
                a(red);
            }
        }
        public Redis Use(int i)
        {
            Open();
            current = i;
            db = redis.GetDatabase(i);
            return this;
        }

        public void Set(string key, string val, TimeSpan? ts = null)
        {
            db.StringSet(key, val, ts);
        }

        public string Get(string key)
        {
            return db.StringGet(key);
        }

        public void Remove(string key)
        {
            db.KeyDelete(key, CommandFlags.HighPriority);
        }

        public bool Exists(string key)
        {
            return db.KeyExists(key);
        }

        public void Dispose()
        {
            db = null;
        }

        #region Redis發佈訂閱

        public delegate void RedisDeletegate(string str);
        public event RedisDeletegate RedisSubMessageEvent;

        /// <summary>
        /// 訂閱
        /// </summary>
        /// <param name="subChannel"></param>
        public void RedisSub(string subChannel)
        {

            redis.GetSubscriber().Subscribe(subChannel, (channel, message) =>
            {
                RedisSubMessageEvent?.Invoke(message); //觸發事件

            });

        }

        /// <summary>
        /// 發佈
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="channel"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public long RedisPub<T>(string channel, T msg)
        {
            string s = JsonConvert.SerializeObject(msg);
            return redis.GetSubscriber().Publish(channel, JsonConvert.SerializeObject(msg));
        }

        /// <summary>
        /// 取消訂閱
        /// </summary>
        /// <param name="channel"></param>
        public void Unsubscribe(string channel)
        {
            redis.GetSubscriber().Unsubscribe(channel);
        }

        /// <summary>
        /// 取消全部訂閱
        /// </summary>
        public void UnsubscribeAll()
        {
            redis.GetSubscriber().UnsubscribeAll();
        }

        #endregion
    }
}
View Code

將redis發佈訂閱模式用做消息隊列和rabbitmq的區別:

可靠性
redis :沒有相應的機制保證消息的可靠消費,如果發佈者發佈一條消息,而沒有對應的訂閱者的話,這條消息將丟失,不會存在內存中;
rabbitmq:具有消息消費確認機制,如果發佈一條消息,還沒有消費者消費該隊列,那麼這條消息將一直存放在隊列中,直到有消費者消費了該條消息,以此可以保證消息的可靠消費;
實時性
redis:實時性高,redis作爲高效的緩存服務器,所有數據都存在在服務器中,所以它具有更高的實時性
消費者負載均衡:
rabbitmq隊列可以被多個消費者同時監控消費,但是每一條消息只能被消費一次,由於rabbitmq的消費確認機制,因此它能夠根據消費者的消費能力而調整它的負載;
redis發佈訂閱模式,一個隊列可以被多個消費者同時訂閱,當有消息到達時,會將該消息依次發送給每個訂閱者;
持久性
redis:redis的持久化是針對於整個redis緩存的內容,它有RDB和AOF兩種持久化方式,可以將整個redis實例持久化到磁盤,以此來做數據備份,防止異常情況下導致數據丟失。
rabbitmq:隊列,消息都可以選擇性持久化,持久化粒度更小,更靈活;
隊列監控
rabbitmq實現了後臺監控平臺,可以在該平臺上看到所有創建的隊列的詳細情況,良好的後臺管理平臺可以方面我們更好的使用;
redis沒有所謂的監控平臺。
總結
redis:       輕量級,低延遲,高併發,低可靠性;
rabbitmq:重量級,高可靠,異步,不保證實時;
rabbitmq是一個專門的AMQP協議隊列,他的優勢就在於提供可靠的隊列服務,並且可做到異步,而redis主要是用於緩存的,redis的發佈訂閱模塊,可用於實現及時性,且可靠性低的功能。

九、Redis五種數據類型的使用場景

1、字符串(Strings)

緩存功能:
字符串最經典的使用場景,redis具有支撐高併發特性,所以緩存通常能起到加速讀寫和降低IO操作的作用。
計數器:
許多運用都會使用redis作爲計數的基礎工具,他可以實現快速計數、查詢緩存的功能,同時數據可以一步落地到其他的數據源。
如:視頻播放數系統就是使用redis作爲視頻播放數計數的基礎組件。
共享session:
出於負載均衡的考慮,分佈式服務會將用戶信息的訪問均衡到不同服務器上,
用戶刷新一次訪問可能會需要重新登錄,爲避免這個問題可以用redis將用戶session集中管理,
在這種模式下只要保證redis的高可用和擴展性的,每次獲取用戶更新或查詢登錄信息
都直接從redis中集中獲取。
限速:
出於安全考慮,每次進行登錄時讓用戶輸入手機驗證碼,爲了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率。

database.StringSet("name", "");//設置StringSet(key, value)
string str = database.StringGet("name");//結果:蒼
database.StringSet("name_two", str, TimeSpan.FromSeconds(10));//設置時間,10s後過期。
//創建對象
Demo demo = new Demo()
{
    Name = "",
    Age = 18,
    Height = 1.83
};
string demojson = JsonConvert.SerializeObject(demo);//序列化
database.StringSet("model", demojson);

string model = database.StringGet("model");
demo = JsonConvert.DeserializeObject<Demo>(model);//反序列化

 

 

2、哈希(Hashes) 

存儲、讀取、修改用戶屬性

常用於存儲一個對象(用戶姓名、年齡、生日......)

使用string增加了序列號反序列化的開銷,key重複值太多內存的浪費

            var user = new User
            {
                Name = "james",
                Age = 18
            };
            string json = JsonConvert.SerializeObject(user);//序列化
            db.HashSet("user", "londo", json);
            db.HashSet("user", "polo", json);
            db.HashSet("user", "kobe", json);

            //獲取Model
            string hashcang = db.HashGet("user", "londo");
            user = JsonConvert.DeserializeObject<User>(hashcang);//反序列化

            //獲取List
            RedisValue[] values = db.HashValues("user");//獲取所有value
            IList<User> demolist = new List<User>();
            foreach (var item in values)
            {
                User hashmodel = JsonConvert.DeserializeObject<User>(item);
                demolist.Add(hashmodel);
            }

 

 

3、列表(Lists)

1).最新消息排行等功能(比如朋友圈的時間線)

2).消息隊列

3).文章列表:

每個用戶都有屬於自己的文章列表,現在需要分頁展示文章列表,此時可以考慮使用列表,列表不但有序
同時支持按照索引範圍獲取元素

 

 

            for (int i = 0; i < 6; i++)
            {
                db.ListRightPush("list", i);//從底部插入數據
            }
            for (int i = 6; i < 10; i++)
            {
                db.ListLeftPush("list", i);//從頂部插入數據
            }
            long length = db.ListLength("list");//長度 10

            RedisValue rightPop = db.ListRightPop("list");//從底部拿出數據 即:刪除5
            var popValue = rightPop.ToString();// 5
            RedisValue leftpop = db.ListLeftPop("list");//從頂部拿出數據 即:刪除9

            RedisValue[] list = db.ListRange("list");//列表數據 8 7 6 0 1 2 3 4
            var s = list[0].ToString(); // 8

 

4、集合(Sets)

1).共同好友

2).利用唯一性,統計訪問網站的所有獨立ip

3).好友推薦時,根據tag求交集,大於某個閾值就可以推薦

標籤(tag):
集合類型比較典型的使用場景,如一個用戶對娛樂、體育比較感興趣,另一個可能對新聞感興趣,
這些興趣就是標籤,有了這些數據就可以得到同一標籤的人,以及用戶的共同愛好的標籤,
這些數據對於用戶體驗以及曾強用戶粘度比較重要。

            db.SetAdd("TagAsNBA", "1張三");
            db.SetAdd("TagAsNBA", "2李四");
            db.SetAdd("TagAsNBA", "3王五");
           
            RedisValue[] setList = db.SetMembers("TagAsNBA");
            foreach (var item in setList)
            {
                Console.WriteLine(item);
            }

 

5、有序集合(Sorted sets)

1).排行榜

2).帶權重的消息隊列

排行榜:
有序集合經典使用場景。例如視頻網站需要對用戶上傳的視頻做排行榜,
榜單維護可能是多方面:按照時間、按照播放量、按照獲得的贊數等。

 

 

            db.SortedSetAdd("RankingAsScore", "張三", 1);
            db.SortedSetAdd("RankingAsScore", "王五", 3);
            db.SortedSetAdd("RankingAsScore", "李四", 2);
            
            RedisValue[] sortedSetList = db.SortedSetRangeByRank("RankingAsScore");
            foreach (RedisValue t in sortedSetList)
            {
                Console.WriteLine(t + "  ---   " + t.HasValue);
            }

 測試源碼:

using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        public class User
        {
            public string Name { get; set; }
            public int Age { get; set; }
        }

        #region ConnectionMultiplexer是StackExchange.Redis的核心,它被整個應用程序共享和重用,應該設置爲單例

        // redis config
        // 哨兵26379 主從服務器6379,6380,開啓哨兵模式,會自動鏈接
        private static readonly ConfigurationOptions ConfigurationOptions =
            ConfigurationOptions.Parse("127.0.0.1:6379,password='',connectTimeout=60");

        //the lock for singleton
        private static readonly object Locker = new object();

        //singleton
        private static ConnectionMultiplexer _redisConn;

        //singleton
        public static ConnectionMultiplexer GetRedisConn()
        {

            if (_redisConn == null)
            {
                lock (Locker)
                {
                    if (_redisConn == null || !_redisConn.IsConnected)
                    {
                        _redisConn = ConnectionMultiplexer.Connect(ConfigurationOptions);
                    }
                }
            }
            return _redisConn;
        }

        #endregion

        static void Main(string[] args)
        {
            _redisConn = GetRedisConn();
            IDatabase db = _redisConn.GetDatabase(1);

            #region sets 集合

            db.SetAdd("TagAsNBA", "1張三");
            db.SetAdd("TagAsNBA", "2李四");
            db.SetAdd("TagAsNBA", "3王五");
           
            RedisValue[] setList = db.SetMembers("TagAsNBA");
            foreach (var item in setList)
            {
                Console.WriteLine(item);
            }
            #endregion

            #region SetSorted 有序集合

            db.SortedSetAdd("RankingAsScore", "張三", 1);
            db.SortedSetAdd("RankingAsScore", "王五", 3);
            db.SortedSetAdd("RankingAsScore", "李四", 2);
            
            RedisValue[] sortedSetList = db.SortedSetRangeByRank("RankingAsScore");
            foreach (RedisValue t in sortedSetList)
            {
                Console.WriteLine(t + "  ---   " + t.HasValue);
            }
            #endregion

            #region hash
            ////創建對象
            //var user = new User
            //{
            //    Name = "james",
            //    Age = 18
            //};
            //string json = JsonConvert.SerializeObject(user);//序列化
            //db.HashSet("user", "londo", json);
            //db.HashSet("user", "polo", json);
            //db.HashSet("user", "kobe", json);

            ////獲取Model
            //string hashcang = db.HashGet("user", "londo");
            //user = JsonConvert.DeserializeObject<User>(hashcang);//反序列化

            ////獲取List
            //RedisValue[] values = db.HashValues("user");//獲取所有value
            //IList<User> demolist = new List<User>();
            //foreach (var item in values)
            //{
            //    User hashmodel = JsonConvert.DeserializeObject<User>(item);
            //    demolist.Add(hashmodel);
            //}

            #endregion

            #region list

            //for (int i = 0; i < 6; i++)
            //{
            //    db.ListRightPush("list", i);//從底部插入數據
            //}
            //for (int i = 6; i < 10; i++)
            //{
            //    db.ListLeftPush("list", i);//從頂部插入數據
            //}
            //long length = db.ListLength("list");//長度 10

            //RedisValue rightPop = db.ListRightPop("list");//從底部拿出數據 即:刪除5
            //var popValue = rightPop.ToString();// 5
            //RedisValue leftpop = db.ListLeftPop("list");//從頂部拿出數據 即:刪除9

            //RedisValue[] list = db.ListRange("list");//列表數據 8 7 6 0 1 2 3 4
            //var s = list[0].ToString(); // 8

            #endregion

            //var strKey = "hello";
            //var strValue = "world";
            //db.StringSet(strKey, strValue, TimeSpan.FromSeconds(500));

            #region 訂閱

            //var redis = new Redis();
            //redis.RedisSubMessageEvent += RedisSubMessageEvent;
            //redis.Use(1).RedisSub("redis_20200105_pay");

            #endregion

            #region 發佈

            //for (var i = 1; i < 20; i++)
            //{

            //    Redis.Using(rd => {
            //        rd.Use(1).RedisPub<string>("redis_20200105_pay", "pay amt=" + i);
            //    });

            //    Thread.Sleep(200);

            //}

            #endregion

            Console.ReadKey();
        }

        private static void RedisSubMessageEvent(string msg)
        {

            Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} RedisSubMessageEvent: {msg}");
        }
    }

    public class Redis : IDisposable
    {

        private static ConnectionMultiplexer redis = null;
        private static bool connected = false;
        private IDatabase db = null;
        private int current = 0;
        public static bool IsConnected { get { Open(); return redis.IsConnected; } }
        public static bool Test()
        {
            bool r = true;
            try
            {
                Redis.Using(rs => { rs.Use(0); });
            }
            catch (Exception e)
            {
                r = false;
            }
            return r;
        }
        private static int Open()
        {
            if (connected) return 1;
            redis = ConnectionMultiplexer.Connect("127.0.0.1:6379,password='',abortConnect = false");
            connected = true;
            return 1;
        }
        public static void Using(Action<Redis> a)
        {
            using (var red = new Redis())
            {
                a(red);
            }
        }
        public Redis Use(int i)
        {
            Open();
            current = i;
            db = redis.GetDatabase(i);
            return this;
        }

        public void Set(string key, string val, TimeSpan? ts = null)
        {
            db.StringSet(key, val, ts);
        }

        public string Get(string key)
        {
            return db.StringGet(key);
        }

        public void Remove(string key)
        {
            db.KeyDelete(key, CommandFlags.HighPriority);
        }

        public bool Exists(string key)
        {
            return db.KeyExists(key);
        }

        public void Dispose()
        {
            db = null;
        }

        #region Redis發佈訂閱

        public delegate void RedisDeletegate(string str);
        public event RedisDeletegate RedisSubMessageEvent;

        /// <summary>
        /// 訂閱
        /// </summary>
        /// <param name="subChannel"></param>
        public void RedisSub(string subChannel)
        {

            redis.GetSubscriber().Subscribe(subChannel, (channel, message) =>
            {
                RedisSubMessageEvent?.Invoke(message); //觸發事件

            });

        }

        /// <summary>
        /// 發佈
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="channel"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public long RedisPub<T>(string channel, T msg)
        {
            string s = JsonConvert.SerializeObject(msg);
            return redis.GetSubscriber().Publish(channel, JsonConvert.SerializeObject(msg));
        }

        /// <summary>
        /// 取消訂閱
        /// </summary>
        /// <param name="channel"></param>
        public void Unsubscribe(string channel)
        {
            redis.GetSubscriber().Unsubscribe(channel);
        }

        /// <summary>
        /// 取消全部訂閱
        /// </summary>
        public void UnsubscribeAll()
        {
            redis.GetSubscriber().UnsubscribeAll();
        }

        #endregion
    }
}
View Code

 

十、Redis分佈式鎖秒殺場景案例

第一問,有沒有用過分佈式鎖?
有,基於redis的分佈鎖

第二問,redis爲什麼可以做分佈式鎖?
Redis爲單進程單線程模式,採用隊列模式將併發訪問變成串行訪問,且多客戶端對Redis的連接並不存在競爭關係。

代碼實現的,主要是針對某一筆數據的流水號加鎖,防止多個線程寫入這個數據。(具有互斥性)

源碼:

using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            const string productKey = "0002_RC";//秒殺key hotelId_RoomType
            
            Thread.Sleep(5000);

            // 2、創建20個請求來秒殺
            for (var i = 0; i < 20; i++)
            {
                var thread = new Thread(() =>
                {
                    SkillProduct(productKey);
                });

                thread.Start();
            }

            Console.ReadKey();
        }

        /// <summary>
        /// 秒殺方法
        /// </summary>
        public static void SkillProduct(string productKey)
        {
            var redisLock = new RedisLock();
            redisLock.Lock(Environment.MachineName);
            // 1、獲取商品庫存
            var stockNum = redisLock.GetStockNum(productKey);

            // 2、判斷商品庫存是否爲空
            if (stockNum == 0)
            {
                // 2.1 秒殺失敗消息
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:不好意思,秒殺已結束,商品編號:{stockNum}");
                redisLock.UnLock(Environment.MachineName);
                return;
            }

            // 3、秒殺成功消息
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:恭喜你,秒殺成功,商品編號:{stockNum}");

            // 4、扣減商品庫存
            redisLock.SubStockNum(productKey);

            redisLock.UnLock(Environment.MachineName);
        }

    }



    /// <summary>
    /// redis分佈式鎖
    /// 分佈式鎖四要素
    /// 1、鎖名
    /// 2、加鎖操作
    /// 3、解鎖操作
    /// 4、鎖超時時間
    /// </summary>
    public class RedisLock
    {
        // 1、redis連接管理類
        private readonly ConnectionMultiplexer _connectionMultiplexer = null;
        // 2、redis數據操作類
        private readonly IDatabase _database;
        public RedisLock()
        {
            _connectionMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1:6379");

            _database = _connectionMultiplexer.GetDatabase(0);
        }

        /// <summary>
        /// 加鎖
        /// 1、key : 鎖名
        /// 2、value : 誰加了這把鎖 : 防止鎖被其線程釋放掉
        /// 3、超時時間 :防止死鎖
        /// </summary>
        public void Lock(string key)
        {
            //如果加鎖失敗,繼續獲取鎖,無限失敗
            while (true)
            {
                //當前服務器id,可以用酒店ID
                var value = Environment.MachineName;//Thread.CurrentThread.ManagedThreadId

                // 1、key:鎖名,可以用酒店id或者商品id 2、value:誰加了這把鎖,防止鎖被其線程釋放掉 3、超時時間:防止死鎖
                bool isSucceed = _database.LockTake(key, value, TimeSpan.FromSeconds(10));
                if (isSucceed)
                {
                    break;
                }
                // 休眠一下
                Thread.Sleep(200);
            }
        }

        /// <summary>
        /// 解鎖
        /// </summary>
        public void UnLock(string key)
        {
            // 1、解鎖
            _database.LockRelease(key, Environment.MachineName);

            // 2、關閉資源
            _connectionMultiplexer.Close();
        }

        /// <summary>
        /// 獲取庫存
        /// </summary>
        public int GetStockNum(string key)
        {
            var redisValue = _database.StringGet(key);

            var num = JsonConvert.DeserializeObject<int>(redisValue);
            return num;
        }

        /// <summary>
        /// 扣減庫存
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public void SubStockNum(string key)
        {
            var redisValue = _database.StringGet(key);

            var num = JsonConvert.DeserializeObject<int>(redisValue);
            var value = num - 1;
            _database.StringSet(key, value);
        }

    }
}
View Code

 

 

使用c#的Lock爲什麼不行?

引文Lock只能鎖線程不能鎖進程。

源碼:

using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            const string productKey = "0002_RC";//秒殺key hotelId_RoomType

            Thread.Sleep(5000);

            ////創建30個請求來秒殺
            //for (var i = 0; i < 30; i++)
            //{
            //    var thread = new Thread(() =>
            //    {
            //        SkillProduct(productKey);
            //    });

            //    thread.Start();
            //}

            //創建30個請求來秒殺
            var clock = new CsharpLock();
            for (var i = 0; i < 30; i++)
            {
                var thread = new Thread(() =>
                {
                    clock.SkillProduct(productKey);
                });

                thread.Start();
            }

            Console.ReadKey();
        }

        /// <summary>
        /// 秒殺方法
        /// </summary>
        public static void SkillProduct(string productKey)
        {
            var redisLock = new RedisLock();
            redisLock.Lock(Environment.MachineName);
            // 1、獲取商品庫存
            var stockNum = redisLock.GetStockNum(productKey);

            // 2、判斷商品庫存是否爲空
            if (stockNum == 0)
            {
                // 2.1 秒殺失敗消息
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:不好意思,秒殺已結束,商品編號:{stockNum}");
                redisLock.UnLock(Environment.MachineName);
                return;
            }

            // 3、秒殺成功消息
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:恭喜你,秒殺成功,商品編號:{stockNum}");

            // 4、扣減商品庫存
            redisLock.SubStockNum(productKey);

            redisLock.UnLock(Environment.MachineName);
        }
    }

    /// <summary>
    /// C#Lock鎖
    /// </summary>
    public class CsharpLock
    {
        public readonly object LockObj = new object();

        /// <summary>
        /// 秒殺方法
        /// </summary>
        public void SkillProduct(string productKey)
        {
            var redisLock = new RedisLock();
            lock (LockObj)
            {
                // 1、獲取商品庫存
                var stockNum = redisLock.GetStockNum(productKey);

                // 2、判斷商品庫存是否爲空
                if (stockNum == 0)
                {
                    // 2.1 秒殺失敗消息
                    Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:不好意思,秒殺已結束,商品編號:{stockNum}");
                    redisLock.UnLock(Environment.MachineName);
                    return;
                }

                // 3、秒殺成功消息
                Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:恭喜你,秒殺成功,商品編號:{stockNum}");

                // 4、扣減商品庫存
                redisLock.SubStockNum(productKey);
            }
        }
    }

    /// <summary>
    /// redis分佈式鎖
    /// 分佈式鎖四要素
    /// 1、鎖名
    /// 2、加鎖操作
    /// 3、解鎖操作
    /// 4、鎖超時時間
    /// </summary>
    public class RedisLock
    {
        // 1、redis連接管理類
        private readonly ConnectionMultiplexer _connectionMultiplexer = null;
        // 2、redis數據操作類
        private readonly IDatabase _database;
        public RedisLock()
        {
            _connectionMultiplexer = ConnectionMultiplexer.Connect("127.0.0.1:6379");

            _database = _connectionMultiplexer.GetDatabase(0);
        }

        /// <summary>
        /// 加鎖
        /// 1、key : 鎖名
        /// 2、value : 誰加了這把鎖 : 防止鎖被其線程釋放掉
        /// 3、超時時間 :防止死鎖
        /// </summary>
        public void Lock(string key)
        {
            //如果加鎖失敗,繼續獲取鎖,無限失敗
            while (true)
            {
                //當前服務器id,可以用酒店ID
                var value = Environment.MachineName;//Thread.CurrentThread.ManagedThreadId

                // 1、key:鎖名,可以用酒店id或者商品id 2、value:誰加了這把鎖,防止鎖被其線程釋放掉 3、超時時間:防止死鎖
                bool isSucceed = _database.LockTake(key, value, TimeSpan.FromSeconds(10));
                if (isSucceed)
                {
                    break;
                }
                // 休眠一下
                Thread.Sleep(200);
            }
        }

        /// <summary>
        /// 解鎖
        /// </summary>
        public void UnLock(string key)
        {
            // 1、解鎖
            _database.LockRelease(key, Environment.MachineName);

            // 2、關閉資源
            _connectionMultiplexer.Close();
        }

        /// <summary>
        /// 獲取庫存
        /// </summary>
        public int GetStockNum(string key)
        {
            var redisValue = _database.StringGet(key);

            var num = JsonConvert.DeserializeObject<int>(redisValue);
            return num;
        }

        /// <summary>
        /// 扣減庫存
        /// </summary>
        public void SubStockNum(string key)
        {
            var redisValue = _database.StringGet(key);

            var num = JsonConvert.DeserializeObject<int>(redisValue);
            var value = num - 1;
            _database.StringSet(key, value);
        }

    }
}
View Code

 

Lock(this)有問題

 

源碼:

 public bool LockTake(RedisKey key, RedisValue value, TimeSpan expiry, CommandFlags flags = CommandFlags.None)
        {
            if (value.IsNull) throw new ArgumentNullException(nameof(value));
            return StringSet(key, value, expiry, When.NotExists, flags);
        }

  

 

 可以看到調用的是set方法,繼續看set方法可以看到

   private Message GetStringSetMessage(RedisKey key, RedisValue value, TimeSpan? expiry = null, When when = When.Always, CommandFlags flags = CommandFlags.None)
        {
            WhenAlwaysOrExistsOrNotExists(when);
            if (value.IsNull) return Message.Create(Database, flags, RedisCommand.DEL, key);

            if (expiry == null || expiry.Value == TimeSpan.MaxValue)
            { // no expiry
                switch (when)
                {
                    case When.Always: return Message.Create(Database, flags, RedisCommand.SET, key, value);
                    case When.NotExists: return Message.Create(Database, flags, RedisCommand.SETNX, key, value);
                    case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX);
                }
            }
            long milliseconds = expiry.Value.Ticks / TimeSpan.TicksPerMillisecond;

            if ((milliseconds % 1000) == 0)
            {
                // a nice round number of seconds
                long seconds = milliseconds / 1000;
                switch (when)
                {
                    case When.Always: return Message.Create(Database, flags, RedisCommand.SETEX, key, seconds, value);
                    case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX);
                    case When.NotExists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX);
                }
            }

            switch (when)
            {
                case When.Always: return Message.Create(Database, flags, RedisCommand.PSETEX, key, milliseconds, value);
                case When.Exists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX);
                case When.NotExists: return Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX);
            }
            throw new NotSupportedException();
        }

RedisCommand.SETNX:不執行set命令如果key存在

RedisCommand.SETEX:命令將覆蓋已有的值

XX : 只在鍵已經存在時, 纔對鍵進行設置操作

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章