diff --git a/README.md b/README.md index a8af5c21..cb10b3ab 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## Features -1. Keep it Simple +1. Keep it Simple (build tools for humans). 2. Support zookeeper as register center @@ -22,9 +22,9 @@ 5. Support Directly Dubbo (const Dubbo = DirectlyDubbo({..})) -6. Middleware, Easy to extend. +6. Middleware the same as Koa middleware, Easy to extend. -7. Tracing +7. Tracing (runtime info, call stack) 8. Supported Dubbox @@ -32,7 +32,7 @@ 10. Convert java dubbo interface to typescript module -11. socket-worker auto retry +11. SocketWorker auto retry ## Getting Started @@ -324,27 +324,192 @@ app.beforeStart(async () => { }); ``` -## dubbo's subscriber +## How to trace dubbo2.js runtime system info? ```javascript const dubbo = Dubbo.from(/*...*/); +//通过subscribe dubbo.subcribe({ - onReady: () => { - //dubbo was ready. - //TODO for example logger - }, - onSysError: err => { - //dubbo occur error - //TODO dingTalkRobot.send('error') - }, - onStatistics: stat => { - //get invoke time statistics info - //in order to know load whether balance + onTrace(msg: ITrace) { + //logger msg }, }); ``` +You will get all runtim system info just like this. + +```text +{ type: 'INFO', msg: 'dubbo:bootstrap version => 2.1.5' } +{ type: 'INFO', msg: 'connected to zkserver localhost:2181' } +{ type: 'INFO', + msg: 'ServerAgent create socket-pool: 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'socket-pool: 172.19.6.203:20880 poolSize: 1' } +{ type: 'INFO', + msg: 'new SocketWorker#1 |> 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'SocketWorker#1 <=connected=> 172.19.6.203:20880' } +{ type: 'INFO', msg: 'scheduler is ready' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'ERR', + msg: Error: Can not be found any agents + at Object.Scheduler._handleZkClientOnData [as onData] (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/scheduler.js:68:29) + at EventEmitter. (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/zookeeper.js:275:30) + at + at process._tickCallback (internal/process/next_tick.js:118:7) } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 6 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 5 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'ERR', + msg: Error: Can not be found any agents + at Object.Scheduler._handleZkClientOnData [as onData] (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/scheduler.js:68:29) + at EventEmitter. (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/zookeeper.js:275:30) + at + at process._tickCallback (internal/process/next_tick.js:118:7) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 4 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 3 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 2 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 1 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'SocketWorker#1 =connecting=> 172.19.6.203:20880' } +{ type: 'ERR', + msg: + { Error: connect ECONNREFUSED 172.19.6.203:20880 + at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1173:14) + errno: 'ECONNREFUSED', + code: 'ECONNREFUSED', + syscall: 'connect', + address: '172.19.6.203', + port: 20880 } } +{ type: 'ERR', + msg: Error: SocketWorker#1 <=closed=> 172.19.6.203:20880 retry: 0 + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:78:29) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'ERR', + msg: Error: 172.19.6.203:20880's pool socket-worker had all closed. delete 172.19.6.203:20880 + at ServerAgent._clearClosedPool (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/server-agent.js:66:33) + at Object.onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/server-agent.js:51:34) + at SocketWorker._onClose (/Users/hufeng/Github/dubbo2.js/packages/dubbo/es7/socket-worker.js:97:34) + at Socket.emit (events.js:180:13) + at TCP._handle.close [as _onclose] (net.js:541:12) } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.DemoProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'ServerAgent create socket-pool: 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'socket-pool: 172.19.6.203:20880 poolSize: 1' } +{ type: 'INFO', + msg: 'new SocketWorker#2 |> 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'SocketWorker#2 =connecting=> 172.19.6.203:20880' } +{ type: 'INFO', + msg: 'SocketWorker#2 <=connected=> 172.19.6.203:20880' } +{ type: 'INFO', msg: 'scheduler is ready' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.BasicTypeProvider/providers, type: NODE_CHILDREN_CHANGED' } +{ type: 'INFO', + msg: 'trigger watch /dubbo/com.alibaba.dubbo.demo.ErrorProvider/providers, type: NODE_CHILDREN_CHANGED' } +``` + ## middleware 通过对调用链路的抽象使用和 koa 相同的 middleware 机制,方便自定义拦截器,比如 logger, diff --git a/packages/dubbo/package.json b/packages/dubbo/package.json index bd545d62..e43c9a84 100644 --- a/packages/dubbo/package.json +++ b/packages/dubbo/package.json @@ -1,6 +1,6 @@ { "name": "dubbo2.js", - "version": "2.1.5", + "version": "2.1.6", "main": "./es7/index.js", "types": "./es7/typings/index.d.ts", "license": "Apache Licence 2.0", diff --git a/packages/dubbo/src/__tests__/async-test.ts b/packages/dubbo/src/__tests__/async-test.ts index 638b20be..9fe99eb0 100644 --- a/packages/dubbo/src/__tests__/async-test.ts +++ b/packages/dubbo/src/__tests__/async-test.ts @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + async function hello() { return {err: null, res: 'hello'}; } diff --git a/packages/dubbo/src/__tests__/binary-test.ts b/packages/dubbo/src/__tests__/binary-test.ts index f986fa77..cd7c34bb 100644 --- a/packages/dubbo/src/__tests__/binary-test.ts +++ b/packages/dubbo/src/__tests__/binary-test.ts @@ -14,27 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {binaryNum, convertBinaryNum} from '../binary'; +import {fromBytes4, fromBytes8, toBytes4, toBytes8} from '../byte'; describe('binary test suite', () => { it('test binaryNum', () => { - expect(binaryNum(1025, 4)).toEqual(new Buffer([0x00, 0x00, 0x04, 0x01])); - expect(binaryNum(201212, 4)).toEqual(new Buffer([0x00, 0x03, 0x11, 0xfc])); + expect(toBytes4(1025)).toEqual(new Buffer([0x00, 0x00, 0x04, 0x01])); + expect(toBytes4(201212)).toEqual(new Buffer([0x00, 0x03, 0x11, 0xfc])); }); it('test convert number', () => { - expect(convertBinaryNum(new Buffer([0x00, 0x00, 0x04, 0x01]), 4)).toEqual( - 1025, - ); - expect(convertBinaryNum(new Buffer([0x00, 0x03, 0x11, 0xfc]), 4)).toEqual( - 201212, - ); + expect(fromBytes4(new Buffer([0x00, 0x00, 0x04, 0x01]))).toEqual(1025); + expect(fromBytes4(new Buffer([0x00, 0x03, 0x11, 0xfc]))).toEqual(201212); }); it('test binary uuid', () => { const seed = 13234234234234234234; - const buffer = binaryNum(seed, 8); - const num = convertBinaryNum(buffer, 8); + const buffer = toBytes8(seed); + const num = fromBytes8(buffer); expect(seed).toEqual(num); }); }); diff --git a/packages/dubbo/src/__tests__/context-test.ts b/packages/dubbo/src/__tests__/context-test.ts index b2ebbdbe..3015cd09 100644 --- a/packages/dubbo/src/__tests__/context-test.ts +++ b/packages/dubbo/src/__tests__/context-test.ts @@ -14,8 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Context from '../context'; import java from 'js-to-java'; +import Context from '../context'; describe('context test suite', () => { it('test default Value', () => { diff --git a/packages/dubbo/src/__tests__/decode-buffer-test.ts b/packages/dubbo/src/__tests__/decode-buffer-test.ts index 93142312..c52c7edc 100644 --- a/packages/dubbo/src/__tests__/decode-buffer-test.ts +++ b/packages/dubbo/src/__tests__/decode-buffer-test.ts @@ -15,8 +15,8 @@ * limitations under the License. */ import {Buffer} from 'buffer'; -import DecodeBuffer from '../decode-buffer'; import {decode} from '../decode'; +import DecodeBuffer from '../decode-buffer'; it('test receive right data', () => { const buffer = Buffer.from([ diff --git a/packages/dubbo/src/__tests__/directly-dubbo-test.ts b/packages/dubbo/src/__tests__/directly-dubbo-test.ts index 3730f9a4..cd10e0bb 100644 --- a/packages/dubbo/src/__tests__/directly-dubbo-test.ts +++ b/packages/dubbo/src/__tests__/directly-dubbo-test.ts @@ -14,9 +14,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import {DirectlyDubbo, java} from 'dubbo2.js'; import { - DemoProvider, DemoProviderWrapper, IDemoProvider, } from './providers/com/alibaba/dubbo/demo/DemoProvider'; diff --git a/packages/dubbo/src/__tests__/dubbo-hessian-parameter-test.ts b/packages/dubbo/src/__tests__/dubbo-hessian-parameter-test.ts index 2139c5a0..8f97a6bd 100644 --- a/packages/dubbo/src/__tests__/dubbo-hessian-parameter-test.ts +++ b/packages/dubbo/src/__tests__/dubbo-hessian-parameter-test.ts @@ -14,14 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Dubbo, java} from 'dubbo2.js'; + +import {Dubbo} from 'dubbo2.js'; import {DemoProvider} from './providers/com/alibaba/dubbo/demo/DemoProvider'; -const dubbo = new Dubbo({ +const service = { + DemoProvider, +}; + +const dubbo = new Dubbo({ application: {name: '@qianmi/node-dubbo'}, register: 'localhost:2181', - dubboVersion: '2.0.0', - interfaces: ['com.alibaba.dubbo.demo.DemoProvider'], + service, }); //use middleware @@ -38,12 +42,10 @@ dubbo.use(async function test(ctx, next) { ); }); -const demoService = DemoProvider(dubbo); - describe('dubbo hessian parameter check test suite', () => { it('test sayHello', async () => { //@ts-ignore - const {res, err} = await demoService.sayHello('node'); + const {res, err} = await dubbo.service.DemoProvider.sayHello('node'); expect(res).toEqual(null); expect(err != null).toEqual(true); expect(err.message).toMatch(/not all arguments are valid hessian type/); diff --git a/packages/dubbo/src/__tests__/dubbo-test.ts b/packages/dubbo/src/__tests__/dubbo-test.ts index 67a9d5cf..9b6b1ee7 100644 --- a/packages/dubbo/src/__tests__/dubbo-test.ts +++ b/packages/dubbo/src/__tests__/dubbo-test.ts @@ -23,6 +23,7 @@ import {ErrorProvider} from './providers/com/alibaba/dubbo/demo/ErrorProvider'; import {TypeRequest} from './providers/com/alibaba/dubbo/demo/TypeRequest'; import {UserRequest} from './providers/com/alibaba/dubbo/demo/UserRequest'; +//==============init dubbo============== const service = { BasicTypeProvider, DemoProvider, @@ -61,22 +62,14 @@ dubbo.use( ); dubbo.subscribe({ - onReady() { - console.log('onReady'); - }, - onSysError(err) { - console.log(err); - }, - onStatistics(stat) { - console.log(stat); + onTrace(msg) { + console.log(msg); }, }); +//=====demoservice========== describe('demoService', () => { it('test sayHello', async () => { - await dubbo.ready(); - - // @ts-ignore const {res, err} = await dubbo.service.DemoProvider.sayHello( java.String('node'), ); @@ -127,11 +120,9 @@ describe('typeBasicServer', () => { }); }); -const errorService = ErrorProvider(dubbo); - describe('error test', () => { it('test errorTest', async () => { - const {res, err} = await errorService.errorTest(); + const {res, err} = await dubbo.service.ErrorProvider.errorTest(); expect(err != null).toEqual(true); expect(res == null).toEqual(true); }); diff --git a/packages/dubbo/src/__tests__/dubbo-timeout-test.ts b/packages/dubbo/src/__tests__/dubbo-timeout-test.ts index 95ae5749..1a5207ce 100644 --- a/packages/dubbo/src/__tests__/dubbo-timeout-test.ts +++ b/packages/dubbo/src/__tests__/dubbo-timeout-test.ts @@ -14,16 +14,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {java, Dubbo} from 'dubbo2.js'; + +import {Dubbo, java} from 'dubbo2.js'; import {DemoProvider} from './providers/com/alibaba/dubbo/demo/DemoProvider'; import {UserRequest} from './providers/com/alibaba/dubbo/demo/UserRequest'; -const dubbo = new Dubbo({ +const service = { + DemoProvider, +}; + +const dubbo = new Dubbo({ application: {name: '@qianmi/node-dubbo'}, register: 'localhost:2181', - dubboVersion: '2.0.0', dubboInvokeTimeout: 0.001, - interfaces: ['com.alibaba.dubbo.demo.DemoProvider'], + service, }); //use middleware @@ -40,26 +44,25 @@ dubbo.use(async function test(ctx, next) { ); }); -const demoService = DemoProvider(dubbo); - describe('dubbo timeout test suite', () => { it('test echo timeout', async () => { - const {res, err} = await demoService.echo(); + const {res, err} = await dubbo.service.DemoProvider.echo(); expect(res).toEqual(null); expect(err != null).toEqual(true); expect(err.message).toMatch(/remote invoke timeout/); }); it('test sayHello', async () => { - //@ts-ignore - const {res, err} = await demoService.sayHello(java.String('node')); + const {res, err} = await dubbo.service.DemoProvider.sayHello( + java.String('node'), + ); expect(res).toEqual(null); expect(err != null).toEqual(true); expect(err.message).toMatch(/remote invoke timeout/); }); it('test getUserInfo', async () => { - const {res, err} = await demoService.getUserInfo( + const {res, err} = await dubbo.service.DemoProvider.getUserInfo( new UserRequest({ id: 1, name: 'nodejs', diff --git a/packages/dubbo/src/__tests__/request-id-test.ts b/packages/dubbo/src/__tests__/request-id-test.ts index 4aa319ea..91ea2449 100644 --- a/packages/dubbo/src/__tests__/request-id-test.ts +++ b/packages/dubbo/src/__tests__/request-id-test.ts @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import {id} from '../request-id'; it('test generate uuid', () => { diff --git a/packages/dubbo/src/__tests__/to-test.ts b/packages/dubbo/src/__tests__/to-test.ts index 276a19ca..0642196c 100644 --- a/packages/dubbo/src/__tests__/to-test.ts +++ b/packages/dubbo/src/__tests__/to-test.ts @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import fs from 'fs'; import {promisify} from 'util'; import {to} from '../to'; diff --git a/packages/dubbo/src/byte.ts b/packages/dubbo/src/byte.ts index d422eef1..fa0348ea 100644 --- a/packages/dubbo/src/byte.ts +++ b/packages/dubbo/src/byte.ts @@ -41,5 +41,6 @@ export const toBytes8 = (num: number) => { export const fromBytes8 = (buf: Buffer) => { const high = buf.readUInt32BE(0); const low = buf.readUInt32BE(4); - return high * Math.pow(2, 32) + low; + //Math.pow(2, 32) = 4294967296 + return high * 4294967296 + low; }; diff --git a/packages/dubbo/src/queue.ts b/packages/dubbo/src/queue.ts index 06ac13e1..701ae61b 100644 --- a/packages/dubbo/src/queue.ts +++ b/packages/dubbo/src/queue.ts @@ -152,7 +152,6 @@ export class Queue implements IObservable { ctx.cleanTimeout(); ctx.reject(err); this._clear(requestId); - traceErr(err); } consume(requestId: TRequestId, node: SocketWorker, providerMeta: DubboUrl) { @@ -172,6 +171,7 @@ export class Queue implements IObservable { node.write(ctx); } catch (err) { this.failed(requestId, err); + traceErr(err); } if (isDevEnv) { log(`current schedule queue ==>`, this.scheduleQueue); diff --git a/packages/dubbo/src/server-agent.ts b/packages/dubbo/src/server-agent.ts index 0bf090f9..05cf7941 100644 --- a/packages/dubbo/src/server-agent.ts +++ b/packages/dubbo/src/server-agent.ts @@ -17,6 +17,7 @@ import debug from 'debug'; import SocketPool from './socket-pool'; +import SocketWorker from './socket-worker'; import {IObservable, ISocketSubscriber} from './types'; import {isDevEnv, noop, traceErr, traceInfo} from './util'; import {TAgentAddr} from './zookeeper'; @@ -74,7 +75,7 @@ export class ServerAgent implements IObservable { * 获取可用负载对应的socketWorker * @param agentAddrList */ - getAvailableSocketWorker(agentAddrList: Array) { + getAvailableSocketWorker(agentAddrList: Array): SocketWorker { const availableAgentList = this._getAvailableSocketAgents(agentAddrList); const len = availableAgentList.length; diff --git a/packages/dubbo/src/types.ts b/packages/dubbo/src/types.ts index f46b7fbe..a0db5dae 100644 --- a/packages/dubbo/src/types.ts +++ b/packages/dubbo/src/types.ts @@ -27,7 +27,7 @@ export interface ITrace { } export interface IDubboSubscriber { - onTrace?: (msg: ITrace) => void; + onTrace: (msg: ITrace) => void; } export interface IZookeeperSubscriber { diff --git a/packages/dubbo/src/zookeeper.ts b/packages/dubbo/src/zookeeper.ts index 5506432d..5881f31f 100644 --- a/packages/dubbo/src/zookeeper.ts +++ b/packages/dubbo/src/zookeeper.ts @@ -21,10 +21,10 @@ import zookeeper from 'node-zookeeper-client'; import qs from 'querystring'; import Context from './context'; import DubboUrl from './dubbo-url'; -import { ZookeeperDisconnectedError, ZookeeperTimeoutError } from './err'; -import { to } from './to'; -import { IObservable, IZkClientProps, IZookeeperSubscriber } from './types'; -import { isDevEnv, msg, noop, traceErr, traceInfo } from './util'; +import {ZookeeperDisconnectedError, ZookeeperTimeoutError} from './err'; +import {to} from './to'; +import {IObservable, IZkClientProps, IZookeeperSubscriber} from './types'; +import {isDevEnv, msg, noop, traceErr, traceInfo} from './util'; const log = debug('dubbo:zookeeper'); const ipAddress = ip.address(); diff --git a/packages/zone-context/.npmignore b/packages/zone-context/.npmignore new file mode 100644 index 00000000..c0691836 --- /dev/null +++ b/packages/zone-context/.npmignore @@ -0,0 +1,7 @@ +**/__tests__/** +**/__mocks__/** +src +tsconfig.json +tsconfig-es6.json +node_modules +Makefile \ No newline at end of file diff --git a/packages/zone-context/Makefile b/packages/zone-context/Makefile new file mode 100644 index 00000000..8a000547 --- /dev/null +++ b/packages/zone-context/Makefile @@ -0,0 +1,10 @@ +tsc = ../../node_modules/.bin/tsc +jest = ../../node_modules/.bin/jest + +compile: clean + $(tsc) + @echo "compile zone-context successfully 👏\n" + +clean: + rm -rf ./lib + @echo "clean zone-context successfully 👏\n" \ No newline at end of file diff --git a/packages/zone-context/README.md b/packages/zone-context/README.md new file mode 100644 index 00000000..f0eb4220 --- /dev/null +++ b/packages/zone-context/README.md @@ -0,0 +1,63 @@ +## zone-context + +a nodejs call stack context just like java ThreadLocal. + +## Why? + +我们在做一些 trace 的时候,需要知道上下文信息,比如一个请求中包含了几次 dubbo 请求的调用,我们需要知道某次 dubbo 的调用在哪个请求的上下文,方便我们的 trace 和排查问题。针对这样的场景我们希望可以做到的隐士的传递参数获取上下文信息 + +## How? + +1. 很早之前 node 有 domain 的 api, +2. Angular 社区的 zone.js +3. node 新特性 async_hooks +4. other... + +## getting started ? + +```sh +npm install zone-context +``` + +``` +//demo +import zone from 'zone-context'; + +it('test zone context', () => { + zone.setRootContext('uuid', 1); + expect(zone.get('uuid')).toEqual(1); + + (() => { + //async + setTimeout(() => { + expect(zone.get('uuid')).toEqual(1); + }, 20); + + //async + process.nextTick(() => { + expect(zone.get('uuid')).toEqual(1); + }); + + //nested + new Promise(resolve => { + new Promise(r => { + setTimeout(() => { + expect(zone.get('uuid')).toEqual(1); + setImmediate(() => { + expect(zone.get('uuid')).toEqual(1); + process.nextTick(() => { + expect(zone.get('uuid')).toEqual(1); + }); + }); + r(); + }, 20); + }).then(() => { + expect(zone.get('uuid')).toEqual(1); + resolve(); + }); + }); + })(); + + expect(zone.get('uuid')).toEqual(1); +}); +``` diff --git a/packages/zone-context/package.json b/packages/zone-context/package.json new file mode 100644 index 00000000..b3c5177d --- /dev/null +++ b/packages/zone-context/package.json @@ -0,0 +1,12 @@ +{ + "name": "zone-context", + "version": "1.0.0", + "description": "zone node async-hook", + "main": "./lib/index.js", + "types": "./lib/typings/index.d.ts", + "keywords": [ + "zone context", + "Nodejs ThreadLocal" + ], + "license": "Apache Licence 2.0" +} diff --git a/packages/zone-context/src/__tests__/index-test.ts b/packages/zone-context/src/__tests__/index-test.ts new file mode 100644 index 00000000..79d3e6f9 --- /dev/null +++ b/packages/zone-context/src/__tests__/index-test.ts @@ -0,0 +1,39 @@ +import zone from '../index'; + +it('test zone context', () => { + zone.setRootContext('uuid', 1); + expect(zone.get('uuid')).toEqual(1); + + (() => { + //async + setTimeout(() => { + expect(zone.get('uuid')).toEqual(1); + }, 20); + + //async + process.nextTick(() => { + expect(zone.get('uuid')).toEqual(1); + }); + + //nested + new Promise(resolve => { + new Promise(r => { + setTimeout(() => { + expect(zone.get('uuid')).toEqual(1); + setImmediate(() => { + expect(zone.get('uuid')).toEqual(1); + process.nextTick(() => { + expect(zone.get('uuid')).toEqual(1); + }); + }); + r(); + }, 20); + }).then(() => { + expect(zone.get('uuid')).toEqual(1); + resolve(); + }); + }); + })(); + + expect(zone.get('uuid')).toEqual(1); +}); diff --git a/packages/zone-context/src/index.ts b/packages/zone-context/src/index.ts new file mode 100644 index 00000000..9fe7b9f3 --- /dev/null +++ b/packages/zone-context/src/index.ts @@ -0,0 +1,120 @@ +import async_hooks from 'async_hooks'; +import debug from 'debug'; +const log = debug('dubbo:zone'); + +//alias type +export type AsyncId = number; +export type RootAsyncId = number; + +/** + * ZoneContext 期待Zone的规范早日落地 + */ +export class ZoneContext { + constructor() { + log('init ZoneContext'); + this.rootMap = new Map(); + this.statckFrameMap = new Map(); + this.initAsyncHook(); + } + + private rootMap: Map; + private statckFrameMap: Map; + + /** + * 初始化async_hooks + */ + initAsyncHook() { + log('init async hooks'); + const self = this; + const {rootMap, statckFrameMap} = this; + + //@ts-ignore + const cleanUpContextNode = (id: AsyncId, type: string) => { + if (!self.statckFrameMap.has(id)) { + // (process as any)._rawDebug(`no id ${id}`); + return; + } + + // (process as any)._rawDebug(`${type}: ${id}`); + //获取当前销毁asyncId对应的rootId + const rootId = statckFrameMap.get(id); + + //销毁当前的栈数据 + statckFrameMap.delete(id); + + //判断当前的stackFrameMap还有对于rootId的引用 + let existsRootRef = false; + //@ts-ignore + for (let [_, v] of self.statckFrameMap) { + if (v === rootId) { + existsRootRef = true; + break; + } + } + //如果不存在rootId的引用,销毁rootMap的引用 + if (!existsRootRef) { + rootMap.delete(rootId); + } + + // (process as any)._rawDebug(statckFrameMap); + // (process as any)._rawDebug(rootMap); + }; + + async_hooks + .createHook({ + //@ts-ignore + init(asyncId, type, triggerAsyncId, resource) { + // (process as any)._rawDebug(Array.from(arguments).join('|>')); + + //如果当前的triggerAsyncId是rootContext,直接将rootAsyncId关联起来,这样可以省点内存(避免对象的拷贝复制) + //如果当前的triggerAsyncId是stackContext,将父的rootAsyncId关联起来 + if (rootMap.has(triggerAsyncId)) { + statckFrameMap.set(asyncId, triggerAsyncId); + } else { + if (statckFrameMap.has(triggerAsyncId)) { + statckFrameMap.set(asyncId, statckFrameMap.get(triggerAsyncId)); + } + } + }, + + destroy(id) { + cleanUpContextNode(id, 'destroy'); + }, + + promiseResolve(id) { + cleanUpContextNode(id, 'resolve'); + }, + + after(id) { + cleanUpContextNode(id, 'after'); + }, + }) + .enable(); + } + + get(key) { + const asyncId = async_hooks.executionAsyncId(); + const value = + this.rootMap.get(this.statckFrameMap.get(asyncId)) || + this.rootMap.get(asyncId) || + {}; + log(`current currentAsyncId ${asyncId} value: ${JSON.stringify(value)}`); + return value[key]; + } + + setRootContext(key, value) { + log('set:', key, value); + + const rootId = async_hooks.executionAsyncId(); + if (this.rootMap.has(rootId)) { + const obj = this.rootMap.get(rootId); + this.rootMap.set(rootId, Object.assign({}, obj, {[key]: value})); + } else { + this.rootMap.set(rootId, {[key]: value}); + } + + log(this.rootMap); + } +} + +export default new ZoneContext(); diff --git a/packages/zone-context/tsconfig.json b/packages/zone-context/tsconfig.json new file mode 100644 index 00000000..40671275 --- /dev/null +++ b/packages/zone-context/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declaration": true, + "declarationDir": "./lib/typings", + "noUnusedLocals": true, + "noUnusedParameters": true + }, + "exclude": ["./src/__tests__/**"] +}