node中的redis使用--ioredis
nodejs 連結 redis/mysql 連線池
redis是單執行緒作業,所以不管查詢任務是由一個連結發來的還是多個連結發來的,redis是序列的執行。並通過當前的連結返回客戶端。nodejs接受redis的返回後,不管是不是並行,都要等主執行緒空閒下來才能一個個處理伺服器返回的資料。
再看mysql~
mysql不是單執行緒服務的,可以並行處理多個查詢請求。
mysql會為每一個連結建立一個單獨的執行緒查詢。redis資料基本在記憶體中,mysql會大量的讀取磁碟的I/O,多執行緒比較快。
但是nodejs是單執行緒的。但是它呼叫的I/O指令等是通過另外的執行緒做的,I/O指令完成後就給主執行緒小任務,回撥函式。
nodejs主執行緒一個,但是IO執行緒會有多個。
nodejs 使用多個連線來連線mysql。多連線是需要連線池的,有連線池就避免了每次連線都要去建立銷燬的消耗了。
綜上:nodejs + mysql用執行緒池是沒什麼問題的。nodejs + redis只用單個連線就夠。
所以有了連線管理模組,egg-redis。
redis效能
錯誤原因:redis client的業務程式碼以及redis client的I/O效能。
redis client採用的是單鏈接模式,底層採用的非阻塞網路I/O,
調優:pipeline,script
無依賴批量請求採用pipeline。
redis高效能體現在服務端處理能力,但瓶頸往往出現在客戶端,因此增強客戶端I/O能力與併發並行多客戶端才是高併發解決方案。
ioredis
效能為中心,功能齊全的,支援Redis >= 2.6.12 and (Node.js >= 6).
npm install ioredis
basic usage
var Redis = require('ioredis');
var redis = new Redis();
redis.set('foo', 'bar');
redis.get('foo', function (err, result) {
console. log(result);
});
// Or using a promise if the last argument isn't a function
redis.get('foo').then(function (result) {
console.log(result);
});
// Arguments to commands are flattened, so the following are the same:
redis.sadd('set', 1, 3, 5, 7);
redis.sadd('set', [1, 3, 5, 7]);
// All arguments are passed directly to the redis server:
redis.set('key', 100, 'EX', 10);
Connect to Redis
new Redis() // Connect to 127.0.0.1:6379
new Redis(6380) // 127.0.0.1:6380
new Redis(6379, '192.168.1.1') // 192.168.1.1:6379
new Redis('/tmp/redis.sock')
new Redis({
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
family: 4, // 4 (IPv4) or 6 (IPv6)
password: 'auth',
db: 0
})
一個特別的連線方法:
// Connect to 127.0.0.1:6380, db 4, using password "authpassword":
new Redis('redis://:[email protected]:6380/4')
pub/sub 釋出/訂閱
publish/subscribe
redis.on進行事件的監聽
var Redis = require('ioredis');
var redis = new Redis();
var pub = new Redis();
redis.subscribe('news', 'music', function (err, count) {
// 現在我們訂閱了news和music兩個頻道
// `count` 代表我們當前訂閱的序號
pub.publish('news', 'Hello world!');
pub.publish('music', 'Hello again!');
});
redis.on('message', function (channel, message) {
// 收到訊息 Hello world! from channel news
// 收到訊息 Hello again! from channel music
console.log('Receive message %s from channel %s', message, channel);
});
// 還有一個是事件叫做`messageBuffer`,和message是一樣的
// 返回buffer,替代字串
redis.on('messageBuffer', function (channel, message) {
// Both `channel` and `message` are buffers.
});
PSUBSCRIBE
釋出
redis.psubscribe('pattern', function (err, count) {});
redis.on('pmessage', function (pattern, channel, message) {});
redis.on('pmessageBuffer', function (pattern, channel, message) {});
當客戶端發出訂閱或PSUBSCRIBE時,該連線將被放入“訂閱”模式。此時,只有修改訂閱集的命令才是有效的。當訂閱集為空時,連線將返回常規模式。
如果在訂閱模式下發送正常的命令,只會開啟一個新的連線。
處理二進位制資料:
redis.set('foo', Buffer.from('bar'));
拿到快取中的資料:
redis.getBuffer('foo', function (err, result) {
// result is a buffer.
});
管道 Pipelining
傳送5條以上的命令,就可以使用管道的將命令放進記憶體的佇列中,然後一起傳送至redis,這個表現能提升百分之50-300.
這些命令在記憶體的佇列李阿敏,並配合exec
方法進行執行。
var pipeline = redis.pipeline();
pipeline.set('foo', 'bar');
pipeline.del('cc');
pipeline.exec(function (err, results) {
// `err` 總是null, and `results` 響應的陣列
// corresponding to the sequence of queued commands.
// Each response follows the format `[err, result]`.
});
// 甚至能夠鏈式呼叫命令
redis.pipeline().set('foo', 'bar').del('cc').exec(function (err, results) {
});
// `exec` 也是返回的promise
var promise = redis.pipeline().set('foo', 'bar').get('foo').exec();
promise.then(function (result) {
// result === [[null, 'OK'], [null, 'bar']]
});
每個鏈式命令也可以有一個回撥,當命令得到回覆時將呼叫它:
redis.pipeline().set('foo', 'bar').get('foo', function (err, result) {
// result === 'bar'
}).exec(function (err, result) {
// result[1][1] === 'bar'
});
除了單獨向管道佇列新增命令外,還可以將一組命令和引數傳遞給建構函式:
redis.pipeline([
['set', 'foo', 'bar'],
['get', 'foo']
]).exec(function () { /* ... */ });
length的屬性:
const length = redis.pipeline().set('foo', 'bar').get('foo').length;
// length === 2
事務:
redis.multi().set('foo', 'bar').get('foo').exec(function (err, results) {
// results === [[null, 'OK'], [null, 'bar']]
});
如果事務的命令鏈中存在語法錯誤(例如,引數數量錯誤,命令名稱錯誤等),則不會執行任何命令,並返回錯誤:
redis.multi().set('foo').set('foo', 'new value').exec(function (err, results) {
// err:
// { [ReplyError: EXECABORT Transaction discarded because of previous errors.]
// name: 'ReplyError',
// message: 'EXECABORT Transaction discarded because of previous errors.',
// command: { name: 'exec', args: [] },
// previousErrors:
// [ { [ReplyError: ERR wrong number of arguments for 'set' command]
// name: 'ReplyError',
// message: 'ERR wrong number of arguments for \'set\' command',
// command: [Object] } ] }
});
就介面而言,multi與管道的不同之處在於,在為每個連結命令指定回撥時,排隊狀態將傳遞給回撥而不是命令的結果:
redis.multi({ pipeline: false });
redis.set('foo', 'bar');
redis.get('foo');
redis.exec(function (err, result) {
// result === [[null, 'OK'], [null, 'bar']]
});
redis.multi([
['set', 'foo', 'bar'],
['get', 'foo']
]).exec(function () { /* ... */ });
管道支援內聯事務,這意味著您可以將管道中的命令子集分組到事務中:
redis.pipeline().get('foo').multi().set('foo', 'bar').get('foo').exec().get('foo').exec();
Lua指令碼
ioredis支援所有指令碼命令,例如EVAL,EVALSHA和SCRIPT。
ioredis公開了一個defineCommand方法,使指令碼編寫更容易使用:
var redis = new Redis();
// This will define a command echo:
redis.defineCommand('echo', {
numberOfKeys: 2,
lua: 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}'
});
// Now `echo` can be used just like any other ordinary command,
// and ioredis will try to use `EVALSHA` internally when possible for better performance.
redis.echo('k1', 'k2', 'a1', 'a2', function (err, result) {
// result === ['k1', 'k2', 'a1', 'a2']
});
// `echoBuffer` is also defined automatically to return buffers instead of strings:
redis.echoBuffer('k1', 'k2', 'a1', 'a2', function (err, result) {
// result[0] equals to Buffer.from('k1');
});
// And of course it works with pipeline:
redis.pipeline().set('foo', 'bar').echo('k1', 'k2', 'a1', 'a2').exec();
如果在定義命令時無法確定鍵的數量,則可以省略numberOfKeys屬性,並在呼叫命令時將鍵數作為第一個引數傳遞:
redis.defineCommand('echoDynamicKeyNumber', {
lua: 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}'
});
// Now you have to pass the number of keys as the first argument every time
// you invoke the `echoDynamicKeyNumber` command:
redis.echoDynamicKeyNumber(2, 'k1', 'k2', 'a1', 'a2', function (err, result) {
// result === ['k1', 'k2', 'a1', 'a2']
});
Redis規範
cluster
建構函式接受兩個引數,
-
第一個引數是連線到的叢集節點列表,不需要列舉所有的叢集節點,但是如果一個節點無法訪問客戶端將會嘗試下一個節點,並且客戶端將至少連線一個節點時自動發現其他節點。
-
第二個引數是選項
clusterRetryStrategy
:當沒有啟動任何節點可訪問時,clusterRetryStrategy
將呼叫,返回一個數字,ioredis將嘗試在指定的延遲(毫秒為單位)後從頭開始重新連線到啟動節點,否則將返回無啟動節點可用錯誤。
// 選項的預設值 function (times) { var delay = Math.min(100 + times * 2, 2000); return delay; }
enableOfflineQueue
:類似於類的enableOfflineQueue
選項Redis
。enableReadyCheck
:啟用後,只有在cluster info
報告叢集準備好處理命令的命令時才會發出“就緒”事件。否則,它將在“connect”發出後立即發出。scaleReads
:配置傳送讀取查詢的位置。maxRedirections
:當收到與群集相關的錯誤(例如MOVED
,等)時,客戶端將命令重定向到另一個節點。此選項限制傳送命令時允許的最大重定向。預設值為。ASK``CLUSTERDOWN``16
retryDelayOnFailover
:如果在傳送命令時目標節點斷開連線,ioredis將在指定的延遲後重試。預設值為100
。您應該確保retryDelayOnFailover * maxRedirections > cluster-node-timeout
在故障轉移期間確保沒有命令失敗。retryDelayOnClusterDown
:當群集關閉時,所有命令都將被拒絕,錯誤為CLUSTERDOWN
。如果此選項是數字(預設情況下為100
),則客戶端將在指定時間(以毫秒為單位)後重新發送命令retryDelayOnTryAgain
:如果此選項是一個數字(預設情況下是100
),則客戶端將TRYAGAIN
在指定時間(以毫秒為單位)後重新發送被拒絕的命令。redisOptions
:Redis
連線到節點時傳遞給建構函式的預設選項。slotsRefreshTimeout
:從群集重新整理插槽時發生超時前的毫秒數(預設值1000
)slotsRefreshInterval
:每個自動插槽重新整理之間的毫秒數(預設5000
)
叢集模式下的釋出和訂閱:(和獨立模式下的釋出訂閱一致)
sub.on('message', function (channel, message) { console.log(channel, message); }); sub.subscribe('news', function () { pub.publish('news', 'highlights'); });