未央花博客

ts-node-dev 的浅析

ts-node-dev 的浅析

0. 前言

本文主要是对 ts-node-dev 进行简单介绍、以及作者本人我遇到的一些问题、还有对出现问题地方的 ts-node-dev 的源码原理分析。

一. 简介

ts-node-dev 是基于node-dev 做的一个用于ts-node 服务重启工具。

相较于node-dev -r ts-node/register ..., nodemon -x ts-node ... 这些同类工具来说,由于其不需要每次重新实例化 ts-node 编译器 ,所以拥有更快的重新启动速度。

下面是原文:

Tweaked version of node-dev that uses ts-node under the hood.

It restarts target node process when any of required files changes (as standard node-dev) but shares Typescript compilation process between restarts. This significantly increases speed of restarting comparing to node-dev -r ts-node/register ..., nodemon -x ts-node ... variations because there is no need to instantiate ts-node compilation each time.

二. 使用

下载:

npm i ts-node-dev --save-dev

ts-node-dev src/index.ts

src/index.ts

import * as http from 'http'

const server = http.createServer(() => {
    console.log('server')
}).listen(3001, () => {
    console.log('server start')
})

function shutdownGracefully(sin: string) {
    server.close()
        .on('close', () => {
            console.log('server close')
            // 最好加上
            process.exit()
        })
}

process.on('SIGTERM', shutdownGracefully)

三. 遇到的问题

1. 实现 Node 服务 的优雅退出时,ts-node-dev 重启服务失败。

场景:当我们在 k8s 环境时, 进行旧 Pod 关闭时,一般 Node 服务 需要实现优雅退出,来关闭 redis数据库 等连接。

一个简单的例子

// 一个简单的例子
async function shutdownGracefully(signal: string, num: number) {
    console.log(`shutdownGracefully, Terminating by signal ${signal}(${num}).`)
    await Promise.all([
        redisClient.quit(),
        mysqlClient.close()
    ])
}

process.on('SIGTERM', shutdownGracefully)

结果截图:

从结果可以看出,我们的服务并没有重新启动。原理将在后面部分进行解析

解决方式:

async function shutdownGracefully(signal: string, num: number) {
    // ....
    process.exit(0) // 退出当前服务的进程
}

服务重新启动:

正确的优雅退出方式:

async function shutdownGracefully(signal: string, num: number) {
    console.log(`shutdownGracefully, Terminating by signal ${signal}(${num}).`)
    
    server.close()
        .on('close', async () => {
            try {
                await Promise.all([
                    redisClient.quit(),
                    mysqlClient.close()
                ])
            } finally {
                // 为了保险,尽量开发者自己添加这行代码。虽然 server.close() 也会执行服务进程退出。
                process.exit(0)
            }
        })
}

- 当使用 --exit-child 参数时,自定义的优雅退出方法失效。

async function shutdownGracefully(signal: string, num: number) {
    console.log(`shutdownGracefully, Terminating by signal ${signal}(${num}).`)
    await Promise.all([
        redisClient.quit(),
        mysqlClient.close()
    ])
    process.exit(0)
}

结果截图:

从结果来看,我们自定义的 优雅退出方法 并没有执行,但是服务已经重新启动了。所以,当需要自定义实现优雅退出时,这个参数慎用

四. 源码分析

当运行 ts-node-dev src/index.ts 命令时,其执行流程是如何?接下来将进行分析。

1. 流程分析

2. 对第三点遇到问题的原理解答

- ts-node-dev 重启服务失败

原因在于自定义的 SIGTERM 处理函数,没有让 子进程 退出,子进程不退出就会让 start() 方法 无法重新执行,也就导致无法创建新的子进程来重启服务。

// 开发者自定义的
async function shutdownGracefully(signal: string, num: number) {
    console.log(`shutdownGracefully, Terminating by signal ${signal}(${num}).`)
    await Promise.all([
        redisClient.quit(),
        mysqlClient.close()
    ])
}

- 在有 --exit-child 参数的情况:


// child-require-hook.js 会自动注册一个 SIGTERM 回调函数,如下
if (exitChild) {
    process.on('SIGTERM', function () {
        console.log('Child got SIGTERM, exiting.');
        // 程序退出
        process.exit();
    });
}
// 这个函数是 SIGTERM 信号处理函数集合的第一个,所以函数执行,子进程就退出了,这样就导致”开发者“自己注册 SIGTERM 的回调函数不会执行。

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »