Spring Cloud: Zuul and Ribbon

2019. 12. 1. 00:11Spring Micro Services/Zuul and Ribbon

반응형

Zuul And Ribbon


Zuul


Zuul의 두가지 큰 기능은 Routing 과 Filter 이다. 


01.Routing

Micro Service는 서비스 단위로 사용자에게 서비스를 제공해준다. 하지만 관리의 측면에서 보면 서비스별로 주소 포트를 일일이 관리하기란 쉬운 일이 아닐것이다. Zuul의 가장 큰 목적은 클라이언트로 부터 모든 요청을 받아 내부 마이크로서비스들에게 요청을 전달하므로 단일 종단점을 갖게한다.




[Zuul과 Ribbon을 사용한 주문/배송조회 서비스흐름]



위의 그림을 보고 예를 들어 보도록 하겠다. 사용자가 주문요청 서비스와, 배송조회 서비스를 요청한다고 하면 서버 설정은 주문서비스는 7000번 포트에서 실행되고 있고, 배송서비스는 8000번 포트에서 실행되고 있다. 기존데로 하면 주문서비스는 http://localhost:7000/order, 배송서비스는 http://localhost:8000/delivery/송장번호 이렇게 호출 했을 것이다.


하지만 Zuul의 Routing을 이용하여서 http://localhost:8070 으로 단일 종단점으로 서비스를 호출 하여서 API Gateway 서버에서 마이크로서비스간 커뮤니케이션을 하게 된다.


02.Filter

클라이언트가 보낸 요청을 라우팅 하기 전, 라우팅할 때, 라우팅한 후 응답을 돌려 받았을 때 필요한 작업을 수행한다.

예제에서는 간단히 라우팅전에 사용자가 요청한 URL과 파라미터 정보를 출력하는 Filter를 만들어 보도록 하겠다.




Ribbon


리본의 주요 개념은 이름기반 서비스 호출 클라이언트(named client)라고 말할 수 있다. 서비스 디스커버리에 접속할 필요 없이 호스트 이름과 포트를 사용한 전체 주소 대신에 이름을 사용해 다른 서비스를 호출 하기 때문이다. - 마스터링스프링클라우드 책 P111 인용 


위의 그림을 보면 주문처리는 01.주문서비스를 호출 하여서 사용자의 주문정보를 저장하고 02.결제서비스를 호출 하여서 사용자의 결제 처리를 하고 정상적으로 결제가 이루어진 경우 03.상품서비스를 호출 하여서 상품의 재고 수량을 업데이트 한다.


이 과정에서 서비스를 호출 할때 결제서비스는 http://payment-service/payment, 상품서비스는 http://product-service/products 호출 하게 된다. 관리 측면에서 보면 네트워크주소(IP)나 포트 정보를 몰라도 서비스 이름만 알아도 된다. 


만약 수많은 서비스가 있다고 가정 했을때 관리측면에서 굉장히 효율적일 것이다.



예제


예제를 설명하기 전에 메이븐모듈 프로젝트로 아래의 프로젝트를 먼저 생성한다. 생성 절차는 초반에 설명 하였으므로 생략한다.


[예제 프로젝트 구조]


위의 프로젝트에서 기존과 변경된 부분만 아래 설명 하도록 하겠다.



gateway-service


Zuul 프로젝트로서 서비스의 종단점 역할 및 Routing 처리를 한다. 그리고 라우팅 전에 서비스의 URL과 파라미터를 출력하는 Pre Filter도 작성해 보도록 하자.


01. application.yml

server:
  port: ${PORT:8070}
  
zuul:
  ignoredServices: '*'
  routes:
    order:
      path: /order/**
      url: http://localhost:7000/order

    delivery:
      path: /delivery/**
      url: http://localhost:8000/delivery      

- 주문, 배송 서비스로 라우팅 처리


02. GatewayApplication

package com.roopy.services.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

import com.roopy.services.gateway.filter.RequestLoggingFilter;

@SpringBootApplication
@EnableZuulProxy
public class GatewayApplication {

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

	@Bean
	public RequestLoggingFilter requestLoggingFilter() {
		return new RequestLoggingFilter();
	}
}

- @EnableZuulProxy: Zuul Proxy Server 생성

- 서비스의 URL 및 파라미터 출력을 위한 RequestLoggingFilter 선언


03. RequestLoggingFilter

package com.roopy.services.gateway.filter;

import javax.servlet.http.HttpServletRequest;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.CharStreams;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.HttpServletRequestWrapper;

public class RequestLoggingFilter extends Filterer {
	
	private static Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);

	
	@Override
	public Boolean should Filter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = new HttpServletRequestWrapper(ctx.getRequest());
		
		log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
		
		String requestData = null;
		JSONParser jsonParser = new JSONParser();
		JSONObject requestJson = null;
		
		try {
			if (request.getContentLength() > 0 ) {
				requestData = CharStreams.toString(request.getReader());
			}
			
			if (requestData == null) {
				return null;
			}
			requestJson = (JSONObject) jsonParser.parse(requestData);
		} catch (Exception e) {
			log.error("Error parsing JSON request", e);
			return null;
		}
		log.info(String.format("%s request payload %s", request.getMethod(), requestJson.toJSONString()));
		return null;
	}

	@Override
	public String filterType() {
		return "pre";
	}

	@Override
	public int filterOrder() {
		return 0;
	}

}

- filterType: pre,route,post

- filterOrder: 실행순서 정의



order-service



01. application.yml

server:
  port: 7000

spring:
  application:
    name: order-service
    
  datasource:
    hikari:
      connection-test-query: SELECT 1
      minimum-idle: 1
      maximum-pool-size: 5
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: admin!@34
   
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    properties:
      hibernate: 
        format_sql: true
        temp.use_jdbc_metadata_defaults: false
    hibernate:
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl

##############################################################################
# payment-service, product-service의 리본클라이언트에 대한 네트워크 주소 설정#
##############################################################################
payment-service:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:7001
    
product-service:
  ribbon:
    eureka:
      enabled: false
    listOfServers: localhost:7002 

기존 소스와 변경된 부분은 ribbon.listOfServers 속성을 사용해 두 개의 다른 리본 클라이언트에 네트워크 주소를 설정한다.



02. OrderApplication

package com.roopy.services.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@RibbonClients({
	@RibbonClient(name = "payment-service"),
	@RibbonClient(name = "product-service")
})
public class OrderApplication {
	
	@Bean
	@LoadBalanced
	public RestTemplate getRestTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) {
		SpringApplication.run(OrderApplication.class, args);
	}

}

application.yml에 구성된 이름 목록을 선언해 리본 클라이언트를 사용하도록 한다. 이를 위해 메인 클래스 또는 다른 스프링 컨피규레이션 클래스에 @RibbonClients를 사용한다. RestTemplate 빈을 등록하고 @LoadBalanced를 사용해 스프링 클라우드 구성 요소와 상호 작용이 가능하도록 설정 - 마스터링스프링클라우드 책 P113 발취



나머지 프로젝트 소스는 변경된 부분이 없으므로 전체 소스를 참고 바랍니다.

소스경로: https://github.com/roopy1210/spring-msa-with-zuul

반응형