redis的高速存取性能讓人印象深刻,雖然是分布式存儲,但相比本地內存,性能毫不遜色。
之所以能做到這點,是由於redis的“單線程,多路復用IO”,同一時刻只有一個操作在進行。
而且多次建立從redis存取數據的鏈接,操作完成后關閉,性能表現超出SQL一大截。(雖然這樣的設計某種程度上算是對redis性能的浪費。。。)
redis的nodejs版本API支持其幾乎所有命令,現對項目中涉及的記錄如下。
根據redis中的數據類型區分:
0、建立node-redis的client端連接
1 // redis 鏈接 2 var redis = require('redis'); 3 var client = redis.createClient('6379', '127.0.0.1'); 4 5 // redis 鏈接錯誤 6 client.on("error", function(error) { 7 console.log(error); 8 }); 9 // redis 驗證 (reids.conf未開啟驗證,此項可不需要) 10 // client.auth("foobared");
在我使用的Nodejs V 0.10中,已附帶了node-reids API,不需要再npm安裝了。
1、set的存取
1 client.set('key001', 'AAA', function (err, response) { 2 if (err) { 3 console.log("err:", err); 4 } else { 5 console.log(response); 6 client.get('key001', function (err, res) { 7 if (err) { 8 console.log("err:", err); 9 } else { 10 console.log(res); 11 client.end(); 12 } 13 }); 14 } 15 });
運行結果為:
> node redistest.js
OK
AAA
2、hash存取
hash set的設值和抽取數據都有單個key和多個key兩種方式:
※ 設定單個key的值,在取值時獲取特定filed下指定key的值:
1 client.hset('filed002', 'key001', 'wherethersisadoor', function (err, res) { 2 if (err) { 3 console.log(err); 4 } else { 5 console.log('res:', res); 6 client.hget('filed002', 'key001', function (err, getRslt) { 7 if (err) { 8 console.log(err); 9 } else { 10 console.log('getRslt:', getRslt); 11 client.end(); 12 } 13 }); 14 } 15 });
運行結果如下:
> node redistest.js
res: 1
getRslt: wherethersisadoor
注意:當hget方法在指定field下找不到指定的key時,會傳給回調函數null,而非空字符或undefined。
※ 設定多個key的值,取值時獲取指定field下指定單個或多個key的值
1 var qe = {a: 2, b:3, c:4}; 2 client.hmset('field003', qe, function(err, response) { 3 console.log("err:", err); 4 console.log("response:", response); 5 client.hmget('field003', ['a', 'c'], function (err, res) { 6 console.log(err); 7 console.log(res); 8 client.end(); 9 }); 10 });
運行結果如下:
> node redistest.js
err: null
response: OK
null
[ '2', '4' ]
hmset方法的設定值可以是JSON格式的數據,但是redis中key的值是以字符串形式存儲的,如果JSON數據層數超過一層,會出現值是'[object Object]'的情況。
hmget方法的返回值是個數組,其中元素的順序對應於參數的key數組中的順序,如果參數數組中有在field內不存在的key,返回結果數組的對應位置會是null,也即無論是否能取到值,結果數組中的元素位置始終與參數的key數組中元素位置一一對應。
獲取hash中所有key的方法是client.keys(fieldname, callback); 需要注意的是如果hash中key的數目很多,這個方法的可能耗費很長時間。
3、sorted sets 有序集合
有序集合是redis中一種有意思的數據結構,集合中元素是有序的,排序的依據是元素對應的score,在向有序集合中加入元素時,需要同時設定數據的值和對應的score,數據在有序集合中的存儲位置依據score確定。
score的類型被限定為浮點數,當兩個不同的元素具有相同的score時,兩者的位置按照字符串大小升序排列。
有序集合中每個元素都被限制為唯一的,向一個有序集合中設定兩個值相同而score不同的元素,只會更新已經被設定元素的score,這點需要注意。
1 var vals = []; 2 for (var score = 0; score < 4; score++) { 3 for (var val = 10; val < 14; val++) { 4 vals.push(score); 5 vals.push(val); 6 } 7 } 8 9 client.zadd('004', vals, function(err, res) { 10 console.log(err); 11 console.log(res); 12 client.zrange('004', 0, -1, function(err, resp) { 13 console.log(err); 14 console.log('range result:', resp); 15 client.zcount('004', -Infinity, Infinity, function(err, respo) { 16 console.log(err); 17 console.log("len:", respo); 18 client.end(); 19 }); 20 }); 21 });
執行結果如下:
> node redistest.js
null
4
null
range result: [ '10', '11', '12', '13' ]
null
len: 4
zadd方法接收數組作為設定值的參數,數組中數據順序為[score1, key1, score2, key2,...]的形式。
參數數組中原本會有score不同的4組值,但是由於score對應的元素值相同,最終集合中僅存在一組值。
zrange方法獲取指定下標范圍的內的所有key值,包括起始位置和終止位置。
zcount方法獲取指定集合指定范圍內的元素個數,設定為-Infinity, Infinity時,可以獲取數組長度。
redis中的游標設計思想類似SQL中的游標,node-redis API中使用scan方法作為游標,對應不同的數據結構,有hscan和zscan等方法。
1 client.zscan('004', 0, 'COUNT', '1', function(err, res) { 2 console.log(err); 3 console.log(res); 4 console.log(res[1].length); 5 client.end(); 6 });
執行結果如下:
> node redistest.js
null
[ '0', [ '10', '3', '11', '3', '12', '3', '13', '3' ] ]
8
返回結果是一個數組。數組第一個元素是游標的返回值,標明當前讀取位置,用這個值作為參數再次調用scan方法,將會從前次終止的位置繼續讀取集合。
數組第二個元素是游標讀取的內容,數組內元素格式是[key1, score1, key2, score2...]。'COUNT'參數的作用是限定scan方法的讀取數量,此處為起作用,原因未明,需繼續調查。
4、lists 列表
列表中的元素順序就是插入順序,元素沒有唯一性限制。
1 client.del("003", function(err, respo) { 2 client.rpush("003", [1, 2, 3, 4, 5], function(err, res) { 3 console.log(err); 4 console.log(res); 5 client.lrange('003', 0, -1, function(err, resp) { 6 console.log(err); 7 console.log("resp:", resp); 8 client.end(); 9 }); 10 }); 11 });
執行結果如下:
> node redistest.js
null
5
null
resp: [ '1', '2', '3', '4', '5' ]
使用sort方法可得到排序后的結果。
list的push方法有左右兩個版本,lpush和rpush,分別表示從list的頭部和尾部插入元素。
總結,對於redis中的大規模數據,單一數據類型並不經常可以很好的實現業務需求,可以通過不同數據類型的組合,在快速存儲數據的基礎上,快速的索引匹配數據查找。在實際使用中,用到的是將大量數據存儲在hash集合中,而hash集合的key存儲在有序集合中,提取key中的數字部分作為score,在有序集合中取出特定位置的key,再根據key去hash集合中取值。
接觸redis的時間不長,這是個很高效的存儲工具,與SQL完全不同,在擴展程序功能方面有很多可以嘗試的地方,以后在設計程序時,可以將redis因素加入考慮。
2016/04/10 補充
使用async並發寫入redis時,由於沒有寫入鎖定,獲取key的當前狀態和寫入數據之間的時間內,redis數據也可能發生變化,造成臟數據寫入,這點必須注意。
目前階段,解決這個問題的方案是估計並發程序的大致執行時間,如果單次並發執行時間較長,則不同並發之間在讀-寫間隔內操作同一個key的幾率降低,可以不考慮臟數據問題(暫時解決方案);而如果單次並發時間較短,則設置並發線程數量為1,限定串行執行。
現在接觸到的新技術,不同於經年完善的傳統方案,有些設定沒有考慮應用於新的應用領域,有些則是技術革新帶來的新型規范,雖然開發過程中都可以算作踩坑,但並不是所有出現問題的地方都一無是處,要注意平時固執於“經典”觀點,不願接受更新的惰性。