프로젝트 기술 분석 - 스레드 풀 + 비동기 처리

배경 사용

시스템 데이터 분석 및 결과 읽기 및 쓰기를 포함하여 데이터 양이 많고 직렬 처리가 느리므로 일괄 작업이 수행되고 여러 작업이 서로 간섭하지 않습니다.

처음으로 비동기식

몇 가지 개념

  • 동기 메서드 호출이 시작되면 호출자는 후속 작업을 진행하기 전에 메서드 호출이 반환될 때까지 기다려야 합니다.
  • 비동기 메서드 호출은 메시지 전달에 가깝습니다. 메서드 호출이 시작되면 즉시 반환되며 호출자는 후속 작업을 계속할 수 있습니다. 비동기 메서드는 일반적으로 다른 스레드에서 "실제"로 실행됩니다. 발신자의 작업을 방해하지 않고 전체 프로세스

비동기를 사용하는 이유

전체: 성능 및 내결함성 향상

  • 첫 번째 이유: 내결함성 및 견고성 데이터 쓰기에 이상이 있으면 데이터 쓰기의 이상으로 인해 처리 데이터가 비정상일 수 없습니다. 포인트 발송이 비정상인 경우 사용자에게 등록에 성공했음을 알리고 나중에 비정상적인 포인트를 보상해야 합니다.
  • 두 번째 이유는 성능 향상을 위한 것으로, 예를 들어 사용자 등록에 20밀리초, 포인트를 보내는 데 50밀리초가 소요되며 동기식을 사용하면 총 시간은 70밀리초가 되고 비동기식을 사용하면 기다릴 필요가 없다. 포인트의 경우 20밀리초가 걸립니다.

비동기 구현 - 스레드 풀 기반

1. 비동기 작업을 실행하기 위한 @Async 어노테이션은 비동기 기능을 수동으로 활성화해야 하며, 활성화하는 방법은 @EnableAsync 추가
2. ThreadPoolTaskExecutor는 수동으로 구현

스레드 풀 알아보기-ThreadPool

스레드 풀의 유형(주로 4개)

1. newCachedThreadPool: 무한히 확장할 수 있는 스레드 생성하는 데 사용되며 , 부하가 적은 시나리오에 적합하고 단기 비동기 작업을 실행합니다.
(작업 수행 시간이 짧기 때문에 작업이 빠르게 실행될 수 있고 빠르게 종료 될 수 있으며 과도한 cpu 전환이 발생하지 않습니다. ) 따라서 실제 스레드 수 절대 변경되지 않으며 부하가 많은 시나리오에 적합하며 현재 스레드 수를 제한합니다. (스레드 수를 제어할 수 있는지 확인하고 너무 많은 스레드를 유발하지 않아 시스템 부하가 더 심각해지지 않도록 합니다.)
3. newSingleThreadExecutor: 실행 순서를 보장해야 하는 필요성에 적합한 단일 스레드 스레드 풀을 생성합니다. 다양한 작업의.
4. newScheduledThreadPool: 지연 또는 주기적인 작업을 실행하는 데 적합합니다 .

핵심 매개변수 설정

JAVA 동시 프로그래밍은
대기열 크기와 기본 스레드 풀의 최대 스레드 수가 모두 Integer의 최대값임을 확인할 수 있으며, 이는 분명히 시스템에 특정 숨겨진 위험을 남길 것입니다.

ask: 초당 작업 수, 500~1000 가정
taskcost: 각 작업에 소요된 시간, 0.1s
가정 응답 시간: 시스템에서 허용하는 최대 응답 시간, 1s 가정

CPU를 많이 사용하는 작업의 경우 더 작은 스레드 풀을
사용해 보십시오 . 최대 스레드 수 = CPU 코어 수 + 1입니다. CPU를 많이 사용하는 작업은 CPU 사용률을 매우 높게 만들기 때문에 너무 많은 스레드가 열리면 과도한 CPU 전환이 발생합니다 . IO 집중 작업(이 프로젝트)은 약간 더 큰 스레드 풀을 사용할있으며 최대 스레드 수 = 2 * CPU 코어 수입니다. **IO 집약적 작업의 CPU 사용률은 높지 않기 때문에** CPU는 IO를 기다리는 동안 다른 작업을 처리하기 위해 다른 스레드를 가질 수 있으며 CPU 시간을 최대한 활용할 수 있습니다. 동시 코어 스레드 수 = 최대 스레드 수 * 20% .




스레드 풀 실행 단계

1. pool 크기가 corePoolSize보다 작을 때 새로운 스레드를 생성하여 요청을 처리
2. pool 크기가 corePoolSize와 같을 때 요청을 workQueue (QueueCapacity)에 넣으면 풀의 유휴 스레드는 이동합니다. 3. workQueue가 작업을 내려놓을
수 없을 때 풀에 새로운 스레드를 생성하여 요청을 처리하고 풀 크기가 maximumPoolSize 에 도달하면 RejectedExecutionHandler를 사용하여 거부를 처리합니다 .
4. 풀의 스레드 수가 corePoolSize 보다 크면 중복 스레드는 keepAliveTime을 오랫동안 대기합니다. 처리할 요청이 없으면 자체적으로 파괴됩니다.

구현

응용 프로그램 1 - 주석 **@Async** 메서드

참고 기사
비동기 메소드 호출을 위해 Spring3부터 @Async 어노테이션이 제공되는데, 이 어노테이션을 메소드에 표시하기만 하면 비동기 호출을 구현할 수 있다.
또한 Enable 모듈 드라이버 주석 @EnableAsync를 통해 비동기 기능을 활성화하려면 구성 클래스도 필요합니다.

@Configuration
@EnableAsync
public class ThreadPoolConfig {

}

스레드 풀은 @Async 주석을 사용하여 ThreadPoolExecutor의 생성자를 통해 수동으로 선언해야 하며 기본적으로 실제 스레드 풀이 아닌 SimpleAsyncTaskExecutor 스레드 풀이 사용됩니다 .

스레드 풀 구성

참조 기사 작성자: Piaomiao Jam은
이 스레드 풀을 사용하여 스레드 재사용을 실현할 수 없으며 호출될 때마다 새 스레드가 생성됩니다. 시스템이 스레드를 계속 생성하면 결국 시스템이 너무 많은 메모리를 차지하게 되어 OutOfMemoryError 오류가 발생합니다.

// 核心线程池大小
private int corePoolSize = ;

// 最大可创建的线程数
private int maxPoolSize = ;

// 队列最大长度
private int queueCapacity = ;

// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = ;

비즈니스 클래스

@Component
public class UserDataHandler {
	
 
	private static final Logger LOG = LoggerFactory.getLogger(SyncBookHandler.class);
	/**
	 * @param userdataList 一段数据集合
	 * @param pageIndex 段数
	 * @return Future<String> future对象
	 * @since JDK 1.8
	 */
	@Async
	public Future<String> syncUserDataPro(List<UserData> userdataList,int pageIndex){
		
 
			//声明future对象-主要是为了返回处理信息
		 	Future<String> result = new AsyncResult<String>("");
		 	//循环遍历该段旅客集合
			if(null != userdataList && userdataList.size() >0){
				for(UserData userdata: userdataList){
					try {
						//数据入库操作
						// 针对每一个获取到的切割子段,进行操作 同时进行
					} catch (Exception e) {
						
						//记录出现异常的时间,线程name
						result = new AsyncResult<String>("fail,time="+System.currentTimeMillis()+",thread id="+Thread.currentThread().getName()+",pageIndex="+pageIndex);
						continue;
					}
				}
			}
			return result;
		}

여기에서 다음 사항에 유의해야 합니다.
1. 일괄 데이터 비동기 작업의 구현은 데이터를 분할해야 하므로 여기에 루프 슬라이스가 있습니다
. Future 타입의 값을 반환할 수 있으며, 그렇지 않으면 어노테이션이 유효하지 않으며, 실행 정보를 반환해야 하는 경우이므로 Future를 사용합니다.
3. 데이터베이스 예외 시나리오는 특정 비즈니스 요구 사항에 트랜잭션 및 롤백이 필요한지 여부에 따라 달라집니다. 여기서 내 비즈니스 시나리오는 데이터 손실을 허용합니다.

사용-CountDownLatch

목적: 다음 단계로 이동하기 전에 모든 이전 스레드가 실행되는지 확인하기 위해
비동기 요청 인터페이스가 구현되었지만 효율성이 크게 향상되었습니다. 그러나 비동기 호출로 인해 데이터가 처리되기 전에 처리 결과가 반환됩니다. 현재 호출 스레드 작업의 메서드로 돌아가기 전에 syncUserDataPro의 모든 스레드가 종료될 때까지 기다려야 합니다.

List<List<UserData>> lists = Lists.partition(res, 10);
CountDownLatch countDownLatch = new CountDownLatch(lists.size());
for (List<UserData> list : lists) {
      while (iterator.hasNext()) {
            UserData userData = iterator.next();
            this.userDataService.asyncinsertUserDataList(userData, countDownLatch);
      }
}
countDownLatch.await(); //保证之前的所有的线程都执行完成,才会走下面的;
log.info("完成!!共耗时:{} m秒", (endTime - startTime));
  	@Override
    @Async("threadPoolTaskExecutor")
    public void asyncinsertUserDataList(UserData userData, CountDownLatch countDownLatch)
    {
        try  {
//            log.info("start executeAsync");
            userDataMapper.insertUserData(userData);
//            log.info("end executeAsync");
        } catch(Exception e){
            e.printStackTrace();
        } finally {
            // 无论上面程序是否异常必须执行 countDown,否则 await 无法释放
            countDownLatch.countDown();
        }
    }

보충 - CountDownLatch 이론

CountDownLatch를 사용하면 모든 스레드의 작업이 실행될 때까지 카운트 스레드가 한 곳에서 차단됩니다.

CountDownLatch 개체를 생성할 때 대기해야 하는 스레드 수를 나타내는 초기 개수 값을 지정해야 합니다. 스레드가 작업을 완료할 때마다 CountDownLatch의 countDown() 메서드를 호출하고 카운터 값이 1씩 감소합니다. 카운터 값이 0이 되면 대기 중인 스레드가 깨어나 작업을 재개합니다.

더 자세한 기본 원칙은 AQS 관련 지식을 참조하십시오.

추천

출처blog.csdn.net/weixin_54594861/article/details/130442978