Redis .NET操作


Redis是一個支持數據結構更多的鍵值對數據庫。它的值不僅可以是字符串等基本數據類型,也可以是類對象,更可以是Set、List、計數器等高級的數據結構。
  Memcached也可以保存類似於Set、List這樣的結構,但是如果說要向List中增加元素,Memcached則需要把List全部元素取出來,然后再把元素增加進去,然后再保存回去,不僅效率低,而且有並發訪問問題。Redis內置的Set、List等可以直接支持增加、刪除元素的操作,效率很高,操作是原子的。

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

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;

2.1.2 redis的缺點:

  1. Redis是單線程的,因此單個Redis實例只能使用一個CPU核,不能充分發揮服務器的性能。可以在一台服務器上運行多個Redis實例,不同實例監聽不同端口,再互相組成集群。
  2. 做緩存性能不如Memcached;

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的key

3.2 數據沒有隔離性

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM