Redis是一個支持數據結構更多的鍵值對數據庫。它的值不只能夠是字符串等基本數據類型,也能夠是類對象,更能夠是Set、List、計數器等高級的數據結構。
Memcached也能夠保存相似於Set、List這樣的結構,可是若是說要向List中增長元素,Memcached則須要把List所有元素取出來,而後再把元素增長進去,而後再保存回去,不只效率低,並且有併發訪問問題。Redis內置的Set、List等能夠直接支持增長、刪除元素的操做,效率很高,操做是原子的。html
Memcached數據存在內存中,memcached重啓後數據就消失;
Redis會把數據持久化到硬盤中,Redis重啓後數據還存在。git
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程序員
1) 支持string、list、set、geo等複雜的數據結構。
2) 高命中的數據運行時是在內存中,數據最終仍是能夠保存到磁盤中,這樣服務器重啓以後數據還在。
3) 服務器是單線程
的,來自全部客戶端的全部命令都是串行執行的,所以不用擔憂併發修改(串行操做固然仍是有併發問題)的問題,編程模型簡單;
4) 支持消息訂閱/通知機制,能夠用做消息隊列;
5) Key、Value最大長度容許512M;github
1) Redis是單線程
的,所以單個Redis實例只能使用一個CPU核,不能充分發揮服務器的性能。能夠在一臺服務器上運行多個Redis實例,不一樣實例監聽不一樣端口,再互相組成集羣。
2) 作緩存性能不如Memcached;redis
1) 多線程,能夠充分利用CPU多核的性能;
2) 作緩存性能最高;算法
1) 只能保存鍵值對數據,鍵值對只能是字符串,若是有對象數據只能本身序列化成json
字符串;
2) 數據保存在內存中,重啓後會丟失;
3) Key最大長度255個字符,Value最長1M。數據庫
Memcached只能當緩存服務器用,也是最合適的;Redis不只能夠作緩存服務器(性能沒有Memcached好),還能夠存儲業務數據。編程
執行
set myKey abc
,就是設置鍵值對myKey=abc
執行get myKey
就是查找名字是myKey的值;
keys *
是查找全部的key
key *n*
是查找全部名字中含有n的keyjson
和Redis同樣,Redis也是不一樣系統放到Redis中的數據都是不隔離的,所以設定Key的時候也要選擇好Key。windows
Redis服務器默認建了16個數據庫,Redis的想法是讓你們把不一樣系統的數據放到不一樣的數據庫中。可是建議你們不要這樣用,由於Redis是單線程的,不一樣業務都放到同一個Redis實例的話效率就不高,建議放到不一樣的實例中。所以儘可能只用默認的db0數據庫。
命令行下能夠用select 0、select 1這樣的指令切換數據庫,最高爲15。試試在不一樣數據庫下新建、查詢數據。
瞭解的經常使用的幾個命令就能夠。全部對數據的操做均可以經過命令行進行,後面講的.net操做Redis的驅動其實就是對這些命令的封裝。
RedisDesktopManager (0.9.3之後須要訂閱)
https://github.com/uglide/RedisDesktopManager/releases
推薦組件: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()
,儘可能用異步方法。
注意看到訪問的參數、返回值是RedisKey
、RedisValue
類型,進行了運算符重載,能夠和string
、
byte[]
之間進行隱式轉換。
由於Redis裏全部數據類型都是用KeyValue保存,所以Key操做針對全部數據類型,
KeyDelete(RedisKey key)
:根據Key刪除;KeyExists(RedisKey key)
判斷Key是否存在,儘可能不要用,由於會有併發問題;KeyExpire(RedisKey key, TimeSpan? expiry)
、KeyExpire(RedisKey key, DateTime? expiry)
設置過時時間;能夠用 StringGet
、StringSet
來讀寫鍵值對,是基礎操做
StringAppend(RedisKey key, RedisValue value)
:向Key的Value中附加內容,不存在則新建;
能夠用做計數器: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>
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)
。不傳start
、end
表示獲取全部數據。指定以後則獲取某個範圍。
能夠把Redis的list當成消息隊列使用,好比向註冊用戶發送歡迎郵件的工做,能夠在註冊的流程中把要發送郵件的郵箱放到list中,另外一個程序從list中pop獲取郵件來發送。 生產者、消費者模式。把生產過程和消費過程隔離。
List與Set區別:
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等,就不用作重複性判斷了。
Redis 有序集合。
與Set不一樣的是,每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
有序集合的成員是惟一的,但分數(score)卻能夠重複。
集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。 集合中最大的成員數爲 232- 1 (4294967295, 每一個集合可存儲40多億個成員)。
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 這個範圍的打分;
存儲文章數據
文章對象序列化後使用一個字符串類型鍵存儲,但是這種方法沒法提供對單個字段的原子讀寫操做,從而產生競態條件。如兩個客戶端同事修改不一樣屬性存儲,後者覆蓋前者。
使用多個字符串類型鍵存儲一個對象,好處是隻要修改一處屬性,十分方便。
使用一個散列類型鍵存儲一個對象更適合。散列更適合這個場景。
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
若是一次性操做不少,會很慢,那麼可使用批量操做,兩種方式:
GeoAdd(RedisKey key, GeoEntry[] values) SortedSetAdd(RedisKey key, SortedSetEntry[] values)
IBatch batch = db.CreateBatch(); db.GeoAdd("ShopsGeo1", new GeoEntry(116.34039, 39.94218, "1")); db.StringSet("abc", "123"); batch.Execute();
會把當前鏈接的CreateBatch()、Execute() 之間的操做一次性提交給服務器。
多線程中的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(); }
發出一個固定金額的紅包,由若干我的來搶,須要知足哪些規則?
1.全部人搶到金額之和等於紅包金額,不能超過,也不能少於。
2.每一個人至少搶到一分錢。
3.要保證全部人搶到金額的概率相等。
參考:程序員小灰——漫畫:如何實現搶紅包算法?
剩餘紅包金額爲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; }