數(shù)據(jù)庫之——Redis

一、特點

  • 速度快:由于基于內(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提供緩沖層:
  1. 內(nèi)存型關(guān)系庫性能遠優(yōu)于關(guān)系型數(shù)據(jù)庫;
  2. 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('秒殺完成');
})()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容