스프링 부트 웹 서비스 개발

책 판매 예제

https://github.com/ihoneymon/tacademy-spring-boot

  • 서비스 기획

  • 기능 분석 및 설계

    • 소스버전 관리 시스템 : git with Github

    • 빌드 배포시스템 : 젠킨스 , aws codedeploy

    • 로그 수집

    • 메일발송( Mail Chimp, SendGrid...)

    • 앱 푸시( AWS SNS, Firebase,...)

    • 개발환경

      • local, test, dev, beta, prod

  • 구현

  • 빌드

    • 젠킨스

  • 배포

  • 운영


도메인 설계

기획자의 문서에서 엔티티로 변경하는 설계

판매 상품은 책이다.
책은 유형별로 관리 되어야한다
책은 작가가 출판사를 통해 출간한다.
책은 서비스 관리자가 등록한다.
고객이 책을 주문한다.
======>
판매 상품(item) 은 책(book)이다.
책은 유형(Category)별로 관리되어야한다.
책은 작가(Author)가 출판사( Publisher)를 통해 출간한다.
책은 서비스 관리자(Administrator)가 등록한다.
고객(Customer)이 책을 주문(Order) 한다.
...

에 - - - 문서 구분자를 사용하여서 사용

Application-api.yml


Prod: 운영

beta: 관계자 확인

dev : 개발내용 확인

test : 자동 테스트

local : 개발자 로컬 실행환경

프로파일 구성

springboot는 여러 환경에서 빌드 사용이 가능하다.

one source Multi Use


message : 알림톡,SMS, 메일 발송 등 담당

Batch : 정기적으로 실행될 배치 프로그램 모음

admin : 서비스를 관리 하기 위한 백 오피스

Api: 외부에서 정보를 제공하는 REST API 모듈

core : 프로젝트 도메인(@Entity, @Repository)

Common : 프로젝트 공통 유틸리티, 예외…

Bookstore24 프로젝트 모듈 구성


도메인을 다 뽑아내고 다시 그것을 중심으로 설계~~~

'BackEnd > SpringBoot' 카테고리의 다른 글

Spring Boot Logging (1)  (0) 2020.04.16
Request Body 의 값 BadRequest 로 보내기  (0) 2020.04.11
SpringBoot(3)-T-Academy  (0) 2019.05.22
SpringBoot(2) -T-Academy  (0) 2019.05.21
SpringBoot 시작하기(1)-T-Academy  (0) 2019.05.20

Service

스프링 Container가 anotation을 bean으로 등록하고 실행하게 되는데,

객체를 생성하고 setter를 통해서 자기가 테스트하려는 기능을 변경하는 작업을 수동으로 하는게 아니라

초기부터 변경을 할수 있기로 하겠다라고 해서 생성자 방식으로 하는 방법이 있음

테스트에서 가장 많은 생성자 주입부분을사용함

테스트케이스에 보면 before 어노테이션이 있어서 시작될때 mocked 이라는 가짜 객체를 넣어서 하는 경우도 있음


Service 계층

@Service //(1) : 전형적인 서비스 코드
@Transactional //(2) 트렌잭션을 관리 하겠다라는 표현
public class BookServiceImpl implements BookService{
 private final BookRepository repository;
 
 //생성자 주입방식으로 bean에 등록
 public BookServiceImpl(BookRepository repository){
   this.repository = repository;
}
 
 //method를 BookService에서 선언해 두었기 때문에 덮어쓰기 했다.
 @Override
 public Optional<Book> findById(Long id){
   return repository.findById(id);
}
 
 @Override
 public List<Book> findAll(OffSetPageRequest request){
   return repository.findAll(request.getPageRequest()).getContent();
}
}package com.merona.study.service;

import com.merona.study.domain.Book;

import java.util.Optional;

public interface BookService {

   Optional<Book> findById(Long id);
}
package com.merona.study.service;

import com.merona.study.domain.Book;
import com.merona.study.domain.BookRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.Optional;

@Service
@Transactional
// save, delete 등을 추가 하기 위해서
public class BookServiceImpl implements BookService {

   private final BookRepository bookRepository;

   //생성자 주입
   public BookServiceImpl(BookRepository bookRepository) {
       this.bookRepository = bookRepository;
  }


   @Override
   public Optional<Book> findById(Long id) {
       return bookRepository.findById(id);
  }

}
package com.merona.study.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class BookServiceImplTest {


   @Autowired
   BookService bookService;

//expected 오류가 날것이라는 예측을 하는 어노테이션 옵션
   @Test(expected=RuntimeException.class)
   public void testFindById(){
       Long id = 1L;
       bookService.findById(id)
              .orElseThrow(()->new RuntimeException("Not found"));
  }

}@ControllerAdvice(annotations = {RestController.class})
@ResponseBody
public class GlobalRestControllerAdvice{
 
 @Exceptionhandler(BookNotFoundException.class)
 public ApiResponse<Void> handleException(Exception e){
   Log.error("Occurred Exception: {} ", e);
   return ApiResponse.error(e.getMassage());
}
}package springboot_api;

import com.merona.study.domain.Book;
import com.merona.study.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/books")
public class BookController {
   @Autowired
   BookService bookService;

   @GetMapping("/{bookId}")
   public ResponseEntity<Book> findById(Long bookId){
       Book book = bookService.findById(bookId)
              .orElseThrow( ()-> new RuntimeException("Not Found" + bookId));
       //http status 는 번호로 관리되는데 정상적으로 하면 200 ok 가 반환된다.
       return ResponseEntity.ok( book);
  }
}

@Profile -Type


Spring REST DOCs : api 문서에 대한 설명을 html로 만들어서 관리할 수 있다.

  • 시스템의 자원에 대한 접근 및 제어를 제공하는 API

  • 자원에 대한 접근 및 제어

    • GET /books

    • GET /books/{bookId}

    • POST /books

    • PUT /books/{bookId}

    • DELETE /books/{booksId}

  • 스프링에서는 요청에 따라 등록 되어 있는 적절한 HttpMessageConverter를 통해서 응답데이터를 반환한다.

REST api


예제 코드

restContrller에 오류가 발행사게 되면 exception handler를 반환할 수도 있다.

  • @ControllerAdvice를 이용한 처리

@ControllerAdvice를 이용한 처리

@Controller 예외 처리

 

  • DispatcherServlet에 등록된 @RequestMapping 등록되고 추후에 요청이 오면 해당하는 메서드를 찾아서 호출됨

  • 템플릿 엔진이 랜더링 할 뷰 페이지를 지정

  • 호출된 API에서 처리한 응답을 반환

@Controller

참고 : https://www.slideshare.net/hanmomhanda/spring-mvc-fullflow

anotation의 타입은 class, method에 추가할 수 있다.

@RequestMapping() : 클래스 타입의 레벨이 있을수도있고, Spring dispatcher Servelt이 controller 라고하는 anotation을 훓으면서 RequestMapping 있는 타입을 찾고 이후 차근차근 내려가게 된다.

@RestController 또는 @Controller

Controller


테스트 코드

BookRepository를 생성자에 주입하여 사용하는 BookServiceImpl 클래스

bookService 인터페이스

예제 코드

  • 트랜잭션(@Transactional) 관리 영역

    • 행위 시작, 종료 시까지 정상적으로 될때까지는 종료, 데이터 이상시에는 되돌리는 롤백을 하게됨

  • 서로 다른 도메인 연계(DI, @Autowired) 작업 영역

    • book과 category라는 엔티티를 서로 상호 작용을 하는 영역

  • @Controller와 @Repository 사이의 중계

@Service


'BackEnd > SpringBoot' 카테고리의 다른 글

Spring Boot Logging (1)  (0) 2020.04.16
Request Body 의 값 BadRequest 로 보내기  (0) 2020.04.11
SpringBoot(4) -T-Academy  (0) 2019.05.24
SpringBoot(2) -T-Academy  (0) 2019.05.21
SpringBoot 시작하기(1)-T-Academy  (0) 2019.05.20

Spring Boot 만들기

애플리케이션 계층

계층(Layer) @Component
표현(presentation) @Controller
서비스(Service) @Service
영속화(Persistence) @Repository

개발 방향은 아래에서 위로 올라간다.

Entity와 repository를 개발하고 그다음 서비스를 작성하고 서비스를 이용하는 컨트롤러를 만드는것 으로 차근차근 개발.

루트 application, 컨트롤러가 있는 애플리케이션 콘텍스트


Repository

  • ORM ( Object Relational Mapping)

    • 대부분의 개발 언어 플랫폼마다 제공
    • 객체(Object) 로 관계형 데이터베이스(RDMBS)를 관리
    • hiberate를 주로 사용
  • JPA(Java Persistence API)

    • Java 객체 정보를 영속화 하는 중간과정을 처리한다.

    • 엔티티 객체를 저장하고 변경하고 삭제하면 그에 대응하는 쿼리를 생성하고 실행한다

    • hibrate를 조금더 사용하기 쉽게 추상화 한 라이브러리가 JPA
      프로그램마다 객체와 entity 테이블 사이의 관계를 맵핑하고 쿼리를 생성해주는 프레임워크 java 에서는 hibernate, springdata jpa로 활용을 하게 된다.
      jpa가 객체의 상태를 보고 update, delete,insert, 등을 사용하게 된다
      https://www.slideshare.net/zipkyh/ksug2015-jpa1-jpa-51213397
      https://www.slideshare.net/zipkyh/spring-datajpa
      @Entity
      class Book{

        Long id;
      String isbn13;
      String isbn10;

      }

      insert into book(id,isbn13,isbn10) value (...);

    Spring Data JPA Repository interface

    public interface BookRepository extends JpaRepository<Book,Long>{
      List<Book> findByNameLike(String name);
    }

    인터페이스를 확장해서 사용하게 된다.
    이름으로 해서 유사 검색을 할수 있도록 하는 것
    Named method query 를 사용해 줌
    JpaRepository

    Optional<T> findById(ID id);
    List<T> findAll();
    List<T> findAll(Sort sort);
    List<T> findAllById(Iterable<ID> ids);
    <S extends T> List<S> saveAll(Iterable<S> entities);
    void flush();
    <S extends T> S saveAndFlush(S entity);
    void deleteInBatch(Iterable<T> entities);
    void deleteAllInBatch();
    T getOne(ID id);
  • JPA 동작원리
    BookRepository --> Spring Data JPA Proxy, SimpleJpaRepository -->Book Repository 구현체
    http://haviyj.tistory.com/28

  • 빨간색 부분이 JPA 부분

    서비스에서 레파지토리 호출 구현체에서 JPA 를 통해서 쿼리로 변경하고 jdbc에서 객체로 맵핑 되어서 가지고옴

    업무(=비즈니스 로직 ) 구현에만 집중해라!!

    • 영속화 계층(@Repository)에서는 엔티티 관리만
    • 비즈니스 로직구현은 도메인 영역에서
    • 서로 다른 도메인 사이에 연계는 서비스 계층(@Service) 에서
    • 외부 요청에 대한 처리는 컨트롤러 계층(@Controller) 에서
      entity를 가지고 오는 것이 간단함

repository만들기 실습

AbstractPersistable : JPA 추상 클래스

Entity들의 id 값은 보통 long을 많이 씀

도메인 : Book 클래스

package com.merona.study.domain;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.AbstractPersistable;

import javax.persistence.Entity;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@Entity
public class Book extends AbstractPersistable<Long> {
//id 값을 Long으로 generic으로 받아서 하기 때문에 @GeneratedValue를 설정 안해도됨
//    @Id
//    private Long id;

    private String name;
    private String isbn13; //13자리
    private String isbn10; //10자리

}

AbstractPersistable 이란 ?

Repository : BookRepository

package com.merona.study.domain;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface BookRepository extends JpaRepository<Book, Long> {
    //T : entity의 타입
    //ID : Long
    List<Book> findByNameLike(String name);

}

TestCode

package com.merona.study.domain;

import jdk.nashorn.internal.ir.LiteralNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;


import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;


@RunWith(SpringRunner.class)
@DataJpaTest
public class BookTest {

    @Autowired
    BookRepository repository;

    @Test
    public void testTave(){

        Book book = new Book();
        book.setName("boot-spring-boot");
        book.setIsbn10("0123456789");
        book.setIsbn13("0123456789123");

//        assertThat("Simple Text",is(equalTo("Simple Text")));
        System.out.println(" book is NEw ? "+ book.isNew());
        repository.save(book);
        System.out.println(" book is NEw ? "+ book.isNew());
//        assertThat(book.isNew(),true);
    }


    @Test
    public void testFindByNameLike(){
        Book book = new Book();
        book.setName("boot-spring-boot");
        book.setIsbn10("0123456789");
        book.setIsbn13("0123456789123");

        repository.save(book);

        List<Book> books = repository.findByNameLike("boot");
        for(Book bookItem : books){
            System.out.println("bookName : "+bookItem.getName());
        }

    }
}

테스트 코드를 적는데 있어서

assertThat이 계속 오류가 나는데

import static org.assertj.core.api.Assertions.assertThat;

위 패키지의 assertThat을 사용하면 해결된다.

'BackEnd > SpringBoot' 카테고리의 다른 글

Spring Boot Logging (1)  (0) 2020.04.16
Request Body 의 값 BadRequest 로 보내기  (0) 2020.04.11
SpringBoot(4) -T-Academy  (0) 2019.05.24
SpringBoot(3)-T-Academy  (0) 2019.05.22
SpringBoot 시작하기(1)-T-Academy  (0) 2019.05.20

스프링부트 만들기

프로젝트구성

spring initializer 사이트에서 받는방법

name 
groupid : 패키지명을 바탕으로 시작
artifactID : 
version : 개발 버전 
descriptoin : 프로젝트 설명
packageName : 실제로 프로젝트에서 실행하는 루트 패키지 이름
type : 그래들 패키지
packaging : jar로 패키징
javaVersion : 자바 버젼
language : 자바
bootVersion : 스프링 부트 버전
dependencies = lombok&dependencies=h2&dependencies=data-jpa&dependencies=web

lombok

  • 자바 프로젝트 필수 라이브러리
  • 클래스에서 필수적으로 작성해야 하는 접근자 / 설정자

(Getter/setter), toString, equalAndHashCode, 생성자 등을 작성하지 않아도 된다

  • 코드를 간결하게 사용 할 수 있다.
  • 배포 버전을 확인하고 결합이 있는지 확인해야한다.
  • 사용시 주의사항
    롬복 버전 업데이트시에 약간 compile 필드 값을 잘못 가지고오면서, 오류가 나는 경우가 있음(권순남 작성 위키백과 )

h2 Databaese

  • 인메모리, 파일, TCP 지원 인터세이스
  • JDBC url 설정으로 데이터베이스 작동방식 지정가능
  • 스프링부트 자동구성으로 기본 제공, /h2-console h2 webconsole 제공
  • 로컬 개발환경에서 mysql…등 설치 없이, 별도의 DB 설치 없이 빠른 프로토타이핑 지원, 개발 베타에서는 설치해야함
    • 가능한이유 : JPA 쓰기 때문에 가능함, 쓰는 사람에 따라서
  • 필요에 따라 운영 가능한 수준의 데이터베이스 활용가능
    • 임베디드로 만들어서 쓰는 경우도 있음

프로젝트 구조

setting.gradle

rootProject.name = 'spring-boot'

  • 멀티 프로젝트 구성시 사용
  • 프로젝트 이름
  • 하위 프로젝트 정의 / include = ' law projectName '
  • 하위 프로젝트 설명()

gradle Wrapper

그래들 수동으로 받아야됬다. 빌드의 차이 때문에 오류가 나는 불편한점이 있었음

프로젝트 생성 후

gradley build 커멘드를 입력하게 되면

Gradle이 wrapper의 properties와 jar파일이 있는지 확인 한다 없으면,

gradle script에 의해서 gradle war 를 받고 jar 파일을 기준으로 빌드를 한다.

gradlew(.bat) clean build


스프링부트 어플리케이션

@SpringBootApplication
public class Application{
  public static void main(String[]args){
    SpringApplication.run(Application.class, args);
  }
}

스프링부트 자바 어플리케이션안에서 실행클래스를 통해 ioc 컨테이너를 띄우고 어노테이션이 있는 위치를기준으로 스프링 빌드를 탐색

Springapplication.run 코드를 보면 어노테이션 config …. 이런콘텍스트를 확인하기 보통은 web application으로 시작하게 된다.

보통은 servelt을 사용함 .(webMvcAutoConfiguration을 보면 됨)

해당 폴더 내에 들어가서 gradlew build 하면 springboot jar 파일이 만들어지고

플러그인에 의해서 만들어진 jar 파일은 fat war 라고도 하는데, 실행환경을다 포함해서 fat jar 라고 부름

프로젝트 밑에

build/libs/snapshot …이 됨

톰캣과 재압축됨

압축된것을 풀어보면

BOOT-INF/classes 내부

static : javascript, css , image

template : template 엔진, controller가 전달해주는 모델에서 동적으로 html 생성하는 것

META-INF : 메인 클레스가 뭐고 스타트가 어디인지 알수 있다.

실직적으로는 jar launcher가 실행되고 이후 메인 클래스가 동작하게 되는것

classes : dml 설정 파일과 application class 파일 존재 , lib : 의존성 라이브러리들

rootpackage를 기준으로

실행 : Java -jar spring-boot-snapshot-1.0.0


SpringApplication:: 스프링부트 앱 시작점

Spring-boot-starter

Spring-boot-dependencies

Spring-boot-autoconfigure

Spring-boot-parent

Spring-boot-starters

위의 순서대로 상속을 받아서 starters 가 만들어지고

아래의 pom.xml에 모듈들을 사용한다고 적어주면됨

autoconfiguration으로 작성되어 있음(자동구성이 어떻게 되어있는지 확인 가능)

Spring-boot-starter-*/pom.xml

servlet과 reactive로 보통 많이 쓰임

dispatcherServlet이 가장 중요한 역할을 함


@Component

스프링 프레임워크는

component라는 스트레오 타입 어노테이션을 기준으로해서

3가지의 하위 컴포넌트들로 나뉜다.

Controller, service, repository

@Bean vs @Component

빈 : 개발자가 제어할수 없는 다른이가 작성한 클래스들을 스프링 빈으로 등록

컴포넌트 : 내가 작성한 계열의 컴포넌트 클래스에다가 붙여 넣게 됨

트랜잭션이라고해서 데이터를 저장하고 관리를 할 경우 service라는 어노테이션을 사용하고

트랜젝션 필요 없는 경우 component 선언을 해서 많이 쓰임

빈 : 콩깍찌가 씌여져있는 객체라고 생각

spring ioc 콘테이너에서 생성, 소멸 되기 까지 생명주기를 관리하는것을 bean 객체 라고 함

객체중의 하나가 bean anotation이 적혀있는것


의존성 주입

라이브러리와 프레임워크의 차이

인스턴스를 만들때는 아래와 같이 라이브러리 객체를 만들어서 객체를문자열로 쭉 작성을 하면서 호출하게 되는 과정이 일반적

public class ObjectMapperTest{
  public void test() throws JsonProcessingException{
    ObjectMapper objectMapper = new ObjectMapper();

    Book book = new Book("test-book", "test-isbn13","test-isbn10");

    String strBook = objectMapper.writeValueAsString(book);
  }
  //검증 생략
}

하지만 스프링과 같은 ioc 컨테이너에서는 @autowired를 선언해놓으면 application context가 구동되는 상황에서 objectmapper를 이미 어딘가에서 읽어서 Spring bean으로 구성해놨기 때문에, 따로 인스턴스를 만들지않고 주입해야 할 수 있다.

public class ObjectMapperTest{
  @Autowired
  ObjectMapper objectMapper;

  public void test() throws JsonProcessingException{
    Book book = new Book("test-book", "test-isbn13", "test-isbn10");

    String strBook = objectMapper.writeValueAsString(book);
    //검증생략
  }
}

프레임워크가 제어의 역전함

프레임워크 쪽에서 필요한 컴포넌트를 가지고 있다가 필요한 곳에서 가져다쓰는 차이점

호출이 아니라 가져다쓰는 차이점


의존성 주입 방법

  • 생성자 주입( 권장)

    @Service
    public class BookServiceImpl implements BookService{
      private final BookRepository repository;
    
      public BookServiceImpl(BookRepository repository){
        this.repository = repository;
      }
    
      //코드 생략
    }

    생성자가 하나만 있어야 가능하다 autowired를 붙이지 않아도 service라느 어노테이션이 있어서, 스프링에서 아~ 내가 관리해야할 컴포넌트 이구나 라고 내가 가지고 있는 Book repository를 쓰네 라고 해서 주입이 됨.
    클래스에서 생성자가 하나만 있어야 사용 가능, @Service 컴포넌트에서 알아서 BookRepository라는 인스터스를 주입을 해줌

  • 설정자 주입( setter)

    @Service
    public class BookServiceImple implements BookService{
      private BookRepository repository;
    
      @Autowired
      public void setRepository(BookRepository repository){
        this.repository = repository;
      }
      //생략
    }

    파라미터 앞에 autowire를 설정하게 되면 사용이 가능함

  • 필드( @Autowired )선언

    public class BookServiceImpl implements BookService{
      @Autowired
      private BookRepository repository;
      //코드 생략
    }

    처음에는 @Autowired를 많이 쓰임

'BackEnd > SpringBoot' 카테고리의 다른 글

Spring Boot Logging (1)  (0) 2020.04.16
Request Body 의 값 BadRequest 로 보내기  (0) 2020.04.11
SpringBoot(4) -T-Academy  (0) 2019.05.24
SpringBoot(3)-T-Academy  (0) 2019.05.22
SpringBoot(2) -T-Academy  (0) 2019.05.21

유사도 검색

텍스트 분석의 기초

텍스트 데이터 분석으 숫자, 날짜, 시간과 같은 다른 타입의 데이터 분석과는 다르다.

문자열 타입으로 지정하거나 종종 해당 필드에 정확히 일치하는 쿼리를 수행할 수 있다.

비구조화된 텍스트 분석에 대해 분석

텍스트 타입을 가진 모든 필드는 분석기를 통해분석된다.

  • 일래스틱서치 분석기 이해
  • 내장형 분석기 사용
  • 맞춤형 분석기로 자동완성 구현

일래스틱서치 분석기 이해

일래스틱 서치는 역색인 구조를 사용한다.

분석기는 도큐먼트와 도큐먼트에 속한 각 필드를 가져와 용어를 추출하여, 검색할수있도록 색인되고, 색인된 용어는 턱정 검색어로 도큐먼트를 찾을때 결과로 포함된다.

분석기가 수행하는 시점

  • 색인 시점
  • 검색 시점

분석기의 핵심 : 도큐먼트 필드를 분석하고 실제 색인을 만드는일

도큐먼트 색인전에 텍스트 타입을 가진 모든 분석이 필요하다.

분석기의 구성요소

  • 문자필터
  • 토크나이저
  • 토큰필터

문자필터(char mapping filter)

입력필드의 문자를 추가, 삭제, 변경 가능

문자 필터는 분석기 과정의 맨앞에 위치한다.

그래서 :). , :( :D. 와 같은 이모티콘들을 텍스트로 변환을 할수있다.

토크나이저

분석기는 정확히 하나의 토크나이저를 갖고있다. 문자열을 받아 토큰스트림을 생성하는 역할을 한다.

이렇게 토큰은 역색인을 만들때 사용된다.

표준 토크나이저는 문자열을 공백문자와, 구두점을 기반으로 분해한다.

POST _analyze
{
  "tokenizer": "standard",
  "text" : "tokenizer breaks character into token!"
}

결과

{
  "tokens" : [
    {
      "token" : "tokenizer",
      "start_offset" : 0,
      "end_offset" : 9,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "breaks",
      "start_offset" : 10,
      "end_offset" : 16,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "character",
      "start_offset" : 17,
      "end_offset" : 26,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "into",
      "start_offset" : 27,
      "end_offset" : 31,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "token",
      "start_offset" : 32,
      "end_offset" : 37,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}

Start_offset, end_offset 항목을 확인이 가능하고 토큰필터가 있는 경우 추가로 처리 가능

토큰필터

모든 토큰 필터는 수신한 입력 토큰 스트림에 있는 토큰의 추가, 삭제, 변경이 가능하다.

분석기는 여러 토큰필터를 가질 수 있으며, 각 토큰 필터의 결과는 모든 토큰필터가 동작할 때까지 다음 토큰필터로 전송된다.

내장 토큰필터에는 아래와 같음

소문자 토큰필터(Lowercase Token Filter): 입력 데이터의 모든 토큰을 소문자로 치환

불용어 토큰필터(Stop Token Filter) : 문맥에서 의미 없는 단어를 제거

영어 같은 경우에는 is, a , an, the 와 같은 관사, 전치사, 조사, 접속사 등

내장형 분석기 사용

자주 사용하는 분석기에는

  • 표준분석기
  • 언어분석기
  • 공백분석기

표준 기본 동작방식

PUT index_standard_analyzer
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std": {
          "type": "standard"
        }
      }
    }
  },
  "mappings":{
    "my_type": {
      "properties":{
        "my_text":{
          "type":"text",
          "analyzer": "std"
        }
      }
    }
  }
}
  1. std라는 이름을 가진 분석기를 명시적으로 정의
  2. 분석기 타입은 standard로 설정 하고 다른추가구성을 하지 않음
  3. 인덱스에 my_type 이라는 타입을 지정
  4. my_text 라는 필드에 필드 레벨 분석기를 명시

색인 테스트

POST index_standard_analyzer/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

결과 :

{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "standard",
      "start_offset" : 4,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "analyzer",
      "start_offset" : 13,
      "end_offset" : 21,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "works",
      "start_offset" : 22,
      "end_offset" : 27,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "this",
      "start_offset" : 28,
      "end_offset" : 32,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "way",
      "start_offset" : 33,
      "end_offset" : 36,
      "type" : "<ALPHANUM>",
      "position" : 5
    }
  ]
}

영어 불용어 추가하기

PUT index_standard_analyzer_english
{
  "settings": {
    "analysis": {
      "analyzer": {
        "std": {
          "type": "standard",
          "stopwords": "_english_"
        }
      }
    }
  },
  "mappings":{
    "my_type": {
      "properties":{
        "my_text":{
          "type":"text",
          "analyzer": "std"
        }
      }
    }
  }
}

Std 라는 명시적인 분석기 안에 stopwords의 값에 english라고 설정

그리고 만들어진 분석기를 실행하면

POST index_standard_analyzer_english/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

결과는 다음과 같이 the, this가 삭제되고 보여줌을 알수 있다.

POST index_standard_analyzer_english/_analyze 
{
  "field": "my_text",
  "text" : "The Standard Analyzer works this way."
}

AmazonData

https://github.com/pranav-shukla/learningelasticstack

를 통해서 데이터를 가지고오려면위의 Readme.md를 참고하면된다.

logstash를 사용한다.

데이터 삽입 후에 쿼리로 데이터를 검색하면

GET /amazon_products/products/_search
{
  "query":{
    "match_all": {}
  }
}

구조화된 대부분의 쿼리는 유사도 기반의 스코어가 필요하지 않으며, 결과에 어떤 항목이 포함돼 있는지 단순히 "예", "아니요"로 나타냄

구조화된 검색 쿼리는 용어 레벨쿼리 (Term Level. Query) 라고 부른다.

검색을 할때 용어에 수행할 분석기가 따로 없기 때문에 역색인에 용어 레벨 쿼리를 직접 실행한다.

구조화된 쿼리 또는 용어 쿼리

  • Range 쿼리
  • Exist 쿼리
  • Term 쿼리
  • Terms 쿼리

Range 쿼리

자연적 질서를 가진 데이터 타입 필드에 적용 가능 (정수, integer, long, date)

이러한 데이터 타입은 순서 정렬을 하고 있으므로 범위를 나타내는 Range 쿼리를 사용할수 있다.

  • 숫자타입
  • 스코어 증폭( score Boosting)
  • 날짜 범위

숫자타입

$10~$20 범위의 제품 검색

GET /amazon_products/products/_search
{
  "query": {
    "range": {
      "price": {
        "gte":10,
        "lte":20
      }
    }
  }
}

결과 :

  "took" : 22,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 278,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "amazon_products",
        "_type" : "products",
        "_id" : "Fx7-4WgBWet64OX_NA57",
        "_score" : 1.0,
        "_source" : {
          "id" : "b000ap2wyw",
          "description" : null,
          "price" : "19.99",
          "manufacturer" : "topics entertainment",
          "title" : "kids power fun for girls"
        }
      },

Max_score가 1인 이유는 range쿼리는 중요도 혹은 유사도를 고려하지 않은 구조화된 쿼리로 필터를 실행

스코어링을 하지 않고 모든 문서를 스코어1로 나타냄

스코어 증폭

다른 쿼리와 함께 Range 쿼리를 사용하고, 일부 조건을 만족할 경우, 결과 도큐먼트에 더 높은 스코어를 할당 하고 싶다면, 어떻게??

쿼리에 여러 타입을 결합할 수 있는 Bool 쿼리와 같이 복합 쿼리 사용

GET /amazon_products/products/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "range": {
      "price": {
        "gte":10,
        "lte":20,
        "boost":2.2
      }
    }
  }
}

날짜 범위

날짜는 기본적으로 정렬되기 때문에 Range 쿼리를 날짜 필드에도 적용할 수 있다.

날짜범위 쿼리에는 날짜형식을 지정한다.

gte가 이전 날짜, lte가 마지막 날짜

GET /orders/order/_search
{
  "query": {
    "range": {
      "orderDate" : {
        "gte": "01/09/2017",
        "lte": "30/09/2017",
        "format": "dd/MM/yyyy"
      }
    }
  }
}

now는 현재 시간, now-7d는 현재로써 7일전 밀리초까지 정밀도의 조건을 가지고 있다.

y(년) M(월) w(주) d(일) h(시간) H(시간) m(분) s(초)

시간단위 하루 반올림 : 예를 들면 "gte" : "now-7d/d", "lte" : "now/d" 사용,
'/d'를 지정하면 시간을 일 단위로 반올림한다.

Exist 쿼리

특정 필드에 null 값과 공백이 아닌 레코드만 가져 오고 싶을 때 유용하게 사용할수 있음

GET /amazon_products/products/_search
{
  "query":{
    "exists":{
      "field": "description"
    }
  }
}

제품 필드가 정의된 모든 제품을 가지고오는 쿼리

Term 쿼리

특정 제조 업체가 만든 제품을찾으려면?

GET /amazon_products/products/_search
{
  "query":{
    "term":{
      "manufacturer.raw": "victory multimedia"
    }
  }
}

Victory multimedia 라고 검색하면 단순히 victory 또는 multimedia 문자열만 포함된 제조업체가 결과로 나오는 것은 아니다.

Term 쿼리는 용어 분석을 수행하지 않는다는 의미에서 하위 레벨 쿼리 이다.

기본적으로는 score없이 필터 컨텍스트에서 term 쿼리를 실행하려면, constant_score 필터를 사용해야한다.

GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "manufacturer.raw": "victory multimedia"
        }
      }
    }
  }
}

모든 도큐먼트에 스코어 1인 결과를 반환

다른 타입의 쿼리를 변환하고 쿼리를 결합하는데 유용한 복합쿼리도 있음.

전문 텍스트 쿼리는 구조화되지 않은 텍스트 필드에 수행할 수 있다.

분석과정으로 인식 , 실제 검색연산을 수헹하기 전에 검색 용어를 대상으로 분석 실행.

우선 필드 레벨에 search_analyzer가 정의 됐는지 확인해 올바른 분석기를 찾음.

이러한 쿼리를 상위레벨 쿼리라고 한다. 이경우 표준 분석기를 사용하므로 역색인은 모든 분할된 용어를 포함한다.

전문텍스트 쿼리

  • Match 쿼리
  • Match Pharse 쿼리
  • Multi Match 쿼리

Match 쿼리

기본 필드에 사용된 분석기를 인식하는 상위레벨쿼리중 하나.

GET /amazon_products/products/_search
{
  "query":{
    "match": {
      "manufacturer": "victory multimedia"
    }
  }
}

이 쿼리는 실질적으로

{
    "query":{
        "bool" : {
            "should":[
                {"term": {"title": "victory"}},
                {"term": {"title": "multimedia"}},
            ]
        }
    }
}

와 같이 실행된다.

  • 모든 도큐먼트의 manufacturer 필드에서 victory multimedia 용어를 검색
  • 가장 일치하는 도큐먼트를 찾아 스코어에 따라 내림차순으로 정렬한다.
  • 도큐먼트가 같은 순서로 나란히 놓인 경우, 도큐먼트 두 용어를 모두 갖고 있지만, 같은 순서가 아니거나, 서로 인접하지 않는 다른도큐먼트보다 더 높은 스코어를 가져야 한다.
  • 높은 스코어에서 낮은 스코어까지 적합한 도큐먼트를 찾아낸다.

다음과 같이 일반적인 옵션 몇가지를 살펴본다.

  • operator
  • Minimum_should_match
  • Fuzziness

Operator

지정된 검색어가 분석기를 적용해 여러 용어로 나뉘면 개별 용어에 따른 결과를 결합하는 방법이 필요함.

GET /amazon_products/products/_search
{
  "query":{
    "match":{
      "manufacturer": {
        "query": "victory multimedia",
        "operator": "and"
      }
    }
  }
}

해당 쿼리는 도큐먼트의 manufacturer 필드에 victory와 multimedia 값이 모두 포함한 결과를 찾는다.

minimum_should_match

연산자 and 를 적용하는 대신 or 연산자를 그대로 사용하되, 결과로 나오는 도큐먼트에서 일치하는 용어의 최소 개수를 지정할 수 있다.

결과로 나오는 도큐먼트에서 일치하는 용어의 최소 개수를 지정할 수 있다.

GET /amazon_products/products/_search
{
  "query":{
    "match": {
      "manufacturer":{
        "query": "victory multimedia",
        "minimum_should_match":2
      }
    }
  }
}

and연산자와 비슷한 방식으로 동작하고, 쿼리는 2개의 검색용어를 갖고 있으며, 최소한 2개의 용어가 일치 해야한다고 지정함

2개의 용어를 검색한다면 and연산이 되고, 2개중 1로 선택을 한다면 or가 된다.

Fuzziness

match쿼리에 fuzziness매개 변수를 사용해 Fuzzy 쿼리로 변경 할 수 있다.

Fuzzyness 는 레벤슈타인 거리 알고리즘을 기반으로 함, 원본 문자열을 다른 문자열로 변경하기 위한 편집 횟수를 측정한다.

매개변수 : 0,1,2, Auto 값중 하나를 사용할 수 있다.

GET /amazon_products/products/_search
{
  "query": {
    "match":{
      "manufacturer": {
        "query": "victor multimedia",
        "fuzziness":1
      }
    }
  }
}

Match Phrase 쿼리

분할해서 용어를 검색하는것 보다 일련의 단어를 일치 시키려면, match_phrase 쿼리를 사용한다.

GET /amazon_products/products/_search
{
  "query": {
    "match_phrase": {
      "description": {
        "query":"know the value"
      }
    }
  }
}

slop매개변수를 사용하면 쿼리 시점에서 건너뛸수 있는 단어의 개수를 추가할 수 있다.

Multi Match 쿼리

Match 쿼리의 확장 버전, 여러 필드에서 일치하는 쿼리를 실행 할 수 있음

다양한 옵션을 위해, 도큐먼트의 전체 스코어를 계산 할 수 있다.

Multi_match 쿼리 사용하기

사용자가 검색할 때 제목과 설명 필드 모두 조회해야할 경우

GET /amazon_products/products/_search
{
  "query":{
    "multi_match": {
      "query": "monitor aquarium",
      "fields": [
        "title",
        "description"
      ]
    }
  }
}

결과 : title 과 description 필드에 monitor 와 aquarium이 들어가는 모든 다큐먼트를 찾는다.

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 120,
    "max_score" : 12.203196,
    "hits" : [
      {
        "_index" : "amazon_products",
        "_type" : "products",
        "_id" : "MaBeBWkBUwGsIMY0bJOy",
        "_score" : 12.203196,
        "_source" : {
          "description" : "my sim aquarium has all the simulated fish and fish tank accessories you need to turn your monitor into a gorgeous fish tank.",
          "manufacturer" : "viva media",
          "id" : "b000f613x2",
          "price" : "19.99",
          "title" : "my sim aquarium"
        }
      },

특정 필드의 스코어를 높이려면 스코어를 높이려는 필드^숫자를 적어주면 해당하는 배 만큼 스코어를 높여 준다.

Fields: [ field ^2]

복합쿼리 작성

하나 이상의 쿼리를 결합해 더 복잡한 쿼리를 작성함

일부 복합 쿼리는 스코어 쿼리를 스코어를 스코어가 없는 쿼리를 변환하고, 다중 스코어 및 스코어가 없는 쿼리를 결합하기도한다.

  • Constant score 쿼리
  • Bool 쿼리

Constant score 쿼리

구조화된 데이터 검색은 스코어링이 필요 없다. 그렇기 때문에 Constant score 쿼리를 사용하면 일반적으로 쿼리 컨텍스트에서 실행하는 스코어링 쿼리를 필터 컨텍스트에서 실행하는 스코어가 없는 쿼리로 변환이 가능하다.

term쿼리를 실행하면 도큐먼트를 분류하고, 모든 항목에 스코어를 할당한다.

GET /amazon_products/products/_search
{
  "query":{
    "term": {
      "manufacturer": "victory multimedia"
    }
  }
}

GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "term": {
          "manufacturer": "victory multimedia"
        }
      },
      "boost":1.2
    }
  }
}

밑의 쿼리는 필터 컨텍스트에서 실행 하도록 변경된다.

또한 기본적으로 중립을 의미하는 1이라는 스코어를 할당한다. 또는 스코어를 1로 할당하지 않고 boost매개변수를 지정하면 원하는 스코어를 지정할 수 있다.

Bool쿼리

복잡한 쿼리 타입을 작성하는데 유용

sql에서 where 절과and or 조건을 사용해 필터링하는 방법을 알고 있듯이 , Bool쿼리를 사용하면 다중 스코어링 및 스코어를 계산하지 않는 쿼리를 결합 할 수 있다.

GET /amazon_products/products/_search
{
  "query": {
    "bool" : {
      "must":{},     # 쿼리 컨텍스트에서 실행되는 스코어를 계산하는 쿼리
      "should":{},     # 쿼리 컨텍스트에서 실행되는 스코어를 계산하는 쿼리
      "filter":{},     # 필터 컨텍스트에서 실행되는 스코어를 계산하지 않는 쿼리
      "must_not":{}  # 필어 컨텍스트에서 실행되는 스코어를 계산하지 않는 쿼리
    }
  }
}

구조화된 검색을 수행하는 스코어를 계한하지 않는 쿼리를 구성하는 방법

  • OR 조건 결합
  • AND 및 OR 조건 결합
  • NOT 조건 추가
GET /amazon_products/products/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "price": {
                  "gte": 10,
                  "lte": 13
                }
              }
            },
            {
              "term": {
                "manufacturer": {
                  "value": "valuesoft"
                }
              }
            }
          ]
        }
      }
    }
  }
}
ElasticSearch 모니터링툴 설치하기

ElasticSearch 모니터링툴 설치하기

두가지 종류가 있음

apache elasiticHQ

 

install HQ

 

 

Kibana monitoring

두 번째는 elasticsearch의 모니터링툴

6.5 부터는 모니터링툴이 자동으로 포함되어잇지만 이전버전들은 설치 해야함 .

 

 

현재 사용중인 6.2.2 에서는 kibana monitoring 을 사용하려면 xpack 설치가 필요

elasticsearch와 kibana 에 x-pack 설치 (sudo 권한)

설치를 하면 bin 디렉토리안에 x-pack 폴더가 생긴다 폴더에 들어가서 비밀번호 설정을 할것이다.

 

kibana xpack 설치

kibana 홈디렉토리의 bin디렉토리로 이동

 

 

kiabana 비밀번호 설정

xpack 을 설치하면 보안이 복잡하다 그렇기 때문에 나는 사용하지 않기로 함 !!

elasticsearch.yml 파일과 kibana.yml 파일에 다음과 같이 추가해줘야함

 

설정 후에 elasticsearch 를 종료하고 다시 시작해야한다.

 

 

참고자료

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/installing-xpack-es.html

http://www.kwangsiklee.com/2017/02/%EC%97%98%EB%9D%BC%EC%8A%A4%ED%8B%B1%EC%84%9C%EC%B9%98%EC%97%90%EC%84%9C-x-pack%EC%9C%BC%EB%A1%9C-%EC%9D%B8%EC%A6%9Dauthentication-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0/

 

 

'BackEnd > ElasticSearch' 카테고리의 다른 글

[Elasticsearch] failed to obtain node locks  (0) 2019.06.21
SuggestAPI 소개  (0) 2019.06.10
Nori 사용 해서 노래 가사 검색하기  (0) 2019.06.10
Elasticsearch Nori  (0) 2019.05.27
Elasticsearch logger  (0) 2019.02.25
logger

Elasticsearch logger

로그 엔트리 3개

  1. 메인로그 (cluster-name.log)

: 동작 중 일때, 무슨일이 일어났는지에 관한 일반적인 정보를 알수 있다. 쿼리 실패, 새로운 노드 클러스터에 추가

  1. 느린 검색 로그(cluster-name_index_search_slowlog.log)

쿼리가 너무 느리게 실행될때 로그를 남기는 곳

  1. 느린 색인 로그(cluster-name_index_indexing_slowlog.log)

느린 검색로그와 유사하지만 기본으로 색인 작업이 일정 시간 지나면 로그를 남기는 곳

 

실행 중이기 때문에 curl 을 이용해서 바로 적용

 

 

 

 

https://stackoverflow.com/questions/21749997/see-all-executed-elasticsearch-queries

'BackEnd > ElasticSearch' 카테고리의 다른 글

[Elasticsearch] failed to obtain node locks  (0) 2019.06.21
SuggestAPI 소개  (0) 2019.06.10
Nori 사용 해서 노래 가사 검색하기  (0) 2019.06.10
Elasticsearch Nori  (0) 2019.05.27
Elasticsearch 모니터링 툴  (0) 2019.03.04

SPARK 에서의 기본 행동(action) 연산자 및 변환(transformation)연산자(2)

sample, take, takeSample 연산으로 RDD 의 일부 요소 가지고 오기

sample

이전의 고객 ID 중 30% 를 무작위로 고른 샘플 데이터셋이 필요하다고 가정할때,

  1. RDD 클래스에 sample 메서드 사용 가능

def sample(widthReplacement: Boolean,fraction:Double, seed:Long=Util.random.nextLong):RDD[T]

첫 번째, widthReplacement 는 같은 요소가 여러번 샘플링될수 있는지에 대한지정

true : 복원샘플링, false : 비복원 샘플링

복원 샘플링은 물고기를 잡았을 때 다시 물고기를 살려주고 다시 물고기를 잡는 상황이라고 볼 수있고,

비복원 샘플링은 반대로 물고기를 잡았을때, 물고기를 제외하고 다시 물고기를 잡는 상황이라고 이해하면된다.

두 번째, fraction 샘플링될 횟수의 기댓값을 의미

세 번째, seed 는 난수 생성에 사용되는 시드로, 같은 시드는 항상 같은 유산 난수를 생성하기 때문에 프로그램을 테스트 하는데 쓰임.

이전의 예제인 uniqueIds 의 값들 중 샘플링 테스트를 해보자.

scala> val uniqueIds = idsStr.distinct
uniqueIds: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[6] at distinct at <console>:28

scala> uniqueIds.collect
res4: Array[String] = Array(80, 20, 98, 15, 16, 31, 94, 77)

scala> val ss = uniqueIds.sample(false,0.3)
ss: org.apache.spark.rdd.RDD[String] = PartitionwiseSampledRDD[11] at sample at <console>:30
/*
sample 의 반환형은 RDD
*/
scala> ss.count
res10: Long = 2

scala> ss.collect
res11: Array[String] = Array(20, 98)

takeSample

위의 sample 메서드는 확률을 통해 값을 가지고 왔지만, 갯수를 가지고 sample을 하려면 takeSmaple 을 사용

def takeSample(widthReplacement: Boolean, num:Int, seed: Long=Utils.random.nextLong):Array[T]

첫번째 인자 : 복원,비복원

두번째는 가지고올 갯수

세 번째, seed 는 난수 생성에 사용되는 시드로, 같은 시드는 항상 같은 유산 난수를 생성하기 때문에 프로그램을 테스트 하는데 쓰임

scala> val taken = uniqueIds.takeSample(false,5)
taken: Array[String] = Array(31, 77, 94, 15, 16)
/*
takeSample의 반환형은 Array 로 반환하게 된다.
*/
scala> uniqueIds.take(3)
res12: Array[String] = Array(80, 20, 98)

take 는 RDD 에서 갯수만큼 가지고 오는 연산자인데, 지정된 개수의 요소를 모을때까지 RDD 파티션 하나씩 처리해 결과를 반환한다.

(파티션을 하나씩 처리 한다는 것은 결국 연산이 전혀 분산이 되지 않는다는 것을 의미한다. 여러 파티션의 요소를 빠르게 가져오고 싶다면 드라이버의 메모리를 넘지 않도록 요소 개수를 적당히 줄이고 collect 연산자를 사용한다.)

'BackEnd > Spark' 카테고리의 다른 글

RDD 영속화(캐싱)  (0) 2019.09.02
Spark BroadCast  (0) 2019.08.28
SPARK 에서의 기본 행동(action) 연산자 및 변환(transformation)연산자(1)  (0) 2018.06.17
Spark(3) SparkContext-1  (0) 2018.05.16
Spark (2) 기본예제 및 scala  (0) 2018.05.15


RDD연산자의 종류는 transformation 과 action둘로 나뉘는데

transformation 은 새로운 RDD 를 생성

action 은 RDD 의 연산자를 호출함

스파크에서 transformation , 과 action둘의 지연 실행

spark 에 대해서 lazy evaluation 개념이 중요한데, 처음에는 lazy 에 대한 이해를 하지 못 한채 그냥 그렇구나 했는데, 개념은 다음과 같다.

transformation의 지연 실행은 action 연산자를 호출하기 전까지는 transformation 연산자의 계산을 실제로 실행 하지 않는 것을 의미한다.
이는 RDD에 action연산자가 호출되면 스파크는 해당 RDD 의 계보를 살펴본 후, 이를 바탕으로 실행해야하는 연산 그래프를 작성해서 action 연산자를 계산한다.
결론은 transformation 연산자는 action 연산자를 호출했을때, 무슨 연산이 어떤 순서로 실행되어야 할지 알려주는 일종의 설계도 라고 할 수 있다.

책의 예제를 따라다 우연히 lazy evaluation 의 예제를 찾은 것같다.

scala> val lines = sc.textFile("/home/morris01/study/spark/data/client-ids.log")
lines: org.apache.spark.rdd.RDD[String] = /home/morris01/study/spark/data/client-ids.log MapPartitionsRDD[4] at textFile at <console>:24

scala> val idsStr = lines.map(line=>line.split(","))
idsStr: org.apache.spark.rdd.RDD[Array[String]] = MapPartitionsRDD[5] at map at <console>:26

scala> idsStr.foreach(println)
org.apache.hadoop.mapred.InvalidInputException: Input path does not exist: file:/home/morris01/study/spark/data/client-ids.log
at org.apache.hadoop.mapred.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:287)
at org.apache.hadoop.mapred.FileInputFormat.listStatus(FileInputFormat.java:229)
at org.apache.hadoop.mapred.FileInputFormat.getSplits(FileInputFormat.java:315)

위의 실행 예제가 에러가 난이유는 filepath가 잘못되어서 나온 에러이다.

그러나 idsStr.foreach를 실행하기전까지는 순수히 진행이 되는 것 같았다. 하지면 foreach 라는 action을 수행을 하면서 이전의 RDD 의 계보를 살펴보다가 잘못되어서 에러가 발생한것같다

RDD 연산자

원본 RDD 의 각 요소를 변환한 후 변환된 요소로 새로운 RDD를 생성하는 map 변환 연산자

scala> val numbers = sc.parallelize(10 to 50 by 10)
numbers: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[4] at parallelize at <console>:24

scala> numbers.foreach(x=>println(x))
10
20
30
40
50

scala> val numberSquared = numbers.map(num=>num*num)
numberSquared: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[5] at map at <console>:26

scala> numberSquared.foreach(x=>println(x))
100
400
900
1600
2500

scala> numberSquared.foreach(println)
100
400
900
1600

distinct, flatMap 연산자

예제 데이터는 물건을 구매한 ID 값을 가진 log 파일이다.

echo "15,16,20,20
77,80,94
94,98,16,31
31,15,20" > ~/client-ids.log

scala> val lines = sc.textFile("/home/morris01/study/spark/data/sparkinaction/client-ids.log")
lines: org.apache.spark.rdd.RDD[String] = /home/morris01/study/spark/data/sparkinaction/client-ids.log MapPartitionsRDD[7] at textFile at <console>:24

scala> val idsStr = lines.map(line=>line.split(","))
idsStr: org.apache.spark.rdd.RDD[Array[String]] = MapPartitionsRDD[8] at map at <console>:26

scala> idsStr.foreach(println)
[Ljava.lang.String;@77278a7d
[Ljava.lang.String;@6876c229
[Ljava.lang.String;@25f5ac40
[Ljava.lang.String;@2d06d673
/*
idsStr 에는 string 하나, 하나의 rdd 가생성되는걸로 예상했는데
string 배열을 가진 RDD 가 생성되었다.
*/
scala> idsStr.first
res5: Array[String] = Array(15, 16, 20, 20)

scala> idsStr.collect
res6: Array[Array[String]] = Array(Array(15, 16, 20, 20), Array(77, 80, 94), Array(94, 98, 16, 31), Array(31, 15, 20))
/*
collect 를 사용하여 새로운 배열을 생성 , RDD의 모든 요소를 이 배열에 모아서 반환
*/

이 배열을 단일 배열로 분해 하려면 flatMap을 사용하게된다.

flatMap은 RDD 모든 요소에 적용이 된다.

익명함수가 반환한 배열의 중첩구조를 한단계 제거하고 모든 배열의 요소를 단일 컬렌션으로 병합한다는것이 flatmap 과 map 의 다른 점이다.

scala 에 대한 지식중 TraversableOnce 에 대해서 꼭 알 필요가 있다.

이유는 flatMap의 시그니쳐는 다음과 같이 가지고 있기 때문이다.

def flatMap[U](f:(T)=>TraversableOnce[U]):RDD[U]

map으로 연산을 했던 것을 flatMap을 사용하게 되면 하나의 배열로 값을 불러올 수 있는 것을 확인 할 수 있다.

scala> val idsStr = lines.flatMap(line=>line.split(","))
idsStr: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[2] at flatMap at <console>:26

scala> idsStr.collect
res1: Array[String] = Array(15, 16, 20, 20, 77, 80, 94, 94, 98, 16, 31, 31, 15, 20)
/*
String 의 값을 Int 로 반환해주기 위해서는 _.toInt 메서드를 사용하면된다.
*/
scala> val idsInt = idsStr.map(_.toInt)
idsInt: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[3] at map at <console>:28

scala> idsInt.collect
res2: Array[Int] = Array(15, 16, 20, 20, 77, 80, 94, 94, 98, 16, 31, 31, 15, 20)

Distinct

구매 고객들의 아이디 값 들을 연산하기 쉽게 하나의 배열로 나타냈지만, 구매고객의 수를 구하려면 중복을 제거를 해주어야한다.

보통은 Scala의 Set 함수에 다시 넣을수도있겠지만, 간편하게 Distinct 를 사용하면 된다.

scala> val uniqueIds = idsInt.distinct
uniqueIds: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[6] at distinct at <console>:30

scala> uniqueIds.collect
res4: Array[Int] = Array(15, 77, 16, 80, 98, 20, 31, 94)

scala> val finalCount = uniqueIds.count
finalCount: Long = 8

예제 파일 github : https://github.com/spark-in-action/first-edition/blob/master/ch02/scala/ch02-listings.scala

'BackEnd > Spark' 카테고리의 다른 글

Spark BroadCast  (0) 2019.08.28
SPARK 에서의 기본 행동(action) 연산자 및 변환(transformation)연산자(2)  (0) 2018.06.18
Spark(3) SparkContext-1  (0) 2018.05.16
Spark (2) 기본예제 및 scala  (0) 2018.05.15
SPARK(1)-환경 구축  (1) 2018.05.13
SPARK(3) SparkContext

SPARK(3) SparkContext

 

지난 시간의 Spark의 실행하기 위해

val conf = new SparkConf().setAppName("HelloWorld").setMaster("local[1]")
      .set("spark.executor.memory", "4g")
      .set("spark.driver.memory", "4g")
val sc = new SparkContext(conf)

다음과 같은 코드를작성 하였는데

풀이를 하면

SparkConf() 는 SparkContext를 생성 하기 위한 설정 파일이다.

setAppName()은 Spark어플리케이션의 이름

setMaster() 는 로컬 피씨에서 사용 하기 위해 local로 적어준것이고 [ N ] 은 실행할 스레드의 개수(core)를 의미한다.

로컬 피씨의 전체 의 core를 쓰고 싶다면 [*] 를 사용해주면 된다.

SparkContext 객체는 클러스터상에서 스파크 작업 실행을 관리 하는 객체이다.

 

SparkContext 는 많은 유용한 메서드를 제공하는데 , 많이 쓰이는 것은 탄력적 분산 데이터셋 을 생성하는 메서드들을 가장 자주 사용하게 된다.

탄력적 분산 데이터셋(RDD) 은 클러스터의 여러 노드에 파티션으로 나뉘어 분산되며, 각 파티션은 RDD전체 데이터중 일부를 담게 된다.

여기서 파티션의 의미는 스파크에서 병렬 처리되는 단위.

RDD를 생성하는 간단한 방법은 로컬 객체 컬렉션을 인수로 SparkContext의 parallelize 메서드를 실행 하는것.

val rdd = sc.parallelize(Array(1,2,3,4),4)

첫 번째 파라미터는 병렬 처리 하려는 객체 컬렉션을 나타내며, 두번째 인수는 파티션의 개수이다.

파티션내의 객체들에 대한 연산을 수행하게 될때, 스파크는 구동자 프로세스로 부터 객체 컬랙션의 일부를 가지고 온다.

 

RDD를 HDFS, 텍스트파일 를 포함한 디렉토리로부터 생성하기 위해서는

val rdd2 = sc.textFile('hdfs:///hadoopData/process01.txt') 

textFile 메서드를 사용한다. 단, 메서드의 인수로 디렉토리 이름을 입력하게되면 스파크는 그 디렉터리의 모든 파일을 RDD로 구성요소로 간주하게된다. 이렇게 parallelize, textFile등의 메서드의 코드 의 시점까지는 데이터를 읽어들이거나, 메모리에 올리는 일은 실제로 일어나지 않는다. 스파크는 파티션 내의 객체들에 대해 연산을 수항할때가 되서야 섹션=스플릿 단위로 읽어 RDD에 정의한 필터링 과 같은 집계같은 작업을 통해 가공을 함.

 

오류가 있거나 궁금한점있으면 같이 공유했으면 좋겠습니다.

+ Recent posts