关于触发器产品的一些思考

不知不觉,从8月份敲下第一行需求描述到现在,已经三个月过去了。这三个月对我来讲很痛苦,对研发和合作团队来讲也很痛苦。这是一项浩大的工程,从后台到控制流再到前端,跨团队合作,几个新瓜蛋子愣是把整个触发器合作流程趟了个遍。真的很不容易。也成功完成了最重要的触发器对接。

好在,大差不差的产品成功上线了,这对整个后续的产品协同和产品接入规划其实有着很深刻的意义。对产品如是,对研发也如是。

其实这些都是题外话了,这次我想分享一些关于触发器产品的思考和总结。也顺带给其他做B端的一些同学警醒和建议。

首先在做一个功能前,需要解决的问题是这个东西有没有用户用。其次才是怎么让用户用的爽。这其实是一个需求的原动力,推合作方如此,推客户更是如此。所以在做需求前,为了不犯错,其实我在私下也会做很多分析&调研。能做是一方面,做出来有价值其实又是另一方面。

上图是艾瑞咨询发的基础云服务行业发展概览图。其实本身云函数其实就是基础云设施,只是它的粒度足够小,足够便捷,也足够即用即走。它可能短时间无法替代主流的虚拟机,数据库,存储,但是它有一个最大的优势 ———— 无孔不入。做触发器其实就是渗入到各个云产品,让各个云产品所产生的事件都能被函数消费,处理。

更准确一点,就是把上图的核心基础服务全部接入函数计算。这带来的价值是不可估量的,这里就以我们已经接入并发布的CLS触发器场景来讲吧,链接在这里。这个其实只是前期调研的冰山一角,我们还探索了很多类似这样的细分场景。它小吗?小,小到根本不值一提,但是它确实解决了用户在日志场景下处理数据的诉求。而且在这些场景下,云函数是不可替代的,其他云服务都干不了这事儿。当然除非另外产品化能力,但与之而来的是高昂的成本。

以这个例子阐述,就可以明白触发器对整个FaaS生态的重要了吧,简单点就是接一个触发器可能会有10个左右的细分场景,接十个就是100个细分场景,在配合函数间调用,或者工作流伴随着用户理解函数计算的概念提升,这里会越来越具价值和战略意义。

那么问题来了,这么多云产品,从哪块入手呢?这个问题其实很简单,使用排除法+简单分析就完全能搞定:

  1. 函数本身提供的就是基础计算能力,所以 VM虚拟机,容器 等一系列计算产品本身就搭不上边
  2. 以艾瑞咨询发布的概览图为例子,我们能搞的只有 对象存储/CDN/LB/数据库?对象存储其实很早就已经有了,CDN 其实主要场景还是刷新,LB也算但对函数来讲不太刚需,数据库本身就有事务的概念所以这里对函数来讲入局的希望不大。

那我们换个思维,除了这些开发者最关注的是啥?那首当其冲的就是日志!日志永远是所有业务最核心而且开发者最常用的。简单梳理了下,便画出了这样的业务流,通过 CLS+SCF 构建整个云上业务的数据流处理枢纽,一个负责数据采集,一个负责数据计算,简直不要太搭:

大方向有了,客户场景也有了,那接下来就是细节设计了。我们想到 AWS Lambda 一定会惊叹于它海量的数据源支持,它其实是Faas乃至整个云服务的祖师爷。过庙留香,何况带我入行的师傅说过一句让我至今记忆犹新的话“如果能把产品策略抄到AWS 90%,其实就能干掉 99% 的做云服务的产品经理”。所以这尊佛还是要拜拜的。

触发器的核心其实就是 event,没有什么能比定一个靠谱的 event 对触发器更重要了,这里调研了三个方案:

祖师爷 AWS:

AWS的做法十分精巧,直接通过gzip压缩+base64来把日志流的日志投递到函数 event,按照条数依次投递到用户函数,具体做法如下:

这个是函数收到的event:

{
  "awslogs": {
    "data": "ewogICAgIm1lc3NhZ2VUeXBlIjogIkRBVEFfTUVTU0FHRSIsCiAgICAib3duZXIiOiAiMTIzNDU2Nzg5MDEyIiwKICAgICJsb2dHcm91cCI6I..."
  }
}

这个是解压+解码后的数据结构:

{
    "messageType": "DATA_MESSAGE",
    "owner": "123456789012",
    "logGroup": "/aws/lambda/echo-nodejs",
    "logStream": "2019/03/13/[$LATEST]94fa867e5374431291a7fc14e2f56ae7",
    "subscriptionFilters": [
        "LambdaStream_cloudwatchlogs-node"
    ],
    "logEvents": [
        {
            "id": "34622316099697884706540976068822859012661220141643892546",
            "timestamp": 1552518348220,
            "message": "REPORT RequestId: 6234bffe-149a-b642-81ff-2e8e376d8aff\tDuration: 46.84 ms\tBilled Duration: 100 ms \tMemory Size: 192 MB\tMax Memory Used: 72 MB\t\n"
        }
    ]
}

这样做大大提升了传输效率并有效降低了用户使用成本。但是,AWS 这里只支持了日志流的日志,意味着EC2其实不能采集到Lambda的,这点完全不可取。

国内老大 阿里云:

阿里云其实做法很讨巧,知道日志服务都为数据量极大的场景,所以他们只传了数据游标,大概是这样的:

{
    "parameter": {},
    "source": {
        "endpoint": "http://cn-shanghai-intranet.log.aliyuncs.com",
        "projectName": "log-com",
        "logstoreName": "log-en",
        "shardId": 0,
        "beginCursor": "MTUyOTQ4MDIwOTY1NTk3ODQ2Mw==",
        "endCursor": "MTUyOTQ4MDIwOTY1NTk3ODQ2NA=="
    },
    "jobName": "1f7043ced683de1a4e3d8d70b5a412843d817a39",
    "taskId": "c2691505-38da-4d1b-998a-f1d4bb8c9994",
    "cursorTime": 1529486425
}

这个做法其实是牺牲了用户体验,并且变相延长了函数的运行事件,如果游标设置过大会导致函数一直超时。这个方案其实很LOW,最起码不符合触发器的定义。按照阿里的做法用户需要使用特定的SDK来获取游标信息,代码如下:

# -*- coding: utf-8 -*-
import logging
import json
from aliyun.log import LogClient
from time import time

def logClient(endpoint, creds):
  logger = logging.getLogger()
  logger.info('creds info')
  logger.info(creds.access_key_id)
  logger.info(creds.access_key_secret)
  logger.info(creds.security_token)
  accessKeyId = 'your accessKeyId'
  accessKey = 'your accessKeyId scr'
  client = LogClient(endpoint, accessKeyId, accessKey)
  return client

def handler(event, context):
  logger = logging.getLogger()
  logger.info('start deal SLS data')
  logger.info(event.decode().encode())
  info_arr = json.loads(event.decode())
  fetchdata(info_arr['source'],context)
  return 'hello world'

年轻人,讨巧可以,但是这么搞用户不会投诉吗?把自己的触发器成本完全转嫁到用户头上。

华为云:

讲真,我真的不想说。抄都不认真抄,当真是垃圾云啊。

{
    "lts": {
        "data": "ewogICAgICAgICJsb2dzIjpbewogICAgICAgICAgICAgICAgIm1lc3NhZ2UiOiIyMDE4LTA2LTI2LzE4OjQwOjUzIFtJTkZdIFtjb25maWcuZ286NzJdIFN1Y2Nlc3NmdWxseSBsb2FkZWQgZ2VuZXJhbCBjb25maWd1cmF0aW9uIGZpbGVcXHJcXG4iLAogICAgICAgICAgICAgICAgInRpbWUiOjE1MzAwMDk2NTMwNTksCiAgICAgICAgICAgICAgICAiaG9zdF9uYW1lIjoiZWNzLXRlc3RhZ2VudC5ub3ZhbG9jYWwiLAogICAgICAgICAgICAgICAgImlwIjoiMTkyLjE2OC4xLjk4IiwKICAgICAgICAgICAgICAgICJwYXRoIjoidXNyL2xvY2FsL3RlbGVzY29wZS9sb2cvY29tbW9uLmxvZyIsCiAgICAgICAgICAgICAgICAibG9nX3VpZCI6IjY2M2Q2OTMwLTc5MmQtMTFlOC04YjA4LTI4NmVkNDg4Y2U3MCIsCiAgICAgICAgICAgICAgICAibGluZV9ubyI6NjE1CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtZXNzYWdlIjoiMjAxOC0wNi0yNi8xODo0MDo1MyBbV1JOXSBbY29uZmlnLmdvOjgyXSBUaGUgcHJvamVjdElkIG9yIGluc3RhbmNlSWQgb2YgY29uZmlnLmpzb24gaXMgbm90IGNvbnNpc3RlbnQgd2l0aCBtZXRhZGF0YSwgdXNlIG1ldGFkYXRhLlxcbiIsCiAgICAgICAgICAgICAgICAidGltZSI6MTUzMDAwOTY1MzA1OSwKICAgICAgICAgICAgICAgICJob3N0X25hbWUiOiJlY3MtdGVzdGFnZW50Lm5vdmFsb2NhbCIsCiAgICAgICAgICAgICAgICAiaXAiOiIxOTIuMTY4LjEuOTgiLAogICAgICAgICAgICAgICAgInBhdGgiOiIvdXNyL2xvY2FsL3RlbGVzY29wZS9sb2cvY29tbW9uLmxvZyIsCiAgICAgICAgICAgICAgICAibG9nX3VpZCI6IjY2M2Q2OTMwLTc5MmQtMTFlOC04YjA5LTI4NmVkNDg4Y2U3MCIsCiAgICAgICAgICAgICAgICAibGluZV9ubyI6NjE2CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtZXNzYWdlIjoiIEluIGNvbmYuanNvbiwgcHJvamVjdElkIGlzIFtdLCBpbnN0YW5jZUlkIGlzIFtdLiBNZXRhRGF0YSBpcyB7NDU0MzI5M2EtNWIyYy00NGM0LWI3YTAtZGUyMThmN2YyZmE2IDYyODBlMTcwYmQ5MzRmNjBhNGQ4NTFjZjVjYTA1MTI5ICB9XFxyXFxuIiwKICAgICAgICAgICAgICAgICJ0aW1lIjoxNTMwMDA5NjUzMDU5LAogICAgICAgICAgICAgICAgImhvc3RfbmFtZSI6ImVjcy10ZXN0YWdlbnQubm92YWxvY2FsIiwKICAgICAgICAgICAgICAgICJpcCI6IjE5Mi4xNjguMS45OCIsCiAgICAgICAgICAgICAgICAicGF0aCI6Ii91c3IvbG9jYWwvdGVsZXNjb3BlL2xvZy9jb21tb24ubG9nIiwKICAgICAgICAgICAgICAgICJsb2dfdWlkIjoiNjYzZDY5MzAtNzkyZC0xMWU4LThiMGEtMjg2ZWQ0ODhjZTcwIiwKICAgICAgICAgICAgICAgICJsaW5lX25vIjo2MTcKICAgICAgICAgICAgfQogICAgICAgICAgICBdLAogICAgICAgICJvd25lciI6ICI2MjgwZTE3MGJkOTM0ZjYwYTRkODUxY2Y1Y2EwNTEyOSIsCiAgICAgICAgImxvZ19ncm91cF9pZCI6ICI5N2E5ZDI4NC00NDQ4LTExZTgtOGZhNC0yODZlZDQ4OGNlNzAiLAogICAgICAgICJsb2dfdG9waWNfaWQiOiAiMWE5Njc1YTctNzg0ZC0xMWU4LTlmNzAtMjg2ZWQ0ODhjZTcwIgogICAgICAgIH0="
    }
}

华为云直接把格式做了一层Base64就投递给函数了!!!!

why? 产品经理该被炒了把,这层Base64有啥意义? AWS做Base64是因为人家做了gzip解压啊,用句老话真的是脱裤子放屁多此一举。不仅增加了用户成本,而且对整个触发器没有任何收益,就是单单抄了个AWS皮毛,怕不是在逗我。这种产品搞出来是开玩笑的吧。

我不相邪,专门查了他们的demo,如下:

import json
import base64
import sys
import os
import requests

from com.obs.client.obs_client import ObsClient
from com.obs.models.put_object_header import PutObjectHeader
from com.obs.models.get_object_header import GetObjectHeader
from com.obs.models.get_object_request import GetObjectRequest
from com.obs.models.server_side_encryption import SseKmsHeader, SseCHeader
from com.obs.log.Log import *

current_file_path = os.path.dirname(os.path.realpath(__file__))
# append current path to search paths, so that we can import some third party libraries.
sys.path.append(current_file_path)
.......
path_style = Truedef handler(event, context):
 # Obtains the data of lts logs.
 print ("*********the data of lts logs********")
 encodingData = event["lts"]["data"]
 data = base64.b64decode(encodingData) # Base64 decoding is required because the information of lts logs has been encoded.
 text = json.loads(data)
.......

嗯嗯,他们确实只做了base64 !!!! 好吧,从此华为云就从竞品分析的名单消失了。

 

一通分析下来,我发现需要一个更严谨的针对腾讯云的Event方案,如下:

首先,AWS 的 Gzip+Base64是可取的,但是又不可完全抄。因为AWS的 cloud watch logs 只支持了一些流式单条数据,但是现有的CLS其实是可以批量采集 Nginx 或 Tomcat 这种传统日志的。所以,单条触发函数一定不可取。那么这里只能借鉴其他触发器了增加 batch size 或者 batch window,和研发同学对齐后发现其实 batch window 会更符合聚合场景也更容易实现。所以 CLS 触发器大致的处理流程是这样的:

为保证单次触发传递数据的效率,数据字段的值是 Base64 编码的 ZIP 文档。

{
  "clslogs": {
    "data": "ewogICAgIm1lc3NhZ2VUeXBlIjogIkRBVEFfTUVTU0FHRSIsCiAgICAib3duZXIiOiAiMTIzNDU2Nzg5MDEyIiwKICAgICJsb2dHcm91cCI6I..."
  }
}

用户可在创建触发器时选择聚合事件,触发器组件会自动按照超时时间打包数据,并一次投递多条消息到云函数,当然这个过程是异步的。在解码和解压缩后,日志数据类似以下 JSON 体,以 CLS Logs 消息数据(已解码)为例:

{
    "topic_id": "xxxx-xx-xx-xx-yyyyyyyy",
    "topic_name": "testname",
    "records": [{
        "timestamp": "1605578090000000",
        "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    }, {
        "timestamp": "1605578090000000",
        "content": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    }]
}

这个,是我能想到业内最完美的 CLS 触发器解决方案了,中和了AWS,阿里云的优势,支持Nginx,CDN等多种产品的日志直接聚合投递。用户体验也非常爽,基本只需要三行代码:

const zlib = require('zlib');

exports.main_handler = async (event, context) => {
    const payload = Buffer.from(event.clslogs.data, 'base64');
    const parsed = JSON.parse(zlib.gunzipSync(payload).toString('utf8'));
    console.log('Decoded payload:', JSON.stringify(parsed));
    return `Successfully `;
};

这个体验基本可以甩阿里云,华为云20多条街。所以还是那句话,跑得快不是成功,不栽跟头想清楚然后在投入做才是。

 

哈哈,如上就是一些关于做这个触发器的一些思考,当然后续还有更多。会在得空的时候分享一些心得。同时也给自己备忘。

见解浅薄,如有疏漏还望见谅。最后打个广告:CLB 触发器请看这里!  使用实践请看这里!

 

切记,切记,切记。做B端产品千万别搞出笑话,华为云真的是。。。。。一言难尽。。。。。