스쿱은 관계형 데이터베이스와 하둡 사이에서 데이터 이관을 지원하는 툴이다. 스쿱을 이용하면 관계형 데이터베이스의 데이터를 HDFS, 하이브, H베이스, Accumulo에 임포트하거나 반대로 익스포트할 수 있다. 스쿱은 클라우데라에서 개발했으며, 현재 아파치 오픈소스 프로젝트로 공개되었다. Sqoop
스쿱은 관계형 데이터베이스를 읽고 쓸 수 있는 커넥터라는 개념을 사용한다. 커넥터는 각 데이터베이스별로 구현돼 있으며, JDBC 드라이버를 이용해 데이터베이스 접속 및 질의 실행을 요청한다.
옵션 | 내용 |
---|---|
–usename | 데이터베이스 접속 계정 |
–password | 데이터베이스 접속 암호 |
–connect | JDBC 접속 URL |
–table | 임포트 대상 테이블 |
–columns | 특정 칼럼만 임포트할 경우 칼럼명을 설정. 이 옵션을 설정하지 않을 경우 전체 칼럼을 임포트 |
–target-dir | 임포트 결과를 저장할 HDFS 디렉터리. 별도로 설정하지 않을 경우 “/user/계졍명/테이블명”에 데이터가 저장됨 |
–query | 특정 테이블을 설정하지 않고 질의문 실행 결과를 임포트. 단 이 옵션을 사용할 경우 $CONDITIONS를 반드시 WHERE절에 사용해야 함. $CONDITIONS는 스쿱이 자동으로 생성하는 WHERE 조건을 나타내는 키워드임 |
-m,–num-mappers | 실행할 맵 태스크 개수, 데이터베이스에 부하를 줄 수 있으므로 적절한 태스크 개수를 설정해야함. |
–where | 임포트할 때 사용할 WHERE절 |
-z, –comppress | 임포트 결과를 압축 |
–compression-codec | 하둡 압축 코덱, 기본값으로 Gzip을 사용함 |
–as-textfile | 임포트 결과를 텍스트 파일로 저장. 스쿱은 텍스트 파일을 기본값으로 사용함 |
–as-sequencefile | 임포트 결과를 시퀀스파일로 저장 |
–null-string<널갑문자열>널갑문자열> | 문자열 칼럼에서 널 값 대신 사용할 문자열 |
–null-non-string<널값 문자열="">널값> | 문자열이 아닌 칼럼에서 널 값 대신 사용할 문자열 |
–direct | 고속 커넥터를 이용할 경우 사용 |
옵션 | 내용 |
---|---|
–export-dir | 익스포트 대상 HDFS 디렉터리 |
-m, –num-mappers | 실행할 맵 태스크 개수, 데이터베이스에 부하를 줄 수 있으므로 적절한 태스크 개수를 설정해야함 |
–table | 데이터베이스 최종 대상 테이블 |
–staging-table | 데이터베이스 중간 데이터 저장용 테이블 |
–clear-staging-table | 중간 데이터용 데이블 데이터 삭제 여부 |
-null-string<널값 문자열="">널값> | 문자열 칼럼에서 널 값 대신 사용할 문자열 |
–null-non-string<널값문자열>널값문자열> | 문자열이 아닌 칼럼에서 널 값 대신 사용할 문자열 |
–direct | 고속커넥터를 이용할 경우 사용 |
logback은 SLF4J의 native 구현체이다. slf4j로 어플리케이션 로그를 남긴다면 logback을 선택하는 것이 좋다고 한다. slf4j의 도움으로 연관 라이브어리들이 다른 loggin framework를 쓰더라도 logback으로 통합할 수 있다.
logbac은 logback-core
, logback-classic
, logback-access
의 3개의 모듈이 있다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="example.logback.level.grandparents" level="TRACE"/>
<logger name="example.logback.level.grandparents.parents.children" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
여기서 root는 root logger를 말한다. logger name=”“과 같은 뜻이라고 생각하면 된다.
<logger name="example.logback.level.grandparents" level="TRACE"/>
example.logback.level.grandparents
이하 모든 Logger들의 level은 TRACE라는 설정이다.
log level은 다음과 같다.
Event마다 Log를 기록하는 기능은 Appender가 처리한다. 그래서 Logger는 어떤 Appender에 해당이 되어 처리 되는지가 중요하다. Appender를 설정해도 log출력에 해당되지 않으면 작동하지 않는다.
Appender는 출력될 형식을 직접 가지고 있이 않고, 해당 기능은 Layout과 Encoder에 위임한다.
ConsoleAppender는 OutputStreamAppender를 상속한다. encoder, pattern으로 PatternLayoutEncoder가 생성해서 Appender에 주입된다.
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
PatternLayoutEncoder는 pattern으로 받은 값을 이용해서 PatternLayout을 생성하고 PatternLayoutEncoder는 log messag를 byte[]로 변환하는 기능도 포함한다. 이렇게 되면 Appender는 Layout기능과 Encoder기능을 모두 가지게 된다. 이것을 이용해 OutputStreamAppender는 byte[]를 OutputStream에 write하게 된다.
참고 - http://logback.qos.ch/manual/layouts.html#ClassicPatternLayout
<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
the key "bySecond" into the logger context. This value will be
available to all subsequent configuration elements. -->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>log-${bySecond}.txt</file>
<append>true</append>
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
로그가 저장될 file을 선언하고, ConsoleAppender처럼 encoder, pattern을 선언하게 되면, log event를 지정된 file에 저장할 수 있다. 이때 파일 포맷중 날짜 형식은 java.text.SimpleDateFormat을 따른다.
<file>
태그에 파일명만 작성하면 톰켓으로 구동할 경우 톰켓 bin
폴더에 로그파일이 생성된다.
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
...
<prudent>true</prudent>
...
</appender>
<prudent>
태그는 file에 저장될때 lock을 생성해서 처리한다. 서로 다른 java vm이 같은 파일을 가릴킬때 사용한다. 성능이 저하되는 단점이 있다.
log가 많아지면 file하나당 최대 용량 제한도 있고, 로그를 파악하기도 어렵다. 이때 대부분 날짜를 기준으로 file을 남긴다. 따로 crontab으로 매일 file을 rename해서 처리할 수도 있지만, logback은 RollingFileAppender로 처리할 수 있다.
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
rollingPolicy로 rolling정책을 등록할 수 있다. fileNamePattern으로 file pattern을 선언하고, <timeBasedFileNameingAndTriggeringPolicy>
로 파일마다 트리거를 걸어 파일 최대 용량을 설정하면 새로운 index이름으로 파일을 생성한다.
filesize는 끝문자열에 kb,mb,gb를 인식하고 대소문자 구분은 없다. whitespace는 모두 무시한다.
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt.zip</fileNamePattern>
확장자에 .zip
을 선언하면 새로운 file이 생성될때 이전 파일은 .zip으로 압축을 할 수 있다.
아래 설정으로 logback이 구동될때 logback상태를 확인 할 수 있다.
<configuration debug="true">
...
</configuration>
additivity의 default값은 true이다. logger name이하 모두 적용이 되는데 additivity를 false로 설정하면 해당 name에만 looger가 적용된다.
<logger name="XXX" level="DEBUG" additivity="false"/>
loggr들은 name으로 등록이 된다. 기본적으로 java package구조와 동일하게 적용한다. tree구조이기때문에 최상단 root을 적용하면 모든 tree이하에 적용 할 수 있다. 이 설정으로 logger를 대신한다.
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
모든 대상에 STDOUT Appender를 적용하고 level이 DEBUG이하인 것만 처리로 설정된다.
간단한 쇼핑몰 log를 json으로 남겨본다.
우선 logback.xml
대신에 logback-sping.xml
파일을 만든다.
내용은 아래와 같다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<appender name="FILE-AUDIT"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/tmp/test.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>
%msg%n
</Pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${DEV_HOME}/archived/debug.%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<logger name="com.newace.model" level="TRACE" additivity="false">
<appender-ref ref="FILE-AUDIT"/>
</logger>
</configuration>
logger를 com.newace.model아래에 TRACE level아래의 모든 로그로 설정하였다.
log모델은 다음과 같다.
public class LogSumModel extends BaseModel {
private static final Logger LOGGER = LoggerFactory.getLogger(LogSumModel.class);
private static final LogSumModel LOG_LOGIN = new LogSumModel("login");
private static final LogSumModel LOG_ADD_CART = new LogSumModel("cart");
private static final LogSumModel LOG_CATEGORY = new LogSumModel("category");
private static final LogSumModel LOG_PRODUCT_BUY = new LogSumModel("buy");
private static final LogSumModel LOG_PRODUCT_DETAIL = new LogSumModel("detail");
private String action;
private long regDate;
private int userId;
private int categoryNum;
private int productNum;
private boolean isCart;
private LogSumModel() {
}
private LogSumModel(String action) {
this();
this.action = action;
}
public static void writeLoginLog(@NotNull UserModel userModel) {
LOGGER.trace(LOG_LOGIN.setRegDate(System.currentTimeMillis())
.setUserId(userModel.getUserId())
.toString());
}
public static void writeAddCartLog(@NotNull UserModel userModel, @NotNull ProductModel productModel) {
LOGGER.trace(LOG_ADD_CART.setRegDate(System.currentTimeMillis())
.setProductNum(productModel.getProductId())
.setUserId(userModel.getUserId())
.toString());
}
public static void writeProductBuyLog(@NotNull UserModel userModel, @NotNull ProductModel productModel, boolean isCart) {
LOGGER.trace(LOG_PRODUCT_BUY.setRegDate(System.currentTimeMillis())
.setProductNum(productModel.getProductId())
.setUserId(userModel.getUserId())
.setCart(isCart)
.toString());
}
public static void writeProductDetailLog(@NotNull UserModel userModel, @NotNull ProductModel productModel) {
LOGGER.trace(LOG_PRODUCT_DETAIL.setRegDate(System.currentTimeMillis())
.setProductNum(productModel.getProductId())
.setUserId(userModel.getUserId())
.toString());
}
public static void writeCategoryLog(@NotNull UserModel userModel, @NotNull CategoryModel productModel) {
LOGGER.trace(LOG_CATEGORY.setRegDate(System.currentTimeMillis())
.setUserId(userModel.getUserId())
.setCategoryNum(productModel.getCategoryId())
.toString());
}
.... SeterGeter메소드
}
BaseModel에서는 object를 json으로 변환하는 함수를 상속해준다.
public class BaseModel {
private static ObjectMapper mapper = new ObjectMapper();
@Override
public String toString() {
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
로그 모델객체를 생성해 로그를 찍고 싶은 곳에서 사용하면 된다. 결과는 아래그림과 같다.
for (Node* node = list->head; node != NULL; node = node->next)
Print(node->data);
Node* node = list->head;
if (node == NULL) return;
while(node->next != NULL) {
Print(node->data);
node = node->next;
}
if (node != NULL) Print(node->data);
return exponent>=0 ? mantissa = (1 << exponent) : mantissa / (1 <<-exponent);
if (exponent >= 0) {
return mantissa * (1 << exponent);
} else {
return mantissa / (1 << -exponent);
}
핵심
코드는 "다른 사람"이 그것을 "이해"하는 데 들이는 시간을 최소화하는 방식으로 작성해야한다.
GetPage()
def GetPage(url):
...
FetchPage()
, DownloadPage()
Size()
class BinaryTree{
int Size();
...
}
Stop()
class Thread {
void Stop();
}
Kill()
Resume()
등으로 다시 돌이킬 수 있다면 Pause()
보다 다양한 단어를 활용
단어 | 유의어 |
---|---|
send | deliver, dispatch, announce, distribute, route |
find | search, extract, locate, recover |
start | launch, create, begin, open |
make | create, set up, build, generate, compose, add, new |
retval
retval
을 쓰지 않기tmp
tmp라는 이름은 대상이 짧게 임시적으로만 존재하고, 임시적 존재 자체가 변수의 가장 중요한 용도일 때 한해서 사용해야한다.
i
,j
,k
for (int i = 0; i < clubs.size(); i++)
for (int j = 0; j < clubs[i].members.size(); j++)
for (int k = 0; k < users.size(); k++)
if (clubs[i].members[k] == users[j])
...
이런 경우는
i
–> club_i
, j
–> members_j
, k
–> users_k
이런 식으로 명확하게 한다.
tmp, it, retval 같은 보편적인 이름을 사용하려면, 꼭 그렇게 해야하는 이유가 있어야 한다.
if (!(file_exists && !is_protected)) Error("...");
if(!file_exists || is_protected)) Error("...");
영리하게 작성한 코드에 유의하라. 나중에 다른 사람이 읽으면 그런 코드가 종종 혼란을 초래한다.
변수가 적용되는 범위를 최대한 좁게 만들어라
class LargeClass {
string str_;
void Method1() {
str_ = ...;
Method2();
}
void Method2() {
// str_ 변수 사용하기
}
// str_을 사용하지 않는 다른 메소드...
};
class LargeClass {
void Method1() {
string str = ...;
Method2(str);
}
void Method2(string str) {
// str 변수 사용하기
}
};
삼항연산자은 X ? {true,false}
라고 적는다. 이를 Predicate로 표현하면 preicate on X
라고 말해주면 된다.
java 8
에서는 Predicate 함수를 인터페이스로 만들어 lambda 표현식이나 method reference로 사용할 수 있게 하였다.
package predicateExample;
public class Employee {
public Employee(Integer id, Integer age, String gender, String fName, String lName){
this.id = id;
this.age = age;
this.gender = gender;
this.firstName = fName;
this.lastName = lName;
}
private Integer id;
private Integer age;
private String gender;
private String firstName;
private String lastName;
//Please generate Getter and Setters
@Override
public String toString() {
return this.id.toString()+" - "+this.age.toString(); //To change body of generated methods, choose Tools | Templates.
}
}
직원 객체를 만들었다. 자 이제 Predicate
를 사용해보자!!
public static Predicate<Employee> isAdultMale() {
return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
public static Predicate<Employee> isAdultFemale() {
return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
public static Predicate<Employee> isAgeMoreThan(Integer age) {
return p -> p.getAge() > age;
}
세가지 Predicate를 하나의 클래스로 만들어 관리하자
package predicateExample;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class EmployeePredicates
{
public static Predicate<Employee> isAdultMale() {
return p -> p.getAge() > 21 && p.getGender().equalsIgnoreCase("M");
}
public static Predicate<Employee> isAdultFemale() {
return p -> p.getAge() > 18 && p.getGender().equalsIgnoreCase("F");
}
public static Predicate<Employee> isAgeMoreThan(Integer age) {
return p -> p.getAge() > age;
}
public static List<Employee> filterEmployees (List<Employee> employees, Predicate<Employee> predicate) {
return employees.stream().filter( predicate ).collect(Collectors.<Employee>toList());
}
}
여기서 filterEmployees()
method를 볼 수 있다. 간단하게 filter를 구현하고 코드가 if문을 남발하지 않아도 되는 예쁜코드를 작성할 수 있게 되었다.
이를 사용한 예를 보자!!
package predicateExample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static predicateExample.EmployeePredicates.*;
public class TestEmployeePredicates {
public static void main(String[] args){
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = new ArrayList<Employee>();
employees.addAll(Arrays.asList(new Employee[]{e1,e2,e3,e4,e5,e6,e7,e8,e9,e10}));
System.out.println(filterEmployees(employees, isAdultMale()));
System.out.println(filterEmployees(employees, isAdultFemale()));
System.out.println(filterEmployees(employees, isAgeMoreThan(35)));
//Employees other than above collection of "isAgeMoreThan(35)" can be get using negate()
System.out.println(filterEmployees(employees, isAgeMoreThan(35).negate()));
}
}
Output:
[1 - 23, 3 - 43, 4 - 26, 8 - 79, 10 - 45]
[5 - 19, 7 - 68]
[3 - 43, 7 - 68, 8 - 79, 10 - 45]
[1 - 23, 2 - 13, 4 - 26, 5 - 19, 6 - 15, 9 - 15]
They move your conditions (sometimes business logic) to a central place. This helps in unit-testing them separately.
Any change need not be duplicated into multiple places. It improves manageability of code.
The code e.g. filterEmployees(employees, isAdultFemale())
is very much readable than writing a if-else block.
removeif()
메소드는 리스트내의 엘리먼트를 필터조건의 만족한는 것만을 골라 삭제할 때 사용된다.
Package: java.util
Java Platform: Java SE 8
Syntax:
removeif(Prdicate<? super E> filter)
형태
public boolean removeIf(Predicate<? super E> filter)
예
import java.util.function.*;
class SamplePredicate<t> implements Predicate<t>{
T varc1;
public boolean test(T varc){
if(varc1.equals(varc)){
return true;
}
return false;
}
}
import java.util.*;
public class test {
public static void main(String[] args) {
ArrayList<String> color_list;
SamplePredicate<String> filter;
color_list = new ArrayList<> ();
filter = new SamplePredicate<> ();
filter.varc1 = "White";
// use add() method to add values in the list
color_list.add("White");
color_list.add("Black");
color_list.add("Red");
color_list.add("White");
color_list.add("Yellow");
color_list.add("White");
System.out.println("List of Colors");
System.out.println(color_list);
// Remove all White colors from color_list
color_list.removeIf(filter);
System.out.println("Color list, after removing White colors :");
System.out.println(color_list);
}
}
결과
List of Colors
[White, Black, Red, White, Yellow, White]
Color list, after removing White colors :
[Black, Red, Yellow]
ServletFilter와 유사하게 DispatcherServlet이 Controller를 호출하기 전/후에 요청 및 응답을 참조, 가공할 수 있는 일종의 필터 역할
Filter와 다른점은 Filter는 DispatcherServlet보다 먼저 실행되기 때문에 Servlet관련 변수들을 사용할 수 없지만 Interceptor는 DispatcherServlet안에서 실행되기 때문에 Servlet관련 변수들을 사용할 수 있다.
HandlerInterceptor
interface