0

NodeJs 实践之他说 - 彭加李

 1 year ago
source link: https://www.cnblogs.com/pengjiali/p/17409232.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

NodeJs 实践之他说

作为前端,我们知道 node 在构建方面是成功的,我们也听说过全栈,那么 node 是否能应用在企业级的后端?一起来看一下腾讯视频的 NodeJs 改造

Tip: 故事大概是 2018 年,主角杨浩,来源于:

o_230517083216_node-tshuo-01.png

腾讯视频是一个内容型的网页。

在 2014 年以前使用的是 C++ 动态生成页面。有两个问题:

  • 前端不太会维护 C++ 的那套东西
  • C++ 定时生成网页。有多少个视频,它就会生成多少个网页,然后推送到对应的服务器中。如果更改了某个视频的信息,得等到下次生成网页才会更新

于是打算使用 NodeJs 来对其进行改造。

第一只怪 - 打通 NodeJs

由于腾讯视频是内容型的网页,当时有 30% 的流量来自搜索引擎,所以需要更好的 SEO,于是选用 SSR(服务器渲染)。

Tip: Vue_SSR中也提到服务端渲染的优势:更快的首屏加载、更好的 SEO

NodeJs 扮演的角色如下:

o_230517083225_node-tshuo-02.png

请求经过 cdn,经过 nginx 通过负载均衡访问 NodeJs 服务,NodeJs 从各个后台服务拉取数据,渲染好了在返回给前端。

Tip:相当于以前用 c++ 生成页面,现在由 NodeJs 生成页面。

打通 RPC 调用

rpc(作用类似 http 协议) 就是远端资源调用,因为 node 需要从各个后台服务拉取数据。这里涉及4个方面的事情:

  • 负载均衡。node 和后台服务之间有一层负载均衡,用的是一种类DNS负载均衡,所以得和负载均衡服务交互,拿到每次需要访问服务器的ip
  • Mongo/mysql/redis(redis - 基于键值对的内存数据库) 存储的打通。比较简单,就是对应 npm 包的使用
  • 后台私有协议。例如二进制的协议某场景下比http协议好一些
  • 监控系统/日志系统

Tip: DNS除了能解析域名之外还具有负载均衡的功能

高并发下进程管理

node 是单线程,使用 cluster 模块创建多个 Nodejs 进程,实现高并发和高可用性。但 cluster 还有点缺陷,做了以下几点优化:

  • 心跳 - master 定时给 cluster 发信息,如果有回复说明它还活着,否则就是僵死,就 kill 它
  • 内存检测 - 监控 cluster 内存,如果内存过高,可能就是内存泄漏,也杀死它
  • 重启 - cluster kill 后,有的应用可能不能用,就需要将其重启

Tip:在 Node.js 中,cluster 模块提供了一种简单的方式来创建多个 Node.js 进程,以实现高并发和高可用性。通过集群模块,开发者可以使用现有的单线程程序代码,并将其自动拆分到多个子进程中执行,从而充分利用 CPU 和内存资源,提高应用的效率和稳定性。

第二只怪 - 维护 NodeJs

终于把 Node 打通了,现在可以用 node 写点东西了。

要用 node 写一个稳定的服务,也不是那么简单。node 很容易挂掉,比如一点语法问题。

Node 人员不足

懂前端的人很多,但懂 node 的就相对要少。写后端需要懂后端那套东西,要会服务器调优,还要懂运维。

为了解决 Node 人员不足,决定使用框架来平滑 node 曲线。

之前要用 node 写项目难度大,是因为需要经历这4步:业务逻辑 -> 会写 NodeJs -> 熟悉 rpc 调用 -> 熟悉运维(性能调优)

现在用框架,只需要写业务逻辑就能开干。

这里框架主要使用配置化,屏蔽底层复杂的实现,对外暴露友好的配置。就像 webpack,让前端构建生态非常繁荣。

要做配置化,就得分析 ssr 本质:从各个后台领取数据,简单处理后进行渲染。

ssr抽象表示:请求参数 -> 后端数据 + 模板 -> 页面文本

ssr 公式:内容=f(数据源,模板)

只要将数据源模板配置化,就可以通过一个函数解决 ssr 的问题。

模板引擎的选型

研究了如下几种模板:

  1. art-template 国内有名的开源模板引擎
  2. es6 template string + vm.runInNewContext(编译和运行代码,作用类似 new Function('console.log("1")'))
  3. vue ssr、react ssr

art-template 中的 forEach 可以使用预编译语法来实现,由于交互较少,所以无需使用 vue和react。而且 es6 模板速度测试比 vue-server-render 快很多。

所以最终选取第二种方案:es6 template

数据源的配置用如下一个 json 表示:

module.exports = {
    video: {
        url: "http://...."
    },
    vidviewcount: {
        dependencies: ['video'],
        url: "protobuf://union.video.qq.com/...."
    },
    rank: {
        url: "redis://admin:admin@135246:65535/get?key=haha"
    }
}

这个 json 表示 ssr 过程中数据获取逻辑,其中 vidviewcount 通过 dependencies 字段指明依赖 video。

这里用 http、protobuf、redis三种协议(方式)获取数据。一个协议对应一个请求器,不在框架中的协议可以注册即可。就像这样:

factory.registerRequestor('http', requestor);
function requestor(){
    ...
}

为了增加配置的灵活性,这里增加了几个 hook:

{
    ...
    fixBefore: function(param){
        // 检测参数合法性
        return param
    },
    fixAfter: function(data){
        // 检测返回数据合法性
        if(!data.vid){
            throw Error('xxx')
        }
        return data
    },
    onError: function(e){
        return err;
    }
}

写配置就是写 SSR 逻辑

只要学会写配置就能搞定 ssr 逻辑。

公式:内容=f(数据源,模板)(参数)

ssr 外部用 koa(nodejs 的web框架) 封装一下就是一个服务:

let app = koa()
let ssr = pigfarm(data, template)
app.use(async ctx => {
    ctx.body = await ssr(ctx.query)
})

第三只怪 - 抢后端饭碗的问题

后台有后台擅长的地方(逻辑、计算密集),前端有前端擅长的地方(前端网页优化)。

寻找一个合作共赢的方式。这里做了如下几个有特色的前端服务:

每次业务逻辑的改动需要经历长时间的发布重启

前面已经将数据源模板做到了配置化,现在修改逻辑,只需要更改数据库中的数据源和模板即可,做到热更新。

v.qq.com 首页包含27个模块

  • 富含个性化内容,无法缓存
  • 页面庞大,速度慢
  • 全网页超过40个rpc
  • 个性化接口调用慢

利用 transfer-encoding:chunked 快速返回首屏数据,后面再加载2、3、4...屏的数据

Tip:BigPipe 是一个前端性能优化技术,采用分块渲染的方式。transfer-encoding:chunked 是一种 HTTP协议中定义的传输编码方式之一。运行服务器在不知道响应体大小的情况下,将响应分成若干个固定大小的快进行传输。

前端容灾是指在前端应用中,为了保障可靠性和稳定性而采用的一系列技术和策略,以确保即使在系统出现部分异常或错误的情况下,仍然可以正常提供服务。比如网络问题、服务器故障等

这里可以做整页备份

js 中用高阶函数非常容易实现缓存。请看示例:

function memoize(func) {
  // 用于缓存
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    // 如果缓存中有值,直接返回
    if (cache[key]) {
      return cache[key];
    }
    
    const result = func.apply(this, args);
    cache[key] = result;
    return result;
  };
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK