1.云函数概述
云函数(Serverless Cloud Function,简称SCF)是一种无服务器(Serverless)计算服务,它允许开发者在不管理服务器的情况下运行代码。云函数通常由事件触发,如HTTP请求、oss上传、消息队列或定时任务。这种服务模型提供了一种简单、灵活且成本效益高的方式来处理各种计算任务。
以下是云函数的一些关键特点:
-
无需管理服务器:云服务提供商负责服务器的维护、扩展和可用性。
-
按需自动扩展:云函数可以根据请求的数量自动扩展,无需手动干预。
-
无状态:是指云函数在执行时不保留任何状态信息。每次调用云函数时,它都是从一个干净的状态开始执行,而不依赖于任何之前的执行或外部状态。
-
事件驱动:云函数可以响应各种事件,如HTTP请求、数据库更改、文件上传等。
-
快速部署:开发者可以快速部署代码,通常只需要几行配置。
-
成本效益:云函数通常按实际使用量计费,只在函数执行时产生费用。
-
多种语言支持:大多数云服务提供商支持多种编程语言,如Node.js、Python、Java、Go、C#等。
-
集成开发工具:云函数通常与IDE、CI/CD工具和其他开发工具集成,方便开发和部署。
-
安全性:云服务提供商提供安全措施,如身份验证、授权和网络隔离。
-
可组合性:云函数可以与其他云服务(如数据库、存储、消息队列等)轻松集成,构建复杂的应用程序。
腾讯云、阿里云、AWS Lambda、Google Cloud Functions 和 Azure Functions 等云服务提供商都提供了云函数服务。每个提供商可能有特定的服务名称和特性,但基本概念和使用方式相似。
这里最重要的两个概念:
Serverless:无服务器不是表示没有服务器,而表示在使用 Serverless 时,无需关心底层资源,也无需登录服务器和优化服务器,只需关注最核心的代码片段,即可跳过复杂的、繁琐的基本工作。
无状态:每次调用云函数都是独立的,函数的执行不依赖于任何外部状态或上下文。这意味着每次调用都是从零开始,函数内部需要处理所有必要的逻辑。
这两个特征使得SCF 拥有近乎无限的扩容能力,空闲时,不运行任何资源。代码运行无状态,可以轻易实现快速迭代、极速部署。
如何与外部资源交互?
无状态并不意味着云函数不能与外部系统交互或存储数据。云函数可以调用数据库、缓存、消息队列和其他服务来存储和检索数据。关键是这些交互是临时的,每次函数调用都是独立的,不会在函数实例之间保留状态。
云函数每次调用是独立的,但是云函数处理的对象本身是可以无状态的。打个比方,部署在云函数的一个游戏接口,功能是扣减道具,每次调用虽然是独立的,但操纵的玩家背包是有状态的。云函数设计的时候需要自行处理内部状态管理,例如可以使用分布式锁或者消息队列。
2.腾讯云函数
2.1.新用户免费试用
本文以腾讯云函数作为演示对象。前往腾讯云 注册帐号,新人可以白嫖试用3个月,足够学习了。
从控制台进入云函数界面, 这里有很多功能比较强大的社区模板,新人当然是从helloword开始了。
官方支持Python,Go,Java,Node,Php几种语言,其中解释型语言Python,Node,Php这三种是支持在线IDE编辑,而Go,Java这两种编译型语言只运行本地编译后打包上传。
下图为Node的在线编辑器(Cloud Studio与VSCode高度类似)。
2.2.不同语言的内存占用
分别以这几种语言,跑下helloworld,观察下每种语言的内存占用。
可以看到,java占用45M,Node13.5M,其余都是8M以内。
java本身JRE体积是比较大的(使用jdk9+可以通过模块化减少体积),另一方面,java主流采用JIT(即时编译),只有热点代码才会被编译成本地代码(可以简单理解为,运行越久,代码跑得越快)。因此对于云函数这种要求启动快,执行时间短来说,java这种“慢热”语言天生不适宜。
3.使用层管理公共库
3.1 创建云函数
本地创建一个云函数,使用到第三方依赖,例如sl4f。
执行mvn package,将第三方依赖
3.1.层的创建与绑定
当多个云函数拥有较多的公共依赖库或公共代码文件,可以使用 SCF 中的层进行管理。使用层管理,您可以将依赖放在层中而不是部署包中,可确保部署包保持较小的体积。
在云函数操作界面,可以看到“层”管理,新建一个层,上传本地依赖zip文件(上一步生成的vendor.zip文件)。
需要注意的是,这里的运行环境需要包含云函数所对应的运行环境。否则,在云函数界面无法进行绑定。
已绑定层的函数被触发运行,启动并发实例时,将会解压加载函数的运行代码至 /var/user/ 目录下,同时会将层内容解压加载至 /opt 目录下。
从官方文档可以看出,java云函数会把层的解压目录(/opt)加入到classpath。这意味着,云函数jar包无须手动设置MANIFEST.MF文件里面的classpath路径。(使用mave打包的话,不用设置以下参数)
编辑云函数代码,如下:
package example;
import com.qcloud.scf.runtime.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Hello {
public String mainHandler(Params params, Context context) {
Logger logger = LoggerFactory.getLogger("");
logger.error("-----------------");
System.out.println("Hello world!");
String content = "requestId=" + context.getRequestId();
return String.format("context = %s, params = %s.", content, params);
}
}
在云函数操作界面,绑定 层
3.2.踩到腾讯云函数层的bug
点击“测试”按钮,报错,无法找到class
初步怀疑,层的依赖没有解压到/opt目录下,修改代码,把/opt目录的文件输出来
package example;
import com.qcloud.scf.runtime.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Objects;
public class Hello {
public String mainHandler(Params params, Context context) {
File file = new File("/opt");
if (file.exists()) {
System.out.println("directory exist");
if (file.isDirectory()) {
for (File f : Objects.requireNonNull(file.listFiles())) {
System.out.println("jar name: "+f.getName() +", path: " + f.getAbsolutePath());
}
} else {
System.out.println("only one file");
}
} else {
System.out.println("directory NOT exist");
}
Logger logger = LoggerFactory.getLogger("");
logger.error("-----------------");
System.out.println("Hello world!");
String content = "requestId=" + context.getRequestId();
return String.format("context = %s, params = %s.", content, params);
}
}
重新上传,部署,测试,输出如下:(可以清楚看到层的依赖确实被解压出来了)
问题仍然存在,百思不得其解,无头苍蝇乱试了很久,一次偶然的操作,我把sl4f.jar不打包成zip上传,居然成功了!
破防!!这意味着,如果需要使用层的功能,有多少个第三方依赖,就会创建多少个层。每一个层是一个未经压缩的jar文件。无奈之下,求助人工客服。
客服委托技术找了半小时之后,说问题需要转交,需要再半小时。然而,半小时后(此时15:30),客服说,五点之前给答复。看来,这个问题以前没人发现啊,一不小时就被我踩到了。16:30客户来信,说是符合预期的,就是需要将依赖跟代码打到同一个jar包云。简直无语~~
客服可能觉得确实有点敷衍,说需要进一步核实,18:00前答复。
感觉这个操作很基础啊,使用层管理公共依赖,减少云函数jar包大小。唯一的解释是,使用java来开发云函数的人真得很少,这种低级bug都没人反馈。
后来又延迟到20:00。估计当初的技术已经被优化了,新来的实习生还在看文档。
当晚11点,技术人员发来消息,笔者隔天才回复(技术加班加点修bug~~)
看来这个问题短期无法解决,需要等技术评估后升级。如果使用java,要么把代码和依赖打成一个胖包(忍受每次上传一个大文件),要么每一个依赖都打包成一个层。希望后续腾讯云能把这个功能修复,方便我们这些javaer。