Remote procedure call

유석현(SeokHyun Yu)·2023년 5월 11일
0

분산 시스템

목록 보기
25/27
post-thumbnail

RPC(원격 프로시저 호출)는 분산 시스템에서 액세스 투명성을 달성하기 위한 중요한 개념이다. 'send'와 'receive' 절차를 통한 명시적인 메시지 교환에서는 통신이 전혀 감추어지지 않는다. 따라서 RPC는 프로그램이 다른 컴퓨터에 위치한 프로시저를 호출할 수 있도록 하는 아이디어로 나타났다. 프로그래머에게는 어떠한 메시지 전달도 보이지 않는다.

RPC는 1984년에 Birrell과 Nelson에 의해 개발되었다. 그러나 RPC 개발에는 약간의 문제가 따르는데, 호출하는 프로시저와 호출된 프로시저가 서로 다른 주소 공간에서 실행된다는 것이다. 또한 매개변수와 결과는 서로 다른 기종을 가진 컴퓨터들 사이에서 전달되어야 한다. 더욱이 어느 한 쪽 또는 모두의 컴퓨터가 다운될 수 있으며, 이러한 다양한 장애는 서로 다른 문제를 야기한다.


1. Conventional procedure call

원격 프로시저 호출(RPC)의 기본 작동 방식을 이해하기 위해, 일반적인 프로시저 호출을 예로 들어보겠다. 예를 들어, count = read(fd, buf, nbytes)라는 프로시저 호출이 있다. 여기서 fd는 파일을 가리키는 정수, buf는 문자의 배열, nbytes는 읽을 바이트 수를 나타내는 정수다. 이 호출을 하기 전의 스택 상태를 확인할 수 있다.

프로시저 호출을 수행하려면 다음의 단계를 거친다. 먼저 호출자는 매개변수를 스택에 순서대로 푸시한다. 이때 마지막 매개변수가 먼저 푸시된다. read 프로시저가 실행을 완료하면, 반환 값을 레지스터에 넣고, 반환 주소를 제거하며, 제어권을 호출자에게 다시 전달한다. 마지막으로 호출자는 스택에서 매개변수를 제거하여 스택을 원래 상태로 돌린다.

이전 예시에서 주의해야 할 사항은 다음과 같다.

첫째, 값에 의한 호출(call-by-value)이다. C언어에서 값 매개변수는 값 자체다. fd, nbytes는 스택에 단순히 복사된다.

둘째, 참조에 의한 호출(call-by-reference)이다. C언어에서 참조 매개변수는 변수를 가리키는 포인터다. buf는 참조 매개변수다.

세 번째로 주목해야 할 점은 다른 매개변수 전달 메커니즘이 있다는 것이다. 이는 복사/복원에 의한 호출(call-by-copy/restore)이다. 이 방식에서는 호출자에 의해 변수가 스택에 복사되며 (값에 의한 호출처럼), 호출 후에 복사본이 원래 값으로 덮어쓴다. 대부분의 상황에서 이는 참조에 의한 호출과 동일한 효과를 나타낸다. 하지만 일부 상황에서는 의미가 달라진다. 예를 들어, 동일한 매개변수가 매개변수 목록에 여러 번 나타나는 경우이다.


2. Client and server stubs

원격 프로시저 호출(RPC)의 기본 아이디어는 투명성에 있다. 즉, 호출하는 프로시저는 호출된 프로시저가 다른 기계에서 실행되고 있다는 사실을 알지 못하도록 하는 것이다. 이는 클라이언트와 서버 양쪽 모두에 해당한다. 이러한 투명성을 실현하기 위해 애플리케이션 아래에서 클라이언트 스텁과 서버 스텁이 사용된다.

RPC의 과정은 다음과 같은 단계를 거친다.

  1. 클라이언트 프로시저가 일반적인 방식으로 클라이언트 스텁을 호출한다.
  2. 클라이언트 스텁은 메시지를 구성하고 로컬 운영 체제를 호출한다.
  3. 클라이언트의 운영 체제는 메시지를 원격 운영 체제로 전송한다.
  4. 원격 운영 체제는 메시지를 서버 스텁에게 전달한다.
  5. 서버 스텁은 매개변수를 풀고 서버를 호출한다.
  6. 서버는 작업을 수행하고 결과를 스텁에게 반환한다.
  7. 서버 스텁은 결과를 메시지에 포장하고 로컬 운영 체제를 호출한다.
  8. 서버의 운영 체제는 메시지를 클라이언트의 운영 체제로 전송한다.
  9. 클라이언트의 운영 체제는 메시지를 클라이언트 스텁에게 전달한다.
  10. 스텁은 결과를 풀고 클라이언트에게 반환한다.

이러한 과정을 통해 RPC는 프로시저의 원격 실행을 가능하게 하면서도, 로컬에서 실행되는 것처럼 보이게 하는 투명성을 제공한다.


3. Passing value parameters

매개변수 마샬링(parameter marshalling)은 메시지로 매개변수를 패킹하는 과정을 의미한다. 예를 들어, result = add(i, j)라는 프로시저 호출에서 i, j, result는 모두 정수 매개변수다. 클라이언트 스텁은 두 매개변수를 가져와 메시지에 넣는다. 클라이언트 스텁은 또한 호출할 프로시저의 이름이나 번호를 메시지에 넣는다. 메시지가 서버에 도착하면, 스텁은 필요한 프로시저가 무엇인지를 확인하고 적절한 호출을 수행한다. 서버 스텁에서의 호출은 원래 클라이언트 호출과 유사하게 보이지만, 매개변수는 들어오는 메시지로부터 초기화된 변수다. 서버 스텁은 서버로부터 반환된 결과를 가져와 메시지에 패킹하고, 결과 메시지는 클라이언트 스텁에게 되돌아간다.

그러나 이 과정에서 문제가 발생할 수 있는데, 두 매개변수를 가진 프로시저를 예로 들어보자. 한 개는 정수이고, 다른 한 개는 네 문자로 이루어진 문자열이다. 각 매개변수는 32비트 워드 하나를 필요로 한다. Intel Pentium에서 클라이언트 스텭에 의해 생성된 메시지의 매개변수 부분이 어떻게 보일지를 나타내는 그림이 있다. 그리고 이 메시지가 SPARC에서 받아질 때 어떻게 보일지를 보여주는 그림도 있다. 서버 스텁이 각각 주소 0과 4에서 매개변수를 읽으면, 정수는 83,886,080이고 문자열은 "JILL"이다.

이 문제를 해결하는 잘못된 접근 방법이 있다. 그것은 각 워드의 바이트를 수신한 후에 단순히 반전시키는 것이다. 이제 정수는 5지만, 문자열은 "LLIJ"가 된다. 정수는 다른 바이트 순서에 의해 반전되지만, 문자열은 그렇지 않다. 이는 바이트 순서의 차이로 인한 문제로, 플랫폼 간 데이터 전송에서 주의해야 하는 중요한 사항이다.


4. Passing reference parameters

원격 프로시저 호출(RPC)에서 참조 매개변수를 전달하는 것은 어려운 문제다. 포인터나 참조는 해당 프로세스의 주소 공간에서만 의미가 있기 때문에, 서버에 그냥 주소 번호를 전달할 수 없다.

이 문제를 해결하기 위한 방법 중 하나는 포인터와 참조 매개변수를 사용하지 않는 것이지만, 이는 바람직하지 않은 해결책이다.

더 합리적인 해결책은 메시지에 배열을 복사하여 서버에 전송하는 것이다. 예를 들어, read() 함수의 buf 매개변수를 고려해보자. 이 배열은 메시지로 복사되어 서버로 전송된다. 서버 스텁은 이 배열에 대한 포인터로 서버를 호출할 수 있다. 이렇게 함으로써 참조에 의한 호출을 복사/복원 방식으로 대체할 수 있다. 이 방법은 항상 완벽하지는 않지만, 대부분의 경우 충분한 결과를 제공한다.

만약 스텁이 버퍼가 서버의 입력 매개변수인지 출력 매개변수인지 알고 있다면, 불필요한 복사 과정을 줄일 수 있다. 입력 매개변수의 경우 되돌릴 필요가 없으며, 출력 매개변수의 경우 처음부터 전송할 필요가 없다.

그러나 이 해결책으로도 임의의 데이터 구조에 대한 포인터를 처리하는 일반적인 경우를 완벽하게 다룰 수 없다. 이는 RPC의 한계로, 복잡한 데이터 구조를 전달할 때 추가적인 처리가 필요하다.


5. Passing specification and stub generation

원격 프로시저 호출에서 매개변수를 전달하려면, 호출자와 피호출자가 교환하는 메시지의 형식에 동의해야 한다. 이는 원격 프로시저 호출을 숨기는 데 필요한 요구사항이다.

예를 들어, foobar(char x; float y; int z[5])라는 함수를 고려해보자. 여기서는 한 단어가 4바이트라고 가정하고, 문자를 단어의 가장 오른쪽 바이트에 전송하고, float를 전체 단어로, 그리고 길이를 앞에 두고 단어 그룹으로 배열을 전송한다.

클라이언트와 서버는 단순한 데이터 구조의 표현에 대해 동의해야 한다. 예를 들어, 정수는 2의 보수로, 문자는 16비트 유니코드로, 부동 소수점은 IEEE 표준 #754 형식으로 표현된다. 모든 것은 리틀 엔디안으로 저장된다.

마지막으로, 호출자와 피호출자는 메시지의 실제 교환에 대해 동의해야 한다. 이는 TCP/IP 또는 오류 제어 체계가 있는 UDP를 사용할 수 있다. 이러한 협의는 원활한 통신을 보장하고, 데이터가 올바르게 해석되고 처리되도록 하는 데 중요하다.

profile
Backend Engineer

0개의 댓글