- Node practice source code: 40 lines of code to implement a compact version of KOA
- Node Practice Log: Asynchronous resource Listening and CLS implementation
More articles:The Node into the order
Why do I need to listen for asynchronous resources?
The most common asynchronous resource listening scenarios in a Node application are:
- The user information must be provided during exception capture to keep the same user information in each client request
- Full link log tracking, design each request for third-party services, databases, Redis to carry consistent traceId
The following figure shows zipkin’s full-link tracking based on traceId positioning:
Let’s look at an example of configuring user information in exception handling:
const session = new Map()
app.use((ctx, next) = > {
try {
await next()
} catch (e) {
const user = session.get('user')
// Report the user to the exception monitoring system
}
})
app.use((ctx, next) = > {
// Set user information
const user = getUserById()
session.set('user', user)
})
Copy the code
Configure user information globally for backend services for exception and log tracking. Because the session used at this time is asynchronous, user information is extremely easy to be overwritten by subsequent requests, so how to obtain user information correctly?
async_hooks
The official documentation describes async_hooks as follows: It is used to track asynchronous resources, that is, to listen for the lifetime of asynchronous resources.
The async_hooks module provides an API to track asynchronous resources.
Since it is used to track asynchronous resources, there are two ids in each asynchronous resource:
asyncId
: INDICATES the ID of the current life cycle of the asynchronous resourcetrigerAsyncId
: indicates the ID of the parent asynchronous resource, that isparentAsyncId
Call from the following API
const async_hooks = require('async_hooks');
const asyncId = async_hooks.executionAsyncId();
const trigerAsyncId = async_hooks.triggerAsyncId();
Copy the code
See the official documentation: Async_hooks API for more details
Asynchronous resources
Now that we’re talking about async_hooks listening for asynchronous resources, what are those asynchronous resources? We often use the following in our daily projects:
Promise
setTimeout
fs
/net
/process
Etc based on the underlying API
However, async_hooks lists this much on the official website. In addition to the several mentioned above, console.log is also an asynchronous resource: TickObject.
FSEVENTWRAP, FSREQCALLBACK, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPINCOMINGMESSAGE,
HTTPCLIENTREQUEST, JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP,
SHUTDOWNWRAP, SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVERWRAP, TCPWRAP,
TTYWRAP, UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
RANDOMBYTESREQUEST, TLSWRAP, Microtask, Timeout, Immediate, TickObject
Copy the code
async_hooks.createHook
We can use asyncId to listen on an asynchronous resource. How can we listen on the creation and destruction of an asynchronous resource?
Create a hook using async_links. createHook.
const asyncHook = async_hooks.createHook({
// asyncId: indicates the Id of an asynchronous resource
// type: indicates the type of the asynchronous resource
// triggerAsyncId: parent asynchronous resource Id
init (asyncId, type, triggerAsyncId, resource) {},
before (asyncId) {},
after (asyncId) {},
destroy(asyncId) {}
})
Copy the code
Let’s just focus on the four most important apis:
init
: Listens for the creation of an asynchronous resource. In this function, we can get the call chain of the asynchronous resource and also the type of the asynchronous resource, which are important.destory
: Listens for the destruction of asynchronous resources. Pay attention tosetTimeout
Can be destroyed, andPromise
Cannot destroy, CLS could leak here if implemented via async_hooks!before
after
setTimeout((a)= > {
// The after lifecycle begins with the callback function
console.log('Async Before')
op()
op()
op()
op()
// After lifecycle is at the end of the callback
console.log('Async After')})Copy the code
Async_hooks debugging and testing
Is it important to debug tools and keep breaking points and Step In?
No, the debug method is console.log.
However, if you debug async_hooks using console.log, problems arise because console.log is also an asynchronous resource: TickObject. Is there an alternative to console.log?
You can use the write system call to print characters to standard output (STDOUT), which has a file descriptor of 1. Thus, the importance of operating system knowledge for server-side development is self-evident.
Node calls the following API:
fs.writeSync(1.'hello, world')
Copy the code
What is a file descriptor?
The complete debugging code is as follows:
function log (. args) {
fs.writeSync(1, args.join(' ') + '\n')
}
async_hooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
log('Init: '.`${type}(asyncId=${asyncId}, parentAsyncId: ${triggerAsyncId}) `)
},
before(asyncId) {
log('Before: ', asyncId)
},
after(asyncId) {
log('After: ', asyncId)
},
destroy(asyncId) {
log('Destory: ', asyncId);
}
}).enable()
Copy the code
Continuation Local Storage implementation
Continuation-local storage works like thread-local storage in threaded programming, but is based on chains of Node-style callbacks instead of threads.
CLS is a key-value pair store that exists throughout the life cycle of an asynchronous resource. One copy of data is maintained for the same asynchronous resource and is not modified by other asynchronous resources. There are many good implementations in the community, and in older versions of Node (>=8.2.1) async_hooks are implemented directly.
- Node – continuation – local – storage: implementation of github.com/joyent/node…
- Cls-hooked: CLS using AsynWrap or async_hooks instead of async-listener for node 4.7+
I implemented a CLS myself using async_hooks: [clS-session](github.com/shfshanyue/… ]
const Session = require('cls-session')
const session = new Session()
function timeout (id) {
session.scope((a)= > {
session.set('a', id)
setTimeout((a)= > {
const a = session.get('a')
console.log(a)
})
})
}
timeout(1)
timeout(2)
timeout(3)
// Output:
/ / 1
/ / 2
/ / 3
Copy the code
summary
This article explains the usage scenario and implementation of asynchronous resource listening, which can be summarized as the following three points:
- CLS is asynchronous resource life-cycle based storage, implemented via asynC_hooks
- After asynC_hooks is enabled, each asynchronous resource has an asyncId and trigerAsyncId, from which you can check the asynchronous call relationship
- CLS common scenarios are used in exception monitoring and full-link log processing