diff --git "a/docs/database/mysql/MySQL-limit\344\274\230\345\214\226.md" "b/docs/database/mysql/MySQL-limit\344\274\230\345\214\226.md" new file mode 100644 index 0000000..67fb0cb --- /dev/null +++ "b/docs/database/mysql/MySQL-limit\344\274\230\345\214\226.md" @@ -0,0 +1,23 @@ +# MySQL limit优化 + +Limit 是一种常用的分页查询语句,它可以指定返回记录行的偏移量和最大数目。例如,下面的语句表示从 demo表中查询 val 等于4的记录,并返回第300001到第300005条记录: + +```sql +select * from demo where val=4 limit 300000,5; +``` + +## MySQL 的索引结构 + +MySQL 支持多种类型的索引,其中最常用的是 B+ 树索引。B+ 树索引是一种平衡多路查找树,它有以下特点: + +- 树中的每个节点最多包含 m 个子节点,m 被称为 B+ 树的阶。 +- 树中的每个节点最少包含 m/2(向上取整)个子节点,除了根节点和叶子节点。 +- 树中的所有叶子节点都位于同一层,并且通过指针相连。 +- 树中的所有非叶子节点只存储键值(索引列)和指向子节点的指针。 +- 树中的所有叶子节点存储键值(索引列)和指向数据记录(聚簇索引)或者数据记录地址(非聚簇索引)的指针。 + + + + + +> https://juejin.cn/post/7278951711553486911 \ No newline at end of file diff --git a/docs/database/mysql/README.md b/docs/database/mysql/README.md index ba41bd9..9a6d521 100644 --- a/docs/database/mysql/README.md +++ b/docs/database/mysql/README.md @@ -8,7 +8,9 @@ [MySQL二进制日志(Binary Log)](MySQL-BinaryLog.md) -## 待分类 +## 小技巧 + +[MySQL-limit优化](./MySQL-limit优化.md) [MySQL-count用法分析](./MySQL-count用法分析.md) diff --git "a/docs/java/base/\347\272\277\347\250\213\346\261\240.md" "b/docs/java/base/\347\272\277\347\250\213\346\261\240.md" index e791a64..f4da174 100644 --- "a/docs/java/base/\347\272\277\347\250\213\346\261\240.md" +++ "b/docs/java/base/\347\272\277\347\250\213\346\261\240.md" @@ -43,8 +43,6 @@ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) > > 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 > -> -> > 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 > > 说明:Executors 返回的线程池对象的弊端如下: @@ -77,7 +75,20 @@ public static void main(String[] args){ } ``` +## 线程数和CPU + +**一个CPU核心,单位时间内只能执行一个线程的指令**,理论上个线程只需要不停的执行指令,就可以跑满一个核心的利用率。 + +现代CPU基本都是多核心的,例如6核心12线程(超线程),可以简单的认为它就是12核心CPU。那么我这个CPU就可以同时做12件事,互不打扰。 + +如果要执行的线程大于核心数,那么就需要通过操作系统的调度了。操作系统给每个线程分配CPU时间片资源,然后不停的切换,从而实现“并行”执行的效果。 + +总结: +1. 一个极端的线程(不停执行“计算”型操作时),就可以把单个核心的利用率跑满,多核心CPU最多只能同时执行等于核心数的“极端”线程数 +2. 如果每个线程都这么“极端”,且同时执行的线程数超过核心数,会导致不必要的切换,造成负载过高,只会让执行更慢 +3. I/O 等暂停类操作时,CPU处于空闲状态,操作系统调度CPU执行其他线程,可以提高CPU利用率,同时执行更多的线程 +4. I/O 事件的频率频率越高,或者等待/暂停时间越长,CPU的空闲时间也就更长,利用率越低,操作系统可以调度CPU执行更多的线程。 ## 线程池参数配置 @@ -85,6 +96,11 @@ public static void main(String[] args){ cpu密集型任务阻塞系数为0,IO密集型一般在0.8-0.9之间。 +常规配置: + +- CPU 密集型的程序: 核心数 + 1 +- I/O 密集型的程序 :核心数 * 2 + ### CPU密集型 CPU密集型任务是指该任务需要进行大量的运算,需要消耗CPU的大量算力,需要CPU的频繁计算,很少情况出现阻塞,所以CPU在处理该类型任务时会处于高速运转。 @@ -116,6 +132,32 @@ IO密集型是指该类型任务在执行时会产生大量的IO(包含磁盘IO 目前实现的思路大多数都是基于美团的关于动态线程池监控的文章。 +## CPU核心数 + +Java 获取CPU核心数: +```java +Runtime.getRuntime().availableProcessors()//获取逻辑核心数,如6核心12线程,那么返回的是12 +``` -> https://juejin.cn/post/7038473601086914597 \ No newline at end of file +Linux 获取CPU核心数: + +```shell +# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 +# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 + +# 查看物理CPU个数 +cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l + +# 查看每个物理CPU中core的个数(即核数) +cat /proc/cpuinfo| grep "cpu cores"| uniq + +# 查看逻辑CPU的个数 +cat /proc/cpuinfo| grep "processor"| wc -l +``` + + + +> https://juejin.cn/post/7038473601086914597 +> +> https://juejin.cn/post/7280429214608146490 \ No newline at end of file diff --git a/docs/spring/spring/README.md b/docs/spring/spring/README.md index 6f0e565..0b5cd3d 100644 --- a/docs/spring/spring/README.md +++ b/docs/spring/spring/README.md @@ -2,6 +2,8 @@ [SpringModulith简介](./SpringModulith简介.md) +[Spring事件](./Spring事件.md) + [Spring事件和事务的关系](./Spring事件和事务的关系.md) [BeanUtils使用注意事项](./BeanUtils.md) diff --git "a/docs/spring/spring/Spring\344\272\213\344\273\266.md" "b/docs/spring/spring/Spring\344\272\213\344\273\266.md" new file mode 100644 index 0000000..db89127 --- /dev/null +++ "b/docs/spring/spring/Spring\344\272\213\344\273\266.md" @@ -0,0 +1,42 @@ +# Spring事件 + + + +## @EventListener + +监听器只需要 在方法上声明为 EventListener注解,Spring就会自动找到对应的监听器。Spring会根据方法入参的事件类型和发布的事件类型自动匹配。 + +服务关闭期间: + +1. **事件发布成功**:发布事件的过程是同步的,因此在事件发布成功之前,应用程序不会继续执行。因此,如果事件发布成功,则意味着事件已被传递到所有的事件监听器。 +2. **监听器无法完成处理**:如果在事件监听器处理事件期间关闭了应用程序,监听器可能无法完成其正常的事件处理逻辑。这可能会导致未完成的操作、资源泄漏或不一致的状态,具体取决于监听器中的操作和应用程序的设计。 + +解决办法: + +1. 优雅停机:并不能完全消除在关闭期间可能发生的问题。如果某些操作耗时很长,或者事件监听器中的操作不受控制,仍然可能在关闭期间引发问题。 +2. 责任链模式了,for循环订阅的策略类。 + +```java +public interface EventSubscriber { + void subscribe(Event event, EventSubscriber nextSubscriber); + void handleEvent(Event event); +} + +public class ConcreteEventSubscriber implements EventSubscriber { + private EventSubscriber nextSubscriber; + + @Override + public void subscribe(Event event, EventSubscriber nextSubscriber) { + this.nextSubscriber = nextSubscriber; + } + + @Override + public void handleEvent(Event event) { + // 处理事件 + if (nextSubscriber != null) { + nextSubscriber.handleEvent(event); + } + } +} +``` + diff --git "a/docs/spring/spring/Spring\346\240\270\345\277\203\344\271\213\351\235\242\345\220\221\345\210\207\351\235\242\347\274\226\347\250\213(AOP).md" "b/docs/spring/spring/Spring\346\240\270\345\277\203\344\271\213\351\235\242\345\220\221\345\210\207\351\235\242\347\274\226\347\250\213(AOP).md" index be626ef..fdb0f26 100644 --- "a/docs/spring/spring/Spring\346\240\270\345\277\203\344\271\213\351\235\242\345\220\221\345\210\207\351\235\242\347\274\226\347\250\213(AOP).md" +++ "b/docs/spring/spring/Spring\346\240\270\345\277\203\344\271\213\351\235\242\345\220\221\345\210\207\351\235\242\347\274\226\347\250\213(AOP).md" @@ -1,3 +1,208 @@ # Spring核心之面向切面编程(AOP) -AOP为Aspect Oriented Programming的缩写,意为:面向切面编程 \ No newline at end of file +文章代码:[spring-learn/spring-aop at main · lizhifuabc/spring-learn (github.com)](https://github.com/lizhifuabc/spring-learn/tree/main/spring-aop) + +AOP为Aspect Oriented Programming的缩写,意为:面向切面编程 + +Spring 框架通过定义切面, 通过拦截切点实现了不同业务模块的解耦,这个就叫**面向切面编程** + +## 基础概念 + +- **前置通知(Before advice)**:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。 +- **后置通知(After returning advice)**:在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。 +- **异常通知(After throwing advice)**:在方法抛出异常退出时执行的通知。 +- **最终通知(After (finally) advice)**:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 +- **环绕通知(Around Advice)**:包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。 + +## Spring AOP和AspectJ + +AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)。 + +**动态织入**和**静态织入**: + +动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术。 + +ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。 + +![img](image/spring-framework-aop-6.png) + +| Spring AOP | AspectJ | +| ------------------------------------------------ | ------------------------------------------------------------ | +| 纯 Java 中实现 | 使用 Java 编程语言的扩展实现 | +| 不需要单独的编译过程 | 除非设置 LTW,否则需要 AspectJ 编译器 (ajc) | +| 只能使用运行时织入 | 运行时织入不可用。支持编译时、编译后和加载时织入 | +| 功能不强-仅支持方法级编织 | 更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等......。 | +| 只能在由 Spring 容器管理的 bean 上实现 | 可以在所有域对象上实现 | +| 仅支持方法执行切入点 | 支持所有切入点 | +| 代理是由目标对象创建的, 并且切面应用在这些代理上 | 在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入 | +| 比 AspectJ 慢多了 | 更好的性能 | +| 易于学习和应用 | 相对于 Spring AOP 来说更复杂 | + +## Spring AOP + +常用配置 + +```java +// 任意公共方法的执行: +execution(public * *(..)) + +// 任何一个名字以“set”开始的方法的执行: +execution(* set*(..)) + +// AccountService接口定义的任意方法的执行: +execution(* com.xyz.service.AccountService.*(..)) + +// 在service包中定义的任意方法的执行: +execution(* com.xyz.service.*.*(..)) + +// 在service包或其子包中定义的任意方法的执行: +execution(* com.xyz.service..*.*(..)) + +// 在service包中的任意连接点(在Spring AOP中只是方法执行): +within(com.xyz.service.*) + +// 在service包或其子包中的任意连接点(在Spring AOP中只是方法执行): +within(com.xyz.service..*) + +// 实现了AccountService接口的代理对象的任意连接点 (在Spring AOP中只是方法执行): +this(com.xyz.service.AccountService)// 'this'在绑定表单中更加常用 + +// 实现AccountService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行): +target(com.xyz.service.AccountService) // 'target'在绑定表单中更加常用 + +// 任何一个只接受一个参数,并且运行时所传入的参数是Serializable 接口的连接点(在Spring AOP中只是方法执行) +args(java.io.Serializable) // 'args'在绑定表单中更加常用; 请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args版本只有在动态运行时候传入参数是Serializable时才匹配,而execution版本在方法签名中声明只有一个 Serializable类型的参数时候匹配。 + +// 目标对象中有一个 @Transactional 注解的任意连接点 (在Spring AOP中只是方法执行) +@target(org.springframework.transaction.annotation.Transactional)// '@target'在绑定表单中更加常用 + +// 任何一个目标对象声明的类型有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行): +@within(org.springframework.transaction.annotation.Transactional) // '@within'在绑定表单中更加常用 + +// 任何一个执行的方法有一个 @Transactional 注解的连接点 (在Spring AOP中只是方法执行) +@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在绑定表单中更加常用 + +// 任何一个只接受一个参数,并且运行时所传入的参数类型具有@Classified 注解的连接点(在Spring AOP中只是方法执行) +@args(com.xyz.security.Classified) // '@args'在绑定表单中更加常用 + +// 任何一个在名为'tradeService'的Spring bean之上的连接点 (在Spring AOP中只是方法执行) +bean(tradeService) + +// 任何一个在名字匹配通配符表达式'*Service'的Spring bean之上的连接点 (在Spring AOP中只是方法执行) +bean(*Service) +``` + +Spring 支持如下三个逻辑运算符来组合切入点表达式: + +```java +&&:要求连接点同时匹配两个切入点表达式 +||:要求连接点匹配任意个切入点表达式 +!::要求连接点不匹配指定的切入点表达式 +``` + +Spring AOP采用的就是基于运行时增强的代理技术: + +1. 接口使用JDK代理 + +2. 非接口使用Cglib代理 + +Spring 使用了@AspectJ框架为AOP的实现提供了一套注解: + +| 注解名称 | 解释 | +| --------------- | ------------------------------------------------------------ | +| @Aspect | 用来定义一个切面。 | +| @pointcut | 用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。 | +| @Before | 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。 | +| @AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。 | +| @Around | 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 | +| @After-Throwing | 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定-一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。 | +| @After | 用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。 | +| @DeclareParents | 用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。 | + +## 示例 + +```java +package com.spring.aop.aspect; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.annotation.*; +import org.aspectj.lang.ProceedingJoinPoint; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.stereotype.Component; + +/** + * LogAspect: 日志切面 + * + * @author lizhifu + * @since 2023/9/17 + */ +@EnableAspectJAutoProxy +@Component +@Aspect +@Slf4j +public class LogAspect { + /** + * 切入点. + */ + @Pointcut("execution(* com.spring.aop.service.*.*(..))") + private void pointCutMethod() { + } + + + /** + * 环绕通知. + * + * @param pjp pjp + * @return obj + * @throws Throwable exception + */ + @Around("pointCutMethod()") + public Object doAround(ProceedingJoinPoint pjp) throws Throwable { + log.info("---------------------环绕通知: 开始---------------------"); + Object o = pjp.proceed(); + log.info("---------------------环绕通知: 结束---------------------"); + return o; + } + + /** + * 前置通知. + */ + @Before("pointCutMethod()") + public void doBefore() { + log.info("前置通知"); + } + + + /** + * 后置通知. + * + * @param result return val + */ + @AfterReturning(pointcut = "pointCutMethod()", returning = "result") + public void doAfterReturning(String result) { + log.info("后置通知, 返回值: {}", result); + } + + /** + * 异常通知. + * + * @param e exception + */ + @AfterThrowing(pointcut = "pointCutMethod()", throwing = "e") + public void doAfterThrowing(Exception e) { + log.error("异常通知, 异常: {}", e.getMessage()); + } + + /** + * 最终通知. + */ + @After("pointCutMethod()") + public void doAfter() { + log.info("最终通知"); + } +} +``` + + + +> https://pdai.tech/md/spring/spring-x-framework-aop.html \ No newline at end of file diff --git a/docs/spring/spring/image/spring-framework-aop-6.png b/docs/spring/spring/image/spring-framework-aop-6.png new file mode 100644 index 0000000..da123d3 Binary files /dev/null and b/docs/spring/spring/image/spring-framework-aop-6.png differ diff --git a/docs/tools/idea/README.md b/docs/tools/idea/README.md index e48ac32..a70f237 100644 --- a/docs/tools/idea/README.md +++ b/docs/tools/idea/README.md @@ -3,3 +3,7 @@ [AI代码插件](./AI代码插件.md) [常见问题](./常见问题.md) + +## 插件 + +mavn helper:解决 Maven 依赖冲突