如何解決node進程間共享內(nèi)存

[toc]

npm i @runnersnail/cache-machine

利用rust幫助node進程間共享內(nèi)存

業(yè)務(wù)場景:調(diào)用算法接口侣签,算法5分鐘后得到數(shù)據(jù)然后調(diào)用node接口返回數(shù)據(jù)俱笛,此時node接口接收數(shù)據(jù)并把數(shù)據(jù)緩存,用戶端訪問node無論哪個進程都可以得到被緩存的數(shù)據(jù)

將解決問題的思路和方法記錄下來

遇到問題

由于部署平臺對于node程序部署采用pm2(所有進程都是fork出來的,就不可能利用node那一套進程進程間通訊)

也曾經(jīng)考慮自己實現(xiàn)一套cluster锯厢,然后利用master進程通訊谆膳。但pm2有其他優(yōu)秀的功能宕機重啟叭爱,cpu,內(nèi)存監(jiān)控等

分析問題

論壇請教有什么進程間通訊(受限于pm2)的方式漱病,大部分的回答都是直接memcache买雾、redis,感覺為了緩存某一輕量數(shù)據(jù)就上redis個人感覺沒有太大意義杨帽,會造成資源浪費和部署麻煩漓穿。

解決問題

解決這個問題我們需要了解進程間有哪些通訊方式,才能尋找更好的解決方案注盈。

詳細了解請戳

  • 管道pipe:管道是一種半雙工的通信方式晃危,數(shù)據(jù)只能單向流動
  • 消息隊列:消息隊列是由消息的鏈表,存放在內(nèi)核中并由消息隊列標識符標識
  • socket: 很常用的方式不再贅述
  • 共享存儲SharedMemory: 映射一段可以被不同內(nèi)存訪問的地址塊

為何采用shared memory幫助node共享內(nèi)存

分析我們的業(yè)務(wù)場景老客,其實就是某一進程得到數(shù)據(jù)緩存到內(nèi)存僚饭,然后其他進程可以無視跨進程讀取緩存的數(shù)據(jù)塊,說一shared memory是最適合的實用場景

如何使用shared memory 快速解決問題

node本身是不支持shared memeory這種底層操作的胧砰,我必須借助底層語言的能力去實現(xiàn)鳍鸵,然后通過ffi調(diào)用。為了避免自己實現(xiàn)原剩代碼操作內(nèi)存朴则,我們需要借助一些三方成熟的包
所以我們需要完成以下三個事情

  • 選擇一門系統(tǒng)語言

  • 尋找一個成熟的三方包共享內(nèi)存

  • 尋找ffi工具快速完成

  • 這里系統(tǒng)語言我選擇rust权纤,如今前端火熱的Deno項目采用rust編寫钓简,rust已經(jīng)變的更靠近web社區(qū)

  • 選擇Rust的第二個原因是它的三方包類似于npm一樣容易集成,挑選shared memory模塊 shared_memory-rs進行共享內(nèi)存

  • 采用成熟的neon進行ffi模塊編寫

項目實施

使用neon腳手架搭建項目

  • neon new cache-machine ---》 創(chuàng)建項目
  • neon build ---》編譯項目
  • node lib/index.js 運行項目

編寫rust 模塊

extern crate neon;
extern crate shared_memory;
use neon::prelude::*;
use neon::register_module;
use shared_memory::*;

use std::ffi::{CStr, CString};

/**
 * 定義緩存區(qū)塊
*/
#[derive(SharedMemCast)]
struct ShmemStructCache {
    num_slaves: u32,
    message: [u8; 256],
}

static GLOBAL_LOCK_ID: usize = 0;

/**
 * sharedMemory全局句柄汹想,避免對進程重復(fù)創(chuàng)建
*/
static mut SHMEM_GLOBAL: Option<shared_memory::SharedMem> = None;

/**
 * 創(chuàng)建sharedMemory
*/
fn create_open_mem() -> Result<shared_memory::SharedMem, SharedMemError> {
    let shmem = match SharedMem::create_linked("shared_mem.link", LockType::Mutex, 4096) {
        Ok(v) => v,
        Err(SharedMemError::LinkExists) => SharedMem::open_linked("shared_mem.link")?,
        Err(e) => return Err(e),
    };

    if shmem.num_locks() != 1 {
        return Err(SharedMemError::InvalidHeader);
    }
    Ok(shmem)
}

/**
 * 設(shè)置SharedMemory
*/
fn set_cache(set_cache: String) -> Result<String, SharedMemError> {
    {
        let mut  shared_state =  unsafe { SHMEM_GLOBAL.as_mut().unwrap().wlock::<ShmemStructCache>(GLOBAL_LOCK_ID)?};
        let set_string: CString = CString::new(set_cache.as_str()).unwrap();
        shared_state.message[0..set_string.to_bytes_with_nul().len()]
            .copy_from_slice(set_string.to_bytes_with_nul());
    }
    Ok("".to_owned())
}

/**
 * 讀取SharedMemory
*/
fn get_cache() -> Result<String, SharedMemError> {
    let   result =
    {
        let shmem = unsafe { SHMEM_GLOBAL.as_mut().unwrap()};
        let shared_state = shmem.rlock::<ShmemStructCache>(GLOBAL_LOCK_ID)?;
        let shmem_str: &CStr = unsafe { CStr::from_ptr(shared_state.message.as_ptr() as *mut i8) };
         shmem_str.to_str().unwrap().into()
    };

    Ok(result)
}

/**
 * 暴露給js端get的方法
*/
fn get(mut cx: FunctionContext) -> JsResult<JsString> {
    match get_cache() {
        Ok(v) => Ok(cx.string(v)),
        Err(_) => Ok(cx.string("error")),
    }
}

/**
 * 暴露給js端的set方法
*/
fn set(mut cx: FunctionContext) -> JsResult<JsString> {
    let value = cx.argument::<JsString>(0)?.value();
    match set_cache(value) {
        Ok(v) => Ok(cx.string(v)),
        Err(e) => Ok(cx.string("error")),
    }
}

register_module!(mut m, {
  unsafe {
    SHMEM_GLOBAL = match create_open_mem() {
      Ok(v) => Some(v),
      _ => None,
    };
  }
  set_cache("".to_owned());
  m.export_function("get", get)?;
  m.export_function("set", set)?;
  Ok(())
});

編寫js模塊包

var addon = require('../native');

/**
 * 
 * @param {緩存的鍵} key 
 * @param {緩存的值} value 
 */
function set(key, value) {
    let cache = get();
    cache[key] = value;
    addon.set(JSON.stringify(cache));
}

/**
 * 
 * @param {根據(jù)鍵名得到內(nèi)容} key 
 */
function get(key) {
    const shared_memory = addon.get();
    if (shared_memory) {
        const cache = JSON.parse(cache)
        if (key) {
            return cache[key];
        } else {
            return cache;
        }

    } else {
        return {};
    }
}

module.exports = {
    set,
    get
}

// cache machine

var cache = require("cache-machine");

cache.set('key', 'value');

cache.get('key');



存在問題

rust端對不同進程做了訪問控制外邓,沒有對線程做控制,考慮到node多線程場景[Worker Threads同時操作某變量]在實際業(yè)務(wù)中并未發(fā)現(xiàn)使用古掏,所以后序增加線程間安全控制

  • 多進程安全的共享內(nèi)存
  • 多線程安全的共享內(nèi)存 TODO
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末损话,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子槽唾,更是在濱河造成了極大的恐慌丧枪,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庞萍,死亡現(xiàn)場離奇詭異拧烦,居然都是意外死亡,警方通過查閱死者的電腦和手機钝计,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門恋博,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人私恬,你說我怎么就攤上這事债沮。” “怎么了本鸣?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵疫衩,是天一觀的道長。 經(jīng)常有香客問我荣德,道長闷煤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任命爬,我火速辦了婚禮曹傀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饲宛。我一直安慰自己皆愉,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布艇抠。 她就那樣靜靜地躺著幕庐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪家淤。 梳的紋絲不亂的頭發(fā)上异剥,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音絮重,去河邊找鬼冤寿。 笑死歹苦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的督怜。 我是一名探鬼主播殴瘦,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼号杠!你這毒婦竟也來了蚪腋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤姨蟋,失蹤者是張志新(化名)和其女友劉穎屉凯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眼溶,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡悠砚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了偷仿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哩簿。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酝静,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羡玛,我是刑警寧澤别智,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站稼稿,受9級特大地震影響薄榛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜让歼,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一敞恋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谋右,春花似錦硬猫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辈挂,卻和暖如春衬横,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背终蒂。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工蜂林, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遥诉,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓噪叙,卻偏偏與公主長得像矮锈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子构眯,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容