На мой взгляд, у вас есть два высокоуровневых способа решения этой проблемы:
- Имеют отдельные приложения («сервер» и «сканер»), которые имеют общее хранилище данных (база данных, Redis и т. Д.). Каждое приложение будет работать независимо, и сканер может просто обновить свой статус в общем хранилище данных. Этот подход, вероятно, мог бы масштабироваться лучше: если вы раскручиваете его в чем-то вроде Docker Swarm, вы можете масштабировать экземпляры искателя столько, сколько можете себе позволить.
- Имейте одно приложение, которое порождает отдельные потоки для искателя и сервера. Поскольку они находятся в одном и том же процессе, вы можете делиться информацией между ними немного быстрее (хотя, если это просто статус сканера, это не должно иметь большого значения) Преимущество этой опции, похоже, заключается в трудности ее раскрутки - вам не понадобится общее хранилище данных и вам не нужно управлять более чем одной службой.
Я бы лично склонялся к (1) здесь, потому что каждая из частей проще. Далее следует решение (1) и быстрое и грязное решение (2).
1. Отдельные процессы с общим хранилищем данных
Я бы использовал Docker Compose для обработки всех служб. Это добавляет дополнительный уровень сложности (так как вам нужно установить Docker), но значительно упрощает управление сервисами.
Весь стек Docker Compose
Опираясь на пример конфигурации здесь Я бы сделал ./docker-compose.yaml
файл, который выглядит как
version: '3'
services:
server:
build: ./server
ports:
- "80:80"
links:
- redis
environment:
- REDIS_URL=redis://cache
crawler:
build: ./crawler
links:
- redis
environment:
- REDIS_URL=redis://cache
redis:
image: "redis/alpine"
container_name: cache
expose:
- 6379
Я бы организовал приложения в отдельные каталоги, например ./server
и ./crawler
, но это не единственный способ сделать это. Как бы вы ни организовали их, ваши build
аргументы в приведенной выше конфигурации должны совпадать.
Сервер
Я бы написал простой сервер на ./server/app.py
, который бы делал что-то вроде
import os
from flask import Flask
import redis
app = Flask(__name__)
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
@app.route('/status')
def index():
stat = r_conn.get('crawler_status')
try:
return stat.decode('utf-8')
except:
return 'error getting status', 500
app.run(host='0.0.0.0', port=8000)
Наряду с этим ./server/requirements.txt
файл с зависимостями
Flask
redis
И, наконец, ./server/Dockerfile
, который сообщает Docker, как построить ваш сервер
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/server
COPY ./ /opt/server
WORKDIR /opt/server
# install deps and run server
RUN pip3 install -qr requirements.txt
EXPOSE 8000
CMD ["python3", "app.py"]
Стоп, чтобы проверить, что все в порядке
На этом этапе, если вы откроете приглашение CMD или терминал в каталоге с помощью ./docker-compose.yaml
, вы сможете запустить docker-compose build && docker-compose up
, чтобы проверить, что все собирается и работает успешно. Вам нужно будет отключить раздел crawler
файла YAML (так как он еще не был записан), но вы должны иметь возможность раскрутить сервер, который общается с Redis. Если вам это нравится, раскомментируйте раздел crawler
YAML и продолжайте.
Процесс сканирования
Так как Docker обрабатывает перезапуск процесса поиска, вы можете просто написать это как очень простой скрипт на Python. Что-то вроде ./crawler/app.py
может выглядеть как
from time import sleep
import os
import sys
import redis
TIMEOUT = 3600 # seconds between runs
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
# ... update status and then do the work ...
r_conn.set('crawler_status', 'crawling')
sleep(60)
# ... okay, it's done, update status ...
r_conn.set('crawler_status', 'sleeping')
# sleep for a while, then exit so Docker can restart
sleep(TIMEOUT)
sys.exit(0)
А потом, как и прежде, вам нужен ./crawler/requirements.txt
файл
redis
И (очень похоже на сервер) ./crawler/Dockerfile
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/crawler
COPY ./ /opt/crawler
WORKDIR /opt/crawler
# install deps and run server
RUN pip3 install -qr requirements.txt
# NOTE that no port is exposed
CMD ["python3", "app.py"]
Wrapup
В 7 файлах у вас есть два отдельных приложения, управляемых Docker, а также экземпляр Redis. Если вы хотите масштабировать его, вы можете посмотреть опцию --scale
для docker-compose up
. Это не обязательно простейшее решение, но оно управляет некоторыми неприятными моментами в управлении процессами. Для справки я также сделал для него репозиторий Git здесь .
Чтобы запустить его как автономный сервис, просто запустите docker-compose up -d
.
Отсюда вы можете и должны добавить более качественную регистрацию в сканер. Конечно, вы можете использовать Django вместо Flask для сервера (хотя я более знаком с Flask, и Django может вводить новые зависимости). И, конечно, вы всегда можете сделать это более сложным.
2. Отдельный процесс с резьбой
Это решение не требует Docker, и для управления им требуется только один файл Python. Я не буду писать полное решение, если OP не захочет, но базовый эскиз будет выглядеть примерно так:
import threading
import time
from flask import Flask
STATUS = ''
# run the server on another thread
def run_server():
app = Flask(__name__)
@app.route('/status')
def index():
return STATUS
server_thread = threading.Thread(target=run_server)
server_thread.start()
# run the crawler on another thread
def crawler_loop():
while True:
STATUS = 'crawling'
# crawl and wait
STATUS = 'sleeping'
time.sleep(3600)
crawler_thread = threading.Thread(target=crawler_loop)
crawler_thread.start()
# main thread waits until the app is killed
try:
while True:
sleep(1)
except:
server_thread.kill()
crawler_thread.kill()
Это решение не имеет ничего общего с поддержанием работы сервисов, в значительной степени связано с обработкой ошибок, а блок в конце не будет хорошо обрабатывать сигналы от ОС.Тем не менее, это быстрое и грязное решение, которое должно поднять вас с ног.