EC2竞价实例终止& Python 2.7

时间:2016-07-28 16:10:51

标签: python multithreading python-2.7 amazon-ec2

我知道终止通知是通过元数据网址提供的,我可以做类似的事情

if requests.get("http://169.254.169.254/latest/meta-data/spot/termination-time").status_code == 200

以确定通知是否已过帐。我在竞价型实例上运行Python服务:

  1. 循环长轮询SQS队列
  2. 如果收到消息,它会暂停轮询并处理有效负载。
  3. 处理有效载荷可能需要5-50分钟。
  4. 处理有效负载将涉及产生最多50个线程的线程池来处理并行上传文件到S3,这是处理有效负载所花费的大部分时间。
  5. 最后,从队列中删除邮件,冲洗,重复。
  6. 这项工作是幂等的,因此如果相同的有效负载多次运行,我会节省处理时间/成本,但不会对应用程序工作流产生负面影响。

    我正在寻找一种优雅的方式,现在也在后台每隔五秒轮询终止通知。一旦终止通知出现,我就立即将消息释放回SQS队列,以便另一个实例尽快将其提取。

    作为奖励,我想关闭工作,杀掉线程池,让服务进入停滞状态。如果我终止服务,supervisord将只是重新启动它。

    更大的奖金!是不是有一个python模块可以简化这个并且只是有效?

1 个答案:

答案 0 :(得分:0)

我编写此代码来演示如何使用线程轮询Spot实例终止。它首先启动一个轮询线程,负责检查http端点。

然后我们创建假工作者(模仿要完成的实际工作)并开始运行池。最终,轮询线程将启动(执行时执行大约10秒)并终止整个事情。

为了防止脚本在Supervisor重新启动后继续工作,我们只需检查__main__的开头,如果终止通知在那里,我们会睡2.5分钟,这比那个更长通知在实例关闭之前持续。

#!/usr/bin/env python
import threading
import Queue
import random
import time
import sys
import os

class Instance_Termination_Poll(threading.Thread):
    """
    Sleep for 5 seconds and eventually pretend that we then recieve the
    termination event

    if requests.get("http://169.254.169.254/latest/meta-data/spot/termination-time").status_code == 200
    """

    def run(self):
        print("Polling for termination")
        while True:
            for i in range(30):
                time.sleep(5)
                if i==2:
                    print("Recieve Termination Poll!")
                    print("Pretend we returned the message to the queue.")
                    print("Now Kill the entire program.")
                    os._exit(1)
            print("Well now, this is embarassing!")

class ThreadPool:
    """
    Pool of threads consuming tasks from a queue
    """

    def __init__(self, num_threads):
        self.num_threads = num_threads
        self.errors = Queue.Queue()
        self.tasks = Queue.Queue(self.num_threads)
        for _ in range(num_threads):
            Worker(self.tasks, self.errors)

    def add_task(self, func, *args, **kargs):
        """
        Add a task to the queue
        """
        self.tasks.put((func, args, kargs))

    def wait_completion(self):
        """
        Wait for completion of all the tasks in the queue
        """
        try:
            while True:
                if self.tasks.empty() == False:
                    time.sleep(10)
                else:
                    break
        except KeyboardInterrupt:
            print "Ctrl-c received! Kill it all with Prejudice..."
            os._exit(1)

        self.tasks.join()

class Worker(threading.Thread):
    """
    Thread executing tasks from a given tasks queue
    """

    def __init__(self, tasks, error_queue):
        threading.Thread.__init__(self)
        self.tasks = tasks
        self.daemon = True
        self.errors = error_queue
        self.start()

    def run(self):
        while True:
            func, args, kargs = self.tasks.get()
            try:
                func(*args, **kargs)
            except Exception, e:
                print("Exception " + str(e))
                error = {'exception': e}
                self.errors.put(error)

            self.tasks.task_done()

def do_work(n):
    """
    Sleeps a random ammount of time, then creates a little CPU usage to
    mimic some work taking place.
    """
    for z in range(100):
        time.sleep(random.randint(3,10))
        print "Thread ID: {} working.".format(threading.current_thread())
        for x in range(30000):
            x*n
        print "Thread ID: {} done, sleeping.".format(threading.current_thread())

if __name__ == '__main__':
    num_threads = 30

    # Start up the termination polling thread
    term_poll = Instance_Termination_Poll()
    term_poll.start()

    # Create our threadpool
    pool = ThreadPool(num_threads)
    for y in range(num_threads*2):
        pool.add_task(do_work, n=y)

    # Wait for the threadpool to complete
    pool.wait_completion()