记一次解决Next.js服务端渲染CPU持续升高问题

547 阅读3分钟

我们公司是做跨境电商的,商城用next自己开发的多租户模式,目前有5个品牌80+个站点在用这套代码

前一段时间发现有一个品牌站点运行一段时间后CPU越来越高,怀疑是内存泄漏,但是排查代码,用clinic.js测试也没发现有什么问题,按道理内存泄漏应该内存一直涨,啥都不涨就涨CPU

image.png image.png

我能想到可能性就是自定义事件在不断重复添加同一个函数,然后事件触发的时候同一个函数执行次数越来越多,才能解释的通,否则内存也一定会跟着涨(哪位大佬有高见,欢迎在评论区讨论)

折腾了好几天一点线索都没有,下午茶聊天的时候,另一个部门的同事说他们有一次内存泄漏是i18n引起的,我就试着注释了初始化i18n的代码......

image.png

我们国际化方案用的i18nextreact-i18next库,翻译数据是从后端接口读取的,翻译做了分组,主要分为基础组,客户端组,和其他组,我们想在_app.js里统一请求基础组,就不用每个页面到处写了,其他分组按需加载,否则一次性加载完,html页面体积会变得很大影响性能,然鹅next-i18next要每个页面级组件写serverSideTranslations,果断抛弃,改为使用react-i18next,初始化代码如下

// _app.js
function MyApp({ Component, pageProps }) {
  const { language, i18nResources } = pageProps;
  
  // 初始化 store
  initStore({ ...pageProps });
  
  // 初始化 i18n
  initI18n({ language, resources: i18nResources });
  
  return (...);
}

// initI18n
export function initI18n({ language, resources }) {
  const namespaces = Object.keys(resources);
  i18n.use(initReactI18next).init({
    lng: language,
    fallbackLng: defaultLanguage,
    resources: { [language]: resources },
    ns: namespaces,
    defaultNS: Enums.i18nNamespace.Default,
    fallbackNS: Enums.i18nNamespace.Fallback,
    initImmediate: true,
  });
}

打死我都没想到竟然是这段平平无奇的代码造成的,搞的劳资整天提心吊胆,隔几天就要重启一次进程

image.png

i18next源码太多没空研究,得先赶紧解决问题,最简单的办法就是自己写一个

// i18next.js
class I18n {
  store = {
    data: {},
  };
  options = {};
  #currentLanguage = "en";
  #defaultAndFallbackNS = [];

  constructor() {
    this.t = this.t.bind(this);
  }

  get currentLanguage() {
    return this.#currentLanguage;
  }

  use() {
    return this;
  }

  #getLoadedNS() {
    return Object.keys(this.store.data[this.#currentLanguage]);
  }

  #fillDefaultAndFallbackNS(ns) {
    ns = this.#toNSArray(ns);
    return ns.length > 0 ? [...ns, ...this.#defaultAndFallbackNS] : this.#getLoadedNS();
  }

  #toNSArray(ns) {
    let result;
    if (Array.isArray(ns)) {
      result = this.#removeDuplicates(ns);
    } else {
      if (typeof ns === "string") {
        result = [ns];
      } else {
        result = [];
      }
    }
    return result;
  }

  #removeDuplicates(arr) {
    arr = Array.isArray(arr) ? arr : [];
    return Array.from(new Set(arr));
  }

  init({ lng, fallbackLng, resources, ns, defaultNS, fallbackNS, initImmediate }) {
    this.store.data = resources;
    this.options = { lng, fallbackLng, ns, defaultNS, fallbackNS, initImmediate };
    this.#currentLanguage = lng;
    this.#defaultAndFallbackNS = this.#removeDuplicates([
      ...this.#toNSArray(defaultNS),
      ...this.#toNSArray(fallbackNS),
    ]);
    return this;
  }

  addResources(lng, ns, resources) {
    if (!this.store.data[lng]?.[ns]) {
      this.store.data[lng][ns] = {};
    }
    Object.keys(resources).forEach((key) => {
      this.store.data[lng][ns][key] = resources[key];
    });
  }

  t(key, args = {}) {
    return this.#TFunction({ lng: this.#currentLanguage, key, args });
  }

  getFixedT(lng, ns) {
    return (key, args = {}) => this.#TFunction({ lng, ns, key, args });
  }

  #TFunction({ lng, ns: specifiedNS, key, args = {} }) {
    const languages = this.#removeDuplicates([lng, this.#currentLanguage, this.options.fallbackLng]);
    const namespaces =
      this.#toNSArray(specifiedNS).length > 0 ? this.#fillDefaultAndFallbackNS(specifiedNS) : this.#getLoadedNS();
    for (const lng of languages) {
      for (const ns of namespaces) {
        const data = i18n.store.data[lng]?.[ns] || {};
        const translated = data[key];
        if (translated) {
          return translated.replace(/\{\{(.*?)}}/g, (matched, p1) => args[p1]);
        }
      }
    }
    return key;
  }
}

const i18n = new I18n();

export default i18n;
// react-i18next.js
import i18n from "i18next";

export function getI18n() {
  return i18n;
}

export function useTranslation(ns) {
  return { t: i18n.getFixedT(i18n.currentLanguage, ns), i18n };
}

export function I18nextProvider(props) {
  return props.children;
}

export function initReactI18next() {}
image.png

秘术:业务代码零改动,卸载i18nextreact-i18next,修改jsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["/*"],
      ...
      "i18next": ["libs/i18next"],
      "react-i18next": ["libs/react-i18next"]
    }
  }
}

问题解决,CPU稳如老狗

image.png

溜了溜了

image.png