Redis .NET操做

Redis是一個支持數據結構更多的鍵值對數據庫。它的值不只能夠是字符串等基本數據類型,也能夠是類對象,更能夠是Set、List、計數器等高級的數據結構。
  Memcached也能夠保存相似於Set、List這樣的結構,可是若是說要向List中增長元素,Memcached則須要把List所有元素取出來,而後再把元素增長進去,而後再保存回去,不只效率低,並且有併發訪問問題。Redis內置的Set、List等能夠直接支持增長、刪除元素的操做,效率很高,操做是原子的。html

Memcached數據存在內存中,memcached重啓後數據就消失;
Redis會把數據持久化到硬盤中,Redis重啓後數據還存在。git

1 安裝

redis for windows >=2.8的版本支持直接安裝爲windows服務(Redis-x64-3.2.100.msi才能夠,zip不行)
https://github.com/MicrosoftArchive/redis
若是下載msi自動裝完服務,若是下載zip須要按照下面的方法安裝爲服務:
http://www.runoob.com/redis/redis-install.html程序員

2 redis與Memcached 區別

2.1 redis優缺點

2.1.1 redis的優勢:

1) 支持string、list、set、geo等複雜的數據結構。
2) 高命中的數據運行時是在內存中,數據最終仍是能夠保存到磁盤中,這樣服務器重啓以後數據還在。
3) 服務器是單線程的,來自全部客戶端的全部命令都是串行執行的,所以不用擔憂併發修改(串行操做固然仍是有併發問題)的問題,編程模型簡單;
4) 支持消息訂閱/通知機制,能夠用做消息隊列;
5) Key、Value最大長度容許512M;github

2.1.2 redis的缺點:

1) Redis是單線程的,所以單個Redis實例只能使用一個CPU核,不能充分發揮服務器的性能。能夠在一臺服務器上運行多個Redis實例,不一樣實例監聽不一樣端口,再互相組成集羣。
2) 作緩存性能不如Memcached;redis

2.2 Memcached的優缺點

2.2.1 Memcached的優勢:

1) 多線程,能夠充分利用CPU多核的性能;
2) 作緩存性能最高;算法

2.2.2 Memcached的缺點:

1) 只能保存鍵值對數據,鍵值對只能是字符串,若是有對象數據只能本身序列化成json
字符串;
2) 數據保存在內存中,重啓後會丟失;
3) Key最大長度255個字符,Value最長1M。數據庫

2.3 總結

Memcached只能當緩存服務器用,也是最合適的;Redis不只能夠作緩存服務器(性能沒有Memcached好),還能夠存儲業務數據。編程

3 redis命令行管理客戶端

3.1 直接啓動redis安裝目錄下的redis-cli便可。

執行set myKey abc,就是設置鍵值對myKey=abc
執行get myKey就是查找名字是myKey的值;
keys *是查找全部的key
key *n*是查找全部名字中含有n的keyjson

3.2 數據沒有隔離性

和Redis同樣,Redis也是不一樣系統放到Redis中的數據都是不隔離的,所以設定Key的時候也要選擇好Key。windows

3.3 儘可能選用默認的數據庫

Redis服務器默認建了16個數據庫,Redis的想法是讓你們把不一樣系統的數據放到不一樣的數據庫中。可是建議你們不要這樣用,由於Redis是單線程的,不一樣業務都放到同一個Redis實例的話效率就不高,建議放到不一樣的實例中。所以儘可能只用默認的db0數據庫。

命令行下能夠用select 0、select 1這樣的指令切換數據庫,最高爲15。試試在不一樣數據庫下新建、查詢數據。

瞭解的經常使用的幾個命令就能夠。全部對數據的操做均可以經過命令行進行,後面講的.net操做Redis的驅動其實就是對這些命令的封裝。

4 GUI管理客戶端

RedisDesktopManager (0.9.3之後須要訂閱)
https://github.com/uglide/RedisDesktopManager/releases

5 .NET鏈接redis

推薦組件:StackExchange.Redis
https://stackexchange.github.io/StackExchange.Redis/

其餘做品:
NewLife.Redis基礎教程
https://www.cnblogs.com/nnhy/p/icache.html

using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379")) 
{ 
 
IDatabase db = redis.GetDatabase();
//默認是訪問db0數據庫,能夠經過方法參數指定數字訪問不一樣的數據庫 
 
db.StringSet("Name", "abc"); 
}

支持設置過時時間:

db.StringSet("name", "rupeng.com", TimeSpan.FromSeconds(10))

獲取數據:

string s = db.StringGet("Name")
//若是查不到則返回null

Redis裏全部方法幾乎都支持異步,好比StringGetAsync()StringSetAsync(),儘可能用異步方法。

注意看到訪問的參數、返回值是RedisKeyRedisValue類型,進行了運算符重載,能夠和string
byte[]之間進行隱式轉換。

6 命令

6.1 鍵(Key)

由於Redis裏全部數據類型都是用KeyValue保存,所以Key操做針對全部數據類型,

  • KeyDelete(RedisKey key):根據Key刪除;
  • KeyExists(RedisKey key)判斷Key是否存在,儘可能不要用,由於會有併發問題;
  • KeyExpire(RedisKey key, TimeSpan? expiry)KeyExpire(RedisKey key, DateTime? expiry) 設置過時時間;

6.2 字符串(String)

能夠用 StringGetStringSet 來讀寫鍵值對,是基礎操做
StringAppend(RedisKey key, RedisValue value):向Key的Value中附加內容,不存在則新建;

6.2.1 場景:計數器

能夠用做計數器:db.StringIncrement("count", 2.5); 給 count 這個計數器增長一個值,若是不存在
則從0開始加;db.StringDecrement("count",1)計數器減值;獲取仍是用StringGet()獲取字符串類型的
值。好比能夠用這個來計算新聞點擊量、點贊量,效率很是高。

public class NewsController : Controller
    {
        private  string NEWSPREFIX = "WX_NEWS_";
        // GET: News
        public async Task<ActionResult> Index(int id)
        {
            
            using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"))
            {

                IDatabase db = redis.GetDatabase();
                //默認是訪問db0數據庫,能夠經過方法參數指定數字訪問不一樣的數據庫 
                string clickCount = NEWSPREFIX + Request.UserHostAddress + "_ClickCount_" + id;
               
               // Task<long> StringIncrementAsync:
                // 返回值:The value of key after the increment. (遞增後的值)
               long increment = await db.StringIncrementAsync(clickCount);
               
              //RedisValue count = await db.StringGetAsync(clickCount);
                //ViewBag.count = count;
                
                ViewBag.count = increment;
            }

            return View();
        }
    }

index.cshtml

<h2>點擊量:@ViewBag.count</h2>

6.2 列表(List)

  • Redis列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)
  • 一個列表最多能夠包含 232 - 1 個元素 (4294967295, 每一個列表超過40億個元素)。

6.2.1 經常使用方法

1.從左側壓棧:ListLeftPush(RedisKey key, RedisValue value)
2.從左側彈出:RedisValue ListLeftPop(RedisKey key)
3.從右側壓棧:ListRightPush(RedisKey key, RedisValue value )
4.從右側彈出:RedisValue ListRightPop(RedisKey key)
5.獲取Key爲key的List中第index個元素的值:RedisValue ListGetByIndex(RedisKey key, long index) ;
6.獲取Key爲key的List中元素個數:long ListLength(RedisKey key) ;

儘可能不要用ListGetByIndex、ListLength由於會有併發問題。
若是是讀取而不pop,則使用 ListRange:RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1)。不傳 startend 表示獲取全部數據。指定以後則獲取某個範圍。

6.2.2 應用場景

  能夠把Redis的list當成消息隊列使用,好比向註冊用戶發送歡迎郵件的工做,能夠在註冊的流程中把要發送郵件的郵箱放到list中,另外一個程序從list中pop獲取郵件來發送。 生產者、消費者模式。把生產過程和消費過程隔離。

6.3 集合(Set)

  • Redis 的 Set 是 String 類型的無序集合。集合成員是惟一的,這就意味着集合中不能出現重複的數據。
  • Redis 中集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是 O(1)。
  • 集合中最大的成員數爲 232- 1 (4294967295, 每一個集合可存儲40多億個成員)。

List與Set區別:

6.3.1 經常使用方法

SetAdd(RedisKey key, RedisValue value) 向set中增長元素
bool SetContains(RedisKey key, RedisValue value) 判斷set中是否存在某個元素;
long SetLength(RedisKey key) 得到set中元素的個數;
SetRemove(RedisKey key, RedisValue value)從set中刪除元素;
RedisValue[] SetMembers(RedisKey key)獲取a集合中的元素;

若是使用set保存封禁用id等,就不用作重複性判斷了。

6.4 有序集合(sorted set)

  • Redis 有序集合。
    與Set不一樣的是,每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。

  • 有序集合的成員是惟一的,但分數(score)卻能夠重複。

  • 集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。 集合中最大的成員數爲 232- 1 (4294967295, 每一個集合可存儲40多億個成員)。

6.4.1 經常使用方法

SortedSetAdd(RedisKey key, RedisValue member, double score)

在key這個sortedset中增長member,而且給這個member打分,若是member已經存在,則覆蓋以前的打分;

double SortedSetIncrement(RedisKey key, RedisValue member, double value)

給key中member這一項增長value分;

double SortedSetDecrement(RedisKey key, RedisValue member, double value)

給key中member這一項減value分;

SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending)

根據排序返回 sortedset中的元素以及元素的打分,start、stop用來分頁查詢、order用來指定排序規則。

RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending)

根據打分排序返回值,能夠根據序號查詢其中一部分;

RedisValue[] SortedSetRangeByScore(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1)

根據打分排序返回值,能夠只返回start- stop 這個範圍的打分;

6.4.2 sortedset應用場景

  1. 用戶每搜一次一個關鍵詞,就給這個關鍵詞加一分;展現熱搜的時候就把前N個獲取出來就好了;
  2. 高積分用戶排行榜;
  3. 熱門商品;
  4. 給寶寶投票;

6.5 哈希(Hash)

  • Redis hash 是一個string類型的field和value的映射表,hash特別適合用於存儲對象。
  • Redis 中每一個 hash 能夠存儲 232 - 1 鍵值對(40多億)。

6.5.1 應用場景

存儲文章數據
文章對象序列化後使用一個字符串類型鍵存儲,但是這種方法沒法提供對單個字段的原子讀寫操做,從而產生競態條件。如兩個客戶端同事修改不一樣屬性存儲,後者覆蓋前者。

使用多個字符串類型鍵存儲一個對象,好處是隻要修改一處屬性,十分方便。

使用一個散列類型鍵存儲一個對象更適合。散列更適合這個場景。

6.6 GEO 地理位置

  • Geo是Redis 3.2版本後新增的數據類型,用來保存興趣點(POI,point of interest)的座標信息。
  • 能夠實現計算兩POI之間的距離、獲取一個點周邊指定距離的POI。

6.6.1 經常使用方法

1.下面添加興趣點數據,」1」、」2」是點的主鍵,點的名稱、地址、電話等存到其餘表中。

db.GeoAdd("ShopsGeo", new GeoEntry(116.34039, 39.94218,"1")); 
db.GeoAdd("ShopsGeo", new GeoEntry(116.340934, 39.942221, "2")); 
db.GeoAdd("ShopsGeo", new GeoEntry(116.341082, 39.941025, "3")); 
db.GeoAdd("ShopsGeo", new GeoEntry(116.340848, 39.937758, "4")); 
db.GeoAdd("ShopsGeo", new GeoEntry(116.342982, 39.937325, "5")); 
db.GeoAdd("ShopsGeo", new GeoEntry(116.340866, 39.936827, "6"));

2.刪除一個點

GeoRemove(RedisKey key, RedisValue member)

3.根據點的主鍵獲取座標:

GeoPosition? pos = db.GeoPosition("ShopsGeo", "1")

4.查詢兩個POI之間的距離:

double? dist = db.GeoDistance("ShopsGeo", "1", "5", GeoUnit.Meters);//最後一個參數爲距離單位

5.獲取一個POI周邊的POI:

GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", "2", 200, GeoUnit.Meters);//獲取」2」這個周邊200米範圍內的POI 
foreach(GeoRadiusResult result in results) 
{ 
        Console.WriteLine("Id="+result.Member+",位置"+result.Position+",距離"+result.Distance); 
}

6.獲取一個座標(這個座標不必定是POI)周邊的POI:

GeoRadiusResult[] results = db.GeoRadius("ShopsGeo", 116.34092, 39.94223, 200, GeoUnit.Meters);// 獲取(116.34092, 39.94223)這個周邊200米範圍內的POI 
foreach(GeoRadiusResult result in results) 
{ 
Console.WriteLine("Id="+result.Member+",位置"+result.Position+",距離"+result.Distance); 
}

Geo Hash原理:http://www.cnblogs.com/LBSer/p/3310455.html

7 批量操做

若是一次性操做不少,會很慢,那麼可使用批量操做,兩種方式:

  1. 幾乎全部的操做都支持數組類型,這樣就能夠一次性操做多條數據:好比
GeoAdd(RedisKey key, GeoEntry[] values)
SortedSetAdd(RedisKey key, SortedSetEntry[] values)
  1. 若是一次性的操做不是簡單的同類型操做,那麼就要使用批量模式:
IBatch batch = db.CreateBatch(); 
db.GeoAdd("ShopsGeo1", new GeoEntry(116.34039, 39.94218, "1")); 
db.StringSet("abc", "123"); 
batch.Execute();

會把當前鏈接的CreateBatch()、Execute() 之間的操做一次性提交給服務器。

8 分佈式鎖

多線程中的lock等的做用範圍是當前的程序範圍內的,若是想跨多臺服務器的鎖(儘可能避免這樣搞),就要使用分佈式鎖

using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379"))
            {
                IDatabase db = redis.GetDatabase();

                RedisValue token = Environment.MachineName;
                //實際項目秒殺此處可換成商品ID 
                //第三個參數爲鎖超時時間,鎖佔用最多10秒鐘,超過10秒鐘若是尚未LockRelease,則也自動釋放鎖,避免了死鎖
                if (db.LockTake("mylock", token, TimeSpan.FromSeconds(10)))
                {
                    try
                    {
                        Console.WriteLine("操做開始~");
                        Thread.Sleep(30000);
                        Console.WriteLine("操做完成~");
                    }
                    finally
                    {
                        db.LockRelease("mylock", token);
                    }
                }
                else
                {
                    Console.WriteLine("得到鎖失敗");
                }
                Console.ReadKey();
            }

9 搶紅包案例

  • 把這個紅包數組以List的形式存到Redis中;
  • 用戶搶紅包就是從List中Pop取紅包。

發出一個固定金額的紅包,由若干我的來搶,須要知足哪些規則?

1.全部人搶到金額之和等於紅包金額,不能超過,也不能少於。
2.每一個人至少搶到一分錢。
3.要保證全部人搶到金額的概率相等。

9.1 二倍均值法

參考:程序員小灰——漫畫:如何實現搶紅包算法?
剩餘紅包金額爲M,剩餘人數爲N,那麼有以下公式:

每次搶到的金額 = 隨機區間 (0, M / N X 2)

這個公式,保證了每次隨機金額的平均值是相等的,不會由於搶紅包的前後順序而形成不公平。
舉個栗子:

假設有10我的,紅包總額100元。100/10X2 = 20, 因此第一我的的隨機範圍是(0,20 ),平都可以搶到10元。
假設第一我的隨機到10元,那麼剩餘金額是100-10 = 90 元。90/9X2 = 20, 因此第二我的的隨機範圍一樣是(0,20 ),平都可以搶到10元。
假設第二我的隨機到10元,那麼剩餘金額是90-10 = 80 元。80/8X2 = 20, 因此第三我的的隨機範圍一樣是(0,20 ),平都可以搶到10元。
以此類推,每一次隨機範圍的均值是相等的。

static void Main(string[] args)
        {
                //例子:50元分配10我的
                List<int> amountList = divideRedPackage(5000, 10);
                foreach (double amount in amountList)
                {
                    double item = (amount / 100);
                    Console.WriteLine($"搶到金額:{item}");
                }
        }

        //發紅包算法,金額參數以分爲單位
        public static List<int> divideRedPackage(int totalAmount, int totalPeopleNum)
        {
            List<int> amountList = new List<int>();
            int restAmount = totalAmount;
            int restPeopleNum = totalPeopleNum;
            Random random = new Random();
            for (int i = 0; i < totalPeopleNum - 1; i++)
            {
                //隨機範圍:[1,剩餘人均金額的兩倍),左閉右開
                int amount = random.Next(restAmount / restPeopleNum * 2 - 1) + 1;
                restAmount -= amount;
                restPeopleNum--;
                amountList.Add(amount);
            }
            amountList.Add(restAmount);
            return amountList;
        }
相關文章
相關標籤/搜索