速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

JavaScript 如何应用在 IoT?

  • 2019-07-05
  • 本文字数:3921 字

    阅读完需:约 13 分钟

JavaScript如何应用在IoT?

在日前的 GMTC 全球大前端技术大会上,Rokid 基础平台研发中心的研发工程师刘亚中发表了《JavaScript in IoT》的演讲,本文整理内容如下。

什么是 IoT

如字面翻译,即物联网,但真正要解释起来,其实就是两点:



  • IoT 是面向服务的 UI。



  • IoT 面临资源受限的问题。



在物联网时代,我们不再像从前那样独立地使用某个固定、单一的产品,而是在享受着整个环境或者是网络给我们提供的服务,比如原来我们买一个闹钟回来,闹钟就是闹钟,在 IoT 环境下,闹钟是其中的一环,当它叫醒你后,整个系统会为你准备起床后需要的所有待办事物,这就是物联网。


接下来我们来看看资源受限的问题:



最右侧的手机自不必说,整个生态已经相当成熟了。最左侧的是目前的低端配置,可以看到内存和可用空间都是 MB 为单位的,CPU 也相当受限,因此这种设备上 Linux 也已经不满足运行的最低需求了,所以一般是采用更轻量级的 RTOS,流行的有 FreeRTOS 和 RT-Thread(国内)。


最中间的是以 Linux 为主的 128+128 组合的设备,最为我们所熟知的就是智能音箱(无屏),对于这类设备来说包括 Alexa、Rokid、小爱同学等,现在大多数厂商在设备端的开发语言也都以 C + Lua 为主,目前也仅有 Rokid 支持 JavaScript 直接运行在设备端,而本文也主要是针对智能音箱这一挡设备来展开。


然后我们来看下,为什么在 128 MB 的设备上运行 JavaScript 也如此困难呢?



上图是 Rokid 智能音箱上各个子模块的内存占用情况:



  • kernel 内核占用,用于保证 Linux 用户态程序能正常运行。



  • dsp 包括从麦克风读出语音数据,然后进行算法处理,最终获取是否语音激活。



  • system 包括一些底层系统功能,如 IPC 服务、多媒体服务、存储服务等。



  • applications 即上层应用的逻辑。



通过上图的比例最终可以看出,JavaScript 真正能用的内存只有 25MB,这对于目前一个 Node.js 进程来说也就刚刚好而已,这也是目前大多数音箱,仅在服务器上提供 JavaScript SDK 的原因。

Why JavaScript?

为什么我们要在 IoT 使用 JavaScript 呢?这里简单给出几个理由。首先 Web 已经是一个相当成熟的社区了,它聚集了大量的开发者,这对于任何一个开放平台来说,都是一个非常有吸引力的开发者来源。


另外得益于 JavaScript 或者说 Web 这种即时更新的机制,在解决设备碎片化问题上,有着天然的优势,因此对于 IoT 的碎片化问题,我们希望借助于 Web 来解决。


另外,Web 标准组织已经提出了 WOT —— Web of Things 的概念,因此对于我们来说,要做的就是跟标准组织一起推进 WOT 的落地,这也是一个非常顺理成章的事情。


如果你想参与更多前端技术交流,获取更多专家分享,可以加入我们的“前端技术交流群“,社群内会经常讨论前端相关的技术、分享免费学习资料,我们也会邀请前端专家进行社群分享、直播、公开课等活动。如果你感兴趣,欢迎添加社群管理员微信 GeekUni004,回复“前端群”申请入群。

ShadowNode —— Node.js on IoT

ShadowNode 截止目前为主,已经支持如下特性:



  1. 支持 macOS 与 Linux 的编译和运行。



  2. 支持 x86、arm 与 aarch64。



  3. 支持大部分核心的 Node.js API,如:assert / buffer / N-API Add-ons / child process / crypto / dns / events / file system / http / https / module / net / os / process / timers / TLS / UDP



  4. 支持了 WebSocket / MQTT 等流行的 IoT 库



  5. 支持 CPU 和 Heap 的 Profiler



  6. 支持 N-API



接下来,关于 ShadowNode 的历史大家可以去看之前的专栏文章:



简单来说呢,ShadowNode 就是为了能让 JavaScript 能愉快地跑在 IoT 设备上而存在的,下面是 ShadowNode 与 Node.js 的一些资源占用上的对比:



可以看出无论是 macOS 上,还是 ARM 上,ShadowNode 在内存占用上有明显地提升,还记得我们之前看到智能音箱上各子模块的内存分布吗?对于应用来说可用内存有 25MB,如果使用 ShadowNode 作为运行时的话,基本上就达到了可以随意新增本地应用的状态了。



然后是启动时间,对于设备端上的应用来说,往往为了省内存,会把不重要,或不再使用的应用(进程)杀掉,当有需要时,再重新启动,因此这对进程的启动时间,包括 CPU 占用都提出了不小的要求,可以看出 ShadowNode 在这方面的表现也优于 Node.js。

N-API on ShadowNode


N-API 作为 Node.js Add-on 的 ABI 兼容的接口,可以让任何程序在不需要重新编译的情况下无缝运行在 node-chakracore 与 node-v8 上,ShadowNode 同样如此,我们按照 N-API 的标准文档和测试集,实现了在 ShadowNode 上的 N-API,这样在不需要重新编译的情况下,也能在 Node.js 和 ShadowNode 跨运行时运行,这使得我们可以根据不同的设备配置,选择合适的运行时,而上层的代码则不需要任何修改。


ShadowNode 的 N-API 完全是基于 Node.js 仓库下的 N-API 测试用例测试的,除了一些我们还不支持的特性外,其余的测试用例都会在 ShadowNode CI 上做验证,所以对于稳定性方面大可放心使用。

性能

我们在 IoT 下,针对一些特定场景,也做了一些性能优化。


比如需要使用一些第三方仓库时,仓库本身太过臃肿,导致在设备上加载起来就已经很花时间了(ShadowNode 没有 JIT),甚至于大多数情况是加载不进来的,因为这些库会在堆里创建大量的对象,而 ShadowNode 有预置的 heap_maximum_size,这样难道就没有其他办法了吗?


后面我们想到了一个办法,那就是保持兼容这些第三方库的 JS API,底层全部用 C/C++ 重写,这样可以减少大量的对象创建,同时也能让开发者使用时没有任何差异,我们使用这种方式分别完成了 WebSocket 和 MQTT 在 ShadowNode 上的移植。


另外一个优化手段是引入 NODE_PRIORITIZED_PATH,大家肯定对 NODE_PATH 不陌生吧,那 NODE_PRIORITIZED_PATH 理解起来也不困难,就是一个最高优先级的 NODE_PATH。因为在 IoT 设备上的情况与服务端往往不同,Node.js 模块都是放在每个项目中的,然而在 IoT 设备上的每个应用都比较轻,因此大部分依赖库都是放在全局的 node_modules,这样就导致每次 require 时,总会从模块的当前路径去搜索,这样造成了大量的浪费。


因此我们引入了 NODE_PRIORITIZED_PATH 来设置为全局路径,这样帮我们节省了 30%的启动时间。


接下来是 Copy-on-Write 技术(以下简称 COW),它是 Linux 针对 fork 函数的优化方法,可以节省进程启动时间与内存。这里首先要科普一个知识,即 child_process.fork 并不是真正的 fork,他依然要让子进程从零开始执行,并且抛弃掉父进程的所有资源。


我们来看一下下面的代码:


function uv_spawn (file, args) {  var pid = fork()  if (pid === 0) {    execvp(file, args) *// this disables COW*    *// starting VM and load script*  }}uv_spawn(‘test.js’, [])
复制代码


在 Node.js 中,无论是 spawn、exec 还是 fork,都调用了同一个 uv_spawn 函数,以上是该函数的伪代码(JavaScript 版本)。可以看到每次 fork 完,都会在子进程调用 execvp 来重新初始化子进程,一旦使用了 execvp 这个函数,就意外着系统将把 COW 禁用了,我们再来看看不调用 execvp 的情况下,如何写代码:


var fork = require(‘linux-sys’).fork
*// load common modules for children*var player = require(‘player’)var http = require(‘http’)var foobar = require(‘foobar’)
*// start forking*var pid = fork()if (pid == 0) { *// here is the child process* *// use player / http / foobar*}
复制代码


上面的代码不再是伪代码了,我们通过将系统的 fork 使用 N-API 暴露给 JavaScript 层,然后从 fork 开始到子进程真正能使用 player、http 和 foobar 模块,仅花了 4ms。


当然,上面的示例代码只是为了证明 COW 拥有卓越的性能提升,因此我们后来就创建了一个新项目 —— Hive。


yodaos-project/hive:https://link.zhihu.com/?target=https%3A//github.com/yodaos-project/hive)


github.comhttps://link.zhihu.com/?target=https%3A//github.com/yodaos-project/hive)


Hive 是一个独立于 ShadowNode 的子项目,可以运行在 Node.js 与 ShadowNode 上,因此我们基于 Node.js,对 hive-fork、nodejs-fork、nodejs-spawn 做了一组对比测试:



我们得出的结论显而易见,Hive 在启动速度上几乎是完胜 child_process,这对于设备端的应用启动加速具有很大的意义。


同时在 FaaS 时代,我相信 Hive 也能占有一席之地,FaaS 典型的场景就是快速启动一个进程来执行,然后退出,使用 Hive 可以轻松地定义脚本中的 API,定义好之后,便不需要担心进程启动后所带来的加载消耗,因为几乎是 0 成本的。


YodaOS 从诞生的第一个版本,到现在已经快 1 年时间了。接下来小小预告一下,下半年我们准备发布第二个大版本,即 YodaOS 8.0。在这个版本中,YodaOS 与 Rokid 开放平台完成了解耦,本地的应用也采用了大家熟悉的 Web 应用(不完全兼容)。届时,开发者可以使用 YodaOS 来接入 Alexa、天猫精灵或者 Google Assistant。

总结

很开心能够参加 GMTC 这样的活动,不管是作为讲师还是听众,都收获颇多,我相信 JavaScript 一定会在 IoT 时代释放更多开发者的能量。

嘉宾介绍

刘亚中,开源爱好者,Node.js Collaborator、ShadowNode 作者,目前主攻:Node.js 在 AIoT 领域的应用, 并负责 YodaOS 的社区推广工作。五年 JavaScript 开发经验,曾就职于:SeedMail、Pixbi、阿里巴巴,目前就职于 Rokid 基础平台研发中心,主要工作方向为基于 JavaScript 的物联网操作系统。近年来参与并负责 YodaOS 项目,将 IoT 和 AI 能力开放给 Node.js 社区,并实现了 Node.js 在 IoT 场景的产品级落地。同时也是 Node.js Collaborator、ShadowNode 和 TensorFlow-Node.js 作者,开源发烧友,目前给 60 个开源项目贡献过代码。


2019-07-05 17:595001

评论

发布
暂无评论
发现更多内容

【Web前端】怎样用记事本写一个简单的网页-html

清风莫追

9月月更

计网复习一,计算机网络原理概述

前端小刘不怕牛牛

计算机网络 计算机基础 HTTP 9月月更

线上问题如何复盘

老张

线上故障 问题复盘

云原生(三十四) | Kubernetes篇之平台存储系统实战

Lansonli

云原生 9月月更

远程TS全栈学习+远程全职工作+远程高质量外包=3R教室

pincman

node.js typescript react.js 远程工作 nestjs

上车上车,快速搞懂Redis 过期策略和内存淘汰策略

知识浅谈

redis 过期策略 9月月更

金融网络安全体系建设

阿泽🧸

9月月更 安全体系建设

为什么这么多品牌迫切想要改变Logo?

Jackpop

易观千帆 | 2022年7月宁波市手机银行应用活跃人数榜单

易观分析

手机银行 宁波

开源IM项目OpenIM单聊及万人群压测报告

Geek_1ef48b

中国智能网联汽车信息安全分析2022案例征集

易观分析

汽车 案例征集 智联网

易观之星 | “2022年度用户推荐数字应用”投票通道开启

易观分析

易观

一名中年码农转型成远程工作及远程全栈教学创业者的故事

pincman

node.js typescript react.js 远程工作 nestjs

C++学习------cctype头文件的作用与源码学习

桑榆

c++ 9月月更

Ribbon源码分析之@LoadBalanced与LoadBalancerClient

急需上岸的小谢

9月月更

学 Go,最常用的技能是什么?打日志

梦想橡皮擦

Python 9月月更

二维容器进行图的DFS搜索和BFS搜索-C++STL模板

清风莫追

c++ 算法 9月日更 9月月更

【数独 2】候选数法解数独谜题-挖掘更深的信息-C++实现

清风莫追

9月月更

[教你做小游戏] 只用几行原生JS,写一个函数,播放音效、播放BGM、切换BGM

HullQin

CSS JavaScript html 前端 9月月更

微服务为什么需要混沌工程

穿过生命散发芬芳

混沌工程 9月月更

阿里顶配版 Spring 全家桶高级笔记+300道硬核面试题,跪着啃完了

钟奕礼

Java 编程 程序员 架构 java面试

中小企业集成AI人工智能的窘境

felix

人工智能 中小企业 开放应用模型

2022-09-02:以下go语言代码输出什么?A:9;B:11;C:编译错误;D:不确定

福大大架构师每日一题

golang 福大大 选择题

SpringCloud 配置中心(Nacos)的简单使用

nacos SpringCloud 配置中心 9月月更

Java进阶(三)Java安全通信:HTTPS与SSL应用配置

No Silver Bullet

https SSL证书 9月月更

Java进阶(二)文件读操作

No Silver Bullet

Java 9月月更 文件读操作

什么是 SAP Business Function

汪子熙

SAP abap Netweaver 业务流程驱动 9月月更

高颜值!程序员专属浏览器

Jackpop

leetcode 21. Merge Two Sorted Lists 合并两个有序链表(简单)

okokabcd

LeetCode 数据结构与算法

iview提供的控件tree

zxhtom

9月月更

分享一套自己制作的Nestjs实战教程

pincman

node.js typescript nestjs

JavaScript如何应用在IoT?_语言 & 开发_刘亚中_InfoQ精选文章