MSA 정리 1

이봐요이상해씨·2021년 12월 30일
0

SpringBoot

목록 보기
2/10

Service Discovery

Service discovery?

Spring Eureka가 해주는 역할 ⇒ 포트 여러개를 하나에 등록 시켜서 사용

외부에서 마이크로서비스를 검색해서 사용하기 위해 사용

key // value로 지정

client ↔ loadbalancer ↔ discoveryservice ↔ msa1, msa2, ms3

@SpringBootApplication
@EnableEurekaServer //유레케 서버로서 지정하기 위해 설정
public class Msa1Application {

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

}
server:
  port: 8761

spring:
  application:
    name: discoveryservice //서비스의 고유한 아이디를 지정 

eureka:
  client:
    register-with-eureka: false //유레카에 등록하는 여부(그런데 이건 서비스로 사용하는것임으로 true로 놓게되면 자기 자신을 서비스로 등록한다는 뜻)
    fetch-registry: false //서버로부터 무언가를 주고 받을것이 아니기 때문에, 서버로서만 상용함으로 false 
<properties>
        <java.version>11</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

UserService

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
package com.example.userservice1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class UserService1Application {

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

}
server:
  port: 9001

spring:
  application:
    name: user-service
eureka:
  client:
    register-with-eureka: true # eurkea서버로부터 인스턴스들의 정보를 주기적으로 가져올지 설정하는 속성, true시 받겠다는 뜻
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #disoveryservice 서버 지정

작동중인걸 확인할 수 있다.

새로운 서비스 등록함 + 버튼 위치의 3번째 버튼 클릭해서 같은 서비스를 카피한 후 그것을 이름 만 바꿔서 저장

그런데 포트번호 중복임으로

environtment 눌러서

VM에 다음과 같이 포트번호 추가(-D는 추가 명령 내릴 때 사용)

터미널에서 실행시 하위 명령어로 실행하면 위와 같이 똑같이 실행 가능 (cmd에선 실행됨 powershell은 안됨 ㅠㅠ)

mvn spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9003'

또 다른 방법(compile로 실행)

  1. mvn clean
  2. mvn compile java
  3. target 파일에 snapshot이 생긴걸 볼 수 있다.

target파일안의 jar파일을 실행함으로서 또다른 포트로 실행

java -jar -Dserver.port=9004 ./target/user-service1-0.0.1-SNAPSHOT.jar

위의 방법을 loadbalancer로 지정하여 실행하는법

server:
  port: 0 #포트번호를 랜덤하게 사용한다 라는 뜻

spring:
  application:
    name: user-service
eureka:
  client:
    register-with-eureka: true # eurkea????? ?????? ??? ????? ???? ???? ??, true? ???? ?
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #disoveryservice ?? ??

그런데 이럴거면 위에서 설정한 applciation 에서 2번째는 포트번호가 설정되어있음으로 이거 삭제하고 해야됨

terminal에서도 mvn spring-boot:run이라고만 처도 알아서 실행됨

그런데

확인해보면 한개만 뜸 왜?

:0은 포트번호가 곳인데, 이건 yml 파일에 써있는 포트번호를 나타냄 따라서 랜덤하게 생성되면 0이 뜨지 않음 그래서 이걸 보여주기 위해

yml 파일을 수정하겠음

server:
  port: 0 #포트번호를 랜덤하게 사용한다 라는 뜻

spring:
  application:
    name: user-service
eureka:
  instance:
		유레카에 출력하기위해 아래와 같이 설정
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance-id:${random.value}}
  client:
    register-with-eureka: true # eurkea????? ?????? ??? ????? ???? ???? ??, true? ???? ?
    fetch-registry: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #disoveryservice ?? ??

이렇게 호스트 네임과 아이디, 랜덤아이디가 출력된것을 볼 수 있다.

API Gateway Service

  1. Proxy 역할을 함

  2. 단일 진입로를 통해 일관적으로 작업을 처리하고자 필요성이 증가하게됨

  3. 따라서 api gateway만 상대하면 됨

  4. 할 수 있는 것들

    1. 인증, 권한 부여
    2. 검색 통합
    3. 응답캐시
    4. 정책, qos다시 시도, 회로차단기
    5. 속도제한
    6. 부하분산
    7. 로깅, 추적, 상관 관계
    8. 헤더, 쿼리 문자열 청구 변환
    9. ip허용 목록에 추가
  5. spring cloude 에서의 msa간 통신

    1. RestTemplate

      RestTemplate resTemplate = new RestTemplate();
      restTemplate.getForObject("http://localhost:8080/", User.class, 200);
    2. Feign Cleint → 마이크로 서비스 이름으로 호출이 가능함(springcloud가 사용하는 것)

      @FeignClient("stores")
      publci interface StoreClient{
      	@RequestMapping(method= RequestMEthod.GET ,value="/stores")
      Lists<Store>getSTroe();
  6. Ribbon : 스프링클라우드 가 로드밸런서로 사용하는 서비스→ 그러나 비동기화 방식이 안맞음 따라서 잘 안사용함(client side loadbalancer)

    1. client 안에 ribbon이 존재하며 이 ribbon이 gateway역할을 했음
    2. 현재는 리본을 2.4에서메인테넌스 상태임
  7. Netflix Zuul: api게이트웨이 역할을 함

    1. 이것도 2.4에서 메인터넌스 상태이다 → 더이상 지원하지 않음
    2. client <> netflix zuul <> services
  8. Spring gateay → 이것을 씀

    server:
      port: 8000
    
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          defaultZone: http://localhost:8761/eureka
    
    spring:
      application:
        name: apigateway-service
      cloud:
        gateway:
          routes: #배열형태로 여러개를 등록 할 수 있다.
            - id: first-service
              uri: http://localhost:8081/
              predicates:
                - Path=/first-service/** #조건절로 이 주소로 접속하면 8081로 보낸다
            - id: second-service
              uri: http://localhost:8082/
              predicates:
                - Path=/second-service/** #조건절로 이 주소로 접속하면 8082로 보낸다
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.4.11</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>apigateway-service</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>apigateway-service</name>
        <description>apigateway-service</description>
        <properties>
            <java.version>11</java.version>
            <spring-cloud.version>2020.0.4</spring-cloud.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>

비동기 방식으로 netty서버가 실행됨을 알 수 있다.

first service, seconde service 셋팅 (second는 8082, second로만 바꾸면된다)

@RestController
@RequestMapping("/first-service") -> 게이트웨이 yml파일에 적은 path를 써줘야함
public class Controller {
    @GetMapping("/welcome")
    public String welcome()
    {
        return "Welcome first service";
    }
}
server:
  port: 8081

spring:
  application:
    name: my-first-service

eureka:
  client:
    fetch-registry: false
    register-with-eureka: false
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>first-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>first-service</name>
    <description>first-service</description>
    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Spring Cloud Gateway - Filter 적용

Gateway Handler Mapping(요정 정보들어옴) → predicate(조건분기) → prefilter(작업이 일어나기전 사전필터), postFilter(처리 끝난후 호출되는 필터) → Gateway Handler Mapping으로 반환

property, javacode로 작업 설정 할 수 있음

람다 → 익명 클래스 → 인스턴스를 바로 생성하고 소멸 시킬 수 있음

람다식(익명의 클래스, 함수를 호출해서 객체를 생성하고 소멸시킴)

메소드체이닝 → 비슷한 메소드를 연속으로 호출

yml 파일 설정을 javacode로

@Configuration
public class FilterConfig {

    //yml 파일에 있는 설정을 자바 코드로 바꿈
    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder){
        return builder.routes() //라우터 등록
                .route(r -> r.path("/first-service/**") //r이라는 값이 전달되면 path를 확인하고
												
												//filter를 적용시킴
                        .filters(f -> f.addRequestHeader("first-request","first-request-header") //request 헤더가 삽입됨
                                .addResponseHeader("first-response","first-response-header")) //response헤더가 삽입됨

                        .uri("http://localhost:8081")) //이 uri로 전달된다
                .route(r -> r.path("/second-service/**")
                        .filters(f -> f.addRequestHeader("second-request","second-request-header")
                                .addResponseHeader("second-response","second-response-header"))
                        .uri("http://localhost:8082"))
                .build();
    }

}

first, second, service(localhost:8000/first-service/message)

@GetMapping("/message")
    public String message(@RequestHeader("first-request") String header){
        log.info(header);
        return "Hello first service";
    }

위에 javcode로 설정했던 방법을 yml 파일로 설정할 수 있다.

이때 config 파일은 @Configuraiton, @Bean은 주석처리해야함

server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters: <-추가된 부분!!!!!!!!!!!!!!!!!!!!
            - AddRequestHeader=first-request, first-reqeust-header2
            - AddResponseHeader=first-request, first-response-header2
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:<-추가된 부분!!!!!!!!!!!!!!!!!!!!
            - AddRequestHeader=second-request, second-reqeust-header2
            - AddResponseHeader=second-request, second-response-header2

custom filter 적용하기

package com.example.apigatewayservice.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

//reactive -> webflux를 사용하는 클래스에 포함됨
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class CustomFilter extends AbstractGatewayFilterFactory<CustomFilter.Config> { // AbstractGatewayFilterFactory<데이터타입>
                                                                                        //여기서 데이터 타입은 우리가 작성한 CustomFilter이고
    public CustomFilter(){ //디폴트 생성자에, Config 상위 클래스만 호출한다.
        super(Config.class);
    }

    @Override //우리가 구현해야할 Config를 오버라이드 한다.
    public GatewayFilter apply(Config config) {
        return (exchange, chain)-> { //람다에서 여기부터는 GatewayFilter의 반환값을 작성한다
            ServerHttpRequest request = exchange.getRequest(); //비동기화된 webflux을 사용할 경우 동기화된 톰캣과 다르게
            ServerHttpResponse response = exchange.getResponse(); //serveletrequest가 아닌 severrequest를 받아야 한다

            //pre필터 적용
            log.info("Custom PRE filter: request id-> {}", request.getId());

            //post 필터 적용
            return chain.filter(exchange).then(Mono.fromRunnable(()-> { //Mono는 webflux처럼 비동기 방식에서 단일값을 전달한다고 지정해주기 위해 작성하는 것
                log.info("Custom POST filter: response code -> {}", response.getStatusCode()); //단일값 하나 전달
            }));
        };
    }

    public static class Config {
        //
    }
}
server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
#            - AddRequestHeader=first-request, first-reqeust-header2
#            - AddResponseHeader=first-request, first-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-reqeust-header2
#            - AddResponseHeader=second-request, second-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록

firstservice, secondservice

@GetMapping("/check")
    public String check(){
        return "HI This is first customservice";
    }

CustomFilter(GLOBAL필터)적용하기

커스텀 필터는 개별적인 라우팅 정보마다 다 등록 해놓아야 함.(id별 등록), 공통적인 부분은 글로벌 필터로 적용 시키면 편하다.

private boolean preLogger처럼 불리언 타입일 경우

isPreLogger() 처럼 is를 앞에 붙여서 메소드 호출 한다 .

server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter #우리가 작성한 클래스 이름
          args:
            baseMessage: Spring Cloud Gateway Global Filter #우리가 클래스 파일에 적은 baseMessaege파라미터값에 들어갈 말을 적음
            preLogger: true
            postLogger: true

      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
#            - AddRequestHeader=first-request, first-reqeust-header2
#            - AddResponseHeader=first-request, first-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-reqeust-header2
#            - AddResponseHeader=second-request, second-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록
package com.example.apigatewayservice.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

//reactive -> webflux를 사용하는 클래스에 포함됨
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class GlobalFilter extends AbstractGatewayFilterFactory<GlobalFilter.Config> {

    public GlobalFilter(){
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain)-> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            //pre필터 적용
            log.info("Custom PRE filter baseMessage: request id-> {}", config.getBaseMessage());

            if (config.isPreLogger()){
                log.info("Global Filter start: request id -> {}", request.getId());
            }

            //post 필터 적용
            return chain.filter(exchange).then(Mono.fromRunnable(()-> {
                if (config.isPostLogger()){
                    log.info("Global Filter start: request id -> {}", response.getStatusCode());
                }
            }));
        };
    }

    @Data
    public static class Config {
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
    }
}

포스트맨으로 보내게 되면 apigateway 인텔리제이 터미널에 다음과 같이 로그가 찍힌다. 보면 글로벌 필터가 먼저 적용되고 그다음 커스텀 필터 그리고 종료 때는 커스텀 필터 먼저 마지막에 글로벌 필터가 적용됨을 알 수 있다. 즉

커스텀필터 적용하기(Loggin filter)

즉 여기까지의 내용 정리

요청이 들어오면

gatway client → gateway handler → globla filter → customfilter → logginfilter → proixed service(우리가 구현한 firts, second service같은것) 응답은 이거 반대 순서로 감

람다식

package com.example.apigatewayservice.filter;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

//reactive -> webflux를 사용하는 클래스에 포함됨
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class LoggingFilter extends AbstractGatewayFilterFactory<LoggingFilter.Config> {

    public LoggingFilter(){
        super(Config.class);
    }

    //구현시켜야할 객체 : apply
    //반환타입 : gatewayFilter
    @Override
    public GatewayFilter apply(Config config) {
//        return (exchange, chain)-> {
//            ServerHttpRequest request = exchange.getRequest();
//            ServerHttpResponse response = exchange.getResponse();
//
//            //pre필터 적용
//            log.info("Custom PRE filter baseMessage: request id-> {}", config.getBaseMessage());
//
//            if (config.isPreLogger()){
//                log.info("Global Filter start: request id -> {}", request.getId());
//            }
//
//
//            //post 필터 적용
//            return chain.filter(exchange).then(Mono.fromRunnable(()-> {
//                if (config.isPostLogger()){
//                    log.info("Global Filter start: request id -> {}", response.getStatusCode());
//                }
//            }));
//        };
        GatewayFilter filter = new OrderedGatewayFilter((exchange, chain)->{
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            //pre필터 적용
            log.info("Logging filter baseMessage: request id-> {}", config.getBaseMessage());

            if (config.isPreLogger()){
                log.info("Logging PRE Filter start: request id -> {}", request.getId());
            }

            //post 필터 적용
            return chain.filter(exchange).then(Mono.fromRunnable(()-> {
                if (config.isPostLogger()){
                    log.info("Logging POST Filter start: response code -> {}", response.getStatusCode());
                }
            }));
        }, Ordered.HIGHEST_PRECEDENCE);//gatewayFilter의 두번재 인자값은 필터의 우선순위를 지정하는 인자값을 넣는다.
        return filter;
    }

    //configuration정보는 내가 자유롭게 지정할 수 있다.
    //내가 여기서 메소드를 지정하면 필터에서 해당 메소드를 지정할 수 있다. 
    @Data
    public static class Config {
        private String baseMessage;
        private boolean preLogger;
        private boolean postLogger;
       //private String sayHello;
    }
}

특히 gatewayfilter는 객체 생성시
oredredGAtewayFilter를 사용해야 한다. 
이건 gatewayfilter를 구현하는 자식 클래스 역할을 한다. 

public class OrderedGatewayFilter implements GatewayFilter, Ordered {
    private final GatewayFilter delegate;
    private final int order;

//우리가 구현함
    public OrderedGatewayFilter(GatewayFilter delegate, int order) {
        this.delegate = delegate;
        this.order = order;
    }

		public GatewayFilter getDelegate() {
        return this.delegate;
    }

	//필터가 해야할 역할을 재정의 할 수 있다. 
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return this.delegate.filter(exchange, chain);
    }

    public int getOrder() {
        return this.order;
    }

    public String toString() {
        return "[" + this.delegate + ", order = " + this.order + "]";
    }
}

Webflux에서는 serveltrequest, response를 더이상 지원하지 않는다.

여기서는

ServerWebExchange를 사용한다 (serverRequest, serverResponse 사용) 이걸로 얻어옴

GateWayFilterChain : pre, post체인 필터를 연결 시켜줌

server:
  port: 8000

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter #우리가 작성한 클래스 이름
          args:
            baseMessage: Spring Cloud Gateway Global Filter #우리가 클래스 파일에 적은 baseMessaege파라미터값에 들어갈 말을 적음
            preLogger: true
            postLogger: true

      routes:
        - id: first-service
          uri: http://localhost:8081/
          predicates:
            - Path=/first-service/**
          filters:
#            - AddRequestHeader=first-request, first-reqeust-header2
#            - AddResponseHeader=first-request, first-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록
             # 비교를 위해 pre필터는 Logging 필터를 넣지 않는다
        - id: second-service
          uri: http://localhost:8082/
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-reqeust-header2
#            - AddResponseHeader=second-request, second-response-header2
             - name: CustomFilter
             - name: LoggingFilter
               args:
                 baseMessage: Logging, filter.
                 preLogger: true
                 postLogger: true

로깅필터가 적용된 second service를 호출하면 나오는 로거이다.

보면 우리가 기대했던 순서와 다르게 나온다. 이유는?

orderedGateWayFilter를 설정할 시 LogginFilter를 Ordered.HIGHEST_PRECDENCE로 설정했기 때문에 가장 먼저 실행이 되었기 때문이다.

만일 Ordered.LOWEST_PRECEDENCE로 설정하면 우리가 기대했던 순서로 작동된다.

         //post 필터 적용
            return chain.filter(exchange).then(Mono.fromRunnable(()-> {
                if (config.isPostLogger()){
                    log.info("Logging POST Filter start: response code -> {}", response.getStatusCode());
                }
            }));
        }, Ordered.HIGHEST_PRECEDENCE);//gatewayFilter의 두번재 인자값은 필터의 우선순위를 지정하는 인자값을 넣는다.
        return filter;

Load Balancer(Eureka 연동)1

클라이언트 호출 → api gateway → eureka server → 서버 확인 → api gateway → 해당서버로 이동

server:
  port: 8000

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8761/eureka

spring:
  application:
    name: apigateway-service

  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            baseMessage: Spring Cloud Gateway Global Filter 
            preLogger: true
            postLogger: true

      routes:
        - id: first-service
          uri: lb://MY-FIRST-SERVICE //lb//등록한 네임으로 uri 지정
          predicates:
            - Path=/first-service/**
          filters:
#            - AddRequestHeader=first-request, first-reqeust-header2
#            - AddResponseHeader=first-request, first-response-header2
             - CustomFilter #우리가 작성한 필터를 여기에 등록
       
        - id: second-service
          uri: lb://MY-SECOND-SERVICE
          predicates:
            - Path=/second-service/**
          filters:
#            - AddRequestHeader=second-request, second-reqeust-header2
#            - AddResponseHeader=second-request, second-response-header2
             - name: CustomFilter
             - name: LoggingFilter
               args:
                 baseMessage: Logging, filter.
                 preLogger: true
                 postLogger: true

first, second-service의 yml파일도 다음과 같이 수정

server:
  port: 8082

spring:
  application:
    name: my-second-service

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8761/eureka

여기서는 총 4개의 파일이 실행된다.

  1. eruka서버(discoveryService)
  2. api gateway
  3. first, second service

Load Balancer(Eureka 연동)2

각 서비스를 2개씩 띄워서 총 4개 돌게끔 실행

그러나 포스트맨으로 요청해도 어느 서비스로 가는지 알 수가 없음

first-service만 랜덤포트 구현

server:
  port: 0#랜덤포트로 설정

spring:
  application:
    name: my-first-service

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8761/eureka

  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

2번째는 터미널로 기동

만일 mvn 컴파일시 버전 안맞아서 에러 난다면

Maven build Compilation error : Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project Maven

참고하여 mvn컴파일 버전을 추가하도록 함

first service controller를 다음과 같이 수정

package com.example.firstservice;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/first-service")
@Slf4j
public class Controller {
    //변수에 바로 Autowired를 쓰지말고
    //이건 yml 파일의 값을 갖고오는 방법 중 하나이다. 
    Environment env;

    //생성자를 통해 주입 후 @Autowired를 통해 빈 등록
    @Autowired
    public Controller(Environment env){
        this.env = env;
    }

    @GetMapping("/welcome")
    public String welcome()
    {
        return "Welcome first service";
    }

    @GetMapping("/message")
    public String message(@RequestHeader("first-request") String header){
        log.info(header);
        return "Hello first service";
    }

    @GetMapping("/check")
    public String check(HttpServletRequest request){
        log.info("Server port= {}", request.getServerPort());
        return String.format("This message indicate First service PORT %S",
                env.getProperty("local.server.port"));//getProperty()안에는
        //우리가 갖고오고 싶은 값을 명시해주면 된다. 우리는 포트 번호를 갖고오고 싶음 
    }
}

라운드 로빈 방식으로 호출 되는 것을 알 수 있다.

데이터 동기화를 위한 Apache Kafka의 활용 1

  1. 브로커(서버)를 이용해서 한 오리진에 있는 리소스를 다른 오리진에 전달해주는 메시지 큐 서비스
  2. 실시간 데이터 피드 관리하기 위해 통일된 높은 처리량, 낮은 지연 시간을 지닌 플렛폼 제공
  3. producer(보내는쪽) consumer(받는쪽) 을 분리,
    1. 메시지를 여러 consumer에게 허용
    2. 높은 처리량을 위한 메시지 최적화
    3. scale-out 가능
    4. eco-system
  4. kafka broker - 카프카 서버
  5. 3대이상의 broker cluster 구성
  6. zookeeper 연동 ←코디네이터 역할(브로커들을 중재)
    1. broker 1↔zookeeper ↔ broker0

                      broker 2
  7. 브로커에 문제가 생기면 또 다른 브로커를 사용하여 메시지를 안전하게 전송

→ 즉 : 브로커 : 메시지를 전달하기위한 저장공간(3대이상 권장)

주키퍼 : 브로커를 중재하는 역할(브로커의 존재 유무, 컨트롤러, 등등의 정보)

여러개의 브로커중 하나는 리더 역할을 해야 한다 이게 메인이 되며, 문제가 생길시 이 리더를 중지 시키고 다른 브로커에서 리더를 선출해서 사용해야함

C:\kafka\kafka_2.13-3.0.0\kafka_2.13-3.0.0

bin : 명령어 모아놓음(window는 bin 안에 windows파일안에 있음 .bat파일로 실행해야함)

config : zooper, broker 등의 서버 설정파일

0개의 댓글