秒杀系统上云,从 1342ms 提升到 138ms

4,436 阅读6分钟

本文正在参加「金石计划」

小伙伴们好呀,拖拖拉拉的我,终于把 秒杀项目 部署到云端上去了!

这次过程也比较顺利,搭建下 RabbitMQ 就好了。

helm install rabbitmq bitnami/rabbitmq --namespace prod -f config.yaml

不过我发现,每次都得研究下这个配置文件,真的是头大,而且后面如果要装插件,开启插件这些好像也挺麻烦的,像 Redis 的 布隆过滤器,RabbitMQ 的延迟插件 等等。

而且捣鼓到最后,还得打包成自己的镜像才能好好保存下来🐷

越操作越不耐烦,还是写代码省心🙃

这个算是我部署在云端的第一个有意义的 k8s 应用,有点小激动,狠狠地测试下👇

代码基本没啥改动,简单地调整下这些 host 。

顺手将 redisson 改成了主从,代码中 lettuce 还没修改。

写个 Dockerfile 部署下。

FROM adoptopenjdk:11-jre-hotspot as builder
MAINTAINER Jav4ye
WORKDIR application
ARG JAR_FILE=/target/seckill-demo.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENV TZ="Asia/Shanghai"
ENV JVM_OPTS="-Xmx512m -Xms512m"
ENTRYPOINT ["sh","-c","java ${JVM_OPTS} org.springframework.boot.loader.JarLauncher"]

单机

jmeter 测试

下面是 十次 测试结果数据,可以跳到阅读 小结 内容

第一次 平均响应 是 1711 ms , 50 % 的请求是 1808ms,99% 是 2552 ms ,最小是 708 ms,最大是 2961 ms ,吞吐量是 224.1/s 。

第一次

第二次 平均响应 是 400 ms , 50 % 的请求是 394 ms,99% 是 1002 ms ,最小是 21 ms,最大是 1198 ms ,吞吐量是 503.3 /s 。

第二次

第三次 平均响应 是 337 ms , 50 % 的请求是 311 ms,99% 是 791 ms ,最小是 11 ms,最大是 847 ms ,吞吐量是 557.4 /s 。

第三次

第四次 平均响应 是 97 ms , 50 % 的请求是 85 ms,99% 是 236 ms ,最小是 17 ms,最大是 365 ms ,吞吐量是 745.2 /s 。

第四次

第五次 平均响应 是 124 ms , 50 % 的请求是 109 ms,99% 是 332 ms ,最小是 11 ms,最大是 3033 ms ,吞吐量是 248 /s 。

第五次

第六次 平均响应 是 84 ms , 50 % 的请求是 83 ms,99% 是 156 ms ,最小是 14 ms,最大是 217 ms ,吞吐量是 805 /s 。

第六次

第七次 平均响应 是 224 ms , 50 % 的请求是 224 ms,99% 是 486 ms ,最小是 18 ms,最大是 565 ms ,吞吐量是 657.5 /s 。

第七次

第八次 平均响应 是 92 ms , 50 % 的请求是 81 ms,99% 是 326 ms ,最小是 9 ms,最大是 3024 ms ,吞吐量是 250.5 /s 。

第八次

第九次 平均响应 是 80 ms , 50 % 的请求是 72 ms,99% 是 173 ms ,最小是 14 ms,最大是 396 ms ,吞吐量是 789.9 /s 。

第九次

第十次 平均响应 是 72 ms , 50 % 的请求是 63 ms,99% 是 138 ms ,最小是 12 ms,最大是 347 ms ,吞吐量是 792.4 /s 。

第十次

小结

第一次这么认真的测试,但是这种测试还不严谨,看网上说要 命令行的方式去运行 jmeter 测试,但是我还是偷个懒,这样和上文测试出来的结果也好有个比较。

在上文 《写个简易版秒杀系统练练手》 中,有下面这份报告 👇

当然,上文的这个报告是 取最好 的那一次,也就是 预热 JVM 后的结果。

这次,测试了 十次 ,也是 500 并发。

可以看到 第一次 请求的数据效果非常差!99% 的请求要 2552 ms,这可能就是没 预热JVM 的情况。

第二次,第三次,效果也很差,但是到 第四次 开始,效果就好起来了 99% 的请求在 350 ms以下。

最好的一次,是第十次, 99% 的请求在 138 ms

对比上文的结果,从 1342ms 提升到 138ms ,硬生生提升了 10倍 的效率,这要是放在生产环境下,那不得把牛吹坏了😂

次数平均响应50%99%minmax吞吐量
11711 ms1808 ms2552 ms708 ms2961 ms224.1/s
2400 ms394 ms1002 ms21 ms1198 ms503.3 /s
3337 ms311 ms791 ms11 ms847 ms557.4 /s
497 ms85 ms236 ms17 ms365 ms745.2 /s
5124 ms109 ms332 ms11 ms3033 ms248 /s
684 ms83 ms156 ms14 ms217 ms805 /s
7224 ms224 ms486 ms18 ms565 ms657.5 /s
892 ms81 ms326 ms9 ms3024 ms250.5 /s
980 ms72 ms173 ms14 ms396 ms789.9 /s
1072 ms63 ms138 ms12 ms347 ms792.4 /s

当然,从表中还可以发现,这 网络抖动 还有点大,不知道怎的,这个 max 突然就卡到 3024 ms 去,严重降低了这个 吞吐量

上文也提到过,最大的问题应该是 网络开销,毕竟还是用了 MQ 异步下单内存标记,**Redis 预扣库存 ** 等手段去优化。

现在都部署到 k8s 上,RabbitMQ,Redis,MySQL 都在上面,就不会有这么大的网路开销了。

接下来怎么优化呢?

目前只有零星的思路,比如吞吐量的话,我可能会尝试下这个 reactive 的方式,或者将 Springboot 内嵌的 Tomcat 换成 jettyundertow 试试。

代码的话,试试将 lettuce 也更换成主从模式的看看,再看看 API 有哪些可以优化的。

最主要的,还是这个 JVM ,下次用 VisualVM 连上去看看 JVM 在这期间的变化,看看 CPU,内存,等的变化。

集群(3个)

虽然一直吐槽部署变得麻烦,但是这个点一点就扩容,一下子就变成集群真的太赞了!

所以我在上文提到直接用 分布式锁 就好了,毕竟很难忍住不试下。

一下子变成多个消费者,但是好在用了这个分布式锁,避免了 重复消费订单 的问题。

这也很符合我理想中的代码,业务代码就归业务代码好了,微服务那一套能分离的就尽量分离出来,现在靠这个 k8s 就能实现 负载均衡 了,确实好方便,当然,不方便的就落到 k8s 运维人员身上去了,比如负载均衡策略的调整啥的。

第一次 平均响应 是 1705 ms , 50 % 的请求是 1795 ms,99% 是 3838 ms ,最小是 16 ms,最大是 4155ms ,吞吐量是 157.1 /s 。

第一次

第二次 平均响应 是 587 ms , 50 % 的请求是 529 ms,99% 是 2162 ms,最小是 20 ms,最大是 2265 ms ,吞吐量是 388.7 /s 。

第二次

这我就有点纳闷了,怎么还这么慢的!

按理说,我这集群是来提高吞吐量的啊!难道是因为我这是 假的集群(都在 minikube 节点上)

结果查看容器日志时,发现居然有错😱

把日志下载到本地发现,原来是这个 ID 重复了……

之前偷个懒,直接用 hutool 工具生成了,现在变成集群也不好修改呀。

那好吧,先暂时作罢,看来还得把这个 分布式ID 生成器搭建下。

那咱们下文见,下文就来看看单机下 JVM 的变化先吧,over!