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
2
3
4
5
6
7
8
9
10
11
12
# 最大工作线程数,默认200。
server.tomcat.max-threads=200
# 最大连接数,默认值与Tomcat的连接器使用的协议有关
server.tomcat.max-connections=10000
# 等待队列长度,默认100。
server.tomcat.accept-count=100
# 最小工作空闲线程数,默认10。tomcat启动时的初始化的线程数
server.tomcat.min-spare-threads=100
#请求头最大长度kb
server.tomcat.max-http-header-size: 1048576
#请请求体最大长度kb
server.tomcat.max-http-post-size: 2097152

针对4C8G配置,可以参考建议值:

1
2
3
4
5
6
7
server:
port: 8080
tomcat:
accept-count: 1000
max-connections: 10000
max-threads: 800
min-spare-threads: 100
  • 线程数的经验值为:1核2G内存,线程数经验值2004核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中线程池的使用

使用方式

  1. 基于ExecutorService自定义线程池
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private Logger logger = LoggerFactory.getLogger(InitBeanConfig.class);

    @Bean
    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;
    }
  2. Spring默认线程池applicationTaskExecutor
  3. 基于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
    @Configuration
    public class ThreadPoolConfig {
    @Bean("partFileUploadExecutor")
    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

线程池监控

实际使用问题

  1. 任务执行异常丢失问题
    • 无返回值的异步方法
      无返回值的异步方法,异常不会传播到调用线程,需要添加额外的配置来处理异常。
      通过实现AsyncUncaughtExceptionHandler接口来创建自定义异步异常处理程序
    • 有返回值的异步方法
      当异步方法返回类型为Future时,Future.get()方法将抛出异常。
  2. 整个服务全局共享一个线程池问题
    次要逻辑拖垮主要逻辑。
    不同业务逻辑定义各自的线程池。
  3. 跟ThradLocal配合使用的问题
  4. 自定义拒绝策略
  5. RejectedExecutionException
    当拒绝策略为AbortPolicy时,如何捕获RejectedExecutionException进行补偿?
    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
    @Override
    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
    @Override
    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);
    }
    }
    对execute、sumit方法进行try-catch,处理相应的异常。

SpringBoot集成数据库连接池

SpringBoot2.x默认 Hikari

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url:
username:
password:
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
hikari:
#最小空闲连接数
minimum-idle: 10
# 数据库连接超时时间,默认30秒
connection-timeout: 2000
# 连接池最大连接数,默认是10
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
data-source-properties:
cachePrepStmts: true
prepStmtCacheSize: 250
prepStmtCacheSqlLimit: 2048
useServerPrepStmts: true
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
max-lifetime: 570000
# 空闲连接存活最大时间,默认10分钟
idle-timeout: 500000
  • SpringBoot2.x默认连接池Hikari是如何起作用的
    • spring-boot-autoconfigure
      DataSourceAutoConfiguration自动配置类
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @Configuration(proxyBeanMethods = false)
      @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
      @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
      @EnableConfigurationProperties(DataSourceProperties.class)
      @Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
      public class DataSourceAutoConfiguration {
      // 先import DataSourceConfiguration.Hikari.class
      @Configuration(proxyBeanMethods = false)
      @Conditional(PooledDataSourceCondition.class)
      @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
      @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
      DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class,
      DataSourceJmxConfiguration.class })
      protected static class PooledDataSourceConfiguration {

      }
      }
    • springboot-starter-jdbc
      springboot-starter-jdbc默认加载了Hikari
      
  • 为什么Hikari会成为默认连接池

SpringBoot集成Redis

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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
2
3
4
5
6
7
8
9
10
11
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}

SpringBoot集成WebSocket

  1. 添加 starter 依赖
  2. 添加 WebSocket 配置:
    • 实现接口WebSocketConfigurer,并重写相应方法
  3. 添加处理器,作用类似 SpringMVC 的处理器映射:
    • 实现接口 WebSocketHandler,并重写相应方法
  4. 添加拦截器(可选),可在 websocket 握手阶段做一些处理,例如校验、保存用户信息。
    • 实现接口:HandshakeInterceptor,并重写相应方法
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

SpringBoot集成Kafka

SpringBoot集成ElasticSearch