亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

一個瀏覽器和NodeJS通用的RPC框架

Tony / 927人閱讀

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

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

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

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

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

一、什么是RPC

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

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

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

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

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

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

rpcManager.create(workers => {
    //首先定義一個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)

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

二、Manager的實現(xiàn) 0、通信標準

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

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

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

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

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

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

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

//一段時間后。。。。。。

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

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

//收到worker發(fā)來的init消息之后
var workers = {
    __send: this.__send.bind(this), //這個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];
        
        //這個__functionCall調(diào)用了Manager底層的通信,具體在后面解釋
        this.__functionCall("${funcName}",params,callback);
    `)
})
//將workers注冊到Manager內(nèi)部
this.__workers = workers;
//如果此時Manager已經(jīng)在等待開始了,那么開始任務(wù)
if (this.__waitingForInit) {
    this.start();
}

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

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

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

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

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

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

2
4
6

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

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

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

2
4
content of xxx

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

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

workers.add(1, 1, stupid_callback);

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

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

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

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

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

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

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

this.__callbackStore[id](result);

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

4、任務(wù)分配

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

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

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

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

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

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

Maus/manager.js at master · starkwang/Maus

三、Worker的實現(xiàn)

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

Worker的實現(xiàn)遠比Manager簡單,因為它只需要對唯一一個Manager通信,它的邏輯只有:

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

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

發(fā)送返回值

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

Maus/worker.js at master · starkwang/Maus

四、寫一個分布式算法

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

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

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

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

//這里的x, y可能是數(shù)字,也可能是個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));
        }))
}

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

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

然后你可以嘗試用你的電腦里、樹莓派里、服務(wù)器里的nodejs、手機平板上的瀏覽器作為一個Worker,總之集合所有的計算能力,一起來計算這個傻傻的算法(事實上相比于單機算法會慢很多很多,因為通信上的延遲遠大于單機的加法計算,但只是為了演示啦):

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

    y1chuan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<