[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