Spring

Dubbo Spring Boot

AlwaysPr 2019. 3. 17. 13:02

Apache Dubbo

고성능, 가벼운 Java 기반 오픈 소스 RPC 프레임워크

  • Interface 기반의 원격 호출(remote call)
  • fault tolerance, load balancing
  • Service 자동 등록 및 감지 (Service Registry)

RPC란?

  • Remote Procedure Call
  • 외부 서비스를 함수나 메소드를 통해 호출하는 형태

부가설명
분산서비스시 외부 서비스는 주로 HTTP를 통해 호출을 한다. 그리고 Application 레벨에서 header, body를 조작한다. 그러나 RPC는 마치 메소드를 호출하듯이 외부 서비스를 사용할 수 있는 간편함을 가지고 있다.

img


백문이 불여일타! 직접 코드를 작성해보자.
필자는 Spring boot와 편의상 maven 멀티모듈 구조를 통해서 코드를 작성하겠다. 구조는 아래와 같다.

Root
  - provider
  - consumer

또한 spring boot starter도 제공하기 때문에 더욱이 간단히 쓸 수 있다. 모든 코드가 다 작성된것이 아니기 때문에 Github의 코드와 같이 보면 좀 더 이해하기 쉬울 것 같다.

다음과 같은 분산시스템 흐름으로 만들어보자. 인터넷쇼핑에서 물건을 구입한다. 배송을 보내야하기 때문에 주소를 가진 회원정보를 가져와야한다. 물론 이외에도 많이 있겠지만 생략하겠다.

  1. 고객이 물건을 구입
  2. consumer에서 provider로 유저정보 조회
  3. 이후 작업

공통 Dependency - 필자는 부모 pom.xml에 설정을 하였다.

<properties>
    <dubbo.version>2.7.0</dubbo.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>${dubbo.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
</dependencies>

Provider

누군가에게 특정 기능을 제공한다. 예를 들면 해당 Provider의 서비스가 회원과 관련된 서비스일 때 누군가 회원의 id로 조회를 하고싶으면 이 Provider를 이용하면 된다. 좀 더 쉽게말하면 Provider가 제공해주는 interface를 단순히 호출하면 된다.

먼저, Dependency부터 설정하자.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

DB에서 회원정보를 조회해야 하기때문에 인메모리 DB인 h2와 JPA를 설정하였다.

다음은 application.properties에서 간단히 dubbo를 setting 하자.

# Spring boot application
spring.application.name=dubbo-provider-demo

# Dubbo Application
## dubbo.application.name=${spring.application.name}
# Dubbo Protocol
dubbo.protocol.name=dubbo
dubbo.protocol.port=12345
## Dubbo Registry
dubbo.registry.address=N/A

dubbo.application.name은 dubbo를 적용하려면 꼭 필요한 설정이다. 그러나 spring.application.name이 있으면 이것을 default로 자동 설정하니 상황에 맞게하자. 필자는 그냥 spring.application.name만 추가하였다.
그리고 protocol로 사용할 name, port를 설정한다. consumer는 이 정보를 통해 연결한다.
registry는 zookeeper같은 Service Registry를 의미한다. 본문에서는 다루지않으니 필요하다면 여기를 참고하자.

이제는 ProviderApplication class로 가보자.

@DubboComponentScan
@SpringBootApplication
public class ProviderApplication implements CommandLineRunner {

    private final MemberRepository memberRepository;

    public ProviderApplication(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

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

    @Override
    public void run(String... args) throws Exception {
        this.memberRepository.save(new Member("minsoo", 27,"강남구" ,LocalDateTime.now()));
    }
}

먼저 눈에 띄는 것은 @DubboComponentScan이다. Spring의 ComponentScan 처럼 Dubbo도 객체들을 Scan하는 과정이 필요하기 때문에 꼭 작성해주어야하며, annotation의 value를 이용하여 특정 package를 선택할 수도 있다.

그외에는 Test를 위해 DB에 data를 insert하는 로직이다.

다음은 consumer에서 provider를 사용하기 위한 수단으로서 interface를 제공해줘야한다.

public interface MemberService {
    Member getMember(Long id);
}

import org.springframework.stereotype.Service;

@Service
@org.apache.dubbo.config.annotation.Service
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public Member getMember(Long id) {
        return this.memberRepository.findById(id)
                .orElseThrow(NotFoundMember::new);
    }

    private static class NotFoundMember extends RuntimeException {
    }
}

첫번째 @Service는 스프링 빈을 등록하는데 쓰이는 Annotation이다. 두번째 @Service는 dubbo로 사용할 객체를 등록하는 Annotation이다. 두개의 이름이 똑같아서 불편함이 있긴한데, 두 Annotation을 상속받는 Custom Annotation을 하나 만들어서 사용하면 좀 더 간편할 것 같다.

@Entity
public class Member implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private Integer age;

    private String addr;

    private LocalDateTime createDate;

    //getter, setter 등 생략
}

dubbo를 통해서 객체를 주고받을때 주의해야할 점은 Serializable을 구현하고있어야만 데이터가 이동할 수 있다. 만약에 그러지 않으면 런타임에러를 내뱉는다. 그로 인해 Optional 같은 객체는 전달할 수는 없다.

Consumer

consumer은 provider에서 제공하는 서비스들을 사용한다.

이것도 Dependency 부터 살펴보자.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.ms</groupId>
        <artifactId>provider</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

spring mvc를 사용하기위해 spring-boot-starter-web를 받았고, provider가 제공해주는 interface를 사용하기위해 provider 또한 디펜던시를 설정했다.

application.properties에서 다음을 설정하자.

spring.application.name=dubbo-consumer-demo

consumer도 역시 name을 설정해줘야한다.

@RestController
@RequestMapping("/orders")
public class OrderController {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Reference(url = "dubbo://127.0.0.1:12345")
    private MemberService memberService;

    @PostMapping
    public String order(@RequestBody OrderRequest request) {
        final Member member = this.memberService.getMember(request.getMemberId());
        logger.info("result ::: {}", member);

        //...
        return "success";
    }
}

여기서 눈여겨 볼것은 @Reference이다. 이것을 스프링의 @Autowired로 쉽게 생각하면된다. URL정보를 토대로 provider와 통신을 한다. 이 url은 provider에서 작성한 dubbo.protocol.name, ip, dubbo.protocol.port 이다.

또한 그 아래 MemberService는 provider에서 제공해주는 interface이다. consumer에는 이 MemberService interface를 구현한 객체가 없으며, 구현한 객체는 provider에 있는 MemberServiceImpl이다.

원격호출을 마치 local에 있는 메소드를 호출하는 것처럼 사용하니 참 간편하지 않은가?

이 처럼 직접적으로 호출을 할 수도 있고, Apache ZooKeeper나 Nacos의 Service Registry 기능을 통하여 간접적으로도 호출이 가능하다.

단, spring boot dubbo의 주의할 점은 아직 정식 Release되지 않은 Incubator 라는 점이다.

참고자료