Spring Cloud Gateway

27882
2020-09-16

해당 코드는 Github 공개되어 있습니다.

용어

명칭 설명
라우트(Route) 라우트는 목적지 URI, 조건자 목록과 필터의 목록을 식별하기 위한 고유 ID로 구성된다. 라우트는 모든 조건자가 충족됐을 때만 매칭된다
조건자(Predicates) 각 요청을 처리하기 전에 실행되는 로직, 헤더와 입력돤값 등 다양한 HTTP 요청이 정의된 기준에 맞는지를 찾는다.
필터(Filters) HTTP 요청 또는 나가는 HTTP 응답을 수정할 수 있게한다. 다운스트림 요청을 보내기전이나 후에 수정할 수 있다. 라우트 필터는 특정 라우트에 한정된다.

Getting Started

1
2
        implementation("org.springframework.cloud:spring-cloud-starter-gateway")
        implementation("org.springframework.boot:spring-boot-starter-actuator")
1
2
3
4
5
@SpringBootApplication
    class GatewayServerApplication
    fun main(args: Array<String>) {
        runApplication<GatewayServerApplication>(*args)
    }

필요한 의존성만 추가하면 빠르게 Srping Cloud Gateway를 만들 수 있습니다.

Gateway Route 노출

1
2
3
4
5
6
7
8
9
    management:
        endpoints:
            web:
                exposure:
                    include:
                        - "gateway"
        endpoint:
            gateway:
                enabled: true

위에서 추가한 actuator의존성으로 gateway를 노출하면 아래처럼 url mapping 정보를 확인할 수 있습니다.

현재 아무것도 설정하지 않은 상태이기 때문에 /actuator/gateway/routes를 호출하면 아래와 같은 결과를 확인할 수 있습니다.

1
2
3
4
5
6
    GET http://127.0.0.1:5555/actuator/gateway/routes
    HTTP/1.1 200 OK
    transfer-encoding: chunked
    Content-Type: application/json
    []
    Response code: 200 (OK); Time: 321ms; Content length: 2 bytes

Route 설정

API를 서버를 만들고 게이트웨이와 연결해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    cloud:
            gateway:
                routes:
                    -   id: order-service
                        uri: http://localhost:8181
                        predicates:
                            - Path=/order/**
                        filters:
                            - RewritePath=/order/(?<path>.*),/$\{path}
                    -   id: cart-service
                        uri: http://localhost:8181
                        predicates:
                            - Path=/cart/**
                        filters:
                            - RewritePath=/cart/(?<path>.*),/$\{path}
  • id: 해당 라우트의 고유 식별자를 나타냅니다.
  • uri: 해당 라우터의 주소를 나타냅니다.
  • predicates: 해당 라우터의 조건을 작성, /order/**으로 시작하는 요청의 경우 해당 라우터로 요청을 보냄
  • filters: 해당 라우터의 필터로, RewritePath는 강제로 Patch를 다시 작성합니다.

연결할 API Server

cart-service, order-service 2 개의 API 서버를 구성합니다. 각 포트의 설정은 cloud.gateway.routes에 등록된 포트를 설정합니다.

order-service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
    @RequestMapping("/orders")
    class OrderApi(
        private val orderRepository: OrderRepository
    ) {
        @GetMapping
        fun getOrders(pageable: Pageable) = orderRepository.findAll(pageable)
    }
    @Entity
    @Table(name = "orders")
    class Order(
        @Column(name = "product_id", nullable = false)
        val productId: Long
    ) : EntityAuditing() {
        @Column(name = "order_number", nullable = false)
        val orderNumber: String = UUID.randomUUID().toString()
    }

cart-service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
    @RequestMapping("/carts")
    class CartApi(
        private val cartRepository: CartRepository
    ) {
        @GetMapping
        fun getCarts(pageable: Pageable) = cartRepository.findAll(pageable)
    }
    @Entity
    @Table(name = "cart")
    class Cart(
        @Column(name = "product_id", nullable = false)
        var productId: Long
    ) : EntityAuditing()

Router 확인

actuator/gateway/routes 확인을 해보면 위에서 설정한 라우터를 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    GET http://127.0.0.1:5555/actuator/gateway/routes
    HTTP/1.1 200 OK
    transfer-encoding: chunked
    Content-Type: application/json
    [
      {
        "predicate": "Paths: [/order/**], match trailing slash: true",
        "route_id": "order-service",
        "filters": [
          "[[RewritePath /order/(?<path>.*) = '/${path}'], order = 1]"
        ],
        "uri": "http://localhost:8181",
        "order": 0
      },
      {
        "predicate": "Paths: [/cart/**], match trailing slash: true",
        "route_id": "cart-service",
        "filters": [
          "[[RewritePath /cart/(?<path>.*) = '/${path}'], order = 1]"
        ],
        "uri": "http://localhost:8181",
        "order": 0
      }
    ]
    Response code: 200 (OK); Time: 207ms; Content length: 404 bytes

연결된 서비스 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    GET http://localhost:5555/order/orders?page=0&size=5
    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 1075
    Date: Sat, 22 Aug 2020 09:29:57 GMT
    CUSTOM-RESPONSE-HEADER: It worked
    {
      "content": [
        {
          "productId": 1,
          "id": 1,
          "createdAt": "2020-08-22T17:19:08.038",
          "updatedAt": "2020-08-22T17:19:08.038",
          "orderNumber": "7d684c44-1ea3-4dc4-9247-12c351606df3"
        },
        ...
      ],
      "pageable": {
        ...
      },
      "last": false,
      ...
    }
    Response code: 200 (OK); Time: 168ms; Content length: 1075 bytes

게이트웨이 /order/orders?page=0&size=5를 호출하면 filters.RewritePath에 의해서 orders?page=0&size=5를 호출하게 됩니다. 즉 라우터에 등록된 order-service를 호출하게 됩니다.

Predicates

Predicates는 조건으로서 해당 라우터에 라우팅 될 조건을 표시합니다. 위 예제에서는 Path=/order/**, Path=/cart/**으로 해당 path로 들어오는 경우 해당 라우터로 라우팅 됩니다. 그 밖에도 여러 가지를 지원합니다. 대표적인 몇 개를 정리해보았습니다. 날짜 관련 매개변수는 ZonedDateTime를 사용해야 합니다.

After

1
2
3
4
5
6
routes:
        -   id: order-service
            uri: http://localhost:8181
            predicates:
                - Path=/order/**
                - After=2020-08-23T19:25:19.126+09:00[Asia/Seoul]

After는 특정 날짜 이후에 호출이 가능합니다. 현재 날짜가 After에서 지정한 날짜 보다 이후 이어야 합니다. 서비스에 대한 이벤트 API 등 특정 시점에 Open 시킬 API가 있다면 유용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
    GET http://localhost:5555/order/orders?page=0&size=5
    HTTP/1.1 404 Not Found
    Content-Type: application/json
    Content-Length: 141
    {
      "timestamp": "2020-08-22T10:37:11.955+00:00",
      "path": "/order/orders",
      "status": 404,
      "error": "Not Found",
      "message": null,
      "requestId": "9b635742-2"
    }
    Response code: 404 (Not Found); Time: 28ms; Content length: 141 bytes

현재 시각 2020-08-22T19:25:19.126+09:00[Asia/Seoul] 이라면 HTTP/1.1 404 Not Found을 응답 받게됩니다.

Before

1
2
3
4
5
6
routes:
        -   id: order-service
            uri: http://localhost:8181
            predicates:
                - Path=/order/**
                - Before=2020-08-20T19:25:19.126+09:00[Asia/Seoul]

Befroe는 특정 날짜 이전 호출이 가능합니다. 현재 날짜가 Befroe에서 지정한 날짜 보다 이전 이어야 합니다. 특정 API가 deprecate가 되는 경우 유용합니다.

Between

1
2
3
4
5
6
routes:
        -   id: order-service
            uri: http://localhost:8181
            predicates:
                - Path=/order/**
                - Between=2020-08-17T19:25:19.126+09:00[Asia/Seoul], 2020-08-20T19:25:19.126+09:00[Asia/Seoul]

Between는 특정 날짜 사이에만 호출이 가능합니다. 특정 기간에만 사용하는 이벤트 API 등에 사용하면 유용합니다.

Weight

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
routes:
        -   id: order-service-high
            uri: http://localhost:8181
            predicates:
                - Path=/order/**
                - Weight=group-order, 7
            filters:
                - RewritePath=/order/(?<path>.*),/$\{path}
        -   id: order-service-low
            uri: http://localhost:8787
            predicates:
                - Path=/order/**
                - Weight=group-order, 3
            filters:
                - RewritePath=/order/(?<path>.*),/$\{path}

grpup, weight를 기반으로 그룹별로 가중치를 계산하게 됩니다. 위 설정은 70% order-service-high, 30% order-service-low으로 라우팅을 분배합니다.

Filters

HTTP Request, Reponse에 대한 수정을 할 수 있습니다. 특정 라우터에에서 안에서 동작하게 됩니다.

RewritePath

RewritePath는 HTTP Request를 수정하여 특정 Server에 전달하게 됩니다. 정규표현식을 사용해서 유연하게 HTTP Request Path를 변경합니다.

1
2
3
4
5
 routes:
         -   id: order-service
             uri: http://localhost:8181    
             filters:
                 - RewritePath=/order/(?<path>.*),/$\{path}

RewritePath를 통해서 /order/orders -> /order/orders으로 재작성합니다. 즉, /order/orders?page=0&size=5 요청이 오면 /order/를제거하고 orders?page=0&size=5를 기반으로 order-service를 호출하게 됩니다.

Retry

name 설명 기본값
retries 재시도 횟수 3번
statuses 재시도해야하는 HTTP 상태 코드(org.springframework.http.HttpStatus) -
series 재시도해야하는 HTTP 상태 코드시리즈(org.springframework.http.HttpStatus.Series) 5XX
methods 재시도해야하는 HTTP 메소드(org.springframework.http.HttpMethod) GET
exceptions 재시도해야하는 Exception IOException, TimeoutException
backoff 재시도하는 시간텀 지정 firstBackoff * (factor ^ n) n번 반복 비활성화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
        cloud:
            gateway:
                discovery:
                    locator:
                        enabled: true
                routes:
                    -   id: order-service
                        uri: lb://order-service
                        predicates:
                            - Path=/order/**
                        filters:
                            - RewritePath=/order/(?<path>.*),/$\{path}
                            -   name: Retry
                                args:
                                    retries: 3
                                    statuses: INTERNAL_SERVER_ERROR
                                    methods: GET
                                    backoff:
                                        firstBackoff: 1000ms
                                        maxBackoff: 6000ms
                                        factor: 2
                                        basedOnPreviousValue: false

제시도 횟수는 retries: 3, 재시도 HTTP Status는 statuses: INTERNAL_SERVER_ERROR (500), 재시도 HTTP method는 GET backoff 설정은 10ms(firstBackoff) * (2(factor)* 3(retries))으로 retries 만큼 반복됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
    @RequestMapping("/orders")
    class OrderApi(
        private val orderRepository: OrderRepository
    ) {
        @GetMapping
        fun getOrders(pageable: Pageable): Page<Order> {
            println("getOrders 호출")
            if(true){
                throw RuntimeException("Error")
            }
            return orderRepository.findAll(pageable)
        }
        @GetMapping("/carts/{id}")
        fun getCarts(@PathVariable id: Long) = cartClient.getCart(id)
    }

해당 API는 RuntimeException("Error")를 발생시키고 있어 Status 500을 응답합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    GET http://localhost:5555/order/orders?page=0&size=5
    HTTP/1.1 500 Internal Server Error
    transfer-encoding: chunked
    Content-Type: application/json
    Date: Sat, 22 Aug 2020 14:57:15 GMT
    CUSTOM-RESPONSE-HEADER: It did not work
    {
      "timestamp": "2020-08-22T14:57:15.122+00:00",
      "status": 500,
      "error": "Internal Server Error",
      "message": "",
      "path": "/orders"
    }
    Response code: 500 (Internal Server Error); Time: 7062ms; Content length: 120 bytes

결과를 확인하면 3번의 Retry가 있었고, 결국 500을 리턴하게 됩니다.

1
2
3
4
5
6
    getOrders 호출
    2020-08-22 23:57:08.080 ERROR [order-service,,,] 17139 --- [nio-8181-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Error] with root cause
    getOrders 호출
    2020-08-22 23:57:09.091 ERROR [order-service,,,] 17139 --- [nio-8181-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Error] with root cause
    getOrders 호출
    2020-08-22 23:57:11.107 ERROR [order-service,,,] 17139 --- [nio-8181-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: Error] with root cause

order-service 로그를 확인해보면 3번의 호출이 있었는지를 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
    @RequestMapping("/orders")
    class OrderApi(
        private val orderRepository: OrderRepository
    ) {
        var errorCount = 0
        @GetMapping
        fun getOrders(pageable: Pageable): Page<Order> {
            println("getOrders 호출")
            if (errorCount < 2) {
                println("예외발생 $errorCount 1증가")
                errorCount++
                throw RuntimeException("Error")
            }
            errorCount = 0 // 초기화
            return orderRepository.findAll(pageable)
        }
    }

해당 코드는 2번 예외가 발생하지만 3번째에서 응답을 리턴해주는 코드입니다. 재시도를 3번 실행하기 때문에 3번째에는 정상적인 응답을 받을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    GET http://localhost:5555/order/orders?page=0&size=5
    HTTP/1.1 200 OK
    transfer-encoding: chunked
    Content-Type: application/json
    Date: Sat, 22 Aug 2020 15:13:16 GMT
    CUSTOM-RESPONSE-HEADER: It worked
    {
      "content": [
        {
          "productId": 1,
          "id": 1,
          "createdAt": "2020-08-23T00:11:24.747",
          "updatedAt": "2020-08-23T00:11:24.747",
          "orderNumber": "519011ff-7eaf-4e85-b48f-a9aa6ab879ac"
        }
        ...
      ],
      "pageable": {
       ...
      "totalPages": 2,
       ...
    }
    Response code: 200 (OK); Time: 3034ms; Content length: 1075 bytes

3번의 응답시간을 기다려야 하기 때문에 3034ms 정도 걸리는 걸 확인할 수 있습니다. 재시도는 단순 조회만 하는 GET 요청에 외에는 신중하게 선택해야 합니다. 게이트웨이에서 재시도를 진행하기 때문에 각 서비스 간의 통신에서 생성, 삭제, 수정 등 조회 조건 외에 동작이 있다면 문제가 생길 가능성이 높습니다. 또 HTTP Status 5XX 응답은 재시도를 하는 것은 바람직하지만, HTTP Status 4XXX에서는 동일한 요청이면 동일한 이유로 실패하기 때문에 재시도를 안 하는 게 더 효율적입니다. 단순 조회 용이 아니면 신중하게 사용해야 합니다.

HTTP Timeout 설정

글로벌 설정

1
2
3
4
5
6
spring:
        cloud:
            gateway:
                httpclient:
                    connect-timeout: 10000
                    response-timeout: 10s

connect-timeout 밀리 초 단위로 지정, response-timeout Duration으로 지정 해야 합니다.

1
2
3
4
5
6
7
8
9
10
    @RestController
    @RequestMapping("/orders")
    class OrderApi(
        private val orderRepository: OrderRepository
    ) {
        @GetMapping
        fun getOrders(pageable: Pageable): Page<Order> {
            Thread.sleep(1100) // timeout 발생
            return orderRepository.findAll(pageable)
        }

클라이언트 응답시간이 1초로 설정했기 때문에 1초를 넘어가면 아래와 같이 HTTP/1.1 504 Gateway Timeout응답을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
    GET http://localhost:5555/order/orders?page=0&size=5
    HTTP/1.1 504 Gateway Timeout
    Content-Type: application/json
    Content-Length: 145
    {
      "timestamp": "2020-08-22T14:05:09.267+00:00",
      "path": "/order/orders",
      "status": 504,
      "error": "Gateway Timeout",
      "message": "",
      "requestId": "0d492aaf-1"
    }
    Response code: 504 (Gateway Timeout); Time: 4798ms; Content length: 145 bytes

라우터별 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
        cloud:
            gateway:
                routes:
                    -   id: order-service
                        uri: lb://order-service
                        predicates:
                            - Path=/order/**
                        filters:
                            - RewritePath=/order/(?<path>.*),/$\{path}
                        metadata:
                            connect-timeout: 1000
                            response-timeout: 1000

metadata설정을 통해서 라우터별 설정을 진행할 수 있습니다. 여기서 중요한 점은 metadata 설정 시 connect-timeout, response-timeout 모두 밀리 초 단위로 지정해야 합니다. Global 설정과는 차이가 있습니다.

Logging Sleuth

Gateway Logging

1
2
3
4
5
class GatewayServerApplication
    fun main(args: Array<String>) {
        System.setProperty("reactor.netty.http.server.accessLogEnabled", "true")
        runApplication<GatewayServerApplication>(*args)
    }

Reactor Netty 액세스 로그를 활성화하려면 System.setProperty("reactor.netty.http.server.accessLogEnabled", "true")을 설정해야 합니다. 공식 문서에 따르면 Spring Boot 설정이 아니기 때문에 yml으로 설정하지 않고 위처럼 설정해야 한다고 합니다.

정상적으로 로킹이 되는 것을 확인할 수 있습니다.

Sleuth

스프링 클라우드 슬루스(Sleuth)는 마이크로 서비스 환경에서 서로 다른 시스템의 요청을 연결하여 로깅을 해줄 수 있게 해주는 도구입니다. 이런 경우 슬루스를 이용해서 쉽게 요청에 대한 로깅을 연결해서 볼 수 있습니다. 또 RestTemplate, 페인 클라이언트, 메시지 채널 등등 다양한 플랫폼과 연결하기 쉽습니다. 아래 예제에서는 폐인 클라이언트와 연결해서 로깅하는 방법을 설명하겠습니다.

의존성 추가

1
    implementation("org.springframework.cloud:spring-cloud-starter-sleuth")

gateway-server, order-service. cart-service 모듈에 해당 의존성을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cloud:
        gateway:
            discovery:
                locator:
                    enabled: true
            routes:
                -   id: order-service
                    uri: lb://order-service
                    predicates:
                        - Path=/order/**
                    filters:
                        - RewritePath=/order/(?<path>.*),/$\{path}
                -   id: cart-service
                    uri: lb://cart-service
                    predicates:
                        - Path=/cart/**
                    filters:
                        - RewritePath=/cart/(?<path>.*),/$\{path}

openfeign을 이용해서 클라이언트를 호출할 것이므로 discovery(Eureka) 설정을 합니다.(아래 포스팅에서 유레카, 페인 관련 설정을 진행하겠습니다.) 호출 순서가 Gateway -> order-service -> cart-service 호출에 대한 로깅입니다.

1
2
3
4
5
6
7
8
9
10
11
    # spring-gateway
    2020-08-22 22:11:20.671 DEBUG [gateway-server,d1905ab24f0b5d1a,d1905ab24f0b5d1a,true] 12133 --- [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: order-service
    2020-08-22 22:11:20.671 DEBUG [gateway-server,d1905ab24f0b5d1a,d1905ab24f0b5d1a,true] 12133 --- [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: GET http://localhost:5555/order/orders/carts/2] to Route{id='order-service', uri=lb://order-service, order=0, predicate=Paths: [/order/**], match trailing slash: true, gatewayFilters=[[[RewritePath /order/(?<path>.*) = '/${path}'], order = 1], [[Retry retries = 3, series = list[SERVER_ERROR], statuses = list[502 BAD_GATEWAY], methods = list[GET, POST], exceptions = list[IOException, TimeoutException]], order = 2]], metadata={}}
    # order-service
    2020-08-22 22:11:20.685  INFO [order-service,d1905ab24f0b5d1a,ba6672843cb90f99,true] 11949 --- [nio-8181-exec-6] com.service.order.HttpLoggingFilter      : 
        ⊙ GET /orders/carts/2
        ├─ Headers: accept: application/json, user-agent: Apache-HttpClient/4.5.12 (Java/11.0.7), accept-encoding: gzip,deflate, custom-request-header: userName, forwarded: proto=http;host="localhost:5555";for="127.0.0.1:57509", x-forwarded-for: 127.0.0.1, x-forwarded-proto: http, x-forwarded-prefix: /order, x-forwarded-port: 5555, x-forwarded-host: localhost:5555, host: 192.168.0.5:8181, x-b3-traceid: d1905ab24f0b5d1a, x-b3-spanid: ba6672843cb90f99, x-b3-parentspanid: d1905ab24f0b5d1a, x-b3-sampled: 1, content-length: 0
    # cart-service
    2020-08-22 22:11:20.683  INFO [cart-service,d1905ab24f0b5d1a,716b9cd4e4e52bdf,true] 11935 --- [nio-8282-exec-5] com.service.cart.HttpLoggingFilter       : 
        ⊙ GET /carts/2
        ├─ Headers: x-b3-traceid: d1905ab24f0b5d1a, x-b3-spanid: 716b9cd4e4e52bdf, x-b3-parentspanid: ba6672843cb90f99, x-b3-sampled: 1, accept: */*, user-agent: Java/1.8.0_212, host: 192.168.0.5:8282, connection: keep-alive

로그를 보면 gateway-server에서 traceId: d1905ab24f0b5d1a발급하고 order-service에게 전달할 때 header 정보에 x-b3-traceid: d1905ab24f0b5d1a를 추가하고, cart-service도 마찬가지로 traceId를 전달받고 자신의 고유한 ID x-b3-parentspanid: ba6672843cb90f99(order-service에서 전달받은) 발급합니다. 결국 d1905ab24f0b5d1a 값 하나로 연결된 하나의 요청을 추적할 수 있습니다.

Eureka & Feign & Ribbon

Spring Cloud Gateway는 유레카 연동도 손쉽게 가능합니다. 본 포스팅은 Spring Cloud Gateway에 대한 포스팅이므로 유레카에 대한 설정은 다루지 않겠습니다. 해당 내용은 실제 코드를 확인해 주세요.

order-service, cart-service 서비스를 유레카에 등록 시켰습니다. 이제 라우터에 uri를 연결하기만 하면 손쉽게 연결이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gateway:
            discovery:
                locator:
                    enabled: true
            routes:
                -   id: order-service
    #                    uri: http://localhost:8181 # 기존 방시
                    uri: lb://order-service # 유레카를 통한 방식
                    predicates:
                        - Path=/order/**
                    filters:
                        - RewritePath=/order/(?<path>.*),/$\{path}
                -   id: cart-service
    #                    uri: http://localhost:8181 # 기존 방시
                    uri: lb://cart-service # 유레카를 통한 방식
                    predicates:
                        - Path=/cart/**
                    filters:
                        - RewritePath=/cart/(?<path>.*),/$\{path}

설정은 간단합니다. uri: lb://{service-name}형식으로 유레카에 등록된 서비스 네임을 작성하게 되면 완료됩니다. 유레카에 등록했기 때문에 Feign, Ribbon 이용한 클라이언트 사이드 로드 밸런싱이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@FeignClient("cart-service")
    @RibbonClient("cart-service")
    interface CartClient {
        @GetMapping("/carts/{id}")
        fun getCart(@PathVariable id: Long): CartResponse
        data class CartResponse(
            val productId: Long
        )
    }
    @RestController
    @RequestMapping("/orders")
    class OrderApi(
        private val cartClient: CartClient
    ) {
        @GetMapping("/carts/{id}")
        fun getCarts(@PathVariable id: Long) = cartClient.getCart(id)
    }

게이트웨이를 호출해서 order-service를 호출하고, 페인 클라이언트를 이용해서 cart-service를 호출하는 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
    GET http://localhost:5555/order/orders/carts/2
    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 15
    Date: Sat, 22 Aug 2020 13:37:48 GMT
    CUSTOM-RESPONSE-HEADER: It worked
    {
      "productId": 2
    }
    Response code: 200 (OK); Time: 109ms; Content length: 15 bytes

출처


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.