欢迎光临散文网 会员登陆 & 注册

Springboot3.0打造能落地的高并发仿12306售票系统吾爱青青草地

2023-03-31 14:57 作者:bili_87640307981  | 我要投稿

Springboot3介绍

Springboot3.0打造能落地的高并发仿12306售票系统

下栽の地止https://lexuecode.com/6719.html


从 2018 年 2 月 28 号发布 Spring Boot 2.0 版本开始,整个 2.X 版本已经经过了 4 年多的时间,累计发布了 95 个不同的版本,而就在前不久,2.X 系列的也已经迎来了他的最终版本:2.7。
前几天我还写了一篇关于 Spring Boot 从 2.1 版本升级到 2.7 的文章,而现在,Spring Boot 3.0 也要来了!
时间就定在本月的 24 号,Spring Boot 将要发布 3.0 的最终 RELEASE 版本。
截止到现在为止,Spring Boot3已经发布了 6 个版本,累计 5 个里程碑版本,2 个 RC 候选版,现在就跟着我一起看下 Spring Boot 3.0 将会要哪些重大的变化。
JAVA 17
对于我们比较关注的第一个最重要的事情就是,Spring Boot3 版本最低支持 Java17,还在万年 Java8 的同学,该升级就升级了吧,这里介绍一下关于 Java17 之后的一些重要改变。
record
record 是在 Java14中引入的,openjdk.org/jeps/395 。
以前我们写一个类需要写一堆 get、set 方法,后来有了lombok之后这些都省了,现在 Java 给我们提供了原生的写法。
public record User() {}
复制代码
本质上 record 修饰之后的类就是一个 final 类,而且他的父类不是 Object,也不是余某军,而是 java.lang.Record。
record 类属性只能声明在头部,所有的成员变量都是 public final 的,而且只能声明静态属性,但是可以声明成员方法和静态方法。
public record User(String username) {
static int id;
public String getName(){
return this.username;
}
}
复制代码
text blocks
text blocks 文本块是在 Java13 引入的,并且在 Java15 版本成为永久特性,openjdk.org/jeps/378。
以前我们复制一个多行的字符串到 Java 里,会自动加上行终止符。
String sql = "SELECT\n" +
"\t* \n" +
"FROM\n" +
"\tsys_user0 \n" +
"WHERE\n" +
"\tuser_name = 'abc'";
复制代码
而有了文本块的功能之后,可以帮助我们更方便的定义包含多行文本的字符串字面量,他使用三引号作为开始和结束的分隔符。
String sqlBlock = """
SELECT
*
FROM
sys_user0
WHERE
user_name = 'abc'
""";
复制代码
switch表达式
switch表达式是在 Java12 中引入的,在 Java14 成为永久特性,openjdk.org/jeps/361。
升级后的 switch 其实包含两个特性,一个是允许 case 使用多个常量,另外一个就是有返回值。
新增case x-> 语法,使用方面更加简洁,而且不需要再每个 case 写一个 break了。
String name = "xiao";
int ret = switch (name) {
case "ai" -> 1;
case "xiao", "xian" -> 2;
default -> 0;
};
复制代码
Pattern Matching 模式匹配
模式匹配可以帮助我们简化instanceof代码。
if (obj instanceof String s) {
System.out.println(s.toLowerCase());
}
复制代码
还可以在 switch-case 语句使用:
static double getDoubleUsingSwitch(Object o) {
return switch (o) {
case Integer i -> i.doubleValue();
case Float f -> f.doubleValue();
case String s -> Double.parseDouble(s);
default -> 0d;
};
}
复制代码
sealed 密封类
sealed 在 Java15中引入,在 Java17成为永久特性。
sealed 密封类的主要作用就是限制类的继承。
比如我们有 Animal类,Dog 和 Cat 分别继承它,实现了 eat 方法,他们吃的动作是不一样的,但是我们不希望人能继承 Animal,不允许他去继承动物吃的行为,就可以像下面这样通过 sealed 和 permits 关键字限制它是一个密封类,只有猫和狗能够继承它。
需要注意,父类被定义为 sealed 之后,子类必须是 sealed、 non-sealed 或者 final。
public abstract sealed class Animal permits Cat, Dog {

public abstract void eat();
}

public non-sealed class Dog extends Animal{
@Override
public void eat() {
System.out.println("dog eat");
}
}

public non-sealed class Cat extends Animal{
@Override
public void eat() {
System.out.println("cat eat");
}
}
复制代码
Jakarta EE 9
另外一个很重要的变化就是本次升级之后,最低只支持 Jakarta EE 9,使用 Servlet5.0 和 JPA3.0 规范,不过最新版本RC2已经升级到了 JakartaEE 10,默认使用 Servlet6.0 和 JPA3.1 规范。
有些同学可能连 Jakarta 是什么都不知道,这个英文单词是印尼首都雅加达的意思,其实就是我们知道的 JavaEE 改名之后就叫 JakartaEE,比如我们之前的javax.servlet包现在就叫jakarta.servlet。
也因此,代码中所有使用到比如 HttpServletRequest 对象的 import 都需要修改。
import javax.servlet.http.HttpServletRequest;
改为
import jakarta.servlet.http.HttpServletRequest;
复制代码
Spring Native
Spring Native 也是升级的一个重大特性,支持使用 GraalVM 将 Spring 的应用程序编译成本地可执行的镜像文件,可以显著提升启动速度、峰值性能以及减少内存使用。
我们传统的应用都是编译成字节码,然后通过 JVM 解释并最终编译成机器码来运行,而 Spring Native 则是通过 AOT 提前编译为机器码,在运行时直接静态编译成可执行文件,不依赖 JVM。
关于 AOT 技术,在我之前写过的文章中有提及到:这样优化Spring Boot,启动速度快到飞起!。
演示
这里我简单演示一下怎么使用,首先我们需要做一些准备工作:

1. www.graalvm.org/downloads/ 下载GraalVM,指定JAVA_HOME,export JAVA_HOME=/Users/user/Desktop/graalvm-ce-java17-22.3.0/Contents/Home
2. 下载个新一点的 IDEA,比如我使用的是最新的 EAP 版本
3. 下载个新一点的 JDK,比如使用 JDK17

然后通过 Spring Initialzr 创建一个新项目,使用最新版本Spring Boot 3.0.0-SNAPSHOT,勾选GraalVM Native Support,创建好项目之后添加一个测试的Controller。
@RestController
public class TestController {

@GetMapping("/")
public String hello(){
return "GraalVM ...";
}
}
复制代码
然后直接运行程序,发现启动时间花费了大概 1 秒。

然后执行命令,生成镜像文件:
./gradlew nativeCompile
复制代码
这个过程挺耗时的,花了大概 2 分多钟才生成好。

最后执行命令:
./build/native/nativeCompile/demo2
复制代码
我们看到,最终启动时间是 0.082 秒,快了 10 多倍。

这里我使用的是 gradle,如果使用 maven 的话,使用如下的命令:
1. mvnw -Pnative native:compile
2. ./target/demo2
复制代码
其他依赖升级
Spring Boot 3 最低依赖 Spring6 版本,因此对应的 Spring 版本也该换了(不会有人还在用 Spring2 的吧),其他的依赖升级如下:

• Kotlin 1.7+
• Lombok 1.18.22+ (JDK17 support支持版本)
• Gradle 7.3+

另外我想说的是,SpringBoot2.7引入了新的自动装配方式META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,原来的写法spring.factories在 3.0 版本以下还能兼容,3.0 新版本之后,老的写法spring.factories不能使用了,中间件相关的开发同学要注意了。
其他一些关于配置的变化,Spring MVC 的一些小变化就不说了,更新日志到时候都可以看到。
最后,如果想升级的话,在新版本发布之后,会有一个基于Spring Boot 2.7 版本的迁移指南




Springboot3.0打高并发仿12306系统 - 大型分布式架构设计

一般网站,刚开始的做法,是三台服务器,一台部署应用,一台部署数据库,一台部署NFS文件系统。

这是前几年比较传统的做法,之前见到一个网站10万多会员,垂直服装设计门户,N多图片。使用了一台服务器部署了应用,数据库以及图片存储。出现了很多性能问题。

如下图:



但是,目前主流的网站架构已经发生了翻天覆地的变化。一般都会采用集群的方式,进行高可用设计。至少是下面这个样子:



使用集群对应用服务器进行冗余,实现高可用;(负载均衡设备可与应用一块部署)
使用数据库主备模式,实现数据备份和高可用;
4、系统容量预估
预估步骤:

注册用户数-日均UV量-每日的PV量-每天的并发量;
峰值预估:平常量的2~3倍;
根据并发量(并发,事务数),存储容量计算系统容量。
根据客户需求:3~5年用户数达到1000万注册用户,可以做每秒并发数预估:

每天的UV为200万(二八原则);
每日每天点击浏览30次;
PV量:200*30=6000万;
集中访问量:24*0.2=4.8小时会有6000万*0.8=4800万(二八原则);
每分并发量:4.8*60=288分钟,每分钟访问4800/288=16.7万(约等于);
每秒并发量:16.7万/60=2780(约等于);
假设:高峰期为平常值的三倍,则每秒的并发数可以达到8340次。
1毫秒=1.3次访问;
没好好学数学后悔了吧?!(不知道以上算是否有错误,呵呵~~)

服务器预估:(以tomcat服务器举例)

按一台web服务器,支持每秒300个并发计算。平常需要10台服务器(约等于);[tomcat默认配置是150],高峰期需要30台服务器;

容量预估:70/90原则

系统CPU一般维持在70%左右的水平,高峰期达到90%的水平,是不浪费资源,并比较稳定的。内存,IO类似。

以上预估仅供参考,因为服务器配置,业务逻辑复杂度等都有影响。在此CPU,硬盘,网络等不再进行评估。

5、网站架构分析
根据以上预估,有几个问题:

需要部署大量的服务器,高峰期计算,可能要部署30台Web服务器。并且这三十台服务器,只有秒杀,活动时才会用到,存在大量的浪费。
所有的应用部署在同一台服务器,应用之间耦合严重。需要进行垂直切分和水平切分。
大量应用存在冗余代码
服务器Session同步耗费大量内存和网络带宽
数据需要频繁访问数据库,数据库访问压力巨大。
大型网站一般需要做以下架构优化(优化是架构设计时,就要考虑的,一般从架构/代码级别解决,调优主要是简单参数的调整,比如JVM调优;如果调优涉及大量代码改造,就不是调优了,属于重构):

业务拆分
应用集群部署(分布式部署,集群部署和负载均衡)
多级缓存
单点登录(分布式Session)
数据库集群(读写分离,分库分表)
服务化
消息队列
其他技术
6、网站架构优化
(1)业务拆分

根据业务属性进行垂直切分,划分为产品子系统,购物子系统,支付子系统,评论子系统,客服子系统,接口子系统(对接如进销存,短信等外部系统)。

根据业务子系统进行等级定义,可分为核心系统和非核心系统。核心系统:产品子系统,购物子系统,支付子系统;非核心:评论子系统,客服子系统,接口子系统。

业务拆分作用:提升为子系统可由专门的团队和部门负责,专业的人做专业的事,解决模块之间耦合以及扩展性问题;每个子系统单独部署,避免集中部署导致一个应用挂了,全部应用不可用的问题。

等级定义作用:用于流量突发时,对关键应用进行保护,实现优雅降级;保护关键应用不受到影响。

拆分后的架构图:



参考部署方案2



如上图每个应用单独部署,核心系统和非核心系统组合部署

(2)应用集群部署(分布式,集群,负载均衡)

分布式部署:将业务拆分后的应用单独部署,应用直接通过RPC进行远程通信;

集群部署:电商网站的高可用要求,每个应用至少部署两台服务器进行集群部署;

负载均衡:是高可用系统必须的,一般应用通过负载均衡实现高可用,分布式服务通过内置的负载均衡实现高可用,关系型数据库通过主备方式实现高可用。

集群部署后架构图:



(3)多级缓存
缓存按照存放的位置一般可分为两类本地缓存和分布式缓存。本案例采用二级缓存的方式,进行缓存的设计。一级缓存为本地缓存,二级缓存为分布式缓存。(还有页面缓存,片段缓存等,那是更细粒度的划分)

一级缓存,缓存数据字典,和常用热点数据等基本不可变/有规则变化的信息,二级缓存缓存需要的所有缓存。当一级缓存过期或不可用时,访问二级缓存的数据。如果二级缓存也没有,则访问数据库。

缓存的比例,一般1:4,即可考虑使用缓存。(理论上是1:2即可)。



根据业务特性可使用以下缓存过期策略:

缓存自动过期;
缓存触发过期;
(4)单点登录(分布式Session)

系统分割为多个子系统,独立部署后,不可避免的会遇到会话管理的问题。一般可采用Session同步,Cookies,分布式Session方式。电商网站一般采用分布式Session实现。

再进一步可以根据分布式Session,建立完善的单点登录或账户管理系统。



流程说明
用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;

用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;

一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;

存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;

结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。

(5)数据库集群(读写分离,分库分表)
大型网站需要存储海量的数据,为达到海量数据存储,高可用,高性能一般采用冗余的方式进行系统设计。一般有两种方式读写分离和分库分表。

读写分离:一般解决读比例远大于写比例的场景,可采用一主一备,一主多备或多主多备方式。

本案例在业务拆分的基础上,结合分库分表和读写分离。如下图:



Springboot3.0打造能落地的高并发仿12306售票系统吾爱青青草地的评论 (共 条)

分享到微博请遵守国家法律