Introducing Python(처음 시작하는 파이썬)
Chapter 11. 병행성과 네트워크
* Distributed Computing 혹은 Networking 환경에서 Concurrency를 보장함으로써 얻을 수 있는 점
1) Performance
2) Robustness (H/W 및 S/W Failures를 피하기 위해 Task를 복제하여 여러 가지 안정적 방식으로 운영)
3) Simplicity
4) Communication
11.1 Concurrency (병행성) (URL)
multiprocessing Module (URL)
JoinableQueue() Function
multiprocessing.JoinableQueue( [maxsize] )
Return:
- Queue의 Subclass인 JoinableQueue 객체를 리턴한다.
Argument:
1) maxsize (Optional)
- JoinableQueue의 크기값이다.
Join() Function
queueObject.Join( [timeout] )
- queueObject의 모든 Item들이 처리될 때 까지 Process를 Block시킨다.
- Deadlock을 피하기 위해, Process는 자기 자신을 Join할 수 없다.
Return:
- Process가 종료되거나, Method에 Timeout이 발생하면, None을 리턴한다.
Argument:
1) timeout (Optional)
- 최대 timeout 시간만큼만 Block되게한다.
Example. multiprocessing Module Usage
import multiprocessing as mp
def washer(dishes, output):
for dish in dishes:
print('Washing', dish, 'dish')
output.put(dish)
def dryer(input):
while True:
dish = input.get()
print('Drying', dish, 'dish')
input.task_done()
dish_queue = mp.JoinableQueue()
dryer_proc = mp.Process(target=dryer, args=(dish_queue,))
dryer_proc.daemon = True
dryer_proc.start()
dishes = ['salad', 'bread', 'entree', 'dessert']
washer(dishes, dish_queue)
dish_queue.join()
# Execution
$ python dishes.py
Washing salad dish
Washing bread dish
Washing entree dish
Washing dessert dish
Drying salad dish
Drying bread dish
Drying entree dish
Drying dessert dish
threading Module
- Thread를 임의로 종료시켜 발생될 수 있는 문제를 예방하기 위해,
threading Module에는 terminate() Function이 정의되어 있지 않다.
- Thread를 사용하는 코드에서는 Global Variable을 매우 신중히 다뤄야 한다.
※ Python의 Thread Mechanism은 CPU Bound Task를 빠르게 처리하지 못한다.
- GIL(Global Interpreter Lock)이라는 Standard Python System의 세부 구현사항의 한계 때문이다.
- I/O Bound Task는 Thread를 사용하여 처리하고,
CPU Bound Task는 Process 혹은 Networking, Event를 사용하여 처리하는 것이 바람직하다.
Example. threading Module Usage
import threading
def do_this(what):
whoami(what)
def whoami(what):
print("Thread %s says: %s" % (threading.current_thread(), what))
if __name__ == "__main__":
whoami("I'm the main program")
for n in range(4):
p = threading.Thread(target=do_this,
args=("I'm function %s" % n,))
p.start()
# Execution
Thread <_MainThread(MainThread, started 140735207346960)> says: I'm the main program
Thread <Thread(Thread-1, started 4326629376)> says: I'm function 0
Thread <Thread(Thread-2, started 4342157312)> says: I'm function 1
Thread <Thread(Thread-3, started 4347412480)> says: I'm function 2
Thread <Thread(Thread-4, started 4342157312)> says: I'm function 3
Example. threading Module Usage
import threading, queue
import time
def washer(dishes, dish_queue):
for dish in dishes:
print ("Washing", dish)
time.sleep(5)
dish_queue.put(dish)
def dryer(dish_queue):
while True:
dish = dish_queue.get()
print ("Drying", dish)
time.sleep(10)
dish_queue.task_done()
dish_queue = queue.Queue()
for n in range(2):
dryer_thread = threading.Thread(target=dryer, args=(dish_queue,))
dryer_thread.start()
dishes = ['salad', 'bread', 'entree', 'desert']
washer(dishes, dish_queue)
dish_queue.join()
gevent Library (URL)
- Event-Based 시스템을 개발하기 위한 도구들을 제공하고 있는 라이브러리이다.
(중앙 Event Loop를 수행하며, 모든 작업을 조금씩 실행하는 Sub Loop를 반복한다.)
- 즉, 코드의 길이가 짧은 여러 프로그램을 처리하는 환경에서 gevent를 사용하는 것이 바람직하다.
- Nginx Web Server는 Event-Based 시스템이며, Apache Web Server는 별도의 Process 혹은 Thread를 수행하는 시스템이다.
Example. gevent 설치 명령어
$ pip2 install gevent
gethostbyname() Function
- Asynchronous하게 수행된다.
gevent.spawn() Method
- 각각의 gevent.socket.gethostbyname(host)를 실행하기 위해 greenlet(Green Thread, Microthread)를 생성한다.
- greenlet은 Thread와 달리, Block시키지 않으며, 만약 한 Thread에 무슨 일이 발생되면 gevent는 제어를 다른 greenlet으로 바꾼다.
gevent.joinall() Method
- 모든 작업이 끝날 때 까지 기다린다.
Example. gevent Usage
# gevent_test.py
import gevent
from gevent import socket
hosts = ['www.crappytaxidermy.com', 'www.walterpottertaxidermy.com',
'www.antique-taxidermy.com']
jobs = [gevent.spawn(gevent.socket.gethostbyname, host) for host in hosts]
gevent.joinall(jobs, timeout=5)
for job in jobs:
print(job.value)
# Execution
$ python2 gevent_test.py
66.6.44.4
74.125.142.121
78.136.12.50
- 위 코드는 3개의 URL에 해당되는 IP주소를 얻어온다.
monkey Module
- gevent 라이브러리에 정의된 모듈로 greenlet을 다루는 socket과 같은 Standard Module의 수정판이다.
Example. monkey Module Usage
# gevent_monkey.py
import gevent
from gevent import monkey; monkey.patch_all()
import socket
hosts = ['www.crappytaxidermy.com', 'www.walterpottertaxidermy.com',
'www.antique-taxidermy.com']
jobs = [gevent.spawn(socket.gethostbyname, host) for host in hosts]
gevent.joinall(jobs, timeout=5)
for job in jobs:
print(job.value)
# Execution
$ python2 gevent_monkey.py
66.6.44.4
74.125.192.121
78.136.12.50
* tornade (URL)
- Event-Based Framework이다.
* gunicorn (URL)
- Event-Based Framework이다.
twisted Framework (URL)
- Asynchronous Event-Driven Networking Framework이다.
- Event를 처리하는 함수는 Callback 구조로 설계되어 있다.
- TCP와 UDP를 지원한다.
Example. twisted Usage
# knock_server.py
from twisted.internet import protocol, reactor
class Knock(protocol.Protocol):
def dataReceived(self, data):
print 'Client:', data
if data.startswith("Knock knock"):
response = "Who's there?"
else:
response = data + " who?"
print 'Server:', response
self.transport.write(response)
class KnockFactory(protocol.Factory):
def buildProtocol(self, addr):
return Knock()
reactor.listenTCP(8000, KnockFactory())
reactor.run()
# knock_client.py
from twisted.internet import reactor, protocol
class KnockClient(protocol.Protocol):
def connectionMade(self):
self.transport.write("Knock knock")
def dataReceived(self, data):
if data.startswith("Who's there?"):
response = "Disappearing client"
self.transport.write(response)
else:
self.transport.loseConnection()
reactor.stop()
class KnockFactory(protocol.ClientFactory):
protocol = KnockClient
def main():
f = KnockFactory()
reactor.connectTCP("localhost", 8000, f)
reactor.run()
if __name__ == '__main__':
main()
# Execution
$ python3 knock_server.py
$ python3 knock_client.py
Client: Knock knock
Server: Who's there?
Client: Disappearing client
Server: Disappearing client who?
* asyncio Module (URL)
- Asynchronous IO Support Rebooted
Redis
Example. redis를 이용한 Produce and Consumer 예시
# redis_washer.py
import redis
conn = redis.Redis()
print('Washer is starting')
dishes = ['salad', 'bread', 'entree', 'dessert']
for dish in dishes:
msg = dish.encode('utf-8')
conn.rpush('dishes', msg)
print('Washed', num)
conn.rpush('dishes', 'quit') # 'quit' is Sentinel
print('Washer is done')
# redis_dryer.py
import redis
conn = redis.Redis()
print('Dryer is starting')
while True:
msg = conn.blpop('dishes')
if not msg:
break
val = msg[1].decode('utf-8')
if val == 'quit':
break
print('Dried', val)
print('Dishes are dried')
# Execution
$ python redis_dryer.py &
[2] 81691
Dryer is starting
$ python redis_washer.py
Washer is starting
Washed salad
Dried salad
Washed bread
Dried bread
Washed entree
Dried entree
Washed dessert
Washer is done
Dried dessert
Dishes are dried
[2]+ Done python redis_dryer.py
Example. 보다 많은 Dryer Process와 각 Dryer에 Timeout이 추가된 예시
# redis_dryer2.py
def dryer():
import redis
import os
import time
conn = redis.Redis()
pid = os.getpid()
timeout = 20
print('Dryer process %s is starting' % pid)
while True:
msg = conn.blpop('dishes', timeout)
if not msg:
break
val = msg[1].decode('utf-8')
if val == 'quit':
break
print('%s: dried %s' % (pid, val))
time.sleep(0.1)
print('Dryer process %s is done' % pid)
import multiprocessing
DRYERS=3
for num in range(DRYERS):
p = multiprocessing.Process(target=dryer)
p.start()
# Execution
$ python redis_dryer2.py &
Dryer process 44447 is starting
Dryer process 44448 is starting
Dryer process 44446 is starting
$ python redis_washer.py
Washer is starting
Washed salad
44447: dried salad
Washed bread
44448: dried bread
Washed entree
44446: dried entree
Washed dessert
Washer is done
44447: dried dessert
# 한 Dryer Process가 Sentinel('quit')을 전달받아 종료한다.
Dryer process 44448 is done
# 20초가 경과된 후, 다른 Dryer Process들은 blpop으로부터 Timeout이 발생하여 None을 리턴받아 종료한다.
Dryer process 44447 is done
Dryer process 44446 is done
# 마지막 Dryer의 하위 Process가 종료된 후, Dryer 메인 프로그램을 종료한다.
[1]+ Done python redis_dryer2.py
* Python Queue Packages
- celery (URL)
- thoonk (URL)
- rq (URL)
11.2 Networks (네트워크)
Client-Server Pattern (Request-Reply Pattern)
- Synchronous한 Pattern으로, Client는 Server의 응답이 올 때 까지 기다린다.
- Web Browser 또한 HTTP Request를 만들어 Web Server의 Reply를 기다리는 Client에 속한다.
Push Pattern (Fanout Pattern)
- 데이터를 Process Pool에 있는 Available Worker에게 전송한다.
- Web Server의 Load Balancer는 데이터를 Available Worker에게 할당하는 역할을 한다.
Pull Pattern (Fanin Pattern)
- 하나 이상의 Source로부터 데이터를 받는다.
- 다수의 Process에서 Text Messege를 받아 하나의 Log File을 작성하는 Logger가 이 Pull Model에 속한다.
Publish-Subscribe Pattern (Pub-Sub Pattern)
- 모든 Subscriber는 Publisher로부터 데이터의 복사본을 전달받는다.
- Subscriber는 특정 데이터 타입(Topic)에 대한 선호도를 표시할 수 있다.
(Publisher는 각각의 Subscriber들의 Topic을 염두하여 데이터를 전송한다.)
- Subscriber가 없는 Topic에 대한 데이터는 무시된다.
Publish-Subscribe Pattern (Pub-Sub Pattern)
- Publisher는 여러 타입의 데이터를 Broadcasting하고,
각각의 Subscriber는 본인이 선호하는 Topic(데이터)를 선별하여 수신한다.
Example. Pub-Sub Model using Redis
# redis_pub.py
import redis
import random
conn = redis.Redis()
cats = ['siamese', 'persian', 'maine coon', 'norwegian forest'] # Topics
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
for msg in range(10):
cat = random.choice(cats)
hat = random.choice(hats)
print('Publish: %s wears a %s' % (cat, hat))
conn.publish(cat, hat)
# redis_sub.py
import redis
conn = redis.Redis()
topics = ['maine coon', 'persian']
sub = conn.pubsub()
sub.subscribe(topics)
for msg in sub.listen():
if msg['type'] == 'message':
cat = msg['channel']
hat = msg['data']
print('Subscribe: %s wears a %s' % (cat, hat))
# Execution
$ python redis_sub.py # Publisher를 실행하기 위해, 하나 이상의 Subscriber를 실행한다.
$ python redis_pub.py # Publisher가 Broadcast하는 모습
Publish: maine coon wears a stovepipe
Publish: norwegian forest wears a stovepipe
Publish: norwegian forest wears a tam-o-shanter
Publish: maine coon wears a bowler
Publish: siamese wears a stovepipe
Publish: norwegian forest wears a tam-o-shanter
Publish: maine coon wears a bowler
Publish: persian wears a bowler
Publish: norwegian forest wears a bowler
Publish: maine coon wears a stovepipe
$ python redis_sub.py # Subscriber가 Topic을 선별하여 수신하는 모습
Subscribe: maine coon wears a stovepipe
Subscribe: maine coon wears a bowler
Subscribe: maine coon wears a bowler
Subscribe: persian wears a bowler
Subscribe: maine coon wears a stovepipe
Example. Pub-Sub Model using ZeroMQ
# zmq_pub.py
import zmq
import random
import time
host = '*'
port = 6789
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.bind('tcp://%s:%s' % (host, port))
cats = ['siamese', 'persian', 'maine coon', 'norwegian forest']
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
time.sleep(1)
for msg in range(10):
cat = random.choice(cats)
cat_bytes = cat.encode('utf-8')
hat = random.choice(hats)
hat_bytes = hat.encode('utf-8')
print('Publish: %s wears a %s' % (cat, hat))
pub.send_multipart([cat_bytes, hat_bytes])
# zmq_sub.py
import zmq
host = '127.0.0.1'
port = 6789
ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.connect('tcp://%s:%s' % (host, port))
topics = ['maine coon', 'persian']
for topic in topics:
sub.setsockopt(zmq.SUBSCRIBE, topic.encode('utf-8'))
while True:
cat_bytes, hat_bytes = sub.recv_multipart()
cat = cat_bytes.decode('utf-8')
hat = hat_bytes.decode('utf-8')
print('Subscribe: %s wears a %s' % (cat, hat))
# Execution
$ python zmq_sub.py
$ python zmq_pub.py
Publish: norwegian forest wears a stovepipe
Publish: siamese wears a bowler
Publish: persian wears a stovepipe
Publish: norwegian forest wears a fedora
Publish: maine coon wears a tam-o-shanter
Publish: maine coon wears a stovepipe
Publish: persian wears a stovepipe
Publish: norwegian forest wears a fedora
Publish: norwegian forest wears a bowler
Publish: maine coon wears a bowler
Subscribe: persian wears a stovepipe
Subscribe: maine coon wears a tam-o-shanter
Subscribe: maine coon wears a stovepipe
Subscribe: persian wears a stovepipe
Subscribe: maine coon wears a bowler
- ZeroMQ는 중앙 서버가 없는 형태의 모델이다.
- 모든 Topic을 Subscribe하고자 하는 경우, 빈 문자열 \(\texttt{b''}\)를 입력하면 된다.
* Othre Pub-Sub Tools
- RabbitMQ (URL)
(pika는 RabbitMQ를 위한 Python API이다.)
- pypubsub (URL)
- pubsubhubbub (URL)
Socket Programming in Python (URL)
socket Module
socket.socket() Method
- Socket을 생성한다.
sockobj.bind() Method
- Socket에 주솟값을 바인딩한다.
sockobj.listen() Method
sockobj.accept() Method
- 수신한 첫 번째 유효한 메세지를 얻는다.
Example. UDP Server-Client
# udp_server.py
from datetime import datetime
import socket
server_address = ('localhost', 6789)
max_size = 4096
print('Starting the server at', datetime.now())
print('Waiting for a client to call.')
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_DGRAM : UDP
server.bind(server_address)
data, client = server.recvfrom(max_size)
print('At', datetime.now(), client, 'said', data)
server.sendto(b'Are you talking to me?', client)
server.close()
# udp_client.py
import socket
from datetime import datetime
server_address = ('localhost', 6789)
max_size = 4096
print('Starting the client at', datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b'Hey!', server_address)
data, server = client.recvfrom(max_size)
print('At', datetime.now(), server, 'said', data)
client.close()
# Execution
$ python udp_server.py
Starting the server at 2014-02-05 21:17:41.945649
Waiting for a client to call.
$ python udp_client.py
Starting the client at 2014-02-05 21:24:56.509682
At 2014-02-05 21:24:56.518670 ('127.0.0.1', 6789) said b'Are you talking to me?'
At 2014-02-05 21:24:56.518473 ('127.0.0.1', 56267) said b'Hey!'
Example. TCP Server-Client
# tcp_client.py
import socket
from datetime import datetime
address = ('localhost', 6789)
max_size = 1000
print('Starting the client at', datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM : TCP
client.connect(address)
client.sendall(b'Hey!')
data = client.recv(max_size)
print('At', datetime.now(), 'someone replied', data)
client.close()
# tcp_server.py
from datetime import datetime
import socket
address = ('localhost', 6789)
max_size = 1000
print('Starting the server at', datetime.now())
print('Waiting for a client to call.')
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(address)
server.listen(5)
client, addr = server.accept()
data = client.recv(max_size)
print('At', datetime.now(), client, 'said', data)
client.sendall(b'Are you talking to me?')
client.close()
server.close()
# Execution
$ python tcp_server.py
Starting the server at 2014-02-06 22:45:13.306971
Waiting for a client to call.
At 2014-02-06 22:45:16.048865 <socket.socket object, fd=6, family=2, type=1,
proto=0> said b'Hey!'
$ python tcp_client.py
Starting the client at 2014-02-06 22:45:16.038642
At 2014-02-06 22:45:16.049078 someone replied b'Are you talking to me?'
At 2014-02-06 22:45:16.048865 <socket.socket object, fd=6, family=2, type=1,
proto=0> said b'Hey!'
zmq Module (ZeroMQ) (URL)
- ZeroMQ는 요청에 대한 Load Balancing과 올바른 전송을 보장한다.
- 소켓이 생성될 때, 소켓의 Connection Type을 바꿔서 Scale Up 혹은 Scale Down한다.
* Socket Type
- tcp: 하나 이상의 머신에서 프로세스 간 통신
- ipc (Inter-Process Communication): 하나의 머신에서 프로세스 간 통신
- inproc (Inter-Thread Communication): 한 프로세스에서 스레드 간 통신 (Lock없이, 스레드 간 데이터를 전달한다.)
* ZeroMQ Socket이 수행하는 작업
- 전체 메시지 교환
- Connection Retry
- 송수신자간 타이밍 불일치에 대비하여 데이터를 보존하기 위한 Buffer 사용
* ZeroMQ Socket Type
ZeroMQ Socket Type | Description |
REQ | Synchronous Request |
REP | Synchronous Reply |
DEALER | Asynchronous Request |
ROUTER | Asynchronous Reply |
PUB | Publish |
SUB | Subscribe |
PUSH | Fanout |
PULL | Fanin |
Example. Single Request-Reply Pattern
# zmq_server.py
import zmq
host = '127.0.0.1'
port = 6789
context = zmq.Context()
server = context.socket(zmq.REP)
server.bind("tcp://%s:%s" % (host, port))
while True:
# Wait for next request from client
request_bytes = server.recv()
request_str = request_bytes.decode('utf-8')
print("That voice in my head says: %s" % request_str)
reply_str = "Stop saying: %s" % request_str
reply_bytes = bytes(reply_str, 'utf-8')
server.send(reply_bytes)
# zmq_client.py
import zmq
host = '127.0.0.1'
port = 6789
context = zmq.Context()
client = context.socket(zmq.REQ)
client.connect("tcp://%s:%s" % (host, port))
for num in range(1, 6):
request_str = "message #%s" % num
request_bytes = request_str.encode('utf-8')
client.send(request_bytes)
reply_bytes = client.recv()
reply_str = reply_bytes.decode('utf-8')
print("Sent %s, received %s" % (request_str, reply_str))
# Execution
$ python zmq_server.py &
$ python zmq_client.py
That voice in my head says 'message #1'
Sent 'message #1', received 'Stop saying message #1'
That voice in my head says 'message #2'
Sent 'message #2', received 'Stop saying message #2'
That voice in my head says 'message #3'
Sent 'message #3', received 'Stop saying message #3'
That voice in my head says 'message #4'
Sent 'message #4', received 'Stop saying message #4'
That voice in my head says 'message #5'
Sent 'message #5', received 'Stop saying message #5'
* 주고받는 메시지가 String이 아닌 다른 타입이라면, MessagePack (URL)과 같은 Library를 이용하는 것이 좋다.
* ZeroMQ는 하나의 Server에 다수의 Client가 연결된 상황에서 동시에 요청된 Request를 일정량은 Buffer에 보관한다.
- Zero는 Broker(Intermediary)가 필요없다는 의미이고, M은 Message를, Q는 Queue를 의미한다.
- 단, DEALER 타입, ROUTER 타입 소켓을 정의하여 Source와 Destination사이를 Asynchronous하게 연결하는 형태는
그 자체로 Broker 역할을 수행한다. (즉, 필요한 경우에는 Broker를 만들 수 있다.)
- 일례로, Web Server Farm 앞의 Proxy Server에 접근하는 다수의 Browser는
여러개의 REQ 소켓들이 하나의 ROUTER 소켓에 연결되고, 각 요청은 ROUTER 소켓을 거쳐 DEALER 소켓으로 전달되고, DEALER 소켓은 연결된 REP 소켓에 접근하는 원리로 전달된다.
* Ventilator
- Networking Pattern중 하나로, PUSH 소켓을 사용하여 Asynchronous 작업을 처리하고,
PULL 소켓을 사용하여 결과를 수집한다.
scapy (URL)
- Packet을 분석하기 위한 기능들을 제공하는 Python Tool이다.
Python Internet Services (URL)
1. DNS (Domain Name Service)
- Python에서는 socket Module에 DNS와 같은 기능들을 제공하고 있다.
Example. gethostbyname() Function and gethostbyname_ex() Function
>>> import socket
>>> socket.gethostbyname('www.crappytaxidermy.com')
'66.6.44.4'
# Domain Name에 대한 IP주소를 리턴한다.
>>> socket.gethostbyname_ex('www.crappytaxidermy.com')
('crappytaxidermy.com', ['www.crappytaxidermy.com'], ['66.6.44.4'])
# Domain Name에 대한 IP주소와 더불어, 다른 형식의 Domain Name도 리턴한다.
>>> socket.getaddrinfo('www.crappytaxidermy.com', 80)
[(2, 2, 17, '', ('66.6.44.4', 80)), (2, 1, 6, '', ('66.6.44.4', 80))]
# Domain Name에 대한 IP주소와 소켓을 생성하기 위한 정보들을 리턴한다.
# 첫 번째 튜플은 UDP, 두 번째 튜플은 TCP에 대한 정보이다.
>>> socket.getaddrinfo('www.crappytaxidermy.com', 80, socket.AF_INET, socket.SOCK_STREAM)
[(2, 1, 6, '', ('66.6.44.4', 80))]
# TCP에 대한 정보만 리턴받을 수 있다.
# 물론, UDP에 대한 정보만 리턴받을 수도 있다.
>>> socket.getservbyname('http')
80
>>> socket.getservbyport(80)
'http'
# 서비스 이름과 포트 번호 둘 중 하나를 알고 있다면, 다른 하나로 변환시킬 수 있다.
2. Python E-Mail Module (URL)
* smtplib Module
- SMTP를 통한 이메일 전송
* email Module
- 이메일 생성 및 파싱
* poplib Module
- POP3를 통한 이메일 읽기
* imaplib Module
- IMAP를 통한 이메일 읽기
* smtpd Module (URL)
- SMTP Server 구축
- 순수 Python SMTP Server인 Lamson (URL)은 이메일 메시지를 DB에 저장할 수도 있고, 스팸 메일을 차단하는 등 여러 기능을 제공하고 있다.
3. Other Protocols
* ftplib Module
- FTP를 통한 바이트 전송
Remote Processing
RPC Client
1. 함수의 인자를 바이트로 변환한다. (Marshalling, Serializing, Encoding)
2. 인코딩된 바이트를 원격 머신으로 전송한다.
Remote Machine
1. 인코딩된 요청 바이트를 수신한다.
2. RPC Client는 다시 원래의 데이터 구조
Example. RPC Client and Server
# xmlrpc_server.py
from xmlrpc.server import SimpleXMLRPCServer
def double(num):
return num * 2
server = SimpleXMLRPCServer(("localhost", 6789))
server.register_function(double, "double")
server.serve_forever()
# xmlrpc_client.py
import xmlrpc.client
proxy = xmlrpc.client.ServerProxy("http://localhost:6789/")
num = 7
result = proxy.double(num)
print("Double %s is %s" % (num, result))
# Execution
$ python xmlrpc_server.py
$ python xmlrpc_client.py
Double 7 is 14
127.0.0.1 - - [13/Feb/2014 20:16:23] "POST / HTTP/1.1" 200 - # Output of Server-side
Example. msgpack에서의 RPC 구현
# msgpack 설치 명령어
$ pip install msgpack-rpc-python
# msgpack_server.py
from msgpackrpc import Server, Address
class Services():
def double(self, num):
return num * 2
server = Server(Services())
server.listen(Address("localhost", 6789))
server.start()
# msgpack_client.py
from msgpackrpc import Client, Address
client = Client(Address("localhost", 6789))
num = 8
result = client.call('double', num)
print("Double %s is %s" % (num, result))
# Execution
$ python msgpack_server.py
$ python msgpack_client.py
Double 8 is 16
fabric Package (URL)
- 원격 또는 로컬 명령이나 파일 업로드 및 다운로드를 sudo 권한의 사용자로 실행하게 한다.
(원격 머신의 프로그램을 실행하는데에 SSH를 사용한다.)
Example. fabric 파일에서 SSH없이 로컬에서 Python 코드를 실행하는 예시
# fab1.py
def iso():
from datetime import date
print(date.today().isoformat())
# Execution
$ fab -f fab1.py -H localhost iso
[localhost] Executing task 'iso'
2014-02-22
Done.
Example. fabric 파일에서 SSH을 통해 로컬에서 Python 코드를 실행하는 예시
# fab2.py
from fabric.api import local
def iso():
local('date -u')
# Execution
$ fab -f fab2.py -H localhost iso # H 옵션은 연결할 머신을 지정하는데 사용된다.
[localhost] Executing task 'iso'
[localhost] local: date -u
Sun Feb 23 05:22:33 UTC 2014
Done.
Disconnecting from localhost... done.
Example. fabric 파일에서 SSH을 통해 원격머신에서 Python 코드를 실행하는 예시
(하지만, 본 예시에서는 local에서 실행하게 한다.)
# fab3.py
from fabric.api import run
def iso():
run('date -u')
# Execution
$ fab -f fab3.py -H localhost iso
[localhost] Executing task 'iso'
[localhost] run: date -u
[localhost] Login password for 'yourname':
[localhost] out: Sun Feb 23 05:26:05 UTC 2014
[localhost] out:
Done.
Disconnecting from localhost... done.
Example. 원격 머신에 접근하기 위한 암호를 미리 지정하여 연결하는 방식의 예시
# fab4.py
from fabric.api import run
from fabric.context_managers import env
env.password = "your password goes here"
def iso():
run('date -u')
# Execution
$ fab -f fab4.py -H localhost iso
[localhost] Executing task 'iso'
[localhost] run: date -u
[localhost] out: Sun Feb 23 05:31:00 UTC 2014
[localhost] out:
Done.
Disconnecting from localhost... done.
- 코드에 암호를 입력하여 실행시키는 방식은 보안에 취약하다.
- 암호를 지정하는 좋은 방식에는 ssh-keygen을 사용하여 공개키와 개인키를 SSH에 지정하는 방식이 있다.
SaltStack (URL)
- SSH가 아닌, ZeroMQ 기반의 시스템 관리 플랫폼으로, 수천 개의 서버를 확장할 수 있다.
Map Reduce
- 여러 머신에서 계산을 수행하여 결과를 수집하는 형태이다.
- Hadoop
- Spark (URL)
- Disco (URL)
Cloud
* 8 Fallacies of Distributed Computing By Peter Deutsch
- The network is reliable.
- Latency is zero.
- Bandwidth is infinite.
- The network is secure.
- Topology doesn’t change.
- There is one administrator.
- Transport cost is zero.
- The network is homogeneous.
* Cloud Vendors
- Google
- Amazon
- OpenStack
Reference: Introducing Python(처음 시작하는 파이썬) (Bill Lubanovic 저, O'Reilly, 2015)