一、特點
- 速度快:由于基于內(nèi)存完成,所以非常快;同時支持持久化方案,服務(wù)器重啟可以恢復(fù)~
- 支持豐富的數(shù)據(jù)類型: 除了key-value,還可以支持string、list、hash、set、sorted-set等;
- 支持事務(wù):由于本身是單進程,可以做原子性操作,經(jīng)典項目案例:秒殺防超賣;
- 有過期時間等功能:如短信驗證碼一分鐘內(nèi)不能重復(fù)申請;
二、應(yīng)用場景
1. 緩存
因為速度快,所以可以不通過koa這層做緩存;
2. 分布式鎖
利用高性能特點設(shè)鎖,設(shè)字段鎖住某條記錄;
3. 自動過期
短信驗證碼,可設(shè)過期時間、定時刪除(活動日期、截至日期);
4. 計數(shù)器和排行榜
傳統(tǒng)數(shù)據(jù)庫存在磁盤中,速度比較慢,Redis讀寫速度更快一些;
5. 緩沖層
如做秒殺功能時的緩沖層
6. 處理簽到和其他狀態(tài)(大數(shù)據(jù)處理)
數(shù)據(jù)量大時,Redis支持位圖,性能很好;
7. 發(fā)布-訂閱
傳統(tǒng)的socket.io由于在內(nèi)存中實現(xiàn),只能在單服務(wù)器運行,當(dāng)并發(fā)量大時,往往希望有多個實例、并有多個服務(wù)器,那么數(shù)據(jù)則無法共享,Redis內(nèi)部實現(xiàn)了訂閱發(fā)布模式,相當(dāng)于每個用戶都訂閱了聊天室消息。
三、實戰(zhàn)——使用訂閱-發(fā)布模式實現(xiàn)聊天室
傳統(tǒng)的實現(xiàn)方式:(利用socket.io處理)
S1:客戶端介入,進入聊天室時,監(jiān)聽connection;
S2: 收到connect,對socket.io訂閱一個主題,如“chat message”;
S3:該主題收到任何消息后會直接廣播(其他成員發(fā)送消息會推送);
=== 問題:每個服務(wù)器單獨訂閱發(fā)布模式,程序很難橫向擴展
Redis提供了一個訂閱發(fā)布模式實現(xiàn),引用就好~
解決痛點:(秒殺功能)
- 短時間內(nèi)大量并發(fā)(要求后端處理速度非??欤鴤鹘y(tǒng)I/O很慢);
- 秒殺保證不會超賣;
- 使用Redis提供緩沖層:
- 內(nèi)存型關(guān)系庫性能遠優(yōu)于關(guān)系型數(shù)據(jù)庫;
- Redis單進程方式保證了原子性;
附代碼
// 創(chuàng)建兩個客戶端: client訂閱端、publish發(fā)布端
const rclient = redis.createClient(6379,'localhost');
rclient.on('ready',err=>{
console.log('client ready...')
});
const publish = redis.createClient(6379,'localhost');
publish.on('ready',err=>{
console.log('publish ready...')
})
// Redis訂閱
rclient.subscribe('chat message'); // chat 為主題
// 收到消息回調(diào)處理
rclient.on('message',(channel,msg)=>{
io.emit('chat message',msg); // 廣播
})
socket.on('chat message',msg=>{
console.log('receive msg'+msg);
publish.publish('chat',msg); // 發(fā)布
})
socket.on('disconnect',()=>{
console.log('user disconnected');
})
// promisify風(fēng)格接口
router.get('/create', async (ctx)=>{
// S1: 先清空歷史-重設(shè)indeX
await client.ltrim('goods',-1,0);
// S2: 創(chuàng)建商品隊列
new Array(30).fill().forEach(async (v,i)=>{
await client.rpush('goods',i);
console.log('添加商品',i);
})
// redis num
const num = await client.llen('goods');
console.log('搶購商品數(shù)量',num);
ctx.body = { ok:1 }; // 應(yīng)答
})
// 商品放入隊列后,秒殺(即將用戶與商品一一對應(yīng))
router.get('/buy',async (ctx)=>{
// 產(chǎn)生一個隨機的用戶ID
const uid = (Math.random() * 999999).toFixed()
let pid = await client.lpop('goods')
if(pid){
// 如果能取出,則寫入=> 創(chuàng)建訂單
await client.hset( 'orders', pid, uid ); // 訂單生成
}else{
console.log('賣光了');
}
})
// 打印訂單,(輸出)秒殺結(jié)束后,寫會數(shù)據(jù)庫
router.get('/order', async(ctx)=>{
const keys = await client.hkeys('orders');
console.log('訂單列表');
// 遍歷
for (const k of keys) {
console.log(`${k}==>${await client.hget('order',k)}`);
}
ctx.body = { ok:1 }; // 應(yīng)答
})
// clear 操作
router.get('/order/clear', async (ctx)=>{
const keys = await client.hkeys('orders');
for (const k of keys) {
console.log(`刪除訂單: ${k=>await client.hdel('orders',k)}`);
}
ctx.body = { ok:1 }; // 應(yīng)答
})
使用測試類驗證高并發(fā)情況下是否能成功
(async ()=>{
const autocannon = require('autocannon');
const result = await autocannon({
url:'http://localhost:3000/buy',
connections: 100, // default
pipelining: 1, // default
duration: 1, //default
})
console.log('秒殺完成');
})()