- 작성시간 : 2014/12/18 12:12
- 퍼머링크 : mcchae.egloos.com/11145479
- 덧글수 : 0
파이썬에서 어떤 API를 제공하는 다양한 방법이 있습니다.
(일전에 Flask를 이용한 Restful API 를 소개한 적이 있습니다)
하지만 더 기본적인 API는 XML-RPC 로 제공하는 경우도 많습니다.
아래와 같은 간단한 XML-RPC 서버를 살펴보겠습니다.
(파일명 b_xmlrpc_server.py)
#!/usr/bin/env python
#coding=utf8
"""
====================================
:mod: 테스트용 blocking XML-RPC 서버
====================================
.. moduleauthor:: 채문창 <mcchae@gmail.com>
.. note:: GNU
설명
=====
테스트용 blocking XML-RPC 서버
"""
##########################################################################################
import time
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
##########################################################################################
class TestSvc(object):
def ping(self, pid, _sleep=0):
for i in xrange(_sleep):
print "[%d] %d" % (pid, i)
time.sleep(1)
return True
##########################################################################################
def doSvc():
mgr = TestSvc()
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/TestSvc')
server = SimpleXMLRPCServer(('0.0.0.0', 9000),
requestHandler=RequestHandler,
logRequests=False,
allow_none=True,
)
server.register_introspection_functions()
server.register_instance(mgr)
server.serve_forever()
##########################################################################################
if __name__=='__main__':
doSvc()
TestSvc가 서비스 클래스이며 ping 이라는 함수가 하나 있습니다.
_sleep에 5가 오면 1초씩 sleep을 하면서 어느 pid에서 몇번째 (0-based) sleep을 하는가를 출력합니다.
이를 간단히 호출하는 클라이언트 프로그램은
(파일명 xmlrpc_client.py)
#!/usr/bin/env python
#coding=utf8
import os
import xmlrpclib
proxy = xmlrpclib.ServerProxy("http://localhost:9000/TestSvc")
print "[%d] TestSvc.ping(5)=%s" % (os.getpid(), proxy.ping(os.getpid(), 5))
와 같이 작성할 수 있습니다.
이제 테스트를 위하여 간단한 bash 스크립트를 만들어 보았습니다.
(파일명 xmlrpc_client.sh)
#!/bin/bash
python xmlrpc_client.py &
python xmlrpc_client.py &
python xmlrpc_client.py &
python xmlrpc_client.py &
python xmlrpc_client.py &
FAIL=0
sleep 2
for job in `jobs -p`; do
wait $job || let "FAIL+=1"
done
if [ "$FAIL" == "0" ]; then
echo "Done!"
else
echo "FAIL! ($FAIL)"
fi
클라이언트 호출을 백그라운드로 5개를 돌리고, 끝날 때 까지 wait 하는
간단한 스크립트 입니다.
$ python b_xmlrpc_server.py
와 같이 서버를 동작시키고
$ time ./xmlrpc_client.sh
라고 실행시키면
[4038] TestSvc.ping(5)=True
[4041] TestSvc.ping(5)=True
[4042] TestSvc.ping(5)=True
[4039] TestSvc.ping(5)=True
[4040] TestSvc.ping(5)=True
Done!
real 0m25.141s
user 0m0.125s
sys 0m0.115s
와 같이 백그라운드로 병행해서 호출하였는데도 시리얼한 결과로 5x5 = 25초의 결과가 나왔습니다.
이 결과는 XML-RPC 호출을 하면 그 함수가 blocking 함수로 동작을 하여
서버는 순차적으로 돌기 때문에 발생한 결과 입니다.
서버의 결과를 확인하여도,
[4038] 0
[4038] 1
[4038] 2
[4038] 3
[4038] 4
[4041] 0
[4041] 1
[4041] 2
[4041] 3
[4041] 4
[4042] 0
[4042] 1
[4042] 2
[4042] 3
[4042] 4
[4039] 0
[4039] 1
[4039] 2
[4039] 3
[4039] 4
[4040] 0
[4040] 1
[4040] 2
[4040] 3
[4040] 4
와 같이 순차적으로 실행되었음을 알 수 있습니다.
그러면 이런 blocking XML-RPC를 해결할 좋은 방법은 없을까요?
위에 그 해답이 있었습니다.
TCP 소켓 서버는 자체로 synchronous (Blocking) 이기 때문에 ForingMixIn 또는 ThreadingMixIn 을 사용하라는 것이지요.
클래스를 사용하면 ThreadingMixIn 방식이 편리하지만 바로 GIL (Global Interpreter Lock) 문제에 걸릴 수 있습니다.
여기서 잠깐 Python의 Thread를 확인해 볼까요?
파이썬으로 클라우드 하고 싶어요 에서 너무나 쉽게 잘 설명해 놓으셔서 그 그림을 이용해 보았습니다.
GIL 이라는 하나의 자물쇠를 이용하므로,
이렇게 병렬로 쓰레드가 구동되기를 바라지만,
위와 같이 병렬로 되지 않고 시리얼 처럼 작업할 수 있습니다.
하지만... 실제로는,
위와 같이 I/O 작업을 하게되면 GIL일 풀리는 효과를 가져와서 병렬 이득이 실제로 생깁니다.
따라서 다음과 같이 서버 프로그램을 수정해 보았습니다.
(파일명 n_xmlrpc_server.py)
#!/usr/bin/env python
#coding=utf8
"""
====================================
:mod: 테스트용 non-blocking XML-RPC 서버
====================================
.. moduleauthor:: 채문창 <mcchae@gmail.com>
.. note:: GNU
설명
=====
테스트용 non-blocking XML-RPC 서버
"""
##########################################################################################
import time
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
##########################################################################################
class TestSvc(object):
def ping(self, pid, _sleep=0):
for i in xrange(_sleep):
print "[%d] %d" % (pid, i)
time.sleep(1)
return True
##########################################################################################
def doSvc():
mgr = TestSvc()
from SocketServer import ThreadingMixIn
class SimpleThreadXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
pass
class RequestHandler(SimpleXMLRPCRequestHandler):
rpc_paths = ('/TestSvc')
server = SimpleThreadXMLRPCServer(('0.0.0.0', 9000),
requestHandler=RequestHandler,
logRequests=False,
allow_none=True,
)
server.register_introspection_functions()
server.register_instance(mgr)
server.serve_forever()
##########################################################################################
if __name__=='__main__':
doSvc()
자, 이제는
$ python n_xmlrpc_server.py
를 실행시키고
$ time ./xmlrpc_client.sh
를 실행시켰더니...
[4060] TestSvc.ping(5)=True
[4058] TestSvc.ping(5)=True
[4059] TestSvc.ping(5)=True
[4062] TestSvc.ping(5)=True
[4061] TestSvc.ping(5)=True
Done!
real 0m5.126s
user 0m0.122s
sys 0m0.113s
왓! 평행하게 돌았던 결과 25초 대신 5초 언저리의 결과가 나왔네요...
서버 결과도 확인해 보면,
[4060] 0
[4058] 0
[4059] 0
[4062] 0
[4061] 0
[4060] 1
[4058] 1
[4059] 1
[4062] 1
[4061] 1
[4060] 2
[4058] 2
[4059] 2
[4062] 2
[4061] 2
[4060] 3
[4058] 3
[4059] 3
[4062] 3
[4061] 3
[4060] 4
[4058] 4
[4059] 4
[4062] 4
[4061] 4
와 같이 시리얼하지 않음을 알 수 있습니다.
어느 분께는 도움이 되셨기를...



덧글