배치잡을 하려면 스레드가 필수!
■ Batch Job 이란?
사용자의 인터랙션 없이 컴퓨터에 의해 일련의 프로그램 집합이 처리되는 것을 의미 : 명령을 내려주는 주체가 없다.
ex) 일주일 이내에 동일한 아이디로 재가입 불가 => 일주일동안은 정보가 보관됨. 일주일 후 삭제 => 배치잡
=> 10만명 동시 탈퇴했다 가정, 월요일 새벽 3시 탈퇴처리 해야한다. -> 그 시간에도 기존 서비스는 영향을 받으면 안된다.
필요한기술:
=> 멀티태스킹이 이루어져야한다. => 멀티 스레딩
백그라운드에서 동작하는 데모 스레드 (눈에 안보이는 스레드)
월요일 새벽 3시를 감지해야함 => 스케쥴러
1. 스레드를 어떻게 동시에 여러개를 이용할 것인가
2. 스레드를 어떤 방식으로 풀링해서 사용할 것인가
->시간이 많이 걸리는 작업은 풀링으로 미리 해두자. 이것을 나눠사용하자 이것을 스레드 풀링이라한다.
->이 스레드풀링을 뭐랑 연결할때 콰츠를 사용하자.
멀티 스레딩 / 멀티 프로세싱의 차이?
프로세스는 독립적인 메모리공간/자원활용공간을 가지고 있다. 독립적인? 서로간의 관여를 하지 않는다.
스레드는 하나의 프로세스 안에서 동작하는 것. 아무리 스레드가 많이 동작해도 프로세스 아이디는 하나이다.
스레드는 독립적인 메모리공간을 가지지 않고 프로세스로부터 정해진 독립적인 메모리공간을 공유하여 사용한다.
A자원을 점유중이라면 또다른 프로세스는 접근할 수 없다. 스레드는 자원관리 시스템도 독립적이지 않아서 공유할 수 있다. 공유의 개념이 깔려있다.
1.프레임웍 없이 배치잡 \languageLab\src\kr\or\ddit\batch\core
매 1초 마다 1씩 증가하는 일련번호를 콘솔에 출력하라. : 멀티스레딩으로 해보자.
>작업 : 출력하라
>스케츌링 : 1초마다
작업과 스케쥴링을 구분
Runnable : The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread.
package kr.or.ddit.batch.core;
/**
* 매 1초 마다 1씩 증가하는 일련번호를 콘솔에 출력하라.
*
*/
public class PlaygroundCore {
public static void main(String[] args) {
//메인스레드 하나 동작 -> 싱글스레드
PrintNumberJob job = new PrintNumberJob();
// job.run();
Thread thread = new Thread(job); //이스레드를 운영할 잡
thread.start(); // 메인스레드 + 메인스레드에서 파생된 스레드 넌데몬스레드 => 두개의 스레드 동작중
// job.run(); // 문장 영원히 종료가 안되어서 실행이 안된다.
System.out.println("이 문장의 출력 시점 예측.");
// 스레드가 1초동안 점유되어있는데 그때 콘솔이 풀리고 그때 이 문장이 출력될 수 있다.
}
public static class PrintNumberJob implements Runnable{
private int number; // 전역으로 선언
@Override
public void run() {
while(true) {
//Thread.currentThread().getName() -> 스레드 정보 확인방법
System.out.printf("%d-%s\n", ++number, Thread.currentThread().getName());
try {
Thread.sleep(1000); // 1초간 아무것도 안한다.
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
단점
1. 종료시점을 명시하지 못한다.
2. 한 스레드 안에 스케쥴링과 작업이 같이 들어간다.
* 1씩 증가하는 일련번호를 콘솔에 출력하라. - job1
* 매 1초 마다 job1 을 새로 생성하는 작업을 수행하라 - job2.
1. 메인스레드 2.스케쥴링 스레드 3. 작업 스레드
왜 문장이 run하면 실행이 안되는거야?
package kr.or.ddit.batch.core;
/**
* 1씩 증가하는 일련번호를 콘솔에 출력하라. - job1
* 매 1초 마다 job1 을 새로 생성하는 작업을 수행하라 - job2.
*
*/
public class PlaygroundCore {
public static void main(String[] args) {
ExecuteJob job2 = new ExecuteJob();
new Thread(job2).start();
System.out.println("이 문장의 출력 시점 예측.");
}
public static class ExecuteJob implements Runnable {
@Override
public void run() {
int number = 1;
while(true) {
PrintNumberJob job1 = new PrintNumberJob(number++);
Thread thread = new Thread(job1); //만들어진잡을 새로운 스레드로 운영
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class PrintNumberJob implements Runnable{
private int number; // 전역으로 선언
public PrintNumberJob(int number) {
super();
this.number = number;
}
@Override
public void run() {
System.out.printf("%d-%s\n", ++number, Thread.currentThread().getName());
}
}
}
고도의 기술로 만든 스레드를 숫자하나만 달랑 쓰고 출력해서 끝냄. 낭비임 그래서 풀링이라는 것이 필요함
\languageLab\src\kr\or\ddit\batch\pooling
1.커넥션을 몇개 생성할거냐
2.추가 요구를 어떤식으로 처리할거냐 ==> 풀링정책이 된다.
풀링 정책을 어떤 식으로 적용할 것인가?
풀링에선 스레드를 직접 가지고 놀지 않는다.
new Thread(job2).start();
이 코드가 사라진다.
그러면 개발자가 고나리할 수 있는 도구가 필요하다. ==> ThreadPoolExecutor : 스래드 풀링 실행자.
기본생성자가 없다.
CorePoolSize 처음 스레드 생성 수
MaximumSize 손님 더오면 더 만들어서 만들어지는 최대 스레드 갯수
keepAliveTime 언제까지 기다리게 할거냐
TimeUnit 단위 (보통 밀리세컨드인데 초단위할거면 초단위 선택하면됨.)
BlockKingQueue 큐:선입선출 , 대기석
처음 3개 만들고 5개까지 만들었어 타임 단위로 기다리다가 5개만큼 기다릴 수 있어
총관리하는 수는? 10개
RejectedExecutionHandler : 손님 자리 없어여 하고 돌려보내는 역할
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(5));
ExecuteJob job2 = new ExecuteJob();
executor.execute(job2);
System.out.println("이 문장의 출력 시점 예측.");
}
1번은 메인에서 사용하고있
자원이 돌려막기 되고있어서 4, 5안만들어도 됨
-장점
1. 부하가 적다. 2. 자원의 재활용 => 메모리의 효율성
2.스프링 코어만 이용해서 배치잡 \springIocLab\src\main\java\kr\or\ddit\task\PrintNumberJob.java
스프링은 포조를 하고싶어해. 그래서 러너블을 임플리먼츠 할 필요가 없어.
package kr.or.ddit.task;
import org.springframework.scheduling.annotation.Scheduled;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class PrintNumberJob {
private int number;
@Scheduled(initialDelay = 0, fixedRate = 1000)//스케쥴링정책
public void print() {
//포조라서 실제 사용할 메서드의 시그니쳐도 내맘대로 가능
log.info("number : {}, thread name : {}", ++number, Thread.currentThread().getName());
}
}
<task:executor id="executor" pool-size="3" keep-alive="2" queue-capacity="5"/>
1초마다를 카운팅할 스레드를 스레드 풀링으로 작업했었는데, 여기서는 api가 형성되어있다.
>> spring task-context 생성하고 풀링정책 설정
pool-size="3" core이면서 max
keep-alive="2" 여기는 초단위
queue-capacity="5" 대기석 >> 스레드를 풀링한다는 개념은 없지만 사용이 가능한 설정이 다 있다.
1초마다가 executor에는 없어서 강제로 sleep을 해주었는데 여기의 scheduler는 정할 수 있다.
<task:scheduler id="scheduler" pool-size="3"/>
default 1초
<task:annotation-driven executor="executor" scheduler="scheduler"/>
<bean class="kr.or.ddit.task.PrintNumberJob" />
빈등록
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd">
<bean class="kr.or.ddit.task.PrintNumberJob" />
<task:executor id="executor" pool-size="3" keep-alive="2" queue-capacity="5"/>
<task:scheduler id="scheduler" pool-size="3"/>
<task:annotation-driven executor="executor" scheduler="scheduler"/>
</beans>
1초마다가 빠졌다.
initialDelay = 0, fixedDelay = 1000 바로시작한 첫번째 작업이 끝나자마자 1초뒤에 작업
initialDelay = 0, fixedRate = 1000 바로시작한 첫번재 작업이 시작하자마자 1초뒤에 작업 시작
package kr.or.ddit.task;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class PlaygroundSpringTask {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("kr/or/ddit/task/conf/task-context.xml");
}
}
풀링되고있는 스레드만 사용하여 3번은 나오지 않고 2번까지만 나오고 있다.
구체적인 스케쥴링을 표현할 크론표현식이 필요하다.
초,분,시,일,월,요일 - 6개 자리 의미
0 * * * * mon-fri : 주말마다 1초마다 뭐 한다. 시작시점상관없이 무조건 0초마다
* * * * * mon : 월요일마다 1초마다 뭐 한다.시작시점상관없이 무조건 1초마다
5 * * * * * : 매일마다 5초일때에 -> 1분5초, 3분 5초...
0 0 * * * * : 0분 0초일대마다
0 0 3 ? * mon : 월요일 새벽3시 정각마다 -> 정각 정각 3시 일은언제일지몰라 매달 월요일마 -> 요일과 일은 상충되니까 물음표
* * * 5 * MON : 매월 월요일이 5일이
* 0 15 ? * MON: 월요일 오후 3시에 1초마다
*/5 * * * * *: 5초마
//멀티스레딩 스레드풀링이 기저에 작용중이다.!
https://www.manpagez.com/man/5/crontab/
man page crontab section 5
manpagez: man pages & more man crontab(5) man pages By Section Choose... 123456789ln Alphabetically Choose... ABCDEFGHIJKLMNOPQRSTUVWXYZOther Other versions ofcrontab Choose... osx-10.6osx-10.5osx-10.4osx-10.3 crontab(5) BSD File Formats Manual crontab(5)
www.manpagez.com
크론표현법 설명
spring task는 거의 소규모에서 사용
볼륨이 큰 엔터프라이즈 규모에서는 한개의 스케쥴러가 최대한 많은 잡을 돌리도록하려고 그만큼 많은 ...
그래서 콰츠를 사용하자. !
3.콰츠이용해서 배치잡
Quartz Enterprise Job Scheduler
잡을 실행하는데 스케쥴러까지 밑에 깔고 실행한다는 것
동시에 실행할 잡의 갯수가 거의 무한대이다. 그래서 엔터프라이즈
이 콰츠와 인티그레이션하여 대부분의 잡 운영되고 있다.
c3p0 풀링하는데 필요한것
노란색 두개 : 스프링과 콰츠를 연동
스프링쓰던안쓰던 잡은 늘 피룡
그런데 스케쥴링은 콰츠가 가져가기 때문에 스케쥴링어노테이션은 이제 필요가 없다.
7.6.1. Using the JobDetailFactoryBean 콰츠는 잡을 잡디테일이라고 표현한다.
얘를 등록하면 팩토리로부터 만들어진 잡디테일이 등록된다.
<bean name="exampleJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="example.ExampleJob"/>
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="5"/>
</map>
</property>
</bean>
package example;
public class ExampleJob extends QuartzJobBean {
private int timeout;
/**
* Setter called after the ExampleJob is instantiated
* with the value from the JobDetailFactoryBean (5)
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
포조가 아닌형태로 예시가 올라와있음 이건 스프링컨테이너에 들어가지 않고 여타의 다른 인젝션을 받지 못하게 된다.
그래서 스프링 컨테이너에의해서 관리되는 빈이 필요하기때문에 이 예제를 사용할 수 없다.
<bean class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"></bean>
특정 메서드를 호출하는 잡
잡하나를 표현하고있는 메서드를 호출한다는 뜻
잡 패키지는 사실 메서드가 진짜 잡
그럼 어떤 잡이 가진 어떤 메서드
p:targetObject-ref="printNumberJob"
포조이기에 정해진 형이 없어서 오브젝트로 받는다.
<!-- 1. JobDetail 등록 + custom job -->
<bean id="methodInvokingJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="printNumberJob"
p:targetMethod="print"
/>
여기 어디에도 시간의 개념이 없다.
월요일 새벽 3시가 방아쇠가 되어야한다. 콰츠에서는 트리거라고 표현
<!-- 2. Trigger 등록 : jobDetail 주입 + cron expression (조건) -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"
p:jobDetail-ref="methodInvokingJobDetail"
p:cronExpression="* * * * * *"
/>
한개의 스케츌러에 여러개의 트리거를 줄 수 있다.
새벽3시에 탈퇴조치와 마일리지 처리 둘다 해줘야하는 경우
trigger가 배열이다.
근데 우리 트리거는 배열이 아니어서 p: 쓸수없다.
<!-- 3. Scheduler 등록 : trigger 주입 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<array>
<ref bean="cronTrigger"/>
</array>
</property>
</bean>
</beans>
메인메서드, 어플리케이션 엔트리 포인트 필요
Caused by: java.text.ParseException: Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.
p:cronExpression="* * * * * ?"
Caused by: java.lang.ClassNotFoundException: org.springframework.transaction.TransactionException
트랜잭션 관리가 필요하다.
트랜잭션 api는 jdbc가 필요하다.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
tx디펜던시 추가
톰캣은 내부적으로 요청하나를 스레드 하나로 처리
스레드풀에서 가져와서 처리했다.
톰캣은 20개의 스레드로 설정되어 있다.
웹으로 어떻게 가져가지?
pom.xml tx는 이미있다.
콰츠 스케줄러는 상위에
'내가 보려고 정리하는 > Spring' 카테고리의 다른 글
웹소켓 (0) | 2023.05.08 |
---|---|
시큐리티 2 - db연결, 비밀번호 단방향 암호화(0503) (1) | 2023.05.03 |
시큐리티 (0) | 2023.05.02 |
검색 조건 설정하는 쿼리문 searchFrag (0) | 2023.04.13 |
웹 : 전략객체에게 검증 넘기기, Tiles사용해보기 : 0411 (0) | 2023.04.11 |