目录
背景
名词解释
整体业务流程
技术实现
调用链路
云开发( Serverless )
云开发的限制
编写云函数
怎么拿到授权查询轻店铺用户订单
轻店铺云网关
数字签名网关
网关技术选型
Vert.x介绍
同步和异步
阻塞和非阻塞
I/O 多路复用
Netty
网关实现
反应式和非阻塞编程
总结
为哔哩哔哩天猫旗舰店添加类似泡泡玛特盲盒抽奖的淘宝小程序,也叫淘宝轻店铺。
由于淘宝不允许直接在轻店铺中发起对第三方服务的请求,只能通过云函数转发,详见:淘宝轻店铺官方说明
轻店铺客户端调用哔哩哔哩服务要经过的链路:轻店铺云函数->轻店铺云服务->奇门→SLB→ECS→OpenApi→哔哩哔哩后端服务。轻店铺云服务必须使用轻店铺奇门,奇门必须使用阿里SLB,阿里SLB只支持阿里ECS。所以我们的方案必须使用到阿里云配套方案。
轻店铺:淘宝APP小程序。轻店铺控制台: https://miniapp.open.taobao.com/#/
盲盒:盒子上没有标注,只有打开才会知道自己抽到了什么。
抽盒机:进行抽奖、发货、订单展示的应用。
奇门:淘宝小程序网关,轻店铺请求bilibili服务先通过奇门,https://open.taobao.com/doc.htm?docId=106847&docType=1 (对于非标准化的场景对接,提供网关基础能力,支持服务商做自己的服务开放). 奇门控制台:https://qimen.taobao.com
SLB:阿里的负载均衡。 轻店铺对应的奇门接口必须使用SLB,SLB只支持阿里云(ECS) 。轻店铺只支持内网SLB,配置阿里SLB的时候要选择免费的私网实例。云控制台:https://console.cloud.tmall.com/#/index
ECS:云主机,部署自定义服务,关联到SLB。 云控制台:https://console.cloud.tmall.com/#/index
RDS: 阿里云数据库,阿里云增值服务,里面聚合了哔哩哔哩淘宝店铺的所有订单。
云函数:轻店铺客户端通过云函数调用哔哩哔哩提供的第三方服务。 云函数控制台: https://console-snipcode.taobao.com/miniapp/index
Serverless 云开发:小程序前后端服务。
云服务: Serverless云函数服务端。
轻店铺共有两种订单:抽奖次数订单、邮费订单。
由于淘宝不允许上架虚拟货币,抽奖次数通过抽盒次数订单购买转换,抽奖之后,根据抽盒次数订单地址,购买运费订单实现发货。
如图是抽盒机若干页面,通过购买抽奖次数订单转换为抽盒机会,消费抽盒机会实现盲盒抽奖,抽奖看到商品,选择商品后发货。淘宝抽奖次数订单转换为抽盒机抽奖次数采用了前端异步刷新,后端Job轮询方式。前端把抽奖用户Id传递给后端,后端根据用户Id实时拉取用户订单,转换为对应的次数,同时后台Job轮询订单实现兜底。
整体业务序列图
如下是购买抽奖订单转换为抽奖次数的一个请求链路,可以看到很长。
小程序基于淘宝小程序SDK,后台基于云Serverless。
云开发(Serverless) 提供以下三种功能:
数据存储服务是基于 MongoDB 托管在云端的数据库,数据以 JSON 格式存储。数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表)。
文件存储服务支持文本、图片和其他由用户生成的内容存储到云端。开发者可以在小程序端和控制台使用云存储功能。图片上传成功后,系统会自动生成一个资源链接。开发者可以在小程序中使用该图片地址。
云函数服务支持使用Node.js进行开发。作为开发者,您可以可将代码提交到云端运行,在客户端使用小程序提供的 API 进行调用。您还可以在云函数中直接通过 API 调用数据存储和文件存储的服务资源。
云开发(Serverless) 具有以下优势:
无运维开发者只需专注业务开发,无需理解后端服务的运维配置。
低成本按实际使用的资源和调用情况计费,有效降低了运维成本和研发成本。
高可用底层能力由阿里云存储、数据库团队提供支持,支持弹性扩容,同时提供可用性保障。
一云多端适配多种平台的小程序端框架,一套代码多端使用。
详情参考官方文档:https://miniapp.open.taobao.com/docV3.htm?docId=118541&docType=1&source=search
每个云函数有最大并发限制。现有业务远达不到云函数最大并发度,但以防万一,目前按业务划分创建了多个云函数+云应用。并且咨询了淘宝技术人员,给出的答复是并发的瓶颈只会在自己购买的阿里云主机上面,云函数会弹性扩容。
如下按业务对云函数进行划分。
下载小程序IDE,安装开发环境,基于Node.js
使用淘宝开发账号关联IDE,基于官方示例编写自定义云函数服务端,然后上传到云函数容器。
上传后基于云函数Client端实现调用服务端
客户端调用自定义云服务例子,获取发货券列表:
import cloud from '@tbmp/mp-cloud-sdk'; // 导入云函数sdk
cloud.init({
env: 'test' // 设置测试环境
});
try {
console.log('begin----------------------------- ');
cloud.function.invoke(‘test’, { // 输入json
"serviceName": "mall.test.coupon",
"content": {
"openId": "AAxxxxxxxxxxxxxx",
"pageNum": 1,
"pageSize": 10
}
}, 'main')
.then(res => {
console.log(res); // 返回结果
})
} catch (e) {
console.log(e);
}
调用成功返回数据
除了自定义云函数,也有一些官方云函数可以在客户端直接调用轻店铺云网
客户端调用官方云服务例子,获取用户授权:
my.authorize({
scopes: ‘scope.userInfo’, // 淘宝官方云服务名
success: (res) => {
my.setClipboard({
text: res.accessToken // 获取授权token
});
my.alert({
content: res.accessToken
})
},
});
};
由于后台通过淘宝增值接口查询轻店铺用户订单需要获取Token授权。这个问题研究了很久,最终根据淘宝文档解决。
参考:https://miniapp.open.taobao.com/doc.htm?spm=a219a.7386797.0.0.3af3669aWi4rBz&source=search&docId=118179&docType=1
用商家端身份登录小程序IDE,使用如下代码获取商家端授权Token。
由于Token一个月过期,还可以生成二维码,使用商家端千牛App扫描二维码授权、续期。
my.authorize({
scopes: ‘scope.userInfo’, // 淘宝官方云服务名
success: (res) => {
my.setClipboard({
text: res.accessToken // 获取授权token
});
my.alert({
content: res.accessToken
})
},
});
};
商家应用(淘宝小程序)到聚石塔容器服务(指后台阿里云主机)的请求需要通过平台的云网关进行转发,调用链路入下所示
首先到云网关控制台配置一下生产环境阿里云服务器,与测试环境服务器
轻店铺奇门云网关要关联阿里云内网SLB。
轻店铺云网关除配置生产阿里云SLB外还可以关联自己的测试环境。
云网关要用到阿里云SLB,我们需要给自己购买的阿里云主机加上阿里云SLB负载网关。
打开阿里云聚石塔控制台,添加SLB。由于轻店铺云网关只支持阿里云SLB内网形式,注意SLB不要配置成公网。
配置了轻店铺云网关、阿里云SLB后,请求就可以从小程序转发到自己购买的阿里云服务器了。我们要对轻店铺的请求做数字签名验证,加签名,首先需要在阿里云上面自定义实现一套网关验证签名、加签名。
阿里云主机配置:两台双核心、4G内存云主机,通过SLB实行负载平衡。
自定义网关基于vert.x实现。
Vert.x项目起始于2011年,是一个仿照Node.js以Java开发的,基于EventLoop的异步非阻塞编程框架。
Vert.x的底层IO基于 Netty4 实现,核心模块 Vertx Core,支持非阻塞 文件IO,TCP,UDP,DNS,HTTP,HTTPS,HTTP2(包括h2和h2c)。比较基础的模块 Vert.x-Web ,提供了包括 URL 路由,模板引擎在内的开发web server所需的API,Vert.x Web Client,提供一个非阻塞Http Client实现。
对于JDBC,MongoDB,Redis,Kafaka,Java Mail,Apache Shiro等都提供了封装集成,还提供了服务发现,断路器,配置中心等微服务所需要的设施,以及基于Hazelcast/Apache Ignite/Zookeeper 的Cluster功能。
接着普及一下异步,非阻塞,I/O多路复用、Netty的概念。
同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。同步、异步的讨论对象是被调用者——内核。
阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。阻塞、非阻塞的讨论对象是调用者——用户线程。
I/O 多路复用,I/O 就是指的我们网络 I/O,多路指多个 TCP 连接(或多个 Channel),复用指复用一个或少量线程。串起来理解就是很多个网络 I/O 复用一个或少量的线程来处理这些连接。
I/O 多路复用的实现有多种方式:select、poll、epoll。
java.nio.channels.Selector 是Java的非阻塞I/O实现的关键。 它使用了 事件通知API以确定在一组非阻塞套接字中有哪些已经就绪能够进行I/O相关的操作。 因为 可以在任何的时间检查任意的读操作或者写操作的完成状态, 所以如图所示, 一个单 一 的线程便可以处理多个并发的连接。
详见: https://blog.csdn.net/chinabhlt/article/details/51983622
Jdk在不同操作系统各有不同的实现,如下Windows、linux的Selector实现,poll0、epollWait均为native方法。
Netty基于Selector对象实现I/O多路复用,通过 Selector, 一个线程可以监听多个连接的Channel事件, 当向一个Selector中注册Channel 后,Selector 内部的机制就可以自动不断地查询(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel。详见: https://juejin.im/post/5bea1d2e51882523d3163657
在Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理,但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。Netty框架将网络编程逻辑与业务逻辑处理分离开来, 其内部会自动处理好网络与异 步处理逻辑, 让我们专心写自己的业务处理逻辑。 同时, Netty的异步非阻塞能力与 CompletableFuture结合可以让我们轻松实现网络请求的异步调用。 Dubbo、 RocketMq、 Zuul 2.0服务网 关、 Spring WebFlux底层网络通信等都是基于Netty来实现的。
Vert.x基于Netty封装了线程模型,简化了业务处理。Event Loop 线程,会不断地轮询获取事件,并将获取到的事件分发到对应的事件处理器中进行处理。
Vert.x简化了Netty的使用,让我们更加关注业务。
如下是网关路由核心代码,仅仅几行代码就实现了异步处理请求,配置线程池,与基于请求路径的路由。
用了异步框架不一定提高性能。
反应式和非阻塞编程通常不会使应用程序运行得更快, 虽然在某些情况下它们可以 (例如使用WebClient并行执行远程调用) 做到更快。 相反以非阻塞的方式来执行, 需要 做更多的额外工作, 并且可能会增加处理所需的时间。
反应式和非阻塞的关键好处是能够使用少量固定数量的线程和更少的内存实现系统可伸缩性。 这使得应用程序在负载下更具弹性, 因为它们以更可预测的方式扩展。 但是为了 得到这些好处, 需要付出一些代价(比如不可预测的网络IO) 。
当连接数 < 1000,并发程度不高或者局域网环境下 NIO 并没有显著的性能优势。如果放到线上环境,网络情况在有时候并不稳定的情况下,这种基于 I/O 复用技术的 NIO 的优势就是传统 BIO 不可同比的了。
可以看一下Vert.x、Spring性能简单对比,如下在硬件、网络资源充足情况下,Vert.x并没有性能优势
Jvm 启动参数:-Xmx1024M -Xmn1024M
Spring 参数,起始线程池600:
server.tomcat.max-connections=20000
server.tomcat.max-threads=10000
server.tomcat.min-spare-threads=600
server.tomcat.accept-count=10000
server.port=8080
Vert.x 参数:
限定工作线程600,按CPU核数分配实例。
基于Gatling性能测试框架,先充分预热,然后模拟500用户并发,持续时间30秒。上为spring ,下位vert.x。占用jvm内存、cpu几乎无区别。
平均响应时间几乎无区别
上为Spring,下位vert.x
以上是基于淘宝小程序从前端到后端的实践。实战过程中也发现一些问题。
由于调用链路过长,偶尔出现超时问题,这个需要通过Job轮询进行补偿兜底。
阿里云控制台可以配置健康检查策略、报警监控,这个对服务可用性尤为重要。
在开发实施过程中尤其要钻研阿里官方文档,文档模棱两可之处还需要提工单等客服回答。
本地调用大量的淘宝增值接口产生了不小的费用,可以把增值接口的调用放到阿里云主机里面,本地再调用阿里云主机,因为阿里云里面调用增值接口费用比较低。