Public Repository

Last pushed: 5 months ago
Short Description
Distributed CRON microservice
Full Description

Districron

What it is?

Districron is CRON-like microservice built on top of akka actors cluster

Why?

Handling long running, time-activated tasks in distributed environment is challenging.
Deployment of simple cron service on single machine is vulnerable to hardware and network failures, and
adding more similar machines creates new problems with synchronization. Districron provides solution for handling
such situation and guarantees that scheduled task will be executed once and only once.

How it works?

Districron uses external MySQL >= 5.6 database to hold distributed lock between distinct nodes. Facts:

  • Its written in pure Scala and utilises Akka actors system
  • It's deployed in Docker container
  • It uses DistributedPubSub for communication inside Akka cluster
  • On start it reads all tasks to schedule from task table in database. Tasks are described by name and Quartz expression
  • When it comes to fire execution, first available node creates lock on database and sends ExecuteTask to specified recipients
  • Recipient performs action specific to given task, and sends back ConfirmExecutionCompleted with execution status

How to deploy it?

Implying that you want to create cluster consisting of database server, 2 districron nodes and 1 worker on following addresses:

  • Database 10.0.0.100
  • Districron 10.0.0.1 and 10.0.0.2
  • Worker 10.0.0.50

you need to perform following operation:

  1. Install MySQL >= 5.6 on database node

  2. Download Liquibase, configure MySQL driver, and from db/ directory in this project run:

    liquibase --driver='com.mysql.jdbc.Driver' --changeLogFile=changelog.yml \
    --url='jdbc:mysql://10.0.0.100:3306/districron' \
    --username='districron' \
    --password='districron' \
    update
    
  3. On both Districron nodes install Docker and run:

    docker run --name districron --restart=always -p 2552:2552 \
    -e DATABASE_DRIVER_CLASS="com.mysql.jdbc.Driver" \
    -e DATABASE_URL="jdbc:mysql://10.0.0.100:3306/districron" \
    -e DATABASE_USERNAME="districron" \
    -e DATABASE_PASSWORD="districron" \
    -e AKKA_CLUSTER_SEED_NODES="akka.tcp://DistricronSystem@10.0.0.1:2552;akka.tcp://DistricronSystem@10.0.0.2:2552;akka.tcp://OtherSystem@10.0.0.50:2552" \
    -d mkorman/districron
    
  4. Run worker application on worker node

How to schedule task?

Connect to MySQL server with your favourite client and perform example statement:

INSERT INTO task(name, cron_expression, global_distribution, recipient, execution_interval)
    VALUES ('SomeTask', '0/30 * * * * ?', 0, '/user/worker', 15);

where
name is just unique name for your task
cron_expression is Quartz expression used to schedule operation
global_distribution and recipient are the way that Districron informs workers about pending task to execute.
global_distribution set to 1 will send event to all workers listening on topic specified by recipient, while
global_distribution set to 0 will select single actor from path specified by recipient field
execution_interval is lock time specified in seconds. No worker will reschedule the same task for period [NOW, NOW + execution_interval]

Tasks will be scheduled after applications restart

How to create worker?

Workers must be able to send and receive message inside Akka cluster. Preferred way is to write them in Scala or Java.
Common place where messages definitions are store is artifact common:

<dependency>
    <groupId>com.github.mkorman9.districron</groupId>
    <artifactId>common</artifactId>
    <version>LATEST_VERSION</version>
</dependency>

Remember to replace LATEST_VERSION with latest version from HERE

Example worker could look like this:

import akka.actor.{Actor, ActorRef}
import akka.cluster.pubsub.DistributedPubSub
import akka.cluster.pubsub.DistributedPubSubMediator.{Put, Send}
import com.github.mkorman9.districron.Messages.{ConfirmExecutionCompleted, ExecuteTask}
import com.github.mkorman9.districron.Success

class Worker extends Actor {
  private val clusterMediator: ActorRef = DistributedPubSub(context.system).mediator
  clusterMediator ! Put(self)

  override def receive: Receive = {
    case ExecuteTask(executionId, taskName) => {
      println(s"Executing task $taskName!")
      clusterMediator ! Send(
        com.github.mkorman9.districron.Actor.Path,
        ConfirmExecutionCompleted(executionId, Success),
        localAffinity = false
      )
    }
  }
}

Is it free?

Yes, the whole codebase is open-source, and published under Apache 2.0 license.

How to contribute?

Project consists of two modules.
common is a place where common dependencies between Districron and workers are stored.
It's deployed to Maven repository, and every change in there requires to be documented in scaladoc.
implementation is where standalone application exists. Code living here doesn't require to be documented, but requires
to be well tested on unit and integration level.

Pushing to master and deploy branches is restricted. Every change must be implemented and tested on dedicated feature branch,
which is merged to master after review. When it comes to publishing new version of product, master is merged to deploy
and thus deployed to Docker Hub and Maven central.

Docker Pull Command
Owner
mkorman

Comments (0)