全部产品
云市场

单实例多并发

更新时间:2019-11-21 19:40:15

函数计算 1.0 的计量模式,是按请求计量 : 系统统计每个请求的执行时长,并将所有请求的时长累加起来,计算执行时长费用。这种计量模式对一些偏 IO 的函数不太经济,例如一个典型的访问数据库的函数 :

  1. exports.handler = (event, context, callback) {
  2. // time = 00:00:00
  3. db.getData(key, (result) => {
  4. // time = 00:00:10
  5. callback(null, result);
  6. });
  7. };

假设访问数据库的延时需要 10 秒钟,那么当并发的 3 个请求被处理后,总的执行时长是 30 秒。

函数计算 2.0 改进了计量模式,变成 按实例占用时长计费 。针对上面的函数,更经济的方式是让这 3 个请求在同一个实例内并发处理,这样只需要占用实例 10 秒钟的时长。函数计算单实例多并发的功能,就是为了解决此类问题而设计的。它允许用户为函数设置一个实例并发度 (InstanceConcurrency) ,表示单个函数实例可以同时处理多少个请求。下面示意图可以看出单并发和多并发的区别 :

instance-1

支持单实例多并发的好处有 :

  1. 节省执行时长费用 : 例如一些偏 IO 的函数可以在一个实例内并发处理,减少实例数从而减少总的执行时长
  2. 请求之间共享状态 : 例如多个请求可以在一个实例内共用数据库连接池,从而减少和数据库之间的连接数
  3. 降低冷启动概率 : 由于多个请求可以在一个实例内处理,创建新实例的次数会变少,冷启动概率也就降低了
  4. 减少 VPC IP 占用 : 在相同负载下,单实例多并发可以降低总的实例数,从而减少 VPC IP 的占用

默认情况下,函数的实例并发度为 1 ,也就是一个实例内同时只会处理一个请求。当用户指定了实例并发度 (InstanceConcurrency) 后,函数计算在弹性伸缩时,会尽可能地充分利用满一个实例的并发度后再创建新的实例。需要注意的是,并不是所有的函数都适合开启单实例多并发。下面列举了一些适用和不适用的场景 :

场景 适用性 理由
函数中有较多时间在等待下游服务的响应 适用 等待响应一般不消耗资源,在一个实例内并发处理可以节省费用
函数中有共享状态且不能并发访问 不适用 例如全局变量,多请求并发执行修改共享状态可能会导致错误
单个请求的执行要消耗大量 CPU/ 内存资源 不适用 多请求并发执行会造成资源争抢,可能会导致 OOM 或者延时增加

设置实例并发度

您可以在创建 / 更新函数时,指定函数的实例并发度 (InstanceConcurrency) 。在获取函数信息时,也可以看到函数的实例并发度的值。

如果同时使用了 预留实例 的功能,那么预留实例和按量实例都可以并发处理多个请求。

通过控制台设置实例并发度

如图,在创建函数,或配置函数时,您可以为当前函数设置 实例并发度

set-concurrency

通过 SDK/API 设置实例并发度

以 nodejs SDK 为例,可以通过下面的方法指定函数的实例并发度 :

  1. // create function
  2. var resp = await client.createFunction(serviceName, {
  3. functionName: funcName,
  4. handler: 'counter.handler',
  5. memorySize: 512,
  6. runtime: 'nodejs10',
  7. code: {
  8. zipFile: fs.readFileSync('/tmp/counter.zip', 'base64'),
  9. },
  10. instanceConcurrency: 10,
  11. });
  12. // update function
  13. var resp = await client.updateFunction(serviceName, funcName, {
  14. instanceConcurrency: 20,
  15. });
  16. // get function
  17. var resp = await client.getFunction(serviceName, funcName);

设置单例多并发的影响

设置了单实例多并发 (InstanceConcurrency>1) 之后,在以下几方面与单并发 (InstanceConcurrency=1) 情况下会有所区别 :

计量

多个请求在一个实例并发处理时,以实例的实际占用时间作为计费的执行时长,即从第一个请求开始,到最后一个请求结束期间的时长。

metering

详见 计费方式文档 中的“执行时长费用”。

并发度流控

函数计算有并发调用的限制,默认的值是 100 。即超过 100 个并发请求后,会收到流控错误 (ResourceExhausted) 。当开启了单实例多并发后,并发度流控的不再是限制并发的请求数,而是限制实际的实例数。例如设置了 InstanceConcurrency=10 的时候,最多允许 1000 个并发请求。

日志

  1. 在单并发模式下,在调用函数时指定 HTTP 头 X-Fc-Log-Type: Tail ,函数计算会在响应头 X-Fc-Log-Result 中包含本次调用所产生的函数日志。在多并发模式下,由于多个请求并发执行,无法获取某个特定请求的日志,响应头中不再包含本次调用的函数日志。
  2. 针对 nodejs runtime ,原来的日志方式是使用 console.info() 函数,它会把当前请求的 request id 包含在日志内容中。当多请求在同一个实例并发处理时,“当前请求”可能有很多个,继续使用 console.info() 打印日志会导致 request id 错乱。此时应该使用 context.logger.info() 函数打印日志 :
  1. exports.handler = (event, context, callback) => {
  2. console.info('logger begin');
  3. context.logger.info('ctxlogger begin');
  4. setTimeout(function() {
  5. context.logger.info('ctxlogger end');
  6. console.info('logger end');
  7. callback(null, 'hello world');
  8. }, 3000);
  9. };

当处理并发请求时,上面的函数将打印的日志如下,注意使用 console.info() 打印 “logger end” 日志中的 request id 都变成了 “req2” ,而使用 context.logger.info() 打印的日志则保留了请求的独立 request id:

  1. 2019-11-06T14:23:37.587Z req1 [info] logger begin
  2. 2019-11-06T14:23:37.587Z req1 [info] ctxlogger begin
  3. 2019-11-06T14:23:37.587Z req2 [info] logger begin
  4. 2019-11-06T14:23:37.587Z req2 [info] ctxlogger begin
  5. 2019-11-06T14:23:40.587Z req1 [info] ctxlogger end
  6. 2019-11-06T14:23:40.587Z req2 [info] ctxlogger end
  7. 2019-11-06T14:23:37.587Z req2 [info] logger end
  8. 2019-11-06T14:23:37.587Z req2 [info] logger end

错误处理

多个请求在一个实例并发处理时,由于一个请求处理不当导致进程退出或者崩溃,会导致正在并发处理的其他请求也收到错误。这要求您在编写函数时,尽量将请求级别的异常接住,不影响其他请求。下面是一个 nodejs 示例 :

  1. exports.handler = (event, context, callback) => {
  2. try {
  3. JSON.parse(event);
  4. } catch (ex) {
  5. callback(ex);
  6. }
  7. callback(null, 'hello world');
  8. };

共享变量

多个请求在一个实例并发处理时,同时修改一个共享的变量,可能会导致错误。这要求您在编写函数时,对于非线程安全的变量修改,要进行互斥保护。下面是一个 java 示例 :

  1. public class App implements StreamRequestHandler
  2. {
  3. private static int counter = 0;
  4. @Override
  5. public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
  6. synchronized (this) {
  7. counter = counter + 1;
  8. }
  9. outputStream.write(new String("hello world").getBytes());
  10. }
  11. }

监控指标

设置函数的实例并发度后,在相同的负载下,可以在控制台的函数监控指标中,可以看到函数的实例数有明显地减少。

instance-2

限制项

限制项
支持的 runtime nodejs6/nodejs8/nodejs10, java8, custom
InstanceConcurrency 的取值范围 最小为 1 ,最大为 100
调用响应中的函数日志 (X-Fc-Log-Result) InstanceConcurrency>1 时不支持