Tencent Serverless Hours 线上分享会

  • 1
    关注“腾讯产业互联网学堂”公众号加群互动有好礼相送
  • 2
    回复【加群】
  • 3
    添加小助手微信号回复关键词【SF】
  • 4
    加入交流群
腾讯产业互联网学堂微信公众号
“腾讯产业互联网学堂”微信公众号

讲师简介

黄文俊

腾讯云函数产品负责人

黄文俊 Alfred 腾讯云高级产品经理,腾讯云函数产品负责人。曾经历过企业级存储、企业级容器平台等产品的架构与开发,对容器、微服务、无服务器、DevOps 都有浓厚兴趣。
蔡卫峰 Wes

腾讯云 高级前端开发工程师

蔡卫峰 Wes 腾讯云高级前端开发工程师,腾讯云云函数团队前端负责人。在前端性能优化、大型前端项目架构、全栈开发积累了丰富的经验。现在主要负责 Node.js 在 Serverless 平台的最佳实践以及 Serverless 平台的开发者体验优化。

简介

主题:Tencent Serverless Hours 线上分享会;周期:每两周一次,第一期5月8日;内容:最新发布产品的使用介绍和实战演示

分享大纲

1、如何借助 Layer 实现云函数快速打包、轻松部署

  • 层的功能特性和使用场景
  • 层的工作原理
  • 打包部署过程中遇到的问题分析
  • 使用层解决问题的实战演示

2、揭秘 Node.js 12.x Runtime 的实现原理

  • Node.js 12 新特性预告
  • Runtime 的工作原理
  • 实战演示

讲义

一、如何使用层功能加速函数部署

1.多函数开发常见问题

因为我们在开发云盘的过程中如果一个项目中有多个函数时,经常会碰到一个问题就是函数依赖库或者一些不常变更的静态文件的问题,这些依赖库会有一些什么样的问题呢?由于我们的函数都是一个一个部署的,他们在本地存储一般来说可能就会是一个文件夹,一个文件夹可能就是一个函数,它里面可能就要包括函数的业务代码以及函数的依赖库。那么如果是有多个函数那么可能就存在多个文件夹,而这些多个函数有些依赖库可能就是相同的、是重复使用的,因为有些很常用的一些依赖库大家可能就需要在这个函数的项目文件夹下每个项目下都需要放置一份。那么在本地的话就会有多个的存储,同样的错误、同样的名字、同样版本的库,但是在函数的每一个项目下都要进行存储。

另外就是由于这种放置,同一个库他可能在多个函数的项目下都有放置,那么后续这个库一旦升级了或者增加了一些新的功能或者他的版本号上升、解决一些新的bug,我们要去升级一下这些库,那么我们的这个操作也会变得很麻烦。我们需要依次找到这些库在哪个函数下被使用,然后在这个函数下去完成这个库的一个升级,然后再进行函数的部署,那么我们才算真正的把这个升级后的库用起来。这就是依赖库的第二个问题。

第三个问题就是上传的耗时问题。因为库通常是有一定体积的代码包,在这种情况下每次我稍微改了一下我的业务代码,我的函数在部署时都需要把函数业务代码和依赖库一同上传。那么在上传速度很慢的情况下其实是一个很耗时的操作,依赖库又通常很大,所以是极其耗时的。

2.解决思路

所以在这种情况下我们从产品角度来看的话就是怎么来解决这些问题,解决的思路的话有两块。

一块就是重复库的一个问题。我们可以看到在我这个图中,Func A它里面包含了三个依赖库Lib A、B、C。Func B也包含了三个依赖库,其中有两个是一样的都是Lib A和B,他有一个不一样的Lib D。那么在这种情况下,无论是在本地重复存放多份还是说后续升级的时候我分别要对Lib A和B进行升级都是一个很麻烦的过程。那么在这种情况下我们就可以考虑将这些依赖库合并起来,针对这两个函数它实际上总共依赖四个库,但其中有两个是重复的。那我们将它们合并管理之后,我们就可以利用后续会讲到的层的功能来管理好这个依赖。我们只需要存储一份,我们后续使用的话也可以复用这个相应的存储的一份。比如说Lib A和B在这个地方就是复用的。那我们后期在使用的过程中无论是本地的存储还是在云上的复用,我们都可以通过层功能来减少管理的一个麻烦程度。这个就是第一块儿,对于这种重复库的管理。

然后第二块就是对于依赖的分离上传。因为在没有层这个功能之前,我们的函数在上传的时候需要把我们开发自己编写的代码以及相应的依赖库一同打包上传。在这个过程中无论是哪一次更新,无论cod是改了多少,上传的时候都需要把两者合并在一起打包上传。而Lib库的话实际上它其实是一个不经常变动的,因为一般来说在项目开发的初期我们可能就完成了Lib的确定,我们需要使用哪个版本的哪个Lib,包括我们下载都已经在本地下载好。在后续的整体项目的开发过程中,对于Lib的变更实际上是很少的。而在上传部署函数的过程中他又在不断地重复上传。这是一个比较浪费、占用比较多带宽,所以也是一个很不划算的过程。

那么通过使用层这个功能实际上我们就可以把两者拆分来看,函数的话它里面所包含代码我们就可以只去关注我们的业务代码,业务代码就指的是我们自身的有我们的逻辑在里面,我们更新会比较多的这种代码。而对于不长变更的Lib库或者是其他的相关的静态文件,其实我们都可以抽取到Layer里面来进行放置。针对Layer的话,其实他本身也是在使用过程中他可以只需要上传一次,后续在业务代码更新过程中他就不再需要持续的上传,对于函数的部署来说他也可以极大地加速这个过程。这个就是我们解决这个依赖库的这个问题的一个思路,那么从最终的话就是最近上线的层这个功能,就是用来解决依赖库和静态软件功能。

3.层功能介绍

层这个功能的话它本身是这样,这个依赖库、静态文件、文件包它以版本化的形式进行保存,然后在具体使用的时候就和云函数进行一个关联,关联绑定之后云函数在正在运行起来时这个内容就可以直接给到云函数的一个运行环境中,云函数在实际运行的时候他的业务代码就可以直接使用到层里面所存储的一些内容。当然这个绑定关系的话实际上是一个M对N的一个绑定关系,也是可以进行一个比较随意的一个绑定,一个函数它可以有多个层,一个层他也可以被多个函数所绑定。

接下来说函数现在我们已经提供了版本功能,就是我们提供了可编辑的版本,并且我们可以使用发布功能,这个函数的函数代码发布更多的版本。发布了版本的话其实也是进行了一个固化,就是发布后的版本实际上在函数侧它的代码和它的配置都是都是无法修改。那么对应Layer这一块它其实也是会有一个版本化的一个过程,使用的过程中无论是在创建第一个Layer还是说在后续更新Layer过程中,每一次的创建或者更新他都会生成新的版本,而原有的的版本其实也是实际存在还可以继续使用的。

那么函数在和Layer进行绑定的时候,实际上也是选择区间某一个Layer的某一个Version进行绑定的。在这个使用过程中其实这个绑定关系也是作为函数的一个配置,在函数后续发版本的时候也会固化下来。就像我在这个PPT上图中所演示的方向,他之前发布的版本一他绑定是这个Layer的过程,当然后续他没有修改,他再次发布了一个版本生成一个版本二,他实际上绑定的可能还是原有的Layer的这个Version1.当然他的Layer可能后续做了一种比如说有些调整他的Layer的库升级,它生成了Version2、Version3。当生成Version3之后它的LATEST Version我想他可能想要使用新的这个库、想使用新的版本,那么它的LATEST Version想做修改,那么他去绑定到我Version3上去。所以绑定关系他本身也是基于版本来进行的。

然后再介绍一下我们最终使用过程中需要注意的点,就是一个函数它本身是可以去绑定多个层的,就像这个右侧他可能绑定了Layer1也可以绑定了Layer2。在多个层的绑定关系情况下它实际上是有一个加载顺序的一个问题,在多个层中中如果他们有相同的同名文件的时候,后加载的会覆盖先加载。这是个什么意思呢?就比如说我在右侧这个图中我们从下往上看,最底层的话可能就操作系统层面的一些文件,然后再之上就可能是Runtime的文件。这些可能就是由SDF本身我们的平台所提供的,而且Layer1、Layer2和cod的话可能就是用户他自身的所创建和编写的代码。Layer1比如说在这个Layer1的目录下他可能有lib.js和lib2.js。Layer2中它可能存在一个同名文件就是lib.js。然后cod本身由于是放在user这个目录下,他可能有一个index.js,最终生成的这个运行环境比如说函数在运营起来之后他可以在他的运行环境中去看到包括他的代码的var/user这么一个文件,当然这个就可能就是组的函数的代码文件。同时它还有lib.js和lib2.js这两个文件,lib.js是从哪里来的呢?它可能就是Layer2里面的lib.js,因为他是后加载的,由于和Layer1是有同名,因此后加载这个文件覆盖了先加载的文件。同时他也注册了lib2.js这个文件,因为这个文件没有同名文件覆盖它,而且他是在Layer1中提供的,所以最终运营起来的这个环境它里面就有相应的lib.js和lib2.js这两个库可以使用。这个的话其实就是由于一个函数他可以绑定多个层,这个地方对于同名文件覆盖需要稍微有些注意,在使用的过程中的话覆盖的这个点在使用过程中他可能会带来一些优势,也可能有时候无意中导致一些问题。所以这个大家在具体使用的时候要注意的一些点。

然后就是我们具体使用的话有哪些使用方式或者说我们平台提供一些便利性,在我们现在的云函数的环境变量中包括python path和node path这两个环境,目前已经添加了opt的目录。为什么是这个目录,是因为我们创建好的Layer最终在这个运行环境中挂载的位置就是挂在opt这个目录之下。同时node path除了有opt之外,它还有一个node_modules这个库,其实也是为了方便大家直接把整个node_modules上传,上传了之后直接就可以在代码中使用。

4.实操分享

二、揭秘Node.js 12.x Runtime实现原理

下午好,我是腾讯云的前端开发工程师,现在主要负责Serverlss这边的前端开发的工作。我今天给大家讲的主题是揭揭秘Node.js 12.x Runtime实现原理。

1.Node.js 12新特性

我们看一下Node.js 12主要推出了哪些新的特性,大家都知道Node.js 底层的运行环境都是V8引擎,其实它的版本升级大部分是依赖于V8引擎的升级,Node.js 12同时从TLS1.2升级到了TLS1.3,提供更好的一个安全性,提供了一个默认对大小的一个动态分配。原来我们必须要通过设置space size来控制一个堆的大小,然后现在它可以自动去进行一些自动动态的分配,不会导致内存的一个爆掉。然后再一个特性是HTP的解析速度提升,他换了一个HTP的解析库然后导致HTP的解析速度有一个很大的提升,同时启动速度大幅提升。再就是一些更好的诊断报告和堆分析工具,然后是更好的一些原生模块的支持。

具体看一下V8引擎的升级带来哪些特性,主要的特性就是说对于async的一些优化,然后解析速度的一些优化以及一些最新的ECMAScript新特性的支持。

这一块的话我们看一下他的HTTP解析速度换成了这个新的模板,然后他的速度是快了不少。

另外就是跟我们的Serverless关系比较大的几个特性,在这里简单提出来讲一下。一个是启动速度的提升,通过V8 code cache的支持,构建时提前为内置库生成代码缓存,提升30%的启动耗时。大家也知道我们的Serverlss Runtime的实现首先你的函数如果没有被启动过的话,在冷启动的过程中他是需要去加载用户的代码以及一些基础库的,而我们的云函数其实是按照那个执行时间来进行计费的或者是进行统计的,所以说它的启动耗时的提升可以为我们节省更多的一些时间。

另外一个是新的实验性的功能诊断报告,可以用来分析崩溃、性能问题、内存泄漏、高cpu占用等问题。这些其实也是我们腾讯云云函数预计准备提供的一些对外的能力,然后这部分就是我们在Node.js 12之前需要自己去用一些第三方工具或者是我们自己去写一些API、一些SDK的方式来实现。但现在的话Node.js 12本身自己就提供了一些这样的能力,对于我们这块提供这些能力的支持的话就变简单了很多,而且后续我们也会考虑把这一块的能力利用上来,可以帮助用户更好地去分析自己的这个应用程序。

第三个就是一个内置heap dump,这个是跟问题定位相关的。其实我们现在低位开发模式我们已经提供了在线调试能力,在线调试的时候其实自己可以去通过浏览器去实现这些能力的,那后面我们也会在我们的浏览器控制台去继承这些能力。

然后再就是一个更好的原生模块的支持,N-API升级到版本4。N-API升级到版本4之后,因为现在我们的执行环境对于用户来说是比较黑核的,所以用户去写一些原生的模块其实不是特别方便。但如果有后面N-API的这个能力越来越强的话,很多时候就完全可以用Node.js做一些模块的支持,可以提供更好的便利性。

2.Runtime工作原理

下面我要讲的是Runtime的工作原理。首先看一下我们相对比较简单的一个云函数入口函数是这样子的一个代码,就导出一个main_handler。然后有三个参数一个是event,一个context,一个callback。event主要是用来传递这个触发事件的数据,他都可以带有相关触发器的一些基本信息。然后context是函数运行时的一个信息,就是包括我们函数运行时的一些基本的配置还有一些我们平台的一些信息的能力,这个后面会具体会让大家看一下都包含了具体哪些内容。然后第三个就是callback,callback是非异步函数中返回执行结果,我们在非异步函数要返回结果的话只能通过callback的方式去返回。

然后我们来看一下event参数的一个详细介绍,使用此参数传递触发事件数据。它包含来自调用程序的信息,调用程序把这个信息作为一个JSON的字符串形式来传递。事件结构因为不同的服务也不同,所以有差别。看一下这是我们一个即时触发的event参数,他是会有一个message,是你在定时触发的时候自己可以在触发器那里去设置携带的一个信息,然后他触发的时间、TriggerName就是你可以定义多个触发器,自己触发就会定义不同的名字。

再下面就是这是我们一个APP的为触发器的event的信息。他就是会把你自己从url上面带带来的参数这些东西拓展到我们的这个云函数里面。

第三个你看一下就是我们一个cos触发器的一个信息,他会把你触发这个cos触发的一些基本信息以及你是因为什么事件触发的,是因为哪个文件更新或者是哪个文件添加触发的,他会把这些具体的信息都带到里面,然后你可以从event里面取到这些信息之后做自己相应的一些处理。

第二个参数是context,函数运行时的信息。该对象包含的信息有第一个就是函数配置,然后第二个月执行身份认证信息,第三个就是环境变量,第四个执行环境基本信息。然后我们会把环境变量放在context里面作为一个信息传递出去。这环境变量一部分是包括了你自己在我们的云函数的环境里面自己配置的一些环境变量,另外一个就是我们系统植入的一些环境变量。

另外一部分就是我们函数的具体信息,包括函数执行的版本、函数的名称以及函数的一个namespace,后面我们还会增加一部分信息就是我们函数当前执行的一些地域信息还有你的appid以及你的UN。这样的话你调用第三方服务的所需要的一些信息我们在context都可以提供给你。

第三个是callback,是一个可选的参数,在非异步函数中返回执行结果。回调函数采用两个参数,一个Error,一个是返回。返回对象必须JSON.stringify能够兼容。异步函数将忽略callback的返回,必须通过return、throw exception或者promise来处理返回或错误。

我们刚刚看的是同步函数的返回,现在看一下异步函数是怎么去返回的。异步函数的返回可以通过return或者throw来发送返回或者错误,函数必须使用async这个关键字。

后面讲的就是我们的一个返回和结束的分离。正常我们自己写一个space或者是cod代码的时候,我们一般情况下用response之后我们会可能还会有一些异步的逻辑继续在执行。然后我们云函数也模拟了这种场景,就我们是可以做到返回之后如果你还有异步在执行的话我们可以让你继续执行,而返回是直接可以先把你的函数值先返回,最后函数的整个时长是按照你的异步执行结束的这个过程来算的,就是完全为了贴合Node.js开发者的开发习惯。

然后我们刚刚说到了异步执行的这种情况,那我们其实还给用户提供了另外一个选项,就是说可以关闭事件循环的等待。就是说你的返回结束之后你可以不等着异步结束,因为有一些第三方库它可能会有一些不是特别清楚的异步逻辑还在处理,那么设置context.callbackWaitsForEmptyEventLoop=false就可以在你返回的时候直接就把当前的执行冻结掉,那个计费时间也只在返回的时候就已经结算结束了。

工作原理另外还有一些其他比较需要注意的一些点,日志:Runtime重启了console的几个主要方法,而且是在require用户文件之后,所以用户自定义选项日志会无效。

第二缓存的服用,因为刚刚也讲到了入口文件里面是不会每次都被重新初始化,所以在入口函数外是可以定义变量,存储可以复用的缓存对象,比如数据库的连接池等。这样就是每次函数调用的时候就不会去重新初始化,可以达到节约时间的一个效果。

第三内置部分的npm包可以直接使用,具体参照我们的一个介绍文档,部署云函数代码时候推荐使用npm install production,减少代码包的体积,提升上传速度和执行速度。因为代码包上传上去之后,每次初始化的时候都需要从服务器推送到我们的这个执行的这个实例里面。所以说你的代码包越小的话对于整个运行速度会有比较大的影响。

第三个就是执行角色。配置执行角色从context中可以获取到临时的密钥信息,可以访问相应的第三方服务。比如说你要执行自定义监控的上报,去上传到cos或者是访问thankyou这些都可以通过。

3.demo演示

三、问答环节

主持人:感谢讲师的精彩讲解,那么我们可以直接把上两讲我们所遇到一些问题或者是不太懂的地方来发到我们会议的聊天室或者是我们腾讯产业互联网学堂的聊天框,我们会及时反馈给讲师为大家去做一些讲解。这块儿刚刚看到有一个用户在提问,就是说当我们的云函数实例执行完一个请求之后挂起,然后没有cpu的一个状态,然后费用长链接可能因未处理心跳检测而被服务端关闭,导致我们的长链接失败。那么针对于这样的情况,麻烦讲师们看一下我们是不是有一些解决方案或者一些措施。

讲师:这个问题就是在那个缓存数据库链接池之后,由于他的挂起导致未处理心跳而被关闭。这个的确有碰到过这种现象,目前的解决方案可能就是说再次在使用这个连接之前还是做一次检测,如果有问题的话再次重连一下。这个可能是临时的一个解决方案,然后长期的远方的话其实我也在想这个有没有什么一个好的解决方案来处理这个问题。

全部评论
讲师/助教

评论

直播日历