多线程原因:
导致高并发情况下接口性能下降。
难点:
高并发下的异步线程记录
SpringUtils 研究下
线程池:主业务记个日志,异步需要给系统有个间隔切换,一般10ms。
线程池全局只能有1个,类似一个大别墅,只有一个池
线程池参数如何设置?不能写死!!!
高大上:分布式配置中心+动态线程池
标准:yml文件读取线程池配置【写个类封装 专门读取配置】 跟主业务无关的 按面向对象思想封装OOP
不要用超过5个@Value来获取值 应该单独读到配置类里使用更合适
yaml配置
thread:
pool:
core-pool-size: 5
# 默认是5个人记录 如果是高并发的话直接把下面两个放大到1000和5000 如果1秒来了1w个
# 我有1000个人来记录 性能就上来了
max-pool-size: 10
queue-capacity: 50
keep-alive-seconds: 60
@Confighration
@ConfigurationProperties(prefix = "thread.pool")【yaml配置里的前缀】
public class ThreadPoolProperties{
private int corePoolSize;
private int maxPoolSize;
}
还需要个线程池类
@Configuration
public class ThreadPoolConfig{
// 线程池配置
@Resource
private ThreadPoolProperties threadPoolProperties;
// 创建线程池 最通用 最标准 普通版
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程池大小
executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
// 最大可创建线程数
executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
// 等待队列最大长度
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
// 线程池维护线程所允许的空闲时间
executor.setKeepAliveSconds(threadPoolProperties.getKeepAliveSeconds());
// 线程池对拒绝任务(无线程可用的处理策略)
executor.setRejectedExectiomHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
// 定时任务版 线程池里的线程每隔10ms或1秒执行一次
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService(){
return new ScheduledThreadPoolExecutor(threadPoolProperties.getCorePoolSize(),
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()){
@Override
protected void afterExecute(Runnable r,Throwable t){
super.afterExecute(r,t);
ThreadUtils.printException(r,t);
}
};
}
}
面试:线程池你如何使用?
1.YML配置线程池核心参数
2.不可以多个@Value读取,多个配置项使用ThreadPoolProperties封装进一个具体的配置类
3.ThreadPoolConfig封装好的线程池对象里面要有2个方法,
1.普通调用
2.定时间隔调用
这里当请求过来后5-10ms后才启动异步定时任务的线程去插入日志,有点时间间隔,不要让系统负担太重
异步工厂
public class AsyncFactory {
// 记录操作日志 OperationLogVo就是数据库操作日志表对应的那个对象
public static TimerTask recordOperation(OperationLogVo operationLog){
// 这里其实是java8写法 new了个TimeTask定时任务
return () -> {
// 容器捞鱼🐟 再save
SpringUtils.getBean(OperationLogService.class).saveOperationLog(operationLog);
};
}
}
@Service
public class OperationLogService{
@Resource
private OperationLogVoMapper operationLogVoMapper;
public void saveOperationLog(OperationLogVo operationLogVo){
operationLogVoMapper.insertSelective(operationLogVo);
}
}
异步管理层
// 因为线程只需要有一个AsyncManager来管理就够了
public class AsyncManager {
// 单例模式确保类只有1个实例
private AsyncManager(){}
// 饿汉式 在类加载的时候立刻实例化
private static final AsyncManager INSTANCE = new AsyncManager();
public static AsyncManager getInstance(){return INSTANCE;}
// 异步操作任务调度线程池
private final ScheduledExecutorService scheduledExecutorService = SpringUtils.getBean("ScheduledExecutorService");
// 执行任务
public void execute(TimerTask task){
scheduledExecutorService.schedule(task,10,TimeUnit.MILLISECONDS);
}
// 停止任务线程池
public void shutdown(){
ThreadUtils.shutdownAndAwaitTermination(scheduledExecutorService);
}
1.异步管理层AsyncManager
2.异步工厂直接生成异步任务,配合自定义间隔时间的线程池使用,每次TimeTask,每次间隔10毫秒做一次操作,多线程来完成。
@Ansy不灵活,AnsyFactory和AnsyManager更加灵活使用
切面
只要用到自定义注解就想到反射+aop
然后想到joinPoint.getSignature()方法签名【调用的什么方法 比如Controller的addUser方法】
joinPoint.getTarget().getClass().getSimpleName(); 获得当前正在使用的那个类 还可以拿到类名
// 这里就不写pointCut了 只要带有OperationLog注解 就执行环绕通知
@Around("@annotation(com.xxx.xxx.annotation.OperationLog)")
// 环绕通知
// 容器捞鱼设计思想
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
// 1.获取方法名
String methodName = joinPoint.getTarget().getClass().getSimpleName();
try{
// 2.记录操作日志 拼装Log对象
OperationLogVo operationLogVo = this.recordLog(JoinPoint);
}catch(Exception e){
log.error("没有合适的web请求",e);
}
// 3.记录业务耗时和其他参数等
try{
result = joinPoint.proceed();
}catch(Throwable e){
// 这里异常用的是commons-lang3 依赖 封装好的
log.error("method:{},throws:",methodName,ExceptionUtils.getStackTrace(e));
if(operationLogVo != null){
// 执行错误也要记录日志
operationLogVo.setErrorMsg();
}
}finally{
}
}