成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一個(gè)瀏覽器和NodeJS通用的RPC框架

Tony / 840人閱讀

摘要:歡迎關(guān)注我的知乎專欄這幾天寫了個(gè)小型的框架,最初只是想用寫個(gè)純平臺(tái)的東西,后來無意中開了個(gè)腦洞,如果基于把瀏覽器當(dāng)做,那豈不是只要是能運(yùn)行瀏覽器或者的設(shè)備,都可以作為分布式計(jì)算中的一個(gè)了嗎打開一張網(wǎng)頁,就能成為分布式計(jì)算的一個(gè)節(jié)點(diǎn),看起

歡迎關(guān)注我的知乎專欄: https://zhuanlan.zhihu.com/starkwang

starkwang/Maus: A Simple JSON-RPC Framework running in NodeJS or Browser, based on websocket.

這幾天寫了個(gè)小型的RPC框架,最初只是想用 TCP-JSON 寫個(gè)純 NodeJS 平臺(tái)的東西,后來無意中開了個(gè)腦洞,如果基于 Websocket 把瀏覽器當(dāng)做 RPC Server ,那豈不是只要是能運(yùn)行瀏覽器(或者nodejs)的設(shè)備,都可以作為分布式計(jì)算中的一個(gè) Worker 了嗎?

打開一張網(wǎng)頁,就能成為分布式計(jì)算的一個(gè)節(jié)點(diǎn),看起來還是挺酷炫的。

一、什么是RPC

可以參考:誰能用通俗的語言解釋一下什么是RPC框架? - 知乎

簡單地說就是你可以這樣注冊一個(gè)任意數(shù)量的worker(姑且叫這個(gè)名字好了),它里面聲明了具體的方法實(shí)現(xiàn):

var rpcWorker = require("maus").worker;
rpcWorker.create({
    add: (x, y) => x + y
}, "http://192.168.1.100:8124");

然后你可以在另一個(gè)node進(jìn)程里這樣調(diào)用:

var rpcManager = require("maus").manager;
rpcManager.create(workers => {
    workers.add(1, 2, result => console.log(result));
}, 8124)

這里我們封裝了底層的通信細(xì)節(jié)(可以是tcp、http、websocket等等)和任務(wù)分配,只需要用異步的方式去調(diào)用worker提供的方法即可,通過這個(gè)我們可以輕而易舉地做到分布式計(jì)算的mapreduce

rpcManager.create(workers => {
    //首先定義一個(gè)promise化的add
    var add = function(x, y){
        return new Promise((resolve, reject)=>{
            workers.add(x, y, result => resolve(result));
        })
    }
    //map&reduce
    Promise.all([add(1,2), add(3,4), add(4,5)])
        .then(result => result.reduce((x, y) => x + y))
        .then(sum => console.log(sum)) //19
}, 8124)

如果我們有三個(gè)已經(jīng)注冊的Worker(可能是本地的另一個(gè)nodejs進(jìn)程、某個(gè)設(shè)備上的瀏覽器、另一個(gè)機(jī)器上的nodejs),那么我們這里會(huì)分別在這三個(gè)機(jī)器上分別計(jì)算三個(gè)add,并且將三個(gè)結(jié)果在本地相加,得到最后的值,這就是分布式計(jì)算的基礎(chǔ)。

二、Manager的實(shí)現(xiàn) 0、通信標(biāo)準(zhǔn)

要實(shí)現(xiàn)雙向的通信,我們首先要定義這樣一個(gè)“遠(yuǎn)程調(diào)用”的通信標(biāo)準(zhǔn),在我的實(shí)現(xiàn)中比較簡單:

{
    [id]: uuid          //在某些通信中需要唯一標(biāo)識(shí)碼
    message: "......"   //消息類別
    body: ......        //攜帶的數(shù)據(jù)
}
1、初始化

首先我們要解決的問題是,如何讓Manager知道Worker提供了哪些方法可供調(diào)用?

這個(gè)問題其實(shí)很簡單,只要在 websocket 建立的時(shí)刻發(fā)送一個(gè)init消息就可以了,init消息大概長這樣:

{
    message: "init",
    body: ["add", "multiply"] //body是方法名組成的數(shù)組
}

同時(shí),我們要將Manager傳入的回調(diào)函數(shù),記錄到Manager.__workersStaticCallback中,以便延遲調(diào)用:

manager.create(callback, port) //記錄下這個(gè)callback

//一段時(shí)間后。。。。。。

manager.start() //任務(wù)開始
2、生成workers實(shí)例

現(xiàn)在我們的Manager收到了一個(gè)遠(yuǎn)程可調(diào)用的方法名組成的數(shù)組,我們接下來需要在Manager中生成一個(gè)workers實(shí)例,它應(yīng)該包含所有這些方法名,但底層依然是調(diào)用一個(gè)webpack通信。這里我們可以用類似元編程的奇技淫巧,下面的是部分代碼:

//收到worker發(fā)來的init消息之后
var workers = {
    __send: this.__send.bind(this), //這個(gè)this指向Manager,而不是自己
    __functionCall: this.__functionCall.bind(this) //同上
};
var funcNames = data.body; //比如["add", "multiply"]
funcNames.forEach(funcName => {
    //使用new Function的奇技淫巧
    rpc[funcName] = new Function(`
        //截取參數(shù)
        var params = Array.prototype.slice.call(arguments,0,arguments.length-1);
        var callback = arguments[arguments.length-1];
        
        //這個(gè)__functionCall調(diào)用了Manager底層的通信,具體在后面解釋
        this.__functionCall("${funcName}",params,callback);
    `)
})
//將workers注冊到Manager內(nèi)部
this.__workers = workers;
//如果此時(shí)Manager已經(jīng)在等待開始了,那么開始任務(wù)
if (this.__waitingForInit) {
    this.start();
}

還記得上面我們有個(gè)start方法么?它是這樣寫的:

start: function() {
    if (this.__workers != undefined) {
        //如果初始化完畢,workers實(shí)例存在
        this.__workersStaticCallback(this.__workers);
        this.__waitingForInit = false;
    } else {
        //否則將等待初始化完畢
        this.__waitingForInit = true;
    }
},
3、序列化

如果只是單個(gè)Worker和單個(gè)Manager,并且遠(yuǎn)程方法都是同步而非異步的,那么我們顯然不需要考慮返回值順序的問題:

比如我們的Manager調(diào)用了下面一堆方法:

workers.add(1, 1, callback);
workers.add(2, 2, callback);
workers.add(3, 3, callback);

由于Workeradd的是同步的方法,那么顯然我們收到返回值的順序是:

2
4
6

但如果Worker中存在一個(gè)異步調(diào)用,那么這個(gè)順序就會(huì)被打亂:

workers.readFile("xxx", callback);
workers.add(1, 1, callback);
workers.add(2, 2, callback);

顯然我們收到的返回值順序是:

2
4
content of xxx

所以這里就需要對發(fā)出的函數(shù)調(diào)用做一個(gè)序列化,具體的方法就是對于每一個(gè)調(diào)用都給一個(gè)uuid(唯一標(biāo)識(shí)碼)。

比如我們調(diào)用了:

workers.add(1, 1, stupid_callback);

那么首先Manager會(huì)對這個(gè)調(diào)用生成一個(gè) uuid :

9557881b-25d7-4c94-84c8-2463c53b67f4

然后在__callbackStore中將這個(gè) uuid 和stupid_callback 綁定,然后向選中的某個(gè)Worker發(fā)送函數(shù)調(diào)用信息(具體怎么選Worker我們后面再說):

{
    id: "9557881b-25d7-4c94-84c8-2463c53b67f4",
    message: "function call",
    body: { 
        funcName: "add", 
        params: [1, 1] 
    }
}

Worker執(zhí)行這個(gè)函數(shù)之后,發(fā)送回來一個(gè)函數(shù)返回值的信息體,大概是這樣:

{
    id: "9557881b-25d7-4c94-84c8-2463c53b67f4",
    message: "function call",
    body: { 
        result: 2 
    }
}

然后我們就可以在__callbackStore中找到這個(gè) uuid 對應(yīng)的 callback ,并且執(zhí)行它:

this.__callbackStore[id](result);

這就是workers.add(1, 1, stupid_callback)這行代碼背后的原理。

4、任務(wù)分配

如果存在多個(gè)Worker,顯然我們不能把所有的調(diào)用都傻傻地發(fā)送到第一個(gè)Worker身上,所以這里就需要有一個(gè)任務(wù)分配機(jī)制,我的機(jī)制比較簡單,大概說就是在一張表里對每個(gè)Worker記錄下它是否繁忙的狀態(tài),每次當(dāng)有調(diào)用需求的時(shí)候,先遍歷這張表,

如果找到有空閑的Worker,那么就將對它發(fā)送調(diào)用;

如果所有Worker都繁忙,那么先把這個(gè)調(diào)用暫存在一個(gè)隊(duì)列之中;

當(dāng)收到某個(gè)Worker的返回值后,會(huì)檢查隊(duì)列中是否有任務(wù),有的話,那么就對這個(gè)Worker發(fā)送最前的函數(shù)調(diào)用,若沒有,就把這個(gè)Worker設(shè)為空閑狀態(tài)。

具體任務(wù)分配的代碼比較冗余,分散在各個(gè)方法內(nèi),所以只介紹方法,就不貼上來了/w

全部的Manager代碼在這里(抱歉還沒時(shí)間補(bǔ)注釋):

Maus/manager.js at master · starkwang/Maus

三、Worker的實(shí)現(xiàn)

這里要再說一遍,我們的RPC框架是基于websocket的,所以Worker可以是一個(gè)PC瀏覽器?。。】梢允且粋€(gè)手機(jī)瀏覽器?。?!可以是一個(gè)平板瀏覽器?。?!

Worker的實(shí)現(xiàn)遠(yuǎn)比Manager簡單,因?yàn)樗恍枰獙ξㄒ灰粋€(gè)Manager通信,它的邏輯只有:

接收Manager發(fā)來的數(shù)據(jù);

根據(jù)數(shù)據(jù)做出相應(yīng)的反應(yīng)(函數(shù)調(diào)用、初始化等等);

發(fā)送返回值

所以我們也不放代碼了,有興趣的可以看這里:

Maus/worker.js at master · starkwang/Maus

四、寫一個(gè)分布式算法

假設(shè)我們的加法是通過這個(gè)框架異步調(diào)用的,那么我們該怎么寫算法呢?

在單機(jī)情況下,寫個(gè)斐波拉契數(shù)列簡直跟喝水一樣簡單(事實(shí)上這種暴力遞歸的寫法非常非常傻逼且性能低下,只是作為范例演示用):

var fib = x => x>1 ? fib(x-1)+fib(x-2) : x

但是在分布式環(huán)境下,我們要將workers.add方法封裝成一個(gè)Promise化的add

//這里的x, y可能是數(shù)字,也可能是個(gè)Promise,所以要先調(diào)用Promise.all
var add = function(x, y){
    return Promise.all([x, y])
        .then(arr => new Promise((resolve, reject) => {
            workers.add(arr[0], arr[1], result => resolve(result));
        }))
}

然后我們就可以用類似同步的遞歸方法這樣寫一個(gè)分布式的fib算法:

var fib = x => x>1 ? add(fib(x-1), fib(x-2)) : x;

然后你可以嘗試用你的電腦里、樹莓派里、服務(wù)器里的nodejs、手機(jī)平板上的瀏覽器作為一個(gè)Worker,總之集合所有的計(jì)算能力,一起來計(jì)算這個(gè)傻傻的算法(事實(shí)上相比于單機(jī)算法會(huì)慢很多很多,因?yàn)橥ㄐ派系难舆t遠(yuǎn)大于單機(jī)的加法計(jì)算,但只是為了演示啦):

//分布式計(jì)算fib(40)
fib(40).then(result => console.log(result));

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79411.html

相關(guān)文章

  • 精讀《REST, GraphQL, Webhooks, & gRPC 如何選型》

    摘要:而利用進(jìn)一步提高了序列化速度,降低了數(shù)據(jù)包大小。帶來的最大好處是精簡請求響應(yīng)內(nèi)容,不會(huì)出現(xiàn)冗余字段,前端可以決定后端返回什么數(shù)據(jù)。再次強(qiáng)調(diào),相比和,是由前端決定返回結(jié)果的反模式。請求者可以自定義返回格式,某些程度上可以減少前后端聯(lián)調(diào)成本。 1 引言 每當(dāng)項(xiàng)目進(jìn)入聯(lián)調(diào)階段,或者提前約定接口時(shí),前后端就會(huì)聚在一起熱火朝天的討論起來。可能 99% 的場景都在約定 Http 接口,討論 URL...

    DevWiki 評論0 收藏0
  • gRPC實(shí)現(xiàn)跨語言微服務(wù)間通信 -- 精通外語電報(bào)員與煲電報(bào)粥小怪獸

    摘要:插畫牛肉框架小怪獸的電報(bào)員一旦系統(tǒng)怪物被拆分成了多個(gè)服務(wù)小怪獸,小怪獸們?nèi)绾螠贤▍f(xié)作就成了我們最關(guān)心的問題。插畫牛肉實(shí)現(xiàn)客戶端小怪獸發(fā)送今晚的月色真美,服務(wù)端小怪獸收到電報(bào)內(nèi)容,并回復(fù)。 作者:亞瑟、文遠(yuǎn) 1. 微服務(wù)框架 -- 從系統(tǒng)怪物到服務(wù)小怪獸 一個(gè)小巧的單體應(yīng)用會(huì)隨著公司業(yè)務(wù)的擴(kuò)張而慢慢成長,逐漸演化成一個(gè)龐大且復(fù)雜的系統(tǒng)怪物,系統(tǒng)任何一處的問題都將影響整個(gè)怪物的表現(xiàn),很少有...

    waltr 評論0 收藏0
  • RPC框架實(shí)踐之:Google gRPC

    摘要:與文章框架實(shí)踐之一文中實(shí)踐的另一種通用框架能通過自動(dòng)生成對應(yīng)語言的接口類似,也能自動(dòng)地生成和的存根,我們只需要一個(gè)命令就能快速搭建起運(yùn)行環(huán)境。類似于之前對于框架的實(shí)踐步驟,下面一一闡述。 showImg(https://segmentfault.com/img/remote/1460000014946557); 概述 gRPC是Google開源的通用高性能RPC框架,它支持的是使用P...

    malakashi 評論0 收藏0
  • RPC框架實(shí)踐之:Google gRPC

    摘要:與文章框架實(shí)踐之一文中實(shí)踐的另一種通用框架能通過自動(dòng)生成對應(yīng)語言的接口類似,也能自動(dòng)地生成和的存根,我們只需要一個(gè)命令就能快速搭建起運(yùn)行環(huán)境。類似于之前對于框架的實(shí)踐步驟,下面一一闡述。 showImg(https://segmentfault.com/img/remote/1460000014946557); 概述 gRPC是Google開源的通用高性能RPC框架,它支持的是使用P...

    vibiu 評論0 收藏0
  • 帶入gRPC:gRPC及相關(guān)介紹

    摘要:原文地址帶入及相關(guān)介紹項(xiàng)目地址作為開篇章,將會(huì)介紹相關(guān)的一些知識(shí)。 原文地址:帶入gRPC:gRPC及相關(guān)介紹 項(xiàng)目地址:go-grpc-example 作為開篇章,將會(huì)介紹 gRPC 相關(guān)的一些知識(shí)。簡單來講 gRPC 是一個(gè) 基于 HTTP/2 協(xié)議設(shè)計(jì)的 RPC 框架,它采用了 Protobuf 作為 IDL 你是否有過疑惑,它們都是些什么?本文將會(huì)介紹一些常用的知識(shí)和概念,更詳...

    y1chuan 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<