近两年来,Serverless 无疑是前端圈里最火热的话题之一,在各种技术峰会、各种技术文章里都能看到它的身影。如果你是一名前端开发者,一定很奇怪:
“我们这些前端切图仔,为什么要关注 Serverless 这种云计算的概念?”
我们就从这个话题开始聊起吧。
为什么前端开发者要关注 Serverless ?
这个问题用一句话回答就是:
“Serverless 解放了端开发者(不仅仅是 Web 开发者)的生产力,让端开发者可以更快、更好、更灵活地开发各种端上应用,不需要投入太多精力关注于后端服务的实现。”
下面我们就来慢慢解释这句话。
在前端的史前时代,那个时候甚至还没有”前端工程师“这个职位分类,所有人都是后台开发,所有的网页和 Web 应用都是来自于后台渲染,CGI、PHP (甚至你可能都没听说过的 Perl)便是当时 Web 技术的代名词,那个时候的 JS 和 CSS,不过是“切图仔”在网页里写动效和弹窗的小玩具而已。
后来,AJAX 技术出现了,最早在 Outlook Web Access 中出现,随后随着 Google Map、Gmail 等大型 Web 应用的实践,逐步为人所知。这项技术让页面内的 JS 也能异步地向服务器发起各种请求,并且把数据渲染到页面上。这个时候,前端工程师们开始接管视图层逻辑:
再后来,Node.js 诞生,大大降低了前端开发者开发一个后台服务的难度,这也让前端开发者逐渐接管了接管了渲染逻辑,在这期间,各大前端框架(代表性的 React、Vue)的服务器端渲染也逐步成熟。
既然能用 Node.js 来做服务端渲染,那么拿 Node.js 来写后台业务逻辑、实现各种 HTTP API 当然也不在话下,所以在一些公司里,前端接管业务逻辑,后台只负责各种底层微服务的架构就出现了,这也是目前很多大公司在实行的架构:
细心的你可能已经注意到了,在这个时候,渲染、HTTP API、后台业务逻辑这些东西,从端的角度看是属于服务端的,但是从分工角度看却属于前端开发的范畴,这就是 BFF(Backend for Frontend)的概念,它的优势在于:
- 把核心业务逻辑完全交给前端工程师,让业务逻辑可以更加灵活快速地变更;
- 让后端工程师可以更加关注于海量服务的稳定性和可靠性,提升服务质量。
这就是为什么大公司的很多业务,都开始越来越多地招 Node.js 全栈工程师的原因。
但是 BFF 并不是银弹,它还是有一定问题的,比如在国内通晓前后端开发的全栈工程师太少,很难撑起大体量的业务开发。而且国内大多数前端工程师缺少服务端开发的经验,让他们运维上百台服务器和一整套海量服务,有点强人所难:
- 每个服务器实例应该有多少核?多少内存?
- 怎么优化 Linux 网络栈的各种参数?
- 服务器宕机怎么办?双活、多活、同城、异地怎么搞?
而 Serverless 正是帮助前端工程师解决运维开发 BFF 的良药。
Serverless 这个单词,直译成中文的话,应该是“无服务器”。当然,这里的“无服务器”绝对不是说不需要任何服务器资源了,而是说不需要关心服务器的具体运维和管理,只需要写代码然后发布即可。
它包含了 FaaS(函数即服务)和 BaaS(后端即服务)两大块功能,把各种基础设施进行了抽象,即使不熟悉后端的开发者,也能快速高质量地开发出海量、稳定的后端服务,而这对于前端工程师维护 BFF 服务来说,几乎是量身定做的。
举一个最简单的例子,你现在需要上线一个新的 HTTP API(比如 /getUser?id=123
),如果使用 Serverless 服务的话,有多快呢?
你只需要写下面这个云函数:
module.exports = async function(events, context) {
const { id } = events.query.id
const userInfo = await fetchUserInfo(id) // 调用后端微服务,拉取用户信息
return userInfo
}
然后发布这个云函数(假设命名为 getUser
),并且为它设置一条路由:
cloudbase service:create -f getUser -p /getUser
然后你就可以通过 https://xxx.com/getUser
来拉取数据了,同时还附赠海量弹性伸缩、异地多活、日志监控等多方位的能力。
如果你觉得这样很酷炫,那么不如来试一试 云开发 Cloudbase 吧!
云开发是什么?
云开发(Cloudbase)是腾讯云 TCB 团队(Tencent Cloudbase)出品的云端一体化产品方案,为广大的小程序、Web、移动端开发者提供一站式的 Serverless 服务。
当然这是官方的说法,用人话讲就是:
用了云开发,各个端的开发者就可以一站式地解决后端服务问题了!
实际上早在 2018 年,云开发就联合微信团队推出了「小程序·云开发」,如果你对它还不怎么了解,可以看这两篇文章:
<a href="https://mp.weixin.qq.com/s/3F4p9GB2eFRhkOmnLna-Mw" rel="noopener" target="_blank">《为小程序搭把手,小程序「云开发」能力上线》</a>
<a href="https://mp.weixin.qq.com/s/6kpCKH8VvfkP_6s9xMGTBQ" rel="noopener" target="_blank">《玩转订阅消息,小程序·云开发为开发者准备了这些内容》</a>
而现在,云开发 For Web 也正式上线了!
云开发有哪些能力?
云开发提供了云数据库、云函数、云存储、用户登录体系等一系列的后端能力,并且提供了各端的 SDK,让各个端的开发者能够基于这些能力,快速、优质地开发出功能丰富的应用。
Talk is cheap, Show me the code!
口说无凭,我们还是来直接看代码吧!
云数据库
云开发提供了一个文档型的 NoSQL 数据库,与传统的云上数据库不同的是,云开发的数据库__可以在各种客户端内使用 SDK 直接进行读写,比如 Web 应用、小程序内、Flutter 客户端等等__。
下面我们以 Web 应用为例,展示云数据库的一系列功能。
基础读写
CURD是数据库最基础的功能,云开发 SDK 提供了一套链式调用接口,对数据库进行读写:
// 使用 Web 端 SDK
const cloudbase = require('tcb-js-sdk')
const app = cloudbase.init({ /* 初始化... */ })
const db = app.database()
// 插入文档
await db.collection('messages').add({
author: 'stark',
message: 'Hello World!'
})
// 查询文档
const data = await db.collection('messages').where({
author: 'stark'
}).get()
// 更新文档
await db.collection('messages').where({
author: 'stark'
}).update({
message: 'Hi, Cloudbase!'
})
// 删除文档
await db.collection('messages').where({
author: 'stark'
}).remove()
聚合搜索
普通的查询可能无法满足一些复杂的需求,比如联表、group等等。
云开发针对这些复杂的查询场景,推出了聚合搜索的功能,把一系列操作抽象为一个管道(pipeline),单次执行即可,避免了多次读的性能问题,我们以 group 操作为例:
const $ = db.command.aggregate
const result = await db
.collection('message')
.aggregate()
.group({
// 以 author 字段作为 key,统计相同 author 的数量
_id: '$author'
messagesCount: $.sum(1)
})
.end()
//=> { "_id": "stark", messagesCount: 12 }
更多的聚合搜索功能,可以参考:Aggregate | 云开发 Cloudbase
事务
在订票、预约、转账等等场景下,开发者可能会要求数据库能够保证一连串读写的原子性,避免出现竞争条件,这就是数据库事务的使用场景。
云开发数据库当然也提供了事务功能:
// 启动事务
const transaction = await db.startTransaction()
// 在事务内读
const data = await transaction.collection('messages')
.where({ /* <query> */})
.get()
// 在事务内写
await transaction.collection('messages')
.where({ /* <query> */})
.update({ /* <data> */})
// 提交事务
await transaction.commit()
实时推送
在实时聊天室、实时数据看板等等场景下,我们经常会需要__订阅数据库的更新__,从而实现__实时数据推送__。
云开发的数据库提供的 watch()
方法就是为此设计的,每当数据库符合条件的文档发生变化时,都会触发 onChange
回调,示例代码如下:
await db.collection('messages')
.where({/* <query> */})
.watch({
onChange: snapshot => {
console.log("收到snapshot!", snapshot)
},
onError: error => {
console.log("收到error!", error)
}
})
更多信息可以参考:数据库实时推送 | 云开发 Cloudbase
云函数
所谓的云函数,便是在云端运行的、事件驱动的一段代码,它可以被 SDK 调用,也可以直接通过 HTTP 调用,还可以设置定时器让它定期运行:
// sum.js
module.exports = async function(events) {
return events.a + events.b
}
这一小段代码很简单,但是隐藏在它之下的却是一整套庞大的 FaaS(函数即服务)基础设施,提供了诸如弹性伸缩、日志、监控告警等多方面的能力。
使用调用云函数
使用云开发的客户端 SDK,可以轻而易举地在各个端上调用云函数,我们以 Web 应用为例:
const cloudbase = require("tcb-js-sdk");
const app = cloudbase.init({/* 初始化 */});
app.callFunction({
// 云函数名称
name: "sum",
// 传给云函数的参数
data: {
a: 1,
b: 2
}
})
.then(res => {
console.log(res); // 输出 "3"
})
.catch(error);
HTTP 访问
你也许会觉得 SDK 体积庞大,太沉重了,那么你也可以选择使用 HTTP 来调用云函数,响应 HTTP 请求。
// hello.js
module.exports = function() {
return 'Hello, World!'
}
然后我们直接通过命令行发布这个函数,并为它创建一条路由:
$ cloudbase functions:deploy hello
$ cloudbase service:create -f hello -p /hello
随后便可以通过 url 直接访问这个云函数:
$ curl https://xxx.service.tcloudbase.com/hello
Hello, World!
具体可以参考:https://docs.cloudbase.net/service/quick-start.html
在云函数内部使用服务端 SDK
在 Cloudbase 的云函数内,你可以直接使用 Node.js SDK,无需在初始化的时候额外注入秘钥:
const cloudbase = require('@cloudbase/node-sdk')
// 无需使用服务端秘钥
const app = cloudbase.init()
const data = await app.database().where().get()
更详细的内容可以参考:https://docs.cloudbase.net/api-reference/server/node/initialization.html
云存储
我们在开发应用的过程中,经常会遇到图片、文件上传的需求,并且可能需要为这些文件设置 CDN 访问。传统的流程是下面这样的:
- 前端应用调用上传接口;
- 后台接收文件,把文件放置到文件存储服务内(例如 腾讯云 COS);
- 如果需要 CDN 加速文件访问,需要为 CDN 设置回源路径到后端 COS 上。
但如果使用云开发,只需要在客户端调用 uploadFile
,就可以一步完成上面的三件事情:
const tcb = require("tcb-js-sdk");
const app = tcb.init({
env: 'your-env-id'
})
const { fileID } = await app.uploadFile({
// 云端路径
cloudPath: "/a/b/c/filename",
// 需要上传的文件,File 类型
filePath: document.getElementById('file').files[0]
})
uploadFile
会返回一个 fileID
,是云开发内文件的唯一标识符,我们可以使用 getTempFileURL
来获取文件 URL 访问链接:
const tcb = require("tcb-js-sdk");
const app = tcb.init({
env: 'your-env-id'
})
const { fileList } = app.getTempFileURL({
fileList: [
'cloud://a/b/c'
]
})
// fileList 是一个有如下结构的对象数组
// [{
// fileID: 'cloud://a/b/c', // 文件 ID
// tempFileURL: 'http://xxx/xxx/xxx', // 临时文件网络链接
// maxAge: 120 * 60 * 1000, // 有效期
// }]
更详细的内容,可以参考:https://docs.cloudbase.net/storage/introduce.html
扩展能力
云开发除了上述的基础功能之外,还提供了一系列的扩展能力,包括但不仅限于:
更详细的内容,可以参考:https://docs.cloudbase.net/extension/introduce.html
总结
上面的能力是不是有些让你看花眼了,完全不知道要怎么搭配起来使用?
其实一张图就可以解决:
图中的客户端SDK包括:
服务端 SDK 包括:
- Node.js
- PHP
- Golang
使用云开发快速搭建实时聊天室
光看示例代码当然没有什么意思,我们接下来就拿云开发的一些能力,来快速开发一个实时在线聊天室吧。
项目代码:https://github.com/TencentCloudBase/cloudbase-realtime-demo
这是一个由 create-react-app
快速生成的脚手架项目,所以大部分构建和工程化的细节这里就略过不谈了,我们直接来看代码实现,大致上实现了三个功能,括号中是使用的云开发能力:
首先是我们的初始化流程,先使用匿名登录,然后建立实时数据推送的连接:
async function init() {
// 使用匿名登录
await auth.anonymousAuthProvider().signIn();
// 使用 refreshToken 的前 6 位作为 uid
setUid(auth.hasLoginState().credential.refreshToken.slice(0, 6));
// 建立实时数据推送连接
await db
.collection("messages")
.where({})
.watch({
onChange(snapshot) {
setList(snapshot.docs);
setLoading(false);
},
onError(err) {
console.log(err);
},
});
}
建立实时连接之后,集合中的任何变化,都会触发 onChange()
回调,然后我们使用 setList()
来更新界面上的消息数据。
当然只读消息是不够的,我们还需要发送消息,具体实现非常简单,可以看 sendMessage()
方法,直接使用 add()
方法向数据库写入数据就可以了:
// 发送消息
async function sendMessage() {
const message = {
timestamp: new Date().getTime(),
text,
uid,
};
await db.collection("test").add(message);
// 清空输入栏
setText("");
}
当然以上只是局部的代码片段,整体代码可以参考:
https://github.com/TencentCloudBase/cloudbase-realtime-demo/blob/master/src/App.js
开发完毕之后,我们便可以使用 云开发静态网站 来托管我们的这个聊天室 Web 应用。
首先我们构建我们的应用:
$ npm run build
构建产物会生成到 build
目录下。
然后我们发布到静态托管即可(托管前,请先开通静态网站):
$ cloudbase hosting:deploy ./build -e your-env-id
发布完成后,你便可以通过 https://xxxx.tcb.qcloud.la/xxxx
的来访问你的应用了。进一步,你还可以为它绑定自定义域名。
PS:实际上,云开发的主页和官方文档,就是这样托管的(毕竟做云服务的,最重要的就是 Eating your own dog food 嘛)。
当然,除了这个实战 Demo 以外,还可以看看一些真正业务的实践:
展望
给 Web 开发者带来效率和质量上的提升,帮助他们开发更多更优质的应用,免去运维、后台开发的烦恼,是云开发不变的愿景。虽然我们目前已经有了很多强大又方便使用的能力,但这远不是我们的终点(实际上我们才刚刚起步),在未来我们将会持续完善下面的能力,进一步完善云开发的体系:
- 更多的支持平台
- 更完整的用户登录鉴权体系
- 对 Vue、React 等主流框架的深度集成
- 对 Vue、React SSR 的一站式支持
- 更好的开发者工具、CLI、脚手架
- 更加强大的日志、监控告警能力
PS:如果你觉得这篇文章对你有帮助,不妨花几分钟时间,来试一试快速开始吧。