10 октября 2012

Диспетчер для cron-задач (pre-alfa ver.)

Прототип скрипта для запуска периодических задач (cron). Понимает 2 типа задач - php и http (расширить думаю не проблема). Пока реализован запуск в блокирующем режиме (т.е. если запустится та же задача при неоконченной предыдущей - она будет отвергнута). Наброски постановки задачи в очередь после существующей - в  разработке.

#!/bin/bash

WORK_DIR="$(dirname $(readlink -f $0))"
JOB_TYPE="$1"
JOB_NAME="$2"
# needed by some php jobs
JOB_PARAMS="$3"
SCRIPT_NAME="${0##*/}"
SCRIPT_PID="$$"

# unique job id by his type and path
JOB_ID=($(md5sum <<< "${JOB_TYPE}${JOB_NAME}"))

LOG_DIR='/var/www/log/cronjob'
LOG_FILE="$LOG_DIR/cronjobs"
DELTA_FILE="$LOG_DIR/deltas"
MULTILOCK_FILE="$LOG_DIR/multilocks"

PHP_JOB="/usr/bin/php -f ${JOB_NAME} ${JOB_PARAMS}"
WGET_JOB="/usr/bin/wget -a $LOG_FILE -nv -S --spider ${JOB_NAME}?megaparam=megaid"
LOCK_BASEDIR="$WORK_DIR/locks"
LOCK_DIR="$LOCK_BASEDIR/$JOB_ID"
LOCK_PID="$LOCK_DIR/lock.pid"
QUEUE_FILE="$WORK_DIR/jobs.queue"

NEED_QUEUE=0
QUEUE_POLL=5

PRG_NAME='Yet Another Job Dispatcher'
PRG_VERSION='alpha'
PRG_COPYLEFTS='put_your_cool_nick_here'

check_lock () {
    if (( NEED_QUEUE == 1 )); then
if [ -r "$QUEUE_FILE" ]; then
   mapfile -t pids < "$QUEUE_FILE"
   added=0
   for pid in "${pids[@]}"; do [[ $pid = $$ ]] && { added=1; break; }; done
   (( added == 0 )) && printf '%s\n' $$ >> "$QUEUE_FILE"
else
   printf '%s\n' $$ >> "$QUEUE_FILE"
fi
read -r first < "$QUEUE_FILE" || {
   printf '[%s] [%s/%s] An error occurred with the queue system. Exiting.\n' "`date`" $JOB_ID $SCRIPT_PID >> ${LOG_FILE}
   exit 1
}
if (( $first == $$ )); then
   printf '[%s] [%s/%s] Successfully acquired lock\n' "`date`" $JOB_ID $SCRIPT_PID >> ${LOG_FILE}
   trap 'sed -i "/^$$$/d" "$QUEUE_FILE"' 0
else
   if kill -0 $first 2>/dev/null; then
sleep "$QUEUE_POLL"
check_lock
   else
printf '[%s] [%s/%s] Cleaning queue, %s is not alive\n' "`date`" $JOB_ID $SCRIPT_PID "$first" >> ${LOG_FILE}
sed -i "/^$first$/d" "$QUEUE_FILE"
sleep "$QUEUE_POLL"
check_lock
   fi
fi
    else
if mkdir "$LOCK_DIR" 2>/dev/null; then
   printf '[%s] [%s/%s] Successfully acquired lock\n' "`date`" $JOB_ID $SCRIPT_PID >> ${LOG_FILE}
   echo $SCRIPT_PID > $LOCK_PID
   return 0
else
   printf '[%s] [%s/%s] Cannot acquire lock, giving up on %s\n' "`date`" \
$JOB_ID $SCRIPT_PID "$LOCK_DIR" >> ${LOG_FILE}
   printf '[%s] Lock failed for %s\n' "`date`" $JOB_NAME >> ${MULTILOCK_FILE}
   return 1
fi
    fi
}

remove_lock () {
    if [ -d "$LOCK_DIR" ]; then
trap 'rm -rf "$LOCK_DIR"' 0
printf '[%s] [%s/%s] Successfully removed lock\n' "`date`" $JOB_ID $SCRIPT_PID >> ${LOG_FILE}
    else
printf '[%s] [%s/%s] WARNING! No lock detected\n' "`date`" $JOB_ID $SCRIPT_PID >> ${LOG_FILE}
printf '[%s] No lock detected for %s\n' "`date`" $JOB_NAME >> ${MULTILOCK_FILE}
    fi;
}

if [ "$#" -ge 2 ]; then
    #check for existense of base lockdir and create if it's not
    if [ ! -d $LOCK_BASEDIR ]; then
mkdir -p $LOCK_BASEDIR;
    fi;
    case $JOB_TYPE in
"http")
   printf '[%s] [%s/%s] Starting HTTP job: %s\n' "`date`" $JOB_ID $SCRIPT_PID $JOB_NAME >> ${LOG_FILE}
   if check_lock; then
echo '---JOB OUTPUT---' >> ${LOG_FILE}
START_TIME=$(date +%s.%N)
${WGET_JOB}
END_TIME=$(date +%s.%N)
DELTA_TIME=$(echo "$END_TIME - $START_TIME"|bc )
echo '---END OF JOB OUTPUT---' >> ${LOG_FILE}
printf '[%s] [%s/%s] HTTP job done (%.3F sec): %s\n' "`date`" $JOB_ID $SCRIPT_PID $DELTA_TIME $JOB_NAME >> $LOG_FILE
printf '[%s] %.3F sec for job %s\n' "`date`" $DELTA_TIME $JOB_NAME >> ${DELTA_FILE}
remove_lock
   fi;
   ;;
"php")
   printf '[%s] [%s/%s] Starting PHP job: %s\n' "`date`" $JOB_ID $SCRIPT_PID $JOB_NAME >> ${LOG_FILE}
   if check_lock; then
echo '---JOB OUTPUT---' >> ${LOG_FILE}
START_TIME=$(date +%s.%N)
${PHP_JOB} 2>> ${LOG_FILE}
END_TIME=$(date +%s.%N)
DELTA_TIME=$(echo "$END_TIME - $START_TIME"|bc )
echo '---END OF JOB OUTPUT---' >> ${LOG_FILE}
printf '[%s] [%s/%s] PHP job done (%.3F sec): %s\n' "`date`" $JOB_ID $SCRIPT_PID $DELTA_TIME $JOB_NAME >> $LOG_FILE
printf '[%s] %.3F sec for job %s\n' "`date`" $DELTA_TIME $JOB_NAME >> ${DELTA_FILE}
remove_lock
   fi;
   ;;
    esac
else
    echo "Usage: $SCRIPT_NAME <JOB_TYPE> <JOB_NAME>"
    echo 'Run HTTP or PHP job with lock. Usually called by cron'
    echo '  <JOB_TYPE> is [http|php]'
    echo '  <JOB_NAME> is URL (if http) or path to php-script (if php)'
    echo "$PRG_NAME $PRG_VERSION (all copylefts belongs to $PRG_COPYLEFTS)"
fi

Лог LOG_FILE содержит всё, DELTA_FILE - времена затраченные на выполнение задач и MULTILOCK_FILE -задачи, запуск которых был блокирован как повторных (конкурентных). По-умолчанию, в папке со скриптом создаётся папка locks - в ней будут появляться папки-локи для задач. Т.е. нужны права на запись пользователю от которого будет  запущен этот скрипт.

Блокировка созданием папок, как я понял - пока единственный адекватно работающий на всём чем только можно способ атомизировать создание лока =) Основная идея подчерпнута отсюда.

Не смущайтесь слова диспетчер (dispatcher), так как это всего лишь менеджер (manager),  просто звучит круто =)

4 комментария:

  1. Анонимный24.06.2013, 22:36

    Друг, у тебя отличный блог в плане контента, но вот
    бекграунд - взрыв мозга. Можно убрать это эхо спектрума?

    ОтветитьУдалить
    Ответы
    1. как же, весь шарм исчезнет =)

      Удалить
  2. Анонимный14.07.2013, 12:47

    Как с вами можно связаться есть у вас icq или скайп ?

    ОтветитьУдалить
  3. заведите аккаунт в Google, тогда сможете оставлять сообщения приватно
    ICQ не пользуюсь лет 6 как, Skype очень редко

    ОтветитьУдалить