User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   问题随笔

问题随笔

2024-06-25 19:53:37
1594  0 2
参考目录 隐藏
1) Spring @RequestHeader用法
2) Springboot 3.2.6 修改 tomcat连接池
3) JKD17运行报错Unable to make field private final byte[] java.lang.String.value accessible
4) maven-failsafe-plugin与maven-surefire-plugin配置
5) Spring Boot 升级 3.2 报错 Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String
6) SpringBoot 3.X 自动配置详解
7) 在 Kotlin + Spring Boot 中启用虚拟线程
8) jdk21 发布了虚拟线程,tomcat 10 从传统线程池切换到虚拟线程使用的好处
9) Spring-cloud-gateway 接口转发无响应
10) SpringBoot 项目能处理多少请求?最大连接数?
11) Spring Boot配置MySQL5和MySQL8
12) MySQL8.0导出SQL转换为5.7的SQL
13) Flyway配置MySQL
14) docker启动mysql无法输入中文以及中文不显示或乱码问题
15) MySQL 8.0 自定义配置文件
16) MySql 连接失败
17) MySql 创建新的用户并赋予权限
18) Linux VG扩展新增硬盘
19) mysql数据库删除千万条数据的操作方案,直接delete会很久

阅读完需:约 35 分钟

Spring @RequestHeader用法

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
//..
 
@Controller
public class HelloController {
 
	@RequestMapping(value = "/hello.htm")
	public String hello(@RequestHeader(value="User-Agent") String userAgent)

	    //..
	}
}

在上面的代码片段中,定义了一个映射到/hello.htm的hello控制器方法。同时用 @RequestHeader注解将请求头中”User-Agent“的变量与”userAgent“变量绑定。当此某个请求映射到了此控制器方法,Spring会检查请求头中的”User-Agent“变量,并将其与”userAgent“变量绑定。

如果 @RequestHeader绑定的变量,在请求头中不存在,Spring会将控制器中的参数初始化为null。如果想给控制器参数提供一个默认值,在 @RequestHeader的defaultParameter属性。

@RequestMapping(value = "/hello.htm")
public String hello(@RequestHeader(value="User-Agent", defaultValue="foo") String userAgent)
 
	//..
}

Springboot 3.2.6 修改 tomcat连接池

package org.config.init;
 
import org.apache.catalina.Context;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.tomcat.util.scan.StandardJarScanner;
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executors;
 
/**
 * @Description: TomcatFactoryConfig
 */
@Configuration
public class TomcatFactoryConfig {
    /**
     * tomcat-embed-jasper引用后提示jar找不到的问题
     */
    @Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
            }
        };
        factory.addConnectorCustomizers(connector -> {
//            connector.setProperty("relaxedPathChars", "[]{}");
//            connector.setProperty("relaxedQueryChars", "[]{}");
 
            connector.setProperty("relaxedPathChars", "\"#<>[\\]^`{|}/");
            connector.setProperty("relaxedQueryChars", "\"#<>[\\]^`{|}/");
 
        });
 
 
        //支持APR  AIO
        //factory.setProtocol("org.apache.coyote.http11.Http11AprProtocol");
        //factory.addContextLifecycleListeners(new AprLifecycleListener());
        //支持APR  AIO
        
        // 关键点在这里,写入自己的配置 
        Collection<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = factory.getTomcatProtocolHandlerCustomizers();
        tomcatProtocolHandlerCustomizers.add(protocolHandlerCustomizer());
        factory.setTomcatProtocolHandlerCustomizers(tomcatProtocolHandlerCustomizers);
 
        return factory;
    }
 
    public static TomcatProtocolHandlerCustomizer<?> protocolHandlerCustomizer(){
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
    
}

JKD17运行报错Unable to make field private final byte[] java.lang.String.value accessible

大致意思就是通过反射获取String类中的私有字段byte[] value 失败了。

那怎么会这样呢,原因是:

在Java9之后引入了module模块的概念,而不同module模块间是不允许使用反射来访问非public的字段/方法/构造函数(field/method/constructor),除非被访问的目标模块为反射设置open即允许自身被其他模块进行non-public反射访问。

解决办法:

Java 9之后引入了一个新的JVM选项 –illegal-access,该选项有四个可选值:

permit:Jdk9、Jdk11的默认值,允许不同模块间进行non-public反射访问,且仅在首次非法访问会给出警告,后续不再警告。在该模式下会自动将Jdk8(或更低版本)的代码进行opens设置,即允许Jdk8的代码被其他模块进行non-public反射访问,也允许Jdk8的代码对其他模块进行non-public反射访问,如此可保证高、低版本混合的Java程序如往常一样正常运行。
warn:类似permit,但是每次非法访问都会警告
debug:类似warn,但在warn的基础上加入了类似e.printStackTrace()的功能
deny:未来的默认值,禁止所有的不同模块间的non-public反射访问,出现非法反射则抛出异常,除了使用特别的命令行参数排除的模块,比如使用 –add-opens排除某些模块使其能够通过非法反射访问
线上使用了–illegal-access=deny,所以出现非法反射时会导致程序抛异常而启动失败,
而本地开发环境运行程序时,未明确设置–illegal-access则使用默认–illegal-access=premit,所以可以启动成功,但在第一次非法反射访问时给出了警告。

综上,在设置了–illegal-access=deny(推荐设置deny,兼容未来Java版本)时,需同时添加–add-opens以开启对应模块/包允许被其他模块进行非法(non-public)反射访问。

例如根据之前的日志:

Unable to make field private final byte[] java.lang.String.value accessible:
module java.base does not “opens java.lang” to unnamed module @1b70203f

将关键提示日志拆解后如下表:

注: ALL-UNNAMED表示所有的未命名模块

反复重启根据提示最终整理如下完整–add-opens选项:

--illegal-access=deny
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.text=ALL-UNNAMED
--add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED

maven-failsafe-plugin与maven-surefire-plugin配置

一些好用的maven插件介绍

https://www.cnblogs.com/wanghengbin/p/17926868.html

Maven Surefire Plugin是Maven生态系统中的一个重要组成部分,主要用于自动化测试和单元测试。它能够方便地集成到Maven项目中,并提供了丰富的功能来支持各种测试框架和工具。

Maven Surefire Plugin的主要作用是在Maven项目中执行测试用例。它支持多种测试框架,如JUnit、TestNG等,并能够生成详细的测试报告,帮助开发人员快速定位和修复问题。

Maven Surefire Plugin通过在项目的构建过程中运行测试用例来工作。当您执行Maven的test命令时,Surefire Plugin会自动扫描项目中的测试源代码,并执行相应的测试。测试结果将显示在控制台或生成的测试报告中。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.0.0-M5</version>
      <configuration>
        <!-- 配置测试类文件所在的目录 -->
        <testSourceDirectory>src/test/java</testSourceDirectory>
        <!-- 配置测试类的匹配规则 -->
        <includes>
          <include>**/*Test.java</include>
        </includes>
      </configuration>
    </plugin>
  </plugins>
</build>

Maven Failsafe插件是Maven提供的一个测试插件,用于运行集成测试。 与Maven Surefire插件不同,Maven Failsafe插件用于运行需要依赖外部环境、例如数据库或网络等的测试。 与Surefire插件类似,Failsafe插件会自动查找和执行符合命名规则的测试类和测试方法。

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>3.0.0-M5</version>
      <executions>
        <execution>
          <id>integration-test</id>
          <goals>
            <goal>integration-test</goal>
          </goals>
        </execution>
        <execution>
          <id>verify</id>
          <goals>
            <goal>verify</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <!-- 配置测试类文件所在的目录 -->
        <testSourceDirectory>src/test/java</testSourceDirectory>
        <!-- 配置测试类的匹配规则 -->
        <includes>
          <include>**/*Test.java</include>
        </includes>
      </configuration>
    </plugin>
  </plugins>
</build>

但是如果用的是JDK17,那么也需要在Maven配置上加上刚刚的忽略配置,例如:

            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <includes>
                        <include>**/*Test.java</include>
                    </includes>
                    <argLine>--add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED @{argLine}</argLine>
                </configuration>
            </plugin>

Spring Boot 升级 3.2 报错 Invalid value type for attribute ‘factoryBeanObjectType‘: java.lang.String

java.lang.IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String

原因分析
mybatis-spring 官方 ISSUE: https://github.com/mybatis/spring/issues/855

项目中使用 mybatis-plus-boot-starter 当前最新版本 3.5.4.1 ,其中依赖的 mybatis-spring 版本为 2.1.1

在 mybatis-spring 2.1.1 版本的 ClassPathMapperScanner#processBeanDefinitions 方法里将 BeanClassName 赋值给 String 变量

并将 beanClassName 赋值给 factoryBeanObjectType

但是在 Spring Boot 3.2 版本中FactoryBeanRegistrySupport#getTypeForFactoryBeanFromAttributes方法已变更,如果 factoryBeanObjectType 不是 ResolvableType 或 Class 类型会抛出 IllegalArgumentException 异常。

此时因为 factoryBeanObjectType 是 String 类型,不符合条件而抛出异常。

解决方案(新)

Mybatis-Plus 于 2023年12月24日发布 mybatis-plus v3.5.5 版本,发布日志声明 升级spring-boot3版本mybatis-spring至3.0.3。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.7</version>
</dependency>

SpringBoot 3.X 自动配置详解

创建自定义的自动配置涉及到几个关键步骤:定义配置类、使用条件注解确保配置仅在合适的条件下生效、声明所需的bean,以及在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中注册自定义自动配置。

mica-auto 自动生成配置

<dependency>
  <groupId>net.dreamlu</groupId>
  <artifactId>mica-auto</artifactId>
  <version>2.3.0</version>
  <scope>provided</scope>
</dependency>

在 Kotlin + Spring Boot 中启用虚拟线程

  • 从Java 19开始, 的概念已作为预览功能Virtual Thread新增,并计划从Java 21 LTS开始将其纳入官方功能。虽然传统的平台线程直接映射到操作系统 (OS)的线程,但虚拟线程作为Java 虚拟机 (JVM)抽象的轻量级虚拟线程运行,消耗的内存显著减少。虚拟线程由 JVM 的调度程序自动管理,使开发人员能够将更多精力放在业务逻辑上,同时享受性能优势。
  • Spring Boot 3和Spring Framework 6正式支持Virtual Thread。本文总结了如何将基于 Spring Boot 的项目中处理Spring Web MVC请求、@Async、协程执行的平台线程替换为虚拟线程。

虚拟线程功能

  • Java 19/20开始Virtual Thread作为预览功能提供,并在Java 21 ( LTS版本)中作为完整功能提供。
  • 传统的平台线程直接包装操作系统线程,无法在由IO或计算(例如网络和数据库操作)引起的阻塞期间使用。另一方面,虚拟线程作为JVM管理的逻辑单元要轻得多,与物理 OS 线程隔离。至关重要的是,当发生阻塞时,正在使用的OS线程可以由另一个虚拟线程使用,从而显著改善并发处理。与Reactive方法不同,这使开发人员无需进行重大更改即可在传统的顺序编程中实现显着的性能改进。(这代表了JVM社区多年来的一次重大创新。)
  • Java社区在引入Virtual Thread时,一直努力保持向后兼容性。可用于轻松创建Executor对象。 将其与 servlet 容器和 Kotlin 协程集成,无需更改现有代码即可立即使用Virtual Thread 。Executors.newVirtualThreadPerTaskExecutor()
  • 虚拟线程有两种使用方式。Java 19/20--release 19 --enable-preview要求您在项目构建时和--enable-preview运行时添加该选项。而Java 21则不需要添加此选项。

将 HTTP 请求处理切换到虚拟线程

  • 在Java生态系统中,servlet 容器通过物理平台线程处理请求,这些线程是针对每个单元请求包装的 OS 线程。以下代码允许您指示Apache Tomcat ( Spring Boot中嵌入的 servlet 实现)使用虚拟线程而不是平台线程处理所有请求。
import org.apache.coyote.ProtocolHandler
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.concurrent.Executors

@Configuration
class TomcatConfig {

    @Bean
    fun protocolHandlerVirtualThreadExecutorCustomizer(): TomcatProtocolHandlerCustomizer<*>? {
        return TomcatProtocolHandlerCustomizer<ProtocolHandler> { protocolHandler: ProtocolHandler ->
            protocolHandler.executor = Executors.newVirtualThreadPerTaskExecutor()
        }
    }
}

将异步执行切换到虚拟线程

  • 在Spring Boot生态系统中,执行异步逻辑最方便的方法之一,传统上在基于平台线程池的AsyncTaskExecutor@Async实现分配的线程上运行。可以按如下方式将其切换到虚拟线程。
/**
 * 异步配置
 *
 * 传统上在基于平台线程池的AsyncTaskExecutor@Async实现分配的线程上运行。可以按如下方式将其切换到虚拟线程。
 *
 * @date 2024/06/17
 */
@Configuration
@EnableAsync
public class AsyncConfig {
    /**
     * 任务执行者
     *
     * @return {@link AsyncTaskExecutor}
     */
    @Bean
    public AsyncTaskExecutor asyncTaskExecutor() {
        TaskExecutorAdapter taskExecutor = new TaskExecutorAdapter(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("virtual-async", 1).factory()));
        taskExecutor.setTaskDecorator(new LoggingTaskDecorator());
        return taskExecutor;
    }
}

将调度程序执行切换到虚拟线程

  • 在Spring Boot生态系统中,@Scheduled任务是根据定义的规则在特定时间执行的,传统上是在由基于平台线程池的ThreadPoolTask ​​Executor 实现分配的线程上运行。可以按如下方式将其切换到虚拟线程。
/**
 * 调度配置
 *
 * @Scheduled 任务是根据定义的规则在特定时间执行的
 * 传统上是在由基于平台线程池的ThreadPoolTask Executor 实现分配的线程上运行。这可以按如下方式切换到虚拟线程。
 *
 * @date 2024/06/17
 */
@Configuration
@EnableScheduling
public class SchedulingConfig {
    /**
     * 任务调度器
     *
     * @return {@link TaskScheduler}
     */
    @Bean
    public TaskScheduler taskScheduler() {
        SimpleAsyncTaskScheduler simpleAsyncTaskScheduler = new SimpleAsyncTaskScheduler();
        simpleAsyncTaskScheduler.setThreadNamePrefix("SchedulingVirtual-");
        simpleAsyncTaskScheduler.setVirtualThreads(true);
        simpleAsyncTaskScheduler.setTaskTerminationTimeout(10*1000);
        return simpleAsyncTaskScheduler;
    }
}

将 Kotlin 协程执行切换到虚拟线程

  • 早在虚拟线程出现之前,Kotlin就早已通过协程为开发人员提供了类似的挂起功能。通过编写下面的代码并使用应用虚拟线程而不是传统使用的Dispatchers.IO 的Dispatchers.LOOM,协程可以在虚拟线程上执行。
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors

val Dispatchers.LOOM: CoroutineDispatcher
    get() = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher()

SpringBoot 3.2:虚线程支持

springboot3.2在上周发布了,可以直接通过springboot提供的配置spring.threads.virtual.enabled: true为许多组件开启虚线程支持

spring:
  # 启用虚拟线程
  threads:
    virtual:
      enabled: true

生效点关键代码在下面

jdk21 发布了虚拟线程,tomcat 10 从传统线程池切换到虚拟线程使用的好处

JDK 21 支持了虚拟线程,这是一种轻量级的线程模型,可以简化高吞吐量并发 Java 应用程序的创建、管理和监控,让开发者能够高效地处理数百万个任务,并更好地利用系统资源。

虚拟线程的好处有:

  • 虚拟线程不是绑定在特定的操作系统线程上,而是由 JDK 提供的协作式调度器来管理,这样可以减少上下文切换的开销,提高 CPU 利用率。
  • 虚拟线程可以在一个操作系统线程上运行多个虚拟线程,这样可以节省内存空间,因为虚拟线程的栈空间和其他资源都比平台线程要小得多。
  • 虚拟线程可以自动挂起和恢复,当虚拟线程执行阻塞 I/O 操作时,它会被挂起,直到 I/O 完成后再被恢复,这样可以避免浪费操作系统线程的时间,让它们去执行其他虚拟线程的任务。
  • 虚拟线程是兼容平台线程的,它们都是 java.lang.Thread 的实例,可以使用现有的 Java 代码和 API 来操作它们,也可以在平台线程和虚拟线程之间进行无缝的迁移和互操作。

虚拟线程的运行方式和对 I/O 的控制如下:

  • 虚拟线程是由 JDK 内置的 Fork/Join 框架来执行的,每个操作系统线程都对应一个 Fork/Join 池,每个池中有一个或多个 Fork/Join 工作线程来运行虚拟线程。
  • 当一个虚拟线程被创建时,它会被放入一个 Fork/Join 池中的一个工作队列中,等待被调度。当一个工作线程空闲时,它会从自己或其他工作队列中取出一个虚拟线程来执行。
  • 当一个虚拟线程执行到一个阻塞 I/O 操作时,它会被挂起,并且释放它所占用的工作线程。JDK 会使用 java.nio.channels 包中的异步 I/O API 来处理阻塞 I/O 操作,并且在 I/O 完成后通知对应的虚拟线程恢复执行。
  • 当一个虚拟线程执行完毕或者被终止时,它会从 Fork/Join 池中移除,并且释放它所占用的资源。

Spring-cloud-gateway 接口转发无响应

问题复述:连接Nacos,转发配置与名称都是对的,服务可以正常的转发到后端,但是gateway无法正常接受后端返回的数据,gateway与后端业务服务没有任何的报错。

问题原因:经过线上的Debug,怀疑是环境变量的错误,最后找到问题response-timeout,配置错误,没有写单位

response-timeout 是 Spring Cloud Gateway 中的一个配置项,用于设置 HTTP 客户端请求的响应超时时间。这个配置项确保网关在设定的时间内等待后端服务的响应,如果超时则会终止请求并返回错误。

下面是如何在 Spring Cloud Gateway 的配置文件中设置 response-timeout 的示例:

spring:
  cloud:
    gateway:
      httpclient:
        response-timeout: 5s  # 设置响应超时时间为 5 秒

单位:超时时间可以使用 ms(毫秒)、s(秒)、m(分钟)等单位。例如,5s 表示 5 秒,200ms 表示 200 毫秒。

默认值:如果不设置,默认的响应超时时间为 30 秒。

完整示例

spring:
  cloud:
    gateway:
      routes:
        - id: example_route
          uri: http://example.com
          predicates:
            - Path=/example/** 
          filters:
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback
      httpclient:
        connect-timeout: 2s        # 设置连接超时时间为 2 秒
        response-timeout: 5s       # 设置响应超时时间为 5 秒
        pool:
          max-idle-time: 60s       # 连接池中连接的最大空闲时间
          max-life-time: 300s      # 连接池中连接的最大生存时间
          max-connections: 1000    # 连接池的最大连接数
          acquire-timeout: 45s     # 连接池获取连接的超时时间
  • connect-timeout:设置与后端服务的连接超时时间。
  • pool.max-idle-time:设置连接池中连接的最大空闲时间。
  • pool.max-life-time:设置连接池中连接的最大生存时间。
  • pool.max-connections:设置连接池的最大连接数。
  • pool.acquire-timeout:设置从连接池中获取连接的超时时间。

在 Spring Cloud Gateway 的配置中,时间参数需要明确指定单位。如果你不指定单位,只写数字 60,会导致配置无法正确解析,进而产生错误,运维在配置参数的时候,以为60就可以了,没有写入 s 单位

如果你只写 60 而不指定单位,配置会被认为是无效的,导致 Spring Cloud Gateway 启动失败或者配置项不起作用:

spring:
  cloud:
    gateway:
      httpclient:
        response-timeout: 60  # 错误:缺少单位

支持的单位

Spring Cloud Gateway 支持的时间单位包括:

  • ns:纳秒
  • us:微秒
  • ms:毫秒
  • s:秒
  • m:分钟
  • h:小时
  • d:天

SpringBoot 项目能处理多少请求?最大连接数?

一个 SpringBoot 项目能同时处理多少请求?

一个 SpringBoot 项目,未进行任何特殊配置,全部采用默认设置,这个项目同一时刻,最多能同时处理多少请求?(SpringBoot 3.3.1)

测试接口

@Slf4j
@RestController
public class TestController {

    @GetMapping("/getTest")
    public void getTest(@RequestParam(defaultValue = "1") int num) throws Exception {
        log.info("{} 接受到请求:num={}", Thread.currentThread().getName(), num);
        TimeUnit.HOURS.sleep(1);
    }
}

请求接口

public static void main(String[] args) {
    for (int i = 0; i < 1000; i++) {
        int finalI = i;
        new Thread(() -> {
            HttpUtil.get("127.0.0.1:8080/getTest?num=" + finalI);
        }).start();
    }
    //阻塞主线程
    Thread.yield();
}

测试结果200次

因为Tomcat默认

server:
  tomcat:
    # 当所有可能的请求处理线程都在使用中时,传入连接请求的最大队列长度
    accept-count: 100
    # 服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以接受基于“acceptCount”属性的连接。
    max-connections: 8192
    threads:
      # 工作线程的最小数量,初始化时创建的线程数
      min-spare: 10
      # 工作线程的最大数量 io密集型建议10倍的cpu数,cpu密集型建议cpu数+1,绝大部分应用都是io密集型
      max: 200
    # 连接器在接受连接后等待显示请求 URI 行的时间。
    connection-timeout: 20000
    # 在关闭连接之前等待另一个 HTTP 请求的时间。如果未设置,则使用 connectionTimeout。设置为 -1 时不会超时。
    keep-alive-timeout: 20000
    # 在连接关闭之前可以进行流水线处理的最大HTTP请求数量。当设置为0或1时,禁用keep-alive和流水线处理。当设置为-1时,允许无限数量的流水线处理或keep-alive请求。 
    max-keep-alive-requests: 100

JDK 的线程池,是先使用核心线程数配置,接着使用队列长度,最后再使用最大线程配置。

Tomcat 的线程池,就是先使用核心线程数配置,再使用最大线程配置,最后才使用队列长度。

JDK 线程池流程:minThreads –> queue –> maxThreads –> Exception
Tomcat 增强后: minThreads –> maxThreads –> queue –> Exception

JDK 线程池架构图

Tomcat 线程架构

然后如果将 server.tomcat.max-connections=10 最大连接数,默认是8192,如果改成10个,最大请求就只有10个了。

因为这个配置是控制连接数量的,好比 NIO 里Socket的连接数量上限大小,一般情况下在NIO模式下,连接上限都是比线程池上限大的

当连接数大于 maxConnections+acceptCount + 1 时,新来的请求不会收到服务器拒绝连接响应,而是不会和新的请求进行 3 次握手建立连接,一段时间后(客户端的超时时间或者 Tomcat 的 20s 后)会出现请求连接超时。

但是当升级到Tomcat 10 开启虚拟线程

spring.threads.virtual.enabled=true

此时请求会突破最大线程数量的限制,启用虚拟线程,但是最大连接数还是会被限制住

Spring Boot配置MySQL5和MySQL8

这里做一个配置的记录

MySQL5

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.17</version>
    <scope>runtime</scope>
</dependency>
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/onlineTest2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=xudong123456

MySQL8

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
    <scope>runtime</scope>
</dependency>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/onlineTest2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=xudong123456

MySQL8.0导出SQL转换为5.7的SQL

将MySQL8.0生成的sql文件,导入MySQL5.7版本时,出现了版本不兼容问题

将MySQL8.0导出的sql文件中,所有的utf8mb4_0900_ai_ci替换为utf8_general_ci,以及所有的utf8mb4替换为utf8

Flyway配置MySQL

Flyway 8.2.1及以后版本不再支持MySQL

MySQL代码被提取出来作为插件,需要另外增加依赖

https://mvnrepository.com/artifact/org.flywaydb/flyway-mysql

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

目前支持mysql5.7的社区版为7.15.0,支持mysql8.0的版本是8.2.0,8.2.1移除了mysql支持

<dependency>
	<groupId>org.flywaydb</groupId>
	<artifactId>flyway-core</artifactId>
	<version>7.15.0</version>
</dependency>

Flyway 兼容 Mysql 5.7 的新发现(因为用的SpringBoot版本是 3.3.1 和旧版本的Flyway不兼容,只能用新的)

Flyway 在 10.x.x 之前的版本,不兼容mysql 5.7

10.x.x 重新兼容了低版本的数据库

要使用 10.x.x版本的Flyway,需要引入依赖

<dependency>
	<groupId>org.flywaydb</groupId>
	<artifactId>flyway-mysql</artifactId>
	<version>10.13.0</version>
</dependency>
<dependency>
	<groupId>org.flywaydb</groupId>
	<artifactId>flyway-core</artifactId>
	<version>10.13.0</version>
</dependency>

docker启动mysql无法输入中文以及中文不显示或乱码问题

这里无论是 mysql5 还是 mysql 8 都是这样的问题无法输入中文或者乱码,但是PG数据库是正常的。

乱码一般都是因为编码引起的,所以我们来查一下数据库的编码,默认都是没问题的

最后找到的原因是容器的编码为设置好

docker exec -it mysql env LANG=C.UTF-8 mysql -uroot -p

当加上 env LANG=C.UTF-8 这个环境变量就可以正常使用中文

MySQL 8.0 自定义配置文件

root@hcss-ecs-8d74:~# cat my.cnf 
[mysqld]

#限制server接受的数据包大小
max_allowed_packet=200M
#最大连接数,不要盲目提高此值
max_connections=1024
#指定大小的内存来缓冲数据和索引,最大可以把该值设置成物理内存的80%
innodb_buffer_pool_size = 10G
innodb_buffer_pool_instances = 10
#此参数确定数据日志文件的大小,以为单位,根据据更新频率调整
innodb_log_file_size = 50M
key_buffer_size = 16M
#设置 sql_mode
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
group_concat_max_len = 1024000
#关闭严格模式
innodb_strict_mode = 0
innodb_file_per_table = ON

注意事项:

1、在MySQL 8.0版本中,由于初始化后无法更改lower_case_table_names参数导致数据库表名大小写问题。官方文档指出该参数必须在服务器初始化时配置。针对此问题,解决方案是重新安装MySQL,并在初始化时指定lower_case_table_names=1。

2、mysql8 启动报错:Error while setting value ‘STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DI

8.0以上已经取消了NO_AUTO_CREATE_USER这个关键字,在mysql.ini中的sql_mode中删掉这个关键字即可

MySql 连接失败

MySQL 连接出现 Authentication plugin 'caching_sha2_password' cannot be loaded

很多用户在使用Navicat Premium 12连接MySQL数据库时会出现Authentication plugin 'caching_sha2_password' cannot be loaded的错误。

出现这个原因是mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是caching_sha2_password, 解决问题方法有两种,一种是升级navicat驱动,一种是把mysql用户登录密码加密规则还原成mysql_native_password. 

查看默认的加密方式;本地用户的加密方式

show variables like 'default_authentication_plugin';
select host,user,plugin from mysql.user;

修改登录用户的加密规则

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';

MySql 创建新的用户并赋予权限

如果你想允许从任何主机连接,可以将 'localhost' 替换为 '%':

CREATE USER 'newuser'@'%' IDENTIFIED BY 'password';

使用 GRANT 语句授予用户对特定数据库的权限。假设数据库名为 sjhl。

同样,如果你想允许从任何主机连接,可以将 'localhost' 替换为 '%':

GRANT ALL PRIVILEGES ON sjhl.* TO 'newuser'@'%';

使用 FLUSH PRIVILEGES 语句使权限更改立即生效。

FLUSH PRIVILEGES;
# 登录 MySQL
mysql -u root -p

# 创建用户
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';

# 授予权限
GRANT ALL PRIVILEGES ON sjhl.* TO 'newuser'@'localhost';

# 应用权限更改
FLUSH PRIVILEGES;

# 验证权限
EXIT;

# 使用新用户登录
mysql -u newuser -p

# 尝试访问 sjhl 数据库
USE sjhl;

# 尝试访问其他数据库
USE otherdb;

Linux VG扩展新增硬盘

fdisk -l 查看一下新增的磁盘信息

fdisk /dev/sdc 对硬盘进行分区和格式化

vgs 查看vg的名称

当我们在安装系统的时候,由于没有合理分配分区空间,在后续维护过程中,发现有些分区空间不够使用,而有的分区空间却有很多剩余空间。如果这些分区在装系统的时候使用了lvm(前提是这些分区要是lvm逻辑卷分区),那么就可以轻松进行扩容或缩容!不同文件系统类型所对应的创建、检查、调整命令不同,下面就针对xfs和ext2/3/4文件系统的lvm分区空间的扩容和缩容的操作做一记录:

特别注意的是:

  • resize2fs命令 针对的是ext2、ext3、ext4文件系统
  • xfs_growfs命令 针对的是xfs文件系统

ext2/ext3/ext4文件系统的调整命令是resize2fs(增大和减小都支持)

  • lvextend -L 120G /dev/mapper/centos-home //增大至120G
  • lvextend -L +20G /dev/mapper/centos-home //增加20G
  • lvreduce -L 50G /dev/mapper/centos-home //减小至50G
  • lvreduce -L -8G /dev/mapper/centos-home //减小8G
  • resize2fs /dev/mapper/centos-home //执行调整

xfs文件系统的调整命令是xfs_growfs(只支持增大)

  • lvextend -L 120G /dev/mapper/centos-home //增大至120G
  • lvextend -L +20G /dev/mapper/centos-home //增加20G
  • xfs_growfs /dev/mapper/centos-home //执行调整

就是说:xfs文件系统只支持增大分区空间的情况,不支持减小的情况(切记!!!!!)

硬要减小的话,只能在减小后将逻辑分区重新通过mkfs.xfs命令重新格式化才能挂载上,这样的话这个逻辑分区上原来的数据就丢失了。

实例1(当系统上还有空闲空间的时候)

查看分区空间。如下可知是xfs文件系统(df的-T参数就能看出文件格式)

[root@localhost ~]# df -hT
文件系统    类型  容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root xfs  200G 2.2G 198G 2% /
devtmpfs    devtmpfs 32G  0 32G 0% /dev
tmpfs     tmpfs  32G  0 32G 0% /dev/shm
tmpfs     tmpfs  32G 49M 32G 1% /run
tmpfs     tmpfs  32G  0 32G 0% /sys/fs/cgroup
/dev/sda1    xfs  197M 139M 59M 71% /boot
tmpfs     tmpfs  6.3G  0 6.3G 0% /run/user/0
/dev/mapper/centos-home xfs  628G 33M 718G 1% /home

使用vgdisplay命令查看系统上的空闲空间

root@localhost ~]# vgdisplay
 --- Volume group ---
 VG Name    centos
 System ID   
 Format    lvm2
 Metadata Areas  1
 Metadata Sequence No 6
 VG Access    read/write
 VG Status    resizable
 MAX LV    0
 Cur LV    3
 Open LV    3
 Max PV    0
 Cur PV    1
 Act PV    1
 VG Size    930.80 GiB
 PE Size    4.00 MiB
 Total PE    238285
 Alloc PE / Size  212736 / 831.00 GiB
 Free PE / Size  25549 / 99.80 GiB  //这一项表示目前该系统上还存在99.80G的空闲空间(25549)
 VG UUID    a5hiAh-LB8M-9lRv-Ps1a-z35L-J4fk-sP3KrF

将上面查到的空闲空间中的90G增减到/home分区上

[root@localhost ~]# lvextend -L +90G /dev/mapper/centos-home  //或者使用-l参数(跟PE数量),即lvextend -l +25500 /dev/mapper/centos-home
 Size of logical volume centos/home changed from 628.00 GiB (160768 extents) to 718.00 GiB (183808 extents).
 Logical volume centos/home successfully resized.

[root@localhost ~]# xfs_growfs /dev/mapper/centos-home  // 刷新磁盘信息
meta-data=/dev/mapper/centos-home isize=512 agcount=4, agsize=41156608 blks
   =      sectsz=512 attr=2, projid32bit=1
   =      crc=1  finobt=0 spinodes=0
data  =      bsize=4096 blocks=164626432, imaxpct=25
   =      sunit=0  swidth=0 blks
naming =version 2    bsize=4096 ascii-ci=0 ftype=1
log  =internal    bsize=4096 blocks=80384, version=2
   =      sectsz=512 sunit=0 blks, lazy-count=1
realtime =none     extsz=4096 blocks=0, rtextents=0
data blocks changed from 164626432 to 188219392

再次看着系统分区,发现home分区已经增加了90G(这种方式增加后,home分区之前的数据还不会丢失)

[root@localhost ~]# df -h
文件系统     容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root 200G 2.2G 198G 2% /
devtmpfs     32G  0 32G 0% /dev
tmpfs      32G  0 32G 0% /dev/shm
tmpfs      32G 49M 32G 1% /run
tmpfs      32G  0 32G 0% /sys/fs/cgroup
/dev/sda1    197M 139M 59M 71% /boot
tmpfs     6.3G  0 6.3G 0% /run/user/0
/dev/mapper/centos-home 718G 33M 718G 1% /home
虽然xfs文件系统只支持增加,不支持减少。但并不是说在xfs系统文件下不能减小,只是减小后,需要重新格式化才能挂载上。这样原来的数据就丢失了!

实例2: 这种情况只适用于系统刚安装好,逻辑分区内没有什么数据或数据不多且不重要可以删除或拷贝的情况下

系统安装好后,发现home分区过大,想从home分区中拿出100G给/分区

[root@localhost ~]# df -hT
文件系统    类型  容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root xfs  205G 2.2G 203G 2% /
devtmpfs    devtmpfs 32G  0 32G 0% /dev
tmpfs     tmpfs  32G  0 32G 0% /dev/shm
tmpfs     tmpfs  32G 49M 32G 1% /run
tmpfs     tmpfs  32G  0 32G 0% /sys/fs/cgroup
/dev/sda1    xfs  197M 139M 59M 71% /boot
tmpfs     tmpfs  6.3G  0 6.3G 0% /run/user/0
/dev/mapper/centos-home xfs  718G 33M 718G 1% /home

[root@localhost ~]# umount /home/

[root@localhost ~]# lvreduce -L -100G /dev/mapper/centos-home
 WARNING: Reducing active logical volume to 618.00 GiB.
 THIS MAY DESTROY YOUR DATA (filesystem etc.)
Do you really want to reduce centos/home? [y/n]: y
 Size of logical volume centos/home changed from 718.00 GiB (183808 extents) to 618.00 GiB (158208 extents).
 Logical volume centos/home successfully resized.

如下,很显然xfs文件系统不能执行分区减小的调整!
[root@localhost ~]# xfs_growfs /dev/mapper/centos-home
xfs_growfs: /dev/mapper/centos-home is not a mounted XFS filesystem
[root@localhost ~]# mount /dev/mapper/centos-home /home/
mount: /dev/mapper/centos-home:不能读超级块

这样,只能通过重新格式化这个分区,格式化后才能再次挂载到home下
[root@localhost ~]# mkfs.xfs /dev/mapper/centos-home -f
meta-data=/dev/mapper/centos-home isize=512 agcount=4, agsize=41156608 blks
   =      sectsz=512 attr=2, projid32bit=1
   =      crc=1  finobt=0, sparse=0
data  =      bsize=4096 blocks=164626432, imaxpct=25
   =      sunit=0  swidth=0 blks
naming =version 2    bsize=4096 ascii-ci=0 ftype=1
log  =internal log   bsize=4096 blocks=80384, version=2
   =      sectsz=512 sunit=0 blks, lazy-count=1
realtime =none     extsz=4096 blocks=0, rtextents=0
  
[root@localhost ~]# mount /dev/mapper/centos-home /home/
再次查看分区,发现home分区已经减小了100G,只不过这个分区里之前的数据都没有了。

[root@localhost ~]# df -hT  
文件系统     类型  容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root xfs  205G 2.2G 203G 2% /
devtmpfs    devtmpfs 32G  0 32G 0% /dev
tmpfs     tmpfs  32G  0 32G 0% /dev/shm
tmpfs     tmpfs  32G 49M 32G 1% /run
tmpfs     tmpfs  32G  0 32G 0% /sys/fs/cgroup
/dev/sda1    xfs  197M 139M 59M 71% /boot
tmpfs     tmpfs  6.3G  0 6.3G 0% /run/user/0
/dev/mapper/centos-home xfs  618G 73M 578G 1% /home

上面在重新格式化的时候,也可以将这个格式化为ext4格式。

[root@localhost ~]# mkfs.ext4 /dev/mapper/centos-home
[root@localhost ~]# cat /etc/fstab //将home分区的开机挂载设置里的xfs改为ext4

然后将上面从home分区拿出的100G放到/分区下

[root@localhost ~]# vgdisplay
 --- Volume group ---
 VG Name    centos
 System ID   
 Format    lvm2
 Metadata Areas  1
 Metadata Sequence No 9
 VG Access    read/write
 VG Status    resizable
 MAX LV    0
 Cur LV    3
 Open LV    3
 Max PV    0
 Cur PV    1
 Act PV    1
 VG Size    930.80 GiB
 PE Size    4.00 MiB
 Total PE    238285
 Alloc PE / Size  211456 / 826.00 GiB
 Free PE / Size  26829 / 104.80 GiB
 VG UUID    a5hiAh-LB8M-9lRv-Ps1a-z35L-J4fk-sP3KrF

[root@localhost ~]# lvextend -L +100G /dev/mapper/centos-root
 Size of logical volume centos/root changed from 205.00 GiB (52480 extents) to 305.00 GiB (78080 extents).
 Logical volume centos/root successfully resized.
  
[root@localhost ~]# xfs_growfs /dev/mapper/centos-root
meta-data=/dev/mapper/centos-root isize=256 agcount=5, agsize=13107200 blks
   =      sectsz=512 attr=2, projid32bit=1
   =      crc=0  finobt=0 spinodes=0
data  =      bsize=4096 blocks=53739520, imaxpct=25
   =      sunit=0  swidth=0 blks
naming =version 2    bsize=4096 ascii-ci=0 ftype=0
log  =internal    bsize=4096 blocks=25600, version=2
   =      sectsz=512 sunit=0 blks, lazy-count=1
realtime =none     extsz=4096 blocks=0, rtextents=0
data blocks changed from 53739520 to 79953920
  
[root@localhost ~]# df -hT  
文件系统     类型  容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root xfs  305G 2.2G 203G 2% /
devtmpfs    devtmpfs 32G  0 32G 0% /dev
tmpfs     tmpfs  32G  0 32G 0% /dev/shm
tmpfs     tmpfs  32G 49M 32G 1% /run
tmpfs     tmpfs  32G  0 32G 0% /sys/fs/cgroup
/dev/sda1    xfs  197M 139M 59M 71% /boot
tmpfs     tmpfs  6.3G  0 6.3G 0% /run/user/0
/dev/mapper/centos-home xfs  618G 73M 578G 1% /hom

若是减小分区空间,减小前必须要先卸载这个分区。如果卸载有问题,解决如下:

[root@localhost ~]# umount /home/
umount: /home: device is busy.
(In some cases useful info about processes that use
the device is found by lsof(8) or fuser(1))
提示无法卸载,则是有进程占用/home,使用如下命令来终止占用进程:
[root@localhost ~]# fuser -m -k /home
/home: 1409 1519ce 1531e 1532e 1533e 1534e 1535e 1536e 1537e 1538e 1539e 1541e 1543e 1544e 1545e 1546e 1547e 1548e 1549e 1550e 1601m
再次卸载home分区就成功了。
[root@localhost ~]# umount /home/
-k 表示自动把霸占home分区的进程kill掉!
如果你不是很明确是否要杀死所有霸占设备的程序,还可以加一个-i 参数,这样每杀死一个程序前,都会询问!(即fuser -m -v -i -k /home)

mysql数据库删除千万条数据的操作方案,直接delete会很久

在线上有一张表很大,积累了几年的消费数据,现在需要把这张表几年前的数据给清掉,数据量将近1亿,直接按年份将其中的数据delete,你会发现删了几小时都没有返回。这是因为每执行一次delete,需要同时将该行的删除操作记录作为事务记录在日志中保存以便进行回滚。

最终采用的方案是:

  • 抽取需要保留的数据到备份表中,
  • truncate旧表
  • 备份表中的数据再插入truncate表

实操如下:

CREATE TABLE charging_record_old SELECT * FROM charging_record WHERE 1=2;
 
INSERT INTO charging_record_old SELECT * FROM charging_record cr WHERE cr.recordtime > "2019-01-01 00:01:00";
 
TRUNCATE TABLE charging_record;
SET foreign_key_checks = 0;
INSERT INTO charging_record SELECT * FROM charging_record_old;

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

2 打赏
评论 (0)

点击这里取消回复。

欢迎您 游客  

Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
4文章 68评论 294点赞 593217浏览

随机文章
Kotlin-1.5—内联值类
4年前
MyBatis笔记13—一对一查询
5年前
Java—JavaAgent探针
3年前
FastDFS—— 构建分布式文件管理系统
5年前
SpringSecurity—OAuth 2(九)第三方应用优化
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1926 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593217 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付