Redis是個好東西,經過上兩個星期的研究和實踐,目前正在項目里大規模的替換掉原來的本地記憶體cache。但是替換過程中卻發現,Redis這東西高端,大氣上檔次,似乎不是我想象里的使用方法。 在沒有深入Redis之前,在我的概念里,緩存,就是key-value。而使用方式肯定不需要改動多少代碼,一切都 ...
Redis是個好東西,經過上兩個星期的研究和實踐,目前正在項目里大規模的替換掉原來的本地記憶體cache。但是替換過程中卻發現,Redis這東西高端,大氣上檔次,似乎不是我想象里的使用方法。
在沒有深入Redis之前,在我的概念里,緩存,就是key-value。而使用方式肯定不需要改動多少代碼,一切都是Get/Set。但是實際用的時候卻發現,我錯了,不是所有的場景都是簡單的Get/Set。也不是所有的數據都適合key-Value,於是有了這個問題,Redis到底該如何使用?我問自己,也向園子里的朋友們求助,希望幫忙解答。當然,這篇文章拋磚引玉,先談談我這兩周的感悟。
使用場景
在我的項目里,有一個提供給Autocomplete的功能,數據量大概在幾萬。這篇文章里我用姓名檢索的例子來說明,列表請戳來自Redis作者的Demo。
在這樣的列表裡全是用戶名,例如我們的系統里有一個用戶對象:
1 public Class User
2 {
3 public string Id{get; set;}
4 public string Name {get; set;}
5 ....
6 public string UserHead {get; set;}
7 }
系統里需要一個用戶的下拉列表,由於數據量大不能一次顯示完,於是就加上了一個AutoComplete功能。如果是不用Redis這樣的集中式緩存,直接緩存在本機記憶體里,那麼結構很簡單如下:
1 var users = new List<User>{...};//讀到一個用戶列表
2 MemoryCache.Set("capqueen:users", users);//放入記憶體
3
4 //讀取
5 var users = MemoryCache.Get<List<User>>("capqueen:users");
因為都是在記憶體里,所以直接存List就可以了,搜索的時候也可以直接的如下:
1 var findUsers = users.Where(user => user.Name.StartWith("A")).ToList();例如輸入的字元是 "A"
相當簡單,完全不用考慮如何存儲,存儲的數據結構。但是換到了Redis這些集中式緩存服務之後,咱們再來思考,該如何存儲。
方案一:類似記憶體式的緩存實現。
本文里使用的Redis鏈接庫是StactkExchange.Redis,出自StackOverFlow的開源產品。
1 var db = redis.GetDataBase();//獲取0資料庫
2
3 var usersJson = JsonConvert.SerializeObject(users)//序列化
4
5 db.StringSet("capqueen:users", usersJson);//存儲
6
7 var usersString = db.StringGet("capqueen:users");
8 var userList = JsonConvert.DeserializeObject<List<User>>(usersString);//反序列化
上面的方式邏輯上是沒有問題的,編譯也可以通過。但是仔細想一想,Redis作為獨立的緩存服務和appSever是分開來的,這樣的讀取方式對redis伺服器的IO是個負擔,甚至這樣的讀取比本地記憶體緩存慢了太多了。
那如何解決呢?試想key-value的精髓是在於Key,那麼對於List來說應該要把item分開來存儲。
方案二:Keys模糊匹配。
在查看了Redis的命令文檔(見參考資料4)之後,發現了命令Keys,如獲至寶,立馬修改了方案。首先我們需要把要搜索的關鍵詞建立為key,這裡我把key定義為 "capqueen:user:{id}:{name}",其中{}內的是要用item對應屬性替換的。代碼如下:
1 var redis = ConnectionMultiplexer.Connect("localhost");
2 var db = redis.GetDatabase();
3
4 var users = new List<User> {
5 new User{Id = 6, Name = "aaren", Age=10},
6 new User{Id = 7, Name = "issy", Age=11},
7 new User{Id = 8, Name = "janina", Age=13},
8 new User{Id = 9, Name = "karena", Age=14}
9 };
10
11 users.ForEach(item => {
12 var key = string.Format("capqueen:user:{0}:{1}", item.Id, item.Name);
13 var value = JsonConvert.SerializeObject(item);
14 db.StringSet(key, value);
15 });
建立好的緩存如下圖所示:
所有的user都以單獨的Key-Value方式存儲,那麼如何利用Keys搜索呢?我們來看下Redis的Keys命令:
1 KEYS pattern
2
3 查找所有符合給定模式 pattern 的 key 。
4
5 KEYS * 匹配資料庫中所有 key 。
6 KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
7 KEYS h*llo 匹配 hllo 和 heeeeello 等。
8 KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
9 特殊符號用 \ 隔開
也就是說Keys能夠進行簡單的模糊匹配,那麼我們這裡的搜索就可以換成如下的方式:
var redis = ConnectionMultiplexer.Connect("192.168.10.178");
var db = redis.GetDatabase();
var server = redis.GetServer("192.168.10.178", 6379);
var keys = server.Keys(pattern: "capqueen:user:*:a*");
var values = db.StringGet(keys.ToArray());
//反序列化
var jsonValues = new StringBuilder("[");
values.ToList().ForEach(item => jsonValues.Append(item).Append(","));
jsonValues.Append("]");
var userList = JsonConvert.DeserializeObject<List<User>>(jsonValues.ToString());
註意以上的代碼里,因為每個value是一個json,為了增加轉化時的效率,我先處理成json arry再進行反序列化。
這種方案,確實是解決了我目前的問題,然而我註意到了Redis文檔里的一段話:
KEYS 的速度非常快,但在一個大的資料庫中使用它仍然可能造成性能問題,如果你需要從一個數據集中查找特定的 key ,你最好還是用 Redis 的集合結構(set)來代替。
這段話換而言之就是慎用Keys搜索的意思,那麼有什麼更好的解決方案呢?由於這篇文章我拖得很久了,這裡留個問題在末尾,期待有大牛能夠幫忙解答,感激不盡。當然還有下一篇內容,我會講講我目前的處理方法。
下篇文章里,我會根據Redis作者的博客(資料1)里的做法以及我最後查到的資料,做一個新的方案,請大家到時指教。
參考資料
- Redis作者博客,這是其中一篇講如何基於Redis實現AutoComplete的文章:http://oldblog.antirez.com/post/autocomplete-with-redis.html
- Redis 第三方管理工具 For Windows:http://redisdesktop.com/
- Redis .NET鏈接工具的Top20:http://nugetmusthaves.com/Tag/Redis
- Redis命令中文文檔:http://redisdoc.com/
文章轉自:http://www.cnblogs.com/capqueen/p/HowToUseRedis.html