Spring boot - Task Execution and Scheduling @Async

SpringBoot的任务执行器

Spring Boot通过auto-configuration机制自动创建了任务执行器Task Execution,因此在SpringBoot项目中,你不需要任何配置、也不需要自己创建Task Execution就可以直接使用它。

Spring Boot通过auto-configuration机制创建的任务执行器有以下作用:

  1. asynchronous task execution (@EnableAsync):通过@EnableAsync以及@Async注解使用任务执行器。
  2. Spring for GraphQL’s asynchronous handling of Callable return values from controller methods(这个没用过)。
  3. Spring MVC’s asynchronous request processing:Spring MVC的异步请求处理。
  4. Spring WebFlux’s blocking execution support

除以上官网提到的,你还可以:手动使用任务执行器执行异步任务。

SpringBoot通过auto-configuration机制帮你创建了任务执行器TaskExecution,至于怎么通过TaskExecution、执行什么异步任务当然是你自己的事情了。

今天研究两部分内容:

  1. SpringBoot的auto-configuration机制创建任务执行器的过程。
  2. 通过任务执行器执行任务,主要是上述提到的官网内容第1项@EnableAsync以及@Async注解的原理及使用。

Spring Boot创建任务执行器

SpringBoot官网说的很明确,TaskExecutor通过SpringBoot的auto-configuration技术创建。我们知道SpringBoot的auto-configuration技术(详情请参考:SpringBoot 自动配置@EnableAutoConfiguration)是通过META-INF/spring.factories文件指定自动配置内容的,我们打开spring.factories文件找一下taskExecutor的相关内容,在EnableAutoConfiguration项下果然发现了TaskExecutionAutoConfiguration:
在这里插入图片描述
TaskExecutionAutoConfiguration在org.springframework.boot.autoconfigure.task包下,代码不算长,比较简单:

@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {

	/**
	 * Bean name of the application {@link TaskExecutor}.
	 */
	public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";

	@Bean
	@ConditionalOnMissingBean
	public TaskExecutorBuilder taskExecutorBuilder(TaskExecutionProperties properties,
			ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
			ObjectProvider<TaskDecorator> taskDecorator) {
		TaskExecutionProperties.Pool pool = properties.getPool();
		TaskExecutorBuilder builder = new TaskExecutorBuilder();
		builder = builder.queueCapacity(pool.getQueueCapacity());
		builder = builder.corePoolSize(pool.getCoreSize());
		builder = builder.maxPoolSize(pool.getMaxSize());
		builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
		builder = builder.keepAlive(pool.getKeepAlive());
		Shutdown shutdown = properties.getShutdown();
		builder = builder.awaitTermination(shutdown.isAwaitTermination());
		builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
		builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
		builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);
		builder = builder.taskDecorator(taskDecorator.getIfUnique());
		return builder;
	}

	@Lazy
	@Bean(name = { APPLICATION_TASK_EXECUTOR_BEAN_NAME,
			AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
	@ConditionalOnMissingBean(Executor.class)
	public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
		return builder.build();
	}

}

它当然是一个Configuration类,在SpringBoot启动的过程@Bean注解指定的方法会被加载到Spring IoC容器中。其次,通过@EnableConfigurationProperties指定了配置类TaskExecutionProperties。

被加载到Ioc容器中的有两个对象:一个是通过taskExecutorBuilder方法加载的TaskExecutorBuilder,另外一个是通过applicationTaskExecutor方法加载的ThreadPoolTaskExecutor。

分别看一下。

TaskExecutorBuilder的创建

TaskExecutor构建器,在applicationTaskExecutor方法中负责构建TaskExecutor。

taskExecutorBuilder方法会接收一个参数TaskExecutionProperties ,用来指定TaskExecutor的各属性,比如queueCapacity、coreSize、keepAlive等等线程池相关参数,线程池相关内容请参考:线程池 - ThreadPoolExecutor源码分析。

简单看一眼TaskExecutionProperties类:

@ConfigurationProperties("spring.task.execution")
public class TaskExecutionProperties {

	private final Pool pool = new Pool();

	private final Shutdown shutdown = new Shutdown();

	/**
	 * Prefix to use for the names of newly created threads.
	 */
	private String threadNamePrefix = "task-";

	public Pool getPool() {
		return this.pool;
	}

	public Shutdown getShutdown() {
		return this.shutdown;
	}

	public String getThreadNamePrefix() {
		return this.threadNamePrefix;
	}

	public void setThreadNamePrefix(String threadNamePrefix) {
		this.threadNamePrefix = threadNamePrefix;
	}

	public static class Pool {

		/**
		 * Queue capacity. An unbounded capacity does not increase the pool and therefore
		 * ignores the "max-size" property.
		 */
		private int queueCapacity = Integer.MAX_VALUE;

		/**
		 * Core number of threads.
		 */
		private int coreSize = 8;

		/**
		 * Maximum allowed number of threads. If tasks are filling up the queue, the pool
		 * can expand up to that size to accommodate the load. Ignored if the queue is
		 * unbounded.
		 */
		private int maxSize = Integer.MAX_VALUE;

		/**
		 * Whether core threads are allowed to time out. This enables dynamic growing and
		 * shrinking of the pool.
		 */
		private boolean allowCoreThreadTimeout = true;

		/**
		 * Time limit for which threads may remain idle before being terminated.
		 */
		private Duration keepAlive = Duration.ofSeconds(60);

		public int getQueueCapacity() {
			return this.queueCapacity;
		}

		public void setQueueCapacity(int queueCapacity) {
			this.queueCapacity = queueCapacity;
		}

		public int getCoreSize() {
			return this.coreSize;
		}

		public void setCoreSize(int coreSize) {
			this.coreSize = coreSize;
		}

		public int getMaxSize() {
			return this.maxSize;
		}

		public void setMaxSize(int maxSize) {
			this.maxSize = maxSize;
		}

		public boolean isAllowCoreThreadTimeout() {
			return this.allowCoreThreadTimeout;
		}

		public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {
			this.allowCoreThreadTimeout = allowCoreThreadTimeout;
		}

		public Duration getKeepAlive() {
			return this.keepAlive;
		}

		public void setKeepAlive(Duration keepAlive) {
			this.keepAlive = keepAlive;
		}

	}

正是@EnableConfigurationProperties以及@ConfigurationProperties注解决定了我们可以在配置文件(比如application.yml)中指定TaskExecutionProperties中的这些有关线程池的参数。

接收到这些配置参数之后,使用配置参数创建TaskExecutorBuilder,交给Spring Ioc容器。

ThreadPoolTaskExecutor 的创建

applicationTaskExecutor方法通过上面创建出来的TaskExecutorBuilder的build方法创建。

TaskExecutorBuilderd的build方法:

	public ThreadPoolTaskExecutor build() {
		return configure(new ThreadPoolTaskExecutor());
	}

new了一个ThreadPoolTaskExecutor对象,调用configure方法:

	public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
		PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
		map.from(this.queueCapacity).to(taskExecutor::setQueueCapacity);
		map.from(this.corePoolSize).to(taskExecutor::setCorePoolSize);
		map.from(this.maxPoolSize).to(taskExecutor::setMaxPoolSize);
		map.from(this.keepAlive).asInt(Duration::getSeconds).to(taskExecutor::setKeepAliveSeconds);
		map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut);
		map.from(this.awaitTermination).to(taskExecutor::setWaitForTasksToCompleteOnShutdown);
		map.from(this.awaitTerminationPeriod).as(Duration::toMillis).to(taskExecutor::setAwaitTerminationMillis);
		map.from(this.threadNamePrefix).whenHasText().to(taskExecutor::setThreadNamePrefix);
		map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator);
		if (!CollectionUtils.isEmpty(this.customizers)) {
			this.customizers.forEach((customizer) -> customizer.customize(taskExecutor));
		}
		return taskExecutor;
	}

将配置文件传递过来的参数传递给创建出来的ThreadPoolTaskExecutor对象并返回。

不配置的情况下,线程池默认参数在TaskExecutionProperties中指定:
在这里插入图片描述
ThreadPoolTaskExecutor创建完成!

TaskExecutor的使用

既然Spring Boot已经帮助我们完成了TaskExecutor的创建并注入了Spring Ioc容器中,接下来我们就看一下该怎么使用它。

首先要尝试的是“手动使用”,不使用Spring的注解、而是想办法在代码中直接从Spring容器中获取到TaskExecutor之后调用他的execute方法。

首先创建一个Spring Boot项目,不需要什么特殊功能,pom文件也很简单,引入spring-web即可:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
<!--        <version>3.1.4</version>-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springbootstart</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootstart</name>
    <description>springbootstart</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>



    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

然后,创建一个userService:


@Service
@Slf4j
public class UserService {
    @Autowired
    private TaskExecutor taskExecutor;
    public void test2(){
        taskExecutor.execute(()->{
            log.info("this is userservice test2 start...");
            try{
                Thread.sleep(10000);
            }catch (Exception e){

            }
            log.info("This is userService' test2 end...");
        });
    }
}

userService非常简单,比较重要的是:

    @Autowired
    private TaskExecutor taskExecutor;

这行代码通过@Autowired自动装配一个TaskExecutor 对象,因为我们从前面对Spring Boot代码的分析,Spring Boot应该是在启动的过程中已经通过auto-configuration机制自动创建并注入了TaskExecutor,所以按道理我们是可以通过自动装配的方式在userService中应用它的。

然后写一个test2方法,log看一下装配进来的taskExecutor到底是个啥对象,再调用taskExecutor的execute的方法模拟异步执行任务,执行前后打印log。

然后,写controller:

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloWorldController {

    public HelloWorldController(){
    }
    @Autowired
    private UserService userService;

    @GetMapping ("/test2")
    public String test2(){
        userService.test2();
        log.info("after userservice test2 ...");
        return "hello";
    }
}

OK,代码准备好了,启动应用,测试。通过应用的端口号8002可以正常访问:
在这里插入图片描述

而且,结果可以立即返回,前台并不需要等待userService的test2方法中睡眠的10秒钟,说明睡眠的线程一定是通过taskExecutor调用起来的异步线程,taskExecutor一定是生效了。

后台log也说明确实如此:
在这里插入图片描述
前面的log是前台调用接口、tomcat的线程nio-8002-exec-1打印的,之后taskExecutor启动了新线程task-1,后面的两行日志是线程task-1打印的。

@Async注解

自己写代码使用taskExecutor线程池启动新线程执行任务这种方式虽然行得通,但是太low太繁琐了,既然使用了Spring框架,我们当然不需要这么麻烦。Spring给我们提供了@Async注解。

@Async注解可以用在方法上,也可以用在类上,不管用在方法上、还是用在类上,都要求当前类必须是受Spring管理的bean,因为@Async注解是通过Spring的BeanPostProcessor机制生效的。

我们改造UserService类,再编写一个test方法:

    @Async
    public void test(){
        log.info("This is userService' test start...");
        try {
            Thread.sleep(10000);
        }catch (Exception e){

        }
        log.info("This is userService' test end...");
        return;
    }

代码逻辑也非常简单,和test2方法一样,睡眠10秒后才返回结果。

重新启动应用后测试,发现@Async没有生效!

不生效的原因是缺少@EnableAsync注解,在启动类增加@EnableAsync注解后重新测试,发现@Async生效了,测试结果和test2的一样,所以也就不贴图了。

接下来的任务是,研究@EnableAsync注解的作用,为什么没有@EnableAsync注解的情况下,@Async注解不能生效。

@EnableAsync注解的底层原理

关于Spring的@Enablexxx注解,我们前面的文章分析过,基本就是通过@Configuration+@Import注解的联合使用达到注入指定对象到Spring IoC容器中。

先看@EnableAsync源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

通过@Import注解引入AsyncConfigurationSelector类,继续跟踪AsyncConfigurationSelector代码:

public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";


	/**
	 * Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration}
	 * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()},
	 * respectively.
	 */
	@Override
	@Nullable
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {ProxyAsyncConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
			default:
				return null;
		}
	}

}

扩展了AdviceModeImportSelector类,而AdviceModeImportSelector类实现了ImportSelector接口,而ImportSelector接口这种方式最终是通过他的方法selectImports来实现注入的(这部分可以参考 SpringBoot 自动配置@EnableAutoConfiguration)。

selectImports方法根据adviceMode(默认是PROXY)会引入ProxyAsyncConfiguration类:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {

	@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
		Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected");
		AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
		bpp.configure(this.executor, this.exceptionHandler);
		Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation");
		if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) {
			bpp.setAsyncAnnotationType(customAsyncAnnotation);
		}
		bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass"));
		bpp.setOrder(this.enableAsync.<Integer>getNumber("order"));
		return bpp;
	}

}

ProxyAsyncConfiguration 是一个配置类,会通过@Bean注解注入一个叫AsyncAnnotationBeanPostProcessor 的BeanPostProcessor。从类名称我们就可以猜测到@Async注解就是通过这个后置处理器进行处理的。

接下来的代码跟踪还是稍稍有点复杂的。

首先,AsyncAnnotationBeanPostProcessor 通过父类AbstractBeanFactoryAwareAdvisingPostProcessor实现了BeanFactoryAware接口,所以我们知道他的setBeanFactory方法在Spring的Bean创建过程中会被回调:

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		super.setBeanFactory(beanFactory);

		AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
		if (this.asyncAnnotationType != null) {
			advisor.setAsyncAnnotationType(this.asyncAnnotationType);
		}
		advisor.setBeanFactory(beanFactory);
		this.advisor = advisor;
	}

setBeanFactory方法中会创建一个advisor类AsyncAnnotationAdvisor,从名字中我们又可以猜测到,@Async注解最终应该会通过AOP技术实现。

继续跟踪AsyncAnnotationAdvisor源码,构造器:

	public AsyncAnnotationAdvisor(
   		@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {

   	Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
   	asyncAnnotationTypes.add(Async.class);
   	try {
   		asyncAnnotationTypes.add((Class<? extends Annotation>)
   				ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
   	}
   	catch (ClassNotFoundException ex) {
   		// If EJB 3.1 API not present, simply ignore.
   	}
   	this.advice = buildAdvice(executor, exceptionHandler);
   	this.pointcut = buildPointcut(asyncAnnotationTypes);
   }

调用buildAdvice和buildPointcut,创建切面和切点:

	protected Advice buildAdvice(
   		@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {

   	AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
   	interceptor.configure(executor, exceptionHandler);
   	return interceptor;
   }

构造切面的方法会创建一个AnnotationAsyncExecutionInterceptor 类,回忆一下AOP相关知识,我们知道Pointcut满足的情况下会调用切面类的invoke方法。

构造Pointcut的方法源码我们就不再跟踪了,可以猜测到他的匹配逻辑应该是检查当前方法(或者当前类)是否有@Async注解。

接下来我们就继续跟踪AnnotationAsyncExecutionInterceptor 类。

AnnotationAsyncExecutionInterceptor继承自父类AsyncExecutionInterceptor,invoke方法在他父类AsyncExecutionInterceptor中。

	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
		Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
		final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

		AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
		if (executor == null) {
			throw new IllegalStateException(
					"No executor specified and no default executor set on AsyncExecutionInterceptor either");
		}

		Callable<Object> task = () -> {
			try {
				Object result = invocation.proceed();
				if (result instanceof Future) {
					return ((Future<?>) result).get();
				}
			}
			catch (ExecutionException ex) {
				handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
			}
			catch (Throwable ex) {
				handleError(ex, userDeclaredMethod, invocation.getArguments());
			}
			return null;
		};

		return doSubmit(task, executor, invocation.getMethod().getReturnType());
	}

invoke方法就是实现异步调用的地方!

首先会通过determineAsyncExecutor方法获取taskExecutor,这也是我们关心的地方,不过我们先放放,先看一下拿到TaskExecutor之后的处理逻辑。

代码并不复杂,lamda方式创建一个callable任务,通过invocation.proceed()执行原方法。

通过doSubmit方法、使用TaskExecutor启动新的线程调用task任务、完成对原方法的执行!

主要代码跟踪完毕。

最后,再来看一下determineAsyncExecutor方法:

	protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
		AsyncTaskExecutor executor = this.executors.get(method);
		if (executor == null) {
			Executor targetExecutor;
			String qualifier = getExecutorQualifier(method);
			if (StringUtils.hasLength(qualifier)) {
				targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
			}
			else {
				targetExecutor = this.defaultExecutor.get();
			}
			if (targetExecutor == null) {
				return null;
			}
			executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
					(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
			this.executors.put(method, executor);
		}
		return executor;
	}

总体的逻辑就是,通过beanFactory从Spring Ioc容器中获取TaskExecutor,首先判断是否有QualifiedExecutor,有的话通过findQualifiedExecutor方法从容器中获取QualifiedExecutor,没有的话通过this.defaultExecutor.get()获取。

this.defaultExecutor.get()的业务逻辑需要基于接口AsyncConfigurer来解释:

public interface AsyncConfigurer {

	/**
	 * The {@link Executor} instance to be used when processing async
	 * method invocations.
	 */
	@Nullable
	default Executor getAsyncExecutor() {
		return null;
	}

	/**
	 * The {@link AsyncUncaughtExceptionHandler} instance to be used
	 * when an exception is thrown during an asynchronous method execution
	 * with {@code void} return type.
	 */
	@Nullable
	default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
		return null;
	}

}

AsyncConfigurer 接口有两个方法,一个用来获取Executor,一个用来获取AsyncUncaughtExceptionHandler。

this.defaultExecutor.get()的业务逻辑大概可以概括为:如果应用实现了AsyncConfigurer接口,则通过该接口获取Executor,否则,如果没有提供AsyncConfigurer的实现类,则向Spring Ioc容器获取默认的TaskExecutor:
在这里插入图片描述
OK,Thanks a lot!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/320829.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6、C语言:输入与输出

输入输出 标准输入输出getchar&putchar函数printf函数sprintf函数格式化输入——scanf函数 文件访问文件读写 错误处理&#xff1a;stderr和exit行输入和行输出常用函数字符串操作函数字符类别测试和转换函数存储管理函数数学函数随机数发生器函数其他 标准输入输出 getch…

读写分离的手段——主从复制,解决读流量大大高于写流量的问题

应用场景 假设说有这么一种业务场景&#xff0c;读流量显著高于写流量&#xff0c;你要怎么优化呢。因为写是要加锁的&#xff0c;可能就会阻塞你读请求。而且其实读多写少的场景还很多见&#xff0c;比如电商平台&#xff0c;用户浏览n多个商品才会买一个。 大部分人的思路可…

BDD(Behavior-Driven Development)行为驱动开发介绍

为什么需要BDD&#xff1f; “开发软件系统最困难的部分就是准确说明开发什么” (“The hardest single part of building a software system is deciding precisely what to build” — No Silver Bullet, Fred Brooks) 。 看一下下面的开发场景&#xff1a; 场景一&#xf…

第 13 章图

文章目录 13.1 图基本介绍13.1.1 为什么要有图13.1.2 图的举例说明 13.2 图的表示方式13.2.1 邻接矩阵13.2.2 邻接表 13.3 图的快速入门案例13.4 图的深度优先遍历介绍13.4.1 图遍历介绍13.4.2 深度优先遍历基本思想13.4.3 深度优先遍历算法步骤13.4.4 深度优先算法的代码实现 …

4D 毫米波雷达:智驾普及的新路径(二)

4 4D 毫米波的技术路线探讨 4.1 前端收发模块 MMIC&#xff1a;级联、CMOS、AiP 4.1.1 设计&#xff1a;级联、单芯片、虚拟孔径 4D 毫米波雷达的技术路线主要分为三种&#xff0c;分别是多级联、级联 虚拟孔径成像技术、以及 集成芯片。&#xff08; 1 &#xff09;多级…

跟着暄桐林曦老师读《宝贵的人生建议》,重视心这颗种子

暄桐林曦老师在《见道明心的笔墨》读书课上讲到&#xff1a;人要在心这颗种子上去进化。当人的动机和果实都清静时&#xff0c;才能在内心具足里转化出更多可能性&#xff0c;进入正面的循环里。“宽以待人&#xff0c;严以律己&#xff0c;反之&#xff0c;则人人身处地狱”&a…

基于Java SSM框架实现体育竞赛成绩管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现体育竞赛成绩管理系统演示 摘要 体育竞赛是各种体育体育项目比赛的总称。是在裁判员的主持下&#xff0c;按统一的规则要求&#xff0c;组织与实施的体育员个体或体育队之间的竞技较量&#xff0c;是竞技体育与社会发生关联&#xff0c;并作用于社会的媒…

Javascript——vue下载blob文档流

<el-table-column label"操作" fixed"right" width"150" showOverflowTooltip><template slot-scope"scope"><el-button type"text" v-has"stbsd-gjcx-down" class"edit-button" click&…

超详细的搭建压测平台笔记

0、前言 最近重新回来学习熊哥的极客教程&#xff0c;结合自己学习的shell编程和Docker的指令学习&#xff0c;对熊哥的一些操作做bash脚本自动化&#xff0c;将搭建压测平台的步骤做记录&#xff0c;目的是分享搭建过程。 过程中会安装docker,mysql,redis,influxdb,grafana,…

python 通过定时任务执行pytest case

这段Python代码使用了schedule库来安排一个任务&#xff0c;在每天的22:50时运行。这个任务执行一个命令来运行pytest&#xff0c;并生成一个报告。 代码开始时将job_done变量设为False&#xff0c;然后运行预定的任务。一旦任务完成&#xff0c;将job_done设置为True并跳出循…

基于Java SSM框架实现学生成绩管理系统项目【项目源码+论文说明】

基于java的SSM框架实现学生成绩管理系统演示 摘要 学生成绩是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。而学生所在学院多采用半手工管理学生成绩的方式&#…

【PyTorch简介】3.Loading and normalizing datasets 加载和规范化数据集

Loading and normalizing datasets 加载和规范化数据集 文章目录 Loading and normalizing datasets 加载和规范化数据集Datasets & DataLoaders 数据集和数据加载器Loading a Dataset 加载数据集Iterating and Visualizing the Dataset 迭代和可视化数据集Creating a Cust…

如何使用csdn中的c知道进行学习?

1.c知道 猜测是通过chatgpt训练链接到CSDN内部的文章内容等&#xff0c;进行生成的一款应用。 2.如何使用呢 打比方说&#xff0c;我想学习下多目标跟踪中的ukf&#xff0c;那么就可以输入这个关键字。 那既然是学习&#xff0c;就要进一步深究&#xff0c;有三种方式&#…

LV.13 D11 Linux驱动移植及内核深化 学习笔记

一、设备树 1.1 设备树 设备树是一种描述硬件信息的数据结构&#xff0c;Linux内核运行时可以通过设备树将硬件信息直接传递给Linux内核&#xff0c;而不再需要在Linux内核中包含大量的冗余编码 举例&#xff1a;让LED2闪烁的代码中&#xff0c;有逻辑代码和设备代码。Li…

Django数据库选移的preserve_default=False是什么意思?

有下面的迁移命令&#xff1a; migrations.AddField(model_namemovie,namemov_group,fieldmodels.CharField(defaultdjango.utils.timezone.now, max_length30),preserve_defaultFalse,),迁移命令中的preserve_defaultFalse是什么意思呢&#xff1f; 答&#xff1a;如果模型定…

点击随机红点的简单游戏(pygame)

import pygame import sys import random# 初始化 Pygame pygame.init()# 设置窗口大小 width, height 800, 600 screen pygame.display.set_mode((width, height)) pygame.display.set_caption("Click the Red Dot")# 定义颜色 black (0, 0, 0) red (255, 0, 0)…

Spring来了,春天还会远吗?

结束了JVM的学习后&#xff0c;要进入的是JavaEE进阶的学习了。JavaEE进阶学习内容很多很丰富&#xff0c;并且也很有难度。今天我们就从Spring开始讲起。 目录 框架的好处 怎么学框架 Spring核心与设计思想 容器 IoC Spring IoC DI&#xff08;Dependency Injection&a…

Android 通知

通知 通知是指 Android 在应用界面之外显示的消息&#xff0c;旨在向用户提供提醒、来自他人的通信或应用中的其他实时信息。用户可以点按通知以打开您的应用&#xff0c;或直接从通知中执行操作。 通知在状态栏中显示为图标&#xff0c;在抽屉式通知栏中显示更详细的条目&…

【书生·浦语】大模型实战营——第五课作业

教程文档&#xff1a;https://github.com/InternLM/tutorial/blob/vansin-patch-4/lmdeploy/lmdeploy.md#tritonserver-%E6%9C%8D%E5%8A%A1%E4%BD%9C%E4%B8%BA%E5%90%8E%E7%AB%AF 视频链接&#xff1a; 作业&#xff1a; 基础作业 使用如下命令创建conda环境 conda create…

【期末考试】数据库综合复习宝典

目录 第一章 数据库系统概述 第二章 关系代数 第四章 关系数据库理论 第五章 数据库设计 第六章 数据库管理系统 第八章 事务管理 第一章 数据库系统概述 1.1三级模式 ①外模式&#xff1a;它为特定的应用程序或用户群体提供了一个数据视图&#xff0c;这个视图是独立于…
最新文章