기초 개념
-
네트워크를 통해 분산 시스템에서 프로시저 호출을 가능하게 하는 프로토콜과 패턴
- 즉, RPC는 네트워크를 통해 분산 시스템에서 프로시저를 호출하는 방식을 의미
-
RPC는 분산 시스템에서 프로시저 호출을 가능하게 하는 기술이지만, RPC라는 용어 자체는 프로그래밍 언어나 기술에 제한되지 않습니다.
- 따라서 Remote Procedure Call은 RPC의 특정 구현 방법을 가리키는 것이고, 이를 이용해 분산 시스템에서 프로시저를 호출하는 것이 가능합니다.
- 예를 들어, Java에서는 RMI(Remote Method Invocation)라는 RPC 구현 방법을 제공하고 있고, Python에서는 Pyro(Python Remote Objects)와 같은 RPC 라이브러리를 사용할 수 있습니다.
- 따라서 RPC는 분산 시스템에서 프로시저 호출을 가능하게 하는 일반적인 개념이며, 이를 구현하기 위해 다양한 방식과 라이브러리가 존재합니다.
python 에서 사용법
Pyro4
- Python용 Remote Procedure Call (RPC) 라이브러리로,
- 분산 시스템과 클라이언트-서버 구조를 구현할 때 사용됩니다.
- Pyro4를 사용하면 Python 객체를 다른 컴퓨터나 프로세스에서 사용할 수 있으며, 네트워크 통신을 통해 이 객체들을 다룰 수 있습니다.
구체적 설명
-
Pyro4는 파이썬으로 작성된 분산 객체 지향 프레임워크입니다.
-
Pyro4는 Remote Procedure Call(RPC)을 기반으로 동작하며, 객체 지향 프로그래밍의 장점인 코드의 재사용성, 유지 보수성, 확장성 등을 분산 환경에서도 쉽게 구현할 수 있도록 지원합니다.
-
Pyro4의 동작 원리는 크게 두 가지로 나눌 수 있습니다.
-
첫 번째는 Pyro4의 객체 노출 기능이며, 두 번째는 Pyro4의 RPC 기능입니다.
-
Pyro4의 객체 노출 기능
- Pyro4는 Pyro daemon이라는 백그라운드 프로세스를 실행하고, Pyro daemon은 Pyro4 객체를 노출합니다.
- 객체 노출은 Pyro4에서 제공하는 decorator를 사용하여 간단하게 구현할 수 있습니다.
- 이때, 노출된 객체는 고유한 URI(Uniform Resource Identifier)를 갖게 되며, 이 URI를 통해 원격으로 호출될 수 있습니다.
-
Pyro4의 RPC 기능
-
Pyro4의 RPC 기능은 클라이언트와 서버 간의 통신을 위해 사용됩니다.
-
클라이언트는 Pyro4의 proxy를 사용하여 노출된 객체를 원격으로 호출하며, 서버는 Pyro daemon을 통해 호출을 받고, 해당 객체의 메서드를 실행하고 결과를 반환합니다.
- RPC에서 Proxy는 원격 객체에 대한 클라이언트 측의 대리자입니다.
- 클라이언트가 원격 객체에 대한 메서드를 호출하면, 프록시는 이를 원격 서버에 전달하고, 서버에서 반환된 결과를 다시 클라이언트에게 전달합니다.
- 이 과정에서 프록시는 네트워크 통신을 처리하고, 서버와의 연결 상태를 관리합니다.
- Pyro4에서도 이와 같은 방식으로 동작합니다.
- 클라이언트는 원격 객체에 대한 프록시를 생성하고, 이를 사용하여 메서드를 호출합니다.
- Pyro4 프록시는 Pyro4 Daemon에 등록된 원격 객체의 위치를 알아내고, 원격 객체로부터 반환된 결과를 클라이언트에게 전달합니다.
- 이 과정에서 Pyro4 프록시는 Pyro4 프로토콜을 사용하여 네트워크 통신을 처리합니다.
- Pyro4 프록시는 다음과 같은 방식으로 네트워크 통신을 처리합니다.
- 클라이언트가 프록시를 사용하여 원격 객체에 대한 메서드를 호출합니다.
- 프록시는 Pyro4 프로토콜을 사용하여 원격 객체의 위치를 알아냅니다.
- 프록시는 원격 객체에 대한 요청 메시지를 Pyro4 프로토콜을 사용하여 전송합니다.
- 원격 객체는 요청 메시지를 받고, 결과를 Pyro4 프로토콜을 사용하여 반환합니다.
- 프록시는 결과를 받아 클라이언트에게 전달합니다.
- Pyro4 프록시는 또한 서버와의 연결 상태를 관리합니다. 이를 위해 Pyro4는 다음과 같은 기능을 제공합니다.
- Keep-alive: 클라이언트와 서버 간의 연결이 끊어졌을 때, Pyro4는 연결을 다시 시도하고 유지할 수 있는 Keep-alive 메커니즘을 제공합니다.
- Connection pooling: Pyro4는 연결 풀링을 지원하여 클라이언트와 서버 간의 연결을 재사용할 수 있습니다.
- Timeout: Pyro4는 클라이언트와 서버 간의 통신이 지연되는 경우, 타임아웃 기능을 사용하여 이를 처리할 수 있습니다.
- 이러한 기능을 통해 Pyro4 프록시는 원격 객체에 대한 안정적인 네트워크 통신을 제공하고, 서버와의 연결 상태를 유지할 수 있습니다.
-
이때, Pyro4는 메모리 관리 기능을 제공합니다.
-
Pyro4는 객체를 캐싱하여 객체를 다시 생성하지 않도록 하고, GC(garbage collector)를 사용하여 더 이상 사용되지 않는 객체를 삭제합니다.
-
이를 통해 객체의 생성과 삭제를 최적화하여, 메모리 사용량을 줄이고, 성능을 향상시킵니다.
-
따라서, Pyro4는 객체 노출 기능과 RPC 기능을 통해 객체 지향 프로그래밍의 장점을 분산 환경에서도 쉽게 구현할 수 있도록 지원하며, 메모리 관리 기능을 제공하여 성능을 최적화합니다.
Pyro 서버
- some_object는 원격으로 호출할 수 있는 객체입니다.
- Pyro4는 이 객체를 Pyro4가 제공하는 Daemon 객체에 등록한 뒤, name server에 등록합니다.
- 이제 다른 컴퓨터나 프로세스에서 이 객체를 호출할 수 있게 됩니다.
- Pyro4.locateNS()
- Pyro4 네임 서버는 Pyro4 객체의 이름과 URI (Uniform Resource Identifier)를 관리하는 중앙 집중식 레지스트리 역할을 합니다.
- 따라서 Pyro4 객체를 다른 컴퓨터나 프로세스에서 사용하려면 해당 객체를 Pyro4 네임 서버에 등록해야 합니다.
- Pyro4.locateNS 함수는 Pyro4 네임 서버의 위치를 찾아주는 역할을 합니다.
- 이 함수는 네트워크에서 Pyro4 네임 서버를 찾고 해당 서버의 URI를 반환합니다.
- 이를 통해 Pyro4 클라이언트는 Pyro4 네임 서버에 등록된 객체의 URI를 얻을 수 있으며, 이를 사용하여 원격 객체를 호출할 수 있습니다.
import Pyro4
daemon = Pyro4.Daemon()
ns = Pyro4.locateNS()
uri = daemon.register(some_object)
ns.register("example.some_object", uri)
print("Pyro4 server running...")
daemon.requestLoop()
Pyro 클라이언트
- PYRONAME은 name server에 등록된 객체를 찾을 때 사용되는 프로토콜입니다.
- "example.some_object"는 name server에 등록된 객체의 이름입니다.
- 이제 Pyro4 Proxy 객체를 이용하여 some_object 객체의 메소드를 호출하고, 반환값을 받아올 수 있습니다.
import Pyro4
proxy = Pyro4.Proxy("PYRONAME:example.some_object")
result = proxy.some_method()
print(result)
간단 예제
import Pyro4
@Pyro4.expose
class Calculator(object):
def add(self, x, y):
return x + y
def subtract(self, x, y):
return x - y
daemon = Pyro4.Daemon()
uri = daemon.register(Calculator())
ns = Pyro4.locateNS()
ns.register("example.calculator", uri)
print("Pyro4 server running...")
daemon.requestLoop()
다른 RPC 라이브러리?
Pyro4
- 장점
- 객체지향적인 접근 방식을 취하기 때문에, 사용하기 간편하고 코드의 가독성이 높습니다.
- Python 객체를 직접 사용할 수 있기 때문에, 데이터 전송 및 구조화의 작업이 줄어듭니다.
- 단점
- Python에 의존하고 있기 때문에, 다른 언어와의 호환성이 낮습니다.
- 높은 대역폭 및 빠른 속도를 요구하는 대규모 시스템에서는 부적합할 수 있습니다.
XML-RPC
- Python 표준 라이브러리에 포함되어 있는 XML-RPC는 XML을 이용하여 데이터를 주고받는 RPC 프로토콜입니다.
- 서버와 클라이언트 간에 HTTP를 이용하여 통신하며, 서버는 SimpleXMLRPCServer 모듈을 이용하여 작성할 수 있습니다.
- 장점
- Python의 표준 라이브러리에 포함되어 있어, 추가적인 설치가 필요하지 않습니다.
- XML 포맷을 사용하기 때문에 다른 플랫폼 간의 호환성이 높습니다.
- 단점
- XML 포맷을 사용하기 때문에, 처리 속도가 느릴 수 있습니다.
- 사용하기가 다른 라이브러리에 비해 복잡합니다.
JSON-RPC
- JSON-RPC는 JSON을 이용하여 데이터를 주고받는 RPC 프로토콜입니다.
- XML-RPC와 마찬가지로 HTTP를 이용하여 통신하며, Python에서는 jsonrpclib 라이브러리를 이용하여 클라이언트를 작성할 수 있습니다.
- 장점
- XML-RPC보다 처리 속도가 빠릅니다.
- JSON 포맷을 사용하기 때문에 다른 플랫폼 간의 호환성이 높습니다.
- 단점
- XML-RPC와 마찬가지로, 사용하기가 다른 라이브러리에 비해 복잡합니다.
- 데이터 구조화를 위한 추가적인 작업이 필요합니다.
gRPC
- gRPC는 Google에서 개발한 고성능 RPC 프레임워크로, Protocol Buffers를 이용하여 데이터를 주고받습니다.
- HTTP/2를 기반으로 하며, 다양한 언어에서 사용할 수 있습니다.
- Python에서는 grpcio 라이브러리를 이용하여 gRPC 서버와 클라이언트를 작성할 수 있습니다.
- 장점
- Protocol Buffers를 사용하여 데이터 전송 및 구조화를 빠르게 처리할 수 있습니다.
- HTTP/2 기반으로 설계되어 있어, 높은 대역폭과 빠른 속도를 보장합니다.
- 다양한 언어에서 사용할 수 있기 때문에, 언어 간 호환성이 높습니다.
- 단점
- 서버와 클라이언트 간의 통신에 대한 이해가 필요합니다.
- 데이터 구조화를 위한 추가적인 작업이 필요합니다.
RPyC
- RPyC(Remote Python Call)은 Python에서 RPC를 구현하는 라이브러리입니다.
- 기본적으로 TCP 소켓을 이용하여 통신합니다.
- Pyro4와 마찬가지로 객체를 원격지에서 직접 사용할 수 있습니다.
- 장점
- TCP 소켓을 이용하여 통신하기 때문에, Python과 다른 언어 간의 호환성이 높습니다.
- 객체지향적인 접근 방식을 취하기 때문에, 사용하기 간편하고 코드의 가독성이 높습니다.
- 단점
- RPyC는 CPython 인터프리터에서만 작동하므로 Jython, IronPython 등의 다른 Python 구현에서는 작동하지 않음
- RPyC는 서버 모드와 클라이언트 모드를 각각 실행해야 하므로, Pyro4나 gRPC와 달리 두 가지 모드를 모두 이해하고 구현해야 함
Redis와의 관계
- Redis는 Remote Procedure Call(RPC) 프로토콜을 직접적으로 지원하지는 않습니다.
- 하지만 Redis는 분산 시스템에서의 데이터 공유와 같은 작업을 지원하는 인-메모리 데이터 저장소이며, 다른 서비스와 통신하여 데이터를 공유하는 것이 가능합니다.
- RPC는 분산 시스템에서 프로시저 호출을 가능하게 하지만, Redis는 분산 시스템에서의 데이터 공유를 위해 설계된 데이터 저장소입니다.
- 따라서 RPC와 Redis는 서로 다른 개념이지만, 분산 시스템에서 서로 연계되어 사용될 수 있습니다.
- 예를 들어, Redis를 사용하여 분산 시스템에서 공유하는 데이터를 캐싱하고, RPC를 이용하여 분산 시스템에서 서로 다른 프로시저를 호출하는 것이 가능합니다.
- 또한, Redis의 Pub/Sub 기능을 이용하여 RPC 메시지를 전송하는 것도 가능합니다.
- 이처럼 Redis와 RPC는 분산 시스템에서 서로 보완적으로 사용될 수 있는 기술입니다.
import Pyro4
import redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)
class RedisCalculator(object):
def add(self, x, y):
redis_client.set('x', x)
redis_client.set('y', y)
return int(redis_client.get('x')) + int(redis_client.get('y'))
calculator = RedisCalculator()
Pyro4.Daemon.serveSimple({
calculator: 'example.calculator'
}, ns=True)
import Pyro4
calculator = Pyro4.Proxy('PYRONAME:example.calculator')
result = calculator.add(1, 2)
print(result)