SpringBoot
SpringBoot核心组件
starter
官方提供的starter是这样的:spring-boot-starter-xxx
非官方的starter是这样的:xxx-spring-boot-starter
autoconfigure
CLI
actuator
SpringBoot集成
SpringBoot集成SpringMVC
SpringBoot集成mybatis
SpringBoot集成Tomcat
Spring Boot 能支持的最大并发量主要看其对Tomcat的设置,可以在配置文件中对其进行更改。
1 | # 最大工作线程数,默认200。 |
针对4C8G配置,可以参考建议值:
1 | server: |
- 线程数的经验值为:1核2G内存,线程数经验值200;4核8G内存, 线程数经验值800。
4核8G内存单进程调度线程数800-1000,超过这个并发数之后,将会花费巨大的时间在CPU调度上 - acceptCount
accept队列的长度;当accept队列中连接的个数达到acceptCount时,队列满,进来的请求一律被拒绝。默认值是100。 - maxThreads
请求处理线程的最大数量。
默认值是200(Tomcat7和8都是的)。如果该Connector绑定了Executor,这个值会被忽略,因为该Connector将使用绑定的Executor,而不是内置的线程池来执行任务。
规定的是Tomcat线程池最多能起的线程数目,并不是实际running的CPU数量。实际上,maxThreads的大小比CPU核心数量要大得多。
这是因为,处理请求的线程真正用于计算的时间可能很少,大多数时间可能在阻塞,如等待数据库返回数据、等待硬盘读写数据等。
因此,在某一时刻,只有少数的线程真正的在使用物理CPU,大多数线程都在等待;因此线程数远大于物理核心数才是合理的。
也就是说,Tomcat通过使用比CPU核心数量多得多的线程数,可以使CPU忙碌起来,大大提高CPU的利用率。 - maxConnections
Tomcat在任意时刻接收和处理的最大连接数。
当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。
如果设置为-1,则连接数不受限制。
默认值与Tomcat的连接器使用的协议有关:NIO的默认值是10000,APR/native的默认值是8192,而BIO的默认值为maxThreads(如果配置了Executor,则默认值是Executor的maxThreads)。 - tomcat同时可以处理的连接数目是maxConnections,但服务器中可以同时接收的连接数为maxConnections + acceptCount。
Tomcat的连接器Connector
Tomcat的线程池
SpringBoot中线程池的使用
使用方式
- 基于ExecutorService自定义线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14private Logger logger = LoggerFactory.getLogger(InitBeanConfig.class);
public ExecutorService callbackThreadPool() {
ThreadFactory factory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((t, e) -> logger.error(t.getName() + " excute error:", e))
.setNameFormat("my-pool-%d").build();
int corePoolSize = 3;
int maxPoolSize = 4;
long keepAliveTime = 5;
ExecutorService pool = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MINUTES,
new ArrayBlockingQueue<Runnable>(100000), factory, new ThreadPoolExecutor.CallerRunsPolicy());
return pool;
} - Spring默认线程池applicationTaskExecutor
- 基于ThreadPoolTaskExecutor自定义线程池
ThreadPoolTaskExecutor,最常使用,推荐。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ThreadPoolConfig {
public Executor partFileUploadExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程名称前缀
executor.setThreadNamePrefix("partFileUploadExecutor-");
// 核心线程数
executor.setCorePoolSize(5);
// 阻塞队列长度
executor.setQueueCapacity(Integer.MAX_VALUE);
// 线程池注册优雅停机:当要停止应用时,等待所有线程执行完再停止
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
@Async
如果不指定自定义线程池的名称,@Async注解使用Spring默认的线程池。
查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor。
SimpleAsyncTaskExecutor,不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。
- 无返回值调用
无返回值的异步方法,异常不会传播到调用线程,需要添加额外的配置来处理异常。
通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序 - 有返回值调用
当异步方法返回类型为Future时,Future.get()方法将抛出异常。- Future
- CompletableFuture
线程池监控
实际使用问题
- 任务执行异常丢失问题
- 无返回值的异步方法
无返回值的异步方法,异常不会传播到调用线程,需要添加额外的配置来处理异常。
通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序 - 有返回值的异步方法
当异步方法返回类型为Future时,Future.get()方法将抛出异常。
- 无返回值的异步方法
- 整个服务全局共享一个线程池问题
次要逻辑拖垮主要逻辑。
不同业务逻辑定义各自的线程池。 - 跟ThradLocal配合使用的问题
- 自定义拒绝策略
- RejectedExecutionException
当拒绝策略为AbortPolicy时,如何捕获RejectedExecutionException进行补偿?对execute、sumit方法进行try-catch,处理相应的异常。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// ThreadPoolTaskExecutor#execute
public void execute(Runnable task) {
Executor executor = getThreadPoolExecutor();
try {
executor.execute(task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
// ThreadPoolTaskExecutor#submit
public Future<?> submit(Runnable task) {
ExecutorService executor = getThreadPoolExecutor();
try {
return executor.submit(task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
SpringBoot集成数据库连接池
SpringBoot2.x默认 Hikari
1 | spring: |
- SpringBoot2.x默认连接池Hikari是如何起作用的
- spring-boot-autoconfigure
DataSourceAutoConfiguration自动配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DataSourceAutoConfiguration {
// 先import DataSourceConfiguration.Hikari.class
protected static class PooledDataSourceConfiguration {
}
} - springboot-starter-jdbc
springboot-starter-jdbc默认加载了Hikari
- spring-boot-autoconfigure
- 为什么Hikari会成为默认连接池
SpringBoot集成Redis
1 | <dependency> |
- spring-boot-starter
- spring-data-redis
spring对redis相关操作的人性化封装,使得redis的操作只需简单的调用接口即可。
redis的操作的实现过程则有lettuce或jedis驱动(客户端)实现,spring-data-redis只是在接口层对他们做了统一。 - io.lettuce.lettuce-core
spring-boot-starter-data-redis 2.x选择的默认驱动
Lettuce和Jedis的区别
从 Spring Boot 2.x 开始 Lettuce 已取代 Jedis 成为首选 Redis 的客户端。
- Lettuce
Lettuce是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。
多个线程可以共享一个连接实例,而不必担心多线程并发问题。
它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型 - Jedis
Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的。
修改RedisTemplate的序列化器
RedisTemplate间接实现了InitializingBean,故在Bean的生命周期内会被回调其afterPropertiesSet方法,此方法用来初始化key和value的序列化器,默认是JDK序列化器,但该序列化器需要数据对象实现Serializable接口,最后显示声明序列化ID,容易出错。
可以修改利用json的序列化与反序列化能力。
1 |
|
SpringBoot集成WebSocket
- 添加 starter 依赖
- 添加 WebSocket 配置:
- 实现接口
WebSocketConfigurer
,并重写相应方法
- 实现接口
- 添加处理器,作用类似 SpringMVC 的处理器映射:
- 实现接口
WebSocketHandler
,并重写相应方法
- 实现接口
- 添加拦截器(可选),可在 websocket 握手阶段做一些处理,例如校验、保存用户信息。
- 实现接口:
HandshakeInterceptor
,并重写相应方法
- 实现接口:
1 | <dependency> |